HTMLEditorDataTransfer.cpp (183956B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=2 sw=2 et tw=78: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "HTMLEditor.h" 8 9 #include <string.h> 10 11 #include "AutoSelectionRestorer.h" 12 #include "EditAction.h" 13 #include "EditorBase.h" 14 #include "EditorDOMPoint.h" 15 #include "EditorUtils.h" 16 #include "HTMLEditHelpers.h" 17 #include "HTMLEditUtils.h" 18 #include "InternetCiter.h" 19 #include "PendingStyles.h" 20 #include "SelectionState.h" 21 #include "WhiteSpaceVisibilityKeeper.h" 22 #include "WSRunScanner.h" 23 24 #include "ErrorList.h" 25 #include "mozilla/dom/Comment.h" 26 #include "mozilla/dom/DataTransfer.h" 27 #include "mozilla/dom/Document.h" 28 #include "mozilla/dom/DocumentFragment.h" 29 #include "mozilla/dom/DOMException.h" 30 #include "mozilla/dom/DOMStringList.h" 31 #include "mozilla/dom/DOMStringList.h" 32 #include "mozilla/dom/Element.h" 33 #include "mozilla/dom/ElementInlines.h" 34 #include "mozilla/dom/Event.h" 35 #include "mozilla/dom/FileBlobImpl.h" 36 #include "mozilla/dom/FileReader.h" 37 #include "mozilla/dom/Selection.h" 38 #include "mozilla/dom/StaticRange.h" 39 #include "mozilla/dom/WorkerRef.h" 40 #include "mozilla/Attributes.h" 41 #include "mozilla/Base64.h" 42 #include "mozilla/BasicEvents.h" 43 #include "mozilla/DebugOnly.h" 44 #include "mozilla/Maybe.h" 45 #include "mozilla/OwningNonNull.h" 46 #include "mozilla/Preferences.h" 47 #include "mozilla/Result.h" 48 #include "mozilla/StaticPrefs_editor.h" 49 #include "mozilla/TextComposition.h" 50 #include "nsAString.h" 51 #include "nsCOMPtr.h" 52 #include "nsCRTGlue.h" // for CRLF 53 #include "nsComponentManagerUtils.h" 54 #include "nsIScriptError.h" 55 #include "nsContentUtils.h" 56 #include "nsDebug.h" 57 #include "nsDependentSubstring.h" 58 #include "nsError.h" 59 #include "nsFocusManager.h" 60 #include "nsGkAtoms.h" 61 #include "nsIClipboard.h" 62 #include "nsIContent.h" 63 #include "nsIDocumentEncoder.h" 64 #include "nsIFile.h" 65 #include "nsIInputStream.h" 66 #include "nsIMIMEService.h" 67 #include "nsINode.h" 68 #include "nsIParserUtils.h" 69 #include "nsIPrincipal.h" 70 #include "nsISupportsImpl.h" 71 #include "nsISupportsPrimitives.h" 72 #include "nsISupportsUtils.h" 73 #include "nsITransferable.h" 74 #include "nsIVariant.h" 75 #include "nsLinebreakConverter.h" 76 #include "nsLiteralString.h" 77 #include "nsNameSpaceManager.h" 78 #include "nsNetUtil.h" 79 #include "nsPrintfCString.h" 80 #include "nsRange.h" 81 #include "nsReadableUtils.h" 82 #include "nsServiceManagerUtils.h" 83 #include "nsStreamUtils.h" 84 #include "nsString.h" 85 #include "nsStringFwd.h" 86 #include "nsStringIterator.h" 87 #include "nsTreeSanitizer.h" 88 #include "nsXPCOM.h" 89 #include "nscore.h" 90 #include "nsContentUtils.h" 91 #include "nsQueryObject.h" 92 93 class nsAtom; 94 class nsILoadContext; 95 96 namespace mozilla { 97 98 using namespace dom; 99 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; 100 using LeafNodeType = HTMLEditUtils::LeafNodeType; 101 102 #define kInsertCookie "_moz_Insert Here_moz_" 103 104 // some little helpers 105 static bool FindIntegerAfterString(const char* aLeadingString, 106 const nsCString& aCStr, 107 int32_t& foundNumber); 108 static void RemoveFragComments(nsCString& aStr); 109 110 nsresult HTMLEditor::InsertDroppedDataTransferAsAction( 111 AutoEditActionDataSetter& aEditActionData, DataTransfer& aDataTransfer, 112 const EditorDOMPoint& aDroppedAt, nsIPrincipal* aSourcePrincipal) { 113 MOZ_ASSERT(aEditActionData.GetEditAction() == EditAction::eDrop); 114 MOZ_ASSERT(GetEditAction() == EditAction::eDrop); 115 MOZ_ASSERT(aDroppedAt.IsSet()); 116 MOZ_ASSERT(aDataTransfer.MozItemCount() > 0); 117 118 if (IsReadonly()) { 119 return NS_OK; 120 } 121 122 aEditActionData.InitializeDataTransfer(&aDataTransfer); 123 RefPtr<StaticRange> targetRange = StaticRange::Create( 124 aDroppedAt.GetContainer(), aDroppedAt.Offset(), aDroppedAt.GetContainer(), 125 aDroppedAt.Offset(), IgnoreErrors()); 126 NS_WARNING_ASSERTION(targetRange && targetRange->IsPositioned(), 127 "Why did we fail to create collapsed static range at " 128 "dropped position?"); 129 if (targetRange && targetRange->IsPositioned()) { 130 aEditActionData.AppendTargetRange(*targetRange); 131 } 132 nsresult rv = aEditActionData.MaybeDispatchBeforeInputEvent(); 133 if (NS_FAILED(rv)) { 134 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 135 "MaybeDispatchBeforeInputEvent() failed"); 136 return rv; 137 } 138 139 if (MOZ_UNLIKELY(!aDroppedAt.IsInContentNode())) { 140 NS_WARNING("Dropped into non-content node"); 141 return NS_OK; 142 } 143 144 const RefPtr<Element> editingHost = ComputeEditingHost( 145 *aDroppedAt.ContainerAs<nsIContent>(), LimitInBodyElement::No); 146 if (MOZ_UNLIKELY(!editingHost)) { 147 NS_WARNING("Dropped onto non-editable node"); 148 return NS_OK; 149 } 150 151 uint32_t numItems = aDataTransfer.MozItemCount(); 152 for (uint32_t i = 0; i < numItems; ++i) { 153 DebugOnly<nsresult> rvIgnored = 154 InsertFromDataTransfer(&aDataTransfer, i, aSourcePrincipal, aDroppedAt, 155 DeleteSelectedContent::No, *editingHost); 156 if (NS_WARN_IF(Destroyed())) { 157 return NS_OK; 158 } 159 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 160 "HTMLEditor::InsertFromDataTransfer(" 161 "DeleteSelectedContent::No) failed, but ignored"); 162 } 163 return NS_OK; 164 } 165 166 nsresult HTMLEditor::LoadHTML(const nsAString& aInputString) { 167 MOZ_ASSERT(IsEditActionDataAvailable()); 168 169 if (NS_WARN_IF(!mInitSucceeded)) { 170 return NS_ERROR_NOT_INITIALIZED; 171 } 172 173 // force IME commit; set up rules sniffing and batching 174 DebugOnly<nsresult> rvIgnored = CommitComposition(); 175 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 176 "EditorBase::CommitComposition() failed, but ignored"); 177 if (NS_WARN_IF(Destroyed())) { 178 return NS_ERROR_EDITOR_DESTROYED; 179 } 180 181 AutoPlaceholderBatch treatAsOneTransaction( 182 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 183 IgnoredErrorResult ignoredError; 184 AutoEditSubActionNotifier startToHandleEditSubAction( 185 *this, EditSubAction::eInsertHTMLSource, nsIEditor::eNext, ignoredError); 186 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 187 return ignoredError.StealNSResult(); 188 } 189 NS_WARNING_ASSERTION( 190 !ignoredError.Failed(), 191 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 192 193 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor(); 194 if (NS_FAILED(rv)) { 195 NS_WARNING("EditorBase::EnsureNoPaddingBRElementForEmptyEditor() failed"); 196 return rv; 197 } 198 199 // Delete Selection, but only if it isn't collapsed, see bug #106269 200 if (!SelectionRef().IsCollapsed()) { 201 nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip); 202 if (NS_FAILED(rv)) { 203 NS_WARNING( 204 "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed"); 205 return rv; 206 } 207 } 208 209 // Get the first range in the selection, for context: 210 RefPtr<const nsRange> range = SelectionRef().GetRangeAt(0); 211 if (NS_WARN_IF(!range)) { 212 return NS_ERROR_FAILURE; 213 } 214 215 // Create fragment for pasted HTML. 216 ErrorResult error; 217 RefPtr<DocumentFragment> documentFragment = 218 range->CreateContextualFragment(aInputString, error); 219 if (error.Failed()) { 220 NS_WARNING("nsRange::CreateContextualFragment() failed"); 221 return error.StealNSResult(); 222 } 223 224 // Put the fragment into the document at start of selection. 225 EditorDOMPoint pointToInsert(range->StartRef()); 226 // XXX We need to make pointToInsert store offset for keeping traditional 227 // behavior since using only child node to pointing insertion point 228 // changes the behavior when inserted child is moved by mutation 229 // observer. We need to investigate what we should do here. 230 (void)pointToInsert.Offset(); 231 EditorDOMPoint pointToPutCaret; 232 for (nsCOMPtr<nsIContent> contentToInsert = documentFragment->GetFirstChild(); 233 contentToInsert; contentToInsert = documentFragment->GetFirstChild()) { 234 Result<CreateContentResult, nsresult> insertChildContentNodeResult = 235 InsertNodeWithTransaction(*contentToInsert, pointToInsert); 236 if (MOZ_UNLIKELY(insertChildContentNodeResult.isErr())) { 237 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed"); 238 return insertChildContentNodeResult.unwrapErr(); 239 } 240 CreateContentResult unwrappedInsertChildContentNodeResult = 241 insertChildContentNodeResult.unwrap(); 242 unwrappedInsertChildContentNodeResult.MoveCaretPointTo( 243 pointToPutCaret, *this, 244 {SuggestCaret::OnlyIfHasSuggestion, 245 SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); 246 // XXX If the inserted node has been moved by mutation observer, 247 // incrementing offset will cause odd result. Next new node 248 // will be inserted after existing node and the offset will be 249 // overflown from the container node. 250 pointToInsert.Set(pointToInsert.GetContainer(), pointToInsert.Offset() + 1); 251 if (NS_WARN_IF(!pointToInsert.Offset())) { 252 // Append the remaining children to the container if offset is 253 // overflown. 254 pointToInsert.SetToEndOf(pointToInsert.GetContainer()); 255 } 256 } 257 258 if (pointToPutCaret.IsSet()) { 259 nsresult rv = CollapseSelectionTo(pointToPutCaret); 260 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { 261 NS_WARNING("EditorBase::CollapseSelectionTo() failed, but ignored"); 262 return NS_ERROR_EDITOR_DESTROYED; 263 } 264 NS_WARNING_ASSERTION( 265 NS_SUCCEEDED(rv), 266 "EditorBase::CollapseSelectionTo() failed, but ignored"); 267 } 268 269 return NS_OK; 270 } 271 272 NS_IMETHODIMP HTMLEditor::InsertHTML(const nsAString& aInString) { 273 nsresult rv = InsertHTMLAsAction(aInString); 274 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 275 "HTMLEditor::InsertHTMLAsAction() failed"); 276 return rv; 277 } 278 279 nsresult HTMLEditor::InsertHTMLAsAction(const nsAString& aInString, 280 nsIPrincipal* aPrincipal) { 281 // FIXME: This should keep handling inserting HTML if the caller is 282 // nsIHTMLEditor::InsertHTML. 283 if (IsReadonly()) { 284 return NS_OK; 285 } 286 287 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertHTML, 288 aPrincipal); 289 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 290 if (NS_FAILED(rv)) { 291 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 292 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 293 return EditorBase::ToGenericNSResult(rv); 294 } 295 296 const RefPtr<Element> editingHost = 297 ComputeEditingHost(LimitInBodyElement::No); 298 if (NS_WARN_IF(!editingHost)) { 299 return NS_ERROR_FAILURE; 300 } 301 302 if (editingHost->IsContentEditablePlainTextOnly()) { 303 nsAutoString plaintextString; 304 nsresult rv = nsContentUtils::ConvertToPlainText( 305 aInString, plaintextString, nsIDocumentEncoder::OutputLFLineBreak, 306 0u /* never wrap lines*/); 307 if (NS_FAILED(rv)) { 308 NS_WARNING("nsContentUtils::ConvertToPlainText() failed"); 309 return EditorBase::ToGenericNSResult(rv); 310 } 311 Maybe<AutoPlaceholderBatch> treatAsOneTransaction; 312 const auto EnsureAutoPlaceholderBatch = [&]() { 313 if (treatAsOneTransaction.isNothing()) { 314 treatAsOneTransaction.emplace(*this, ScrollSelectionIntoView::Yes, 315 __FUNCTION__); 316 } 317 }; 318 if (mComposition && 319 mComposition->CanRequsetIMEToCommitOrCancelComposition()) { 320 EnsureAutoPlaceholderBatch(); 321 CommitComposition(); 322 if (NS_WARN_IF(Destroyed())) { 323 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED); 324 } 325 if (NS_WARN_IF(editingHost != 326 ComputeEditingHost(LimitInBodyElement::No))) { 327 return EditorBase::ToGenericNSResult( 328 NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 329 } 330 } 331 if (MOZ_LIKELY(!plaintextString.IsEmpty())) { 332 EnsureAutoPlaceholderBatch(); 333 rv = InsertTextAsSubAction(plaintextString, InsertTextFor::NormalText); 334 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 335 "EditorBase::InsertTextAsSubAction() failed"); 336 } else if (!SelectionRef().IsCollapsed()) { 337 EnsureAutoPlaceholderBatch(); 338 rv = DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip); 339 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 340 "EditorBase::DeleteSelectionAsSubAction() failed"); 341 } 342 return EditorBase::ToGenericNSResult(rv); 343 } 344 AutoPlaceholderBatch treatAsOneTransaction( 345 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 346 rv = InsertHTMLWithContextAsSubAction( 347 aInString, u""_ns, u""_ns, u""_ns, SafeToInsertData::Yes, 348 EditorDOMPoint(), DeleteSelectedContent::Yes, 349 InlineStylesAtInsertionPoint::Clear, *editingHost); 350 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 351 "HTMLEditor::InsertHTMLWithContextAsSubAction(" 352 "SafeToInsertData::Yes, DeleteSelectedContent::Yes, " 353 "InlineStylesAtInsertionPoint::Clear) failed"); 354 return EditorBase::ToGenericNSResult(rv); 355 } 356 357 class MOZ_STACK_CLASS HTMLEditor::HTMLWithContextInserter final { 358 public: 359 MOZ_CAN_RUN_SCRIPT HTMLWithContextInserter(HTMLEditor& aHTMLEditor, 360 const Element& aEditingHost) 361 : mHTMLEditor(aHTMLEditor), mEditingHost(aEditingHost) {} 362 363 HTMLWithContextInserter() = delete; 364 HTMLWithContextInserter(const HTMLWithContextInserter&) = delete; 365 HTMLWithContextInserter(HTMLWithContextInserter&&) = delete; 366 367 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> Run( 368 const nsAString& aInputString, const nsAString& aContextStr, 369 const nsAString& aInfoStr, SafeToInsertData aSafeToInsertData, 370 InlineStylesAtInsertionPoint aInlineStylesAtInsertionPoint); 371 372 private: 373 class FragmentFromPasteCreator; 374 class FragmentParser; 375 /** 376 * CollectTopMostChildContentsCompletelyInRange() collects topmost child 377 * contents which are completely in the given range. 378 * For example, if the range points a node with its container node, the 379 * result is only the node (meaning does not include its descendants). 380 * If the range starts start of a node and ends end of it, and if the node 381 * does not have children, returns no nodes, otherwise, if the node has 382 * some children, the result includes its all children (not including their 383 * descendants). 384 * 385 * @param aStartPoint Start point of the range. 386 * @param aEndPoint End point of the range. 387 * @param aOutArrayOfContents [Out] Topmost children which are completely in 388 * the range. 389 */ 390 static void CollectTopMostChildContentsCompletelyInRange( 391 const EditorRawDOMPoint& aStartPoint, const EditorRawDOMPoint& aEndPoint, 392 nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents); 393 394 EditorDOMPoint GetNewCaretPointAfterInsertingHTML( 395 const EditorDOMPoint& aLastInsertedPoint) const; 396 397 /** 398 * Insert nodes in aArrayOfTopMostChildContents or their children to 399 * aPointToInsert (if the container is not a proper parent of inserting node, 400 * this splits the ancestors). 401 */ 402 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateContentResult, nsresult> 403 InsertContents( 404 const EditorDOMPoint& aPointToInsert, 405 nsTArray<OwningNonNull<nsIContent>>& aArrayOfTopMostChildContents, 406 const nsINode* aFragmentAsNode); 407 408 /** 409 * @param aContextStr as indicated by nsITransferable's kHTMLContext. 410 * @param aInfoStr as indicated by nsITransferable's kHTMLInfo. 411 */ 412 nsresult CreateDOMFragmentFromPaste( 413 const nsAString& aInputString, const nsAString& aContextStr, 414 const nsAString& aInfoStr, nsCOMPtr<nsINode>* aOutFragNode, 415 nsCOMPtr<nsINode>* aOutStartNode, nsCOMPtr<nsINode>* aOutEndNode, 416 uint32_t* aOutStartOffset, uint32_t* aOutEndOffset, 417 SafeToInsertData aSafeToInsertData) const; 418 419 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult MoveCaretOutsideOfLink( 420 Element& aLinkElement, const EditorDOMPoint& aPointToPutCaret); 421 422 // MOZ_KNOWN_LIVE because this is set only by the constructor which is 423 // marked as MOZ_CAN_RUN_SCRIPT and this is allocated only in the stack. 424 MOZ_KNOWN_LIVE HTMLEditor& mHTMLEditor; 425 MOZ_KNOWN_LIVE const Element& mEditingHost; 426 }; 427 428 class MOZ_STACK_CLASS 429 HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator final { 430 public: 431 nsresult Run(const Document& aDocument, const nsAString& aInputString, 432 const nsAString& aContextStr, const nsAString& aInfoStr, 433 nsCOMPtr<nsINode>* aOutFragNode, 434 nsCOMPtr<nsINode>* aOutStartNode, nsCOMPtr<nsINode>* aOutEndNode, 435 SafeToInsertData aSafeToInsertData) const; 436 437 private: 438 nsresult CreateDocumentFragmentAndGetParentOfPastedHTMLInContext( 439 const Document& aDocument, const nsAString& aInputString, 440 const nsAString& aContextStr, SafeToInsertData aSafeToInsertData, 441 nsCOMPtr<nsINode>& aParentNodeOfPastedHTMLInContext, 442 RefPtr<DocumentFragment>& aDocumentFragmentToInsert) const; 443 444 static nsAtom* DetermineContextLocalNameForParsingPastedHTML( 445 const nsIContent* aParentContentOfPastedHTMLInContext); 446 447 static bool FindTargetNodeOfContextForPastedHTMLAndRemoveInsertionCookie( 448 nsINode& aStart, nsCOMPtr<nsINode>& aResult); 449 450 static bool IsInsertionCookie(const nsIContent& aContent); 451 452 /** 453 * @param aDocumentFragmentForContext contains the merged result. 454 */ 455 static nsresult MergeAndPostProcessFragmentsForPastedHTMLAndContext( 456 DocumentFragment& aDocumentFragmentForPastedHTML, 457 DocumentFragment& aDocumentFragmentForContext, 458 nsIContent& aTargetContentOfContextForPastedHTML); 459 460 /** 461 * @param aInfoStr as indicated by nsITransferable's kHTMLInfo. 462 */ 463 [[nodiscard]] static nsresult MoveStartAndEndAccordingToHTMLInfo( 464 const nsAString& aInfoStr, nsCOMPtr<nsINode>* aOutStartNode, 465 nsCOMPtr<nsINode>* aOutEndNode); 466 467 static nsresult PostProcessFragmentForPastedHTMLWithoutContext( 468 DocumentFragment& aDocumentFragmentForPastedHTML); 469 470 static nsresult PreProcessContextDocumentFragmentForMerging( 471 DocumentFragment& aDocumentFragmentForContext); 472 473 static void RemoveHeadChildAndStealBodyChildsChildren(nsINode& aNode); 474 475 /** 476 * This is designed for a helper class to remove disturbing nodes at inserting 477 * the HTML fragment into the DOM tree. This walks the children and if some 478 * elements do not have enough children, e.g., list elements not having 479 * another visible list elements nor list item elements, 480 * will be removed. 481 * 482 * @param aNode Should not be a node whose mutation may be observed by 483 * JS. 484 */ 485 static void RemoveIncompleteDescendantsFromInsertingFragment(nsINode& aNode); 486 487 enum class NodesToRemove { 488 eAll, 489 eOnlyListItems /*!< List items are always block-level elements, hence such 490 whitespace-only nodes are always invisible. */ 491 }; 492 static nsresult 493 RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces( 494 nsIContent& aNode, NodesToRemove aNodesToRemove); 495 }; 496 497 EditorDOMPoint 498 HTMLEditor::HTMLWithContextInserter::GetNewCaretPointAfterInsertingHTML( 499 const EditorDOMPoint& aLastInsertedPoint) const { 500 EditorDOMPoint pointToPutCaret; 501 502 // but don't cross tables 503 nsIContent* containerContent = nullptr; 504 // FIXME: GetChild() might be nullptr, but it's referred as non-null in the 505 // block! 506 if (!aLastInsertedPoint.GetChild() || 507 !aLastInsertedPoint.GetChild()->IsHTMLElement(nsGkAtoms::table)) { 508 containerContent = HTMLEditUtils::GetLastLeafContent( 509 *aLastInsertedPoint.GetChild(), {LeafNodeType::OnlyEditableLeafNode}, 510 BlockInlineCheck::Unused, 511 aLastInsertedPoint.GetChild()->GetAsElementOrParentElement()); 512 if (containerContent) { 513 Element* mostDistantInclusiveAncestorTableElement = nullptr; 514 for (Element* maybeTableElement = 515 containerContent->GetAsElementOrParentElement(); 516 maybeTableElement && 517 maybeTableElement != aLastInsertedPoint.GetChild(); 518 maybeTableElement = maybeTableElement->GetParentElement()) { 519 if (maybeTableElement->IsHTMLElement(nsGkAtoms::table)) { 520 mostDistantInclusiveAncestorTableElement = maybeTableElement; 521 } 522 } 523 // If we're in table elements, we should put caret into the most ancestor 524 // table element. 525 if (mostDistantInclusiveAncestorTableElement) { 526 containerContent = mostDistantInclusiveAncestorTableElement; 527 } 528 } 529 } 530 // If we are not in table elements, we should put caret in the last inserted 531 // node. 532 if (!containerContent) { 533 containerContent = aLastInsertedPoint.GetChild(); 534 } 535 536 // If the container is a text node or a container element except `<table>` 537 // element, put caret a end of it. 538 if (containerContent->IsText() || 539 (HTMLEditUtils::IsContainerNode(*containerContent) && 540 !containerContent->IsHTMLElement(nsGkAtoms::table))) { 541 pointToPutCaret.SetToEndOf(containerContent); 542 } 543 // Otherwise, i.e., it's an atomic element, `<table>` element or data node, 544 // put caret after it. 545 else { 546 pointToPutCaret.SetAfter(containerContent); 547 } 548 549 // Make sure we don't end up with selection collapsed after an invisible 550 // `<br>` element. 551 const WSScanResult prevVisibleThing = 552 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 553 // We want to put caret to an editable point so that we need to scan 554 // only editable nodes. 555 {WSRunScanner::Option::OnlyEditableNodes}, pointToPutCaret); 556 if (prevVisibleThing.ReachedInvisibleBRElement()) { 557 const WSScanResult prevVisibleThingOfBRElement = 558 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 559 {WSRunScanner::Option::OnlyEditableNodes}, 560 EditorRawDOMPoint(prevVisibleThing.BRElementPtr())); 561 if (prevVisibleThingOfBRElement.InVisibleOrCollapsibleCharacters()) { 562 pointToPutCaret = prevVisibleThingOfBRElement 563 .PointAfterReachedContent<EditorDOMPoint>(); 564 } else if (prevVisibleThingOfBRElement.ReachedSpecialContent()) { 565 pointToPutCaret = prevVisibleThingOfBRElement 566 .PointAfterReachedContentNode<EditorDOMPoint>(); 567 } 568 } 569 570 return pointToPutCaret; 571 } 572 573 nsresult HTMLEditor::InsertHTMLWithContextAsSubAction( 574 const nsAString& aInputString, const nsAString& aContextStr, 575 const nsAString& aInfoStr, const nsAString& aFlavor, 576 SafeToInsertData aSafeToInsertData, const EditorDOMPoint& aPointToInsert, 577 DeleteSelectedContent aDeleteSelectedContent, 578 InlineStylesAtInsertionPoint aInlineStylesAtInsertionPoint, 579 const Element& aEditingHost) { 580 MOZ_ASSERT(IsEditActionDataAvailable()); 581 582 if (NS_WARN_IF(!mInitSucceeded)) { 583 return NS_ERROR_NOT_INITIALIZED; 584 } 585 586 CommitComposition(); 587 588 IgnoredErrorResult ignoredError; 589 AutoEditSubActionNotifier startToHandleEditSubAction( 590 *this, EditSubAction::ePasteHTMLContent, nsIEditor::eNext, ignoredError); 591 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 592 return ignoredError.StealNSResult(); 593 } 594 NS_WARNING_ASSERTION( 595 !ignoredError.Failed(), 596 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 597 ignoredError.SuppressException(); 598 599 { 600 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(); 601 if (MOZ_UNLIKELY(result.isErr())) { 602 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); 603 return result.unwrapErr(); 604 } 605 if (result.inspect().Canceled()) { 606 return NS_OK; 607 } 608 } 609 610 // If we have a destination / target node, we want to insert there rather than 611 // in place of the selection. Ignore aDeleteSelectedContent here if 612 // aPointToInsert is not set since deletion will also occur later in 613 // HTMLWithContextInserter and will be collapsed around there; this block 614 // is intended to cover the various scenarios where we are dropping in an 615 // editor (and may want to delete the selection before collapsing the 616 // selection in the new destination) 617 if (aPointToInsert.IsSet()) { 618 nsresult rv = 619 PrepareToInsertContent(aPointToInsert, aDeleteSelectedContent); 620 if (NS_FAILED(rv)) { 621 NS_WARNING("EditorBase::PrepareToInsertContent() failed"); 622 return rv; 623 } 624 aDeleteSelectedContent = DeleteSelectedContent::No; 625 } 626 627 HTMLWithContextInserter htmlWithContextInserter(*this, aEditingHost); 628 629 Result<EditActionResult, nsresult> result = htmlWithContextInserter.Run( 630 aInputString, aContextStr, aInfoStr, aSafeToInsertData, 631 aInlineStylesAtInsertionPoint); 632 if (MOZ_UNLIKELY(result.isErr())) { 633 return result.unwrapErr(); 634 } 635 636 // If nothing is inserted and delete selection is required, we need to 637 // delete selection right now. 638 if (result.inspect().Ignored() && 639 aDeleteSelectedContent == DeleteSelectedContent::Yes) { 640 nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip); 641 if (NS_FAILED(rv)) { 642 NS_WARNING( 643 "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed"); 644 return rv; 645 } 646 } 647 return NS_OK; 648 } 649 650 Result<EditActionResult, nsresult> HTMLEditor::HTMLWithContextInserter::Run( 651 const nsAString& aInputString, const nsAString& aContextStr, 652 const nsAString& aInfoStr, SafeToInsertData aSafeToInsertData, 653 InlineStylesAtInsertionPoint aInlineStylesAtInsertionPoint) { 654 MOZ_ASSERT(mHTMLEditor.IsEditActionDataAvailable()); 655 656 // create a dom document fragment that represents the structure to paste 657 nsCOMPtr<nsINode> fragmentAsNode, streamStartParent, streamEndParent; 658 uint32_t streamStartOffset = 0, streamEndOffset = 0; 659 660 nsresult rv = CreateDOMFragmentFromPaste( 661 aInputString, aContextStr, aInfoStr, address_of(fragmentAsNode), 662 address_of(streamStartParent), address_of(streamEndParent), 663 &streamStartOffset, &streamEndOffset, aSafeToInsertData); 664 if (NS_FAILED(rv)) { 665 NS_WARNING( 666 "HTMLEditor::HTMLWithContextInserter::CreateDOMFragmentFromPaste() " 667 "failed"); 668 return Err(rv); 669 } 670 671 // we need to recalculate various things based on potentially new offsets 672 // this is work to be completed at a later date (probably by jfrancis) 673 674 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfTopMostChildContents; 675 // If we have stream start point information, lets use it and end point. 676 // Otherwise, we should make a range all over the document fragment. 677 EditorRawDOMPoint streamStartPoint = 678 streamStartParent 679 ? EditorRawDOMPoint(streamStartParent, 680 AssertedCast<uint32_t>(streamStartOffset)) 681 : EditorRawDOMPoint(fragmentAsNode, 0); 682 EditorRawDOMPoint streamEndPoint = 683 streamStartParent ? EditorRawDOMPoint(streamEndParent, streamEndOffset) 684 : EditorRawDOMPoint::AtEndOf(fragmentAsNode); 685 686 (void)streamStartPoint; 687 (void)streamEndPoint; 688 689 HTMLWithContextInserter::CollectTopMostChildContentsCompletelyInRange( 690 EditorRawDOMPoint(streamStartParent, 691 AssertedCast<uint32_t>(streamStartOffset)), 692 EditorRawDOMPoint(streamEndParent, 693 AssertedCast<uint32_t>(streamEndOffset)), 694 arrayOfTopMostChildContents); 695 696 if (arrayOfTopMostChildContents.IsEmpty()) { 697 return EditActionResult::IgnoredResult(); // Nothing to insert. 698 } 699 700 // Are there any table elements in the list? 701 // check for table cell selection mode 702 bool cellSelectionMode = 703 HTMLEditUtils::IsInTableCellSelectionMode(mHTMLEditor.SelectionRef()); 704 705 if (cellSelectionMode) { 706 // do we have table content to paste? If so, we want to delete 707 // the selected table cells and replace with new table elements; 708 // but if not we want to delete _contents_ of cells and replace 709 // with non-table elements. Use cellSelectionMode bool to 710 // indicate results. 711 if (!HTMLEditUtils::IsAnyTableElementExceptColumnElement( 712 *arrayOfTopMostChildContents[0])) { 713 cellSelectionMode = false; 714 } 715 } 716 717 if (!cellSelectionMode) { 718 rv = mHTMLEditor.DeleteSelectionAndPrepareToCreateNode(); 719 if (NS_FAILED(rv)) { 720 NS_WARNING("HTMLEditor::DeleteSelectionAndPrepareToCreateNode() failed"); 721 return Err(rv); 722 } 723 724 if (aInlineStylesAtInsertionPoint == InlineStylesAtInsertionPoint::Clear) { 725 // pasting does not inherit local inline styles 726 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = 727 mHTMLEditor.ClearStyleAt( 728 EditorDOMPoint(mHTMLEditor.SelectionRef().AnchorRef()), 729 EditorInlineStyle::RemoveAllStyles(), SpecifiedStyle::Preserve, 730 mEditingHost); 731 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 732 NS_WARNING("HTMLEditor::ClearStyleAt() failed"); 733 return pointToPutCaretOrError.propagateErr(); 734 } 735 if (pointToPutCaretOrError.inspect().IsSetAndValid()) { 736 nsresult rv = 737 mHTMLEditor.CollapseSelectionTo(pointToPutCaretOrError.unwrap()); 738 if (NS_FAILED(rv)) { 739 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 740 return Err(rv); 741 } 742 } 743 } 744 } else { 745 // Delete whole cells: we will replace with new table content. 746 747 // Braces for artificial block to scope AutoSelectionRestorer. 748 // Save current selection since DeleteTableCellWithTransaction() perturbs 749 // it. 750 { 751 AutoSelectionRestorer restoreSelectionLater(&mHTMLEditor); 752 rv = mHTMLEditor.DeleteTableCellWithTransaction(1); 753 if (NS_FAILED(rv)) { 754 NS_WARNING("HTMLEditor::DeleteTableCellWithTransaction(1) failed"); 755 return Err(rv); 756 } 757 } 758 // collapse selection to beginning of deleted table content 759 IgnoredErrorResult ignoredError; 760 mHTMLEditor.SelectionRef().CollapseToStart(ignoredError); 761 NS_WARNING_ASSERTION(!ignoredError.Failed(), 762 "Selection::Collapse() failed, but ignored"); 763 } 764 765 { 766 Result<EditActionResult, nsresult> result = 767 mHTMLEditor.CanHandleHTMLEditSubAction(); 768 if (MOZ_UNLIKELY(result.isErr())) { 769 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); 770 return result; 771 } 772 if (result.inspect().Canceled()) { 773 return result; 774 } 775 } 776 777 mHTMLEditor.UndefineCaretBidiLevel(); 778 779 rv = mHTMLEditor.EnsureNoPaddingBRElementForEmptyEditor(); 780 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 781 return Err(NS_ERROR_EDITOR_DESTROYED); 782 } 783 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 784 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() " 785 "failed, but ignored"); 786 787 if (NS_SUCCEEDED(rv) && mHTMLEditor.SelectionRef().IsCollapsed()) { 788 nsresult rv = 789 mHTMLEditor.EnsureCaretNotAfterInvisibleBRElement(mEditingHost); 790 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 791 return Err(NS_ERROR_EDITOR_DESTROYED); 792 } 793 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 794 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() " 795 "failed, but ignored"); 796 if (NS_SUCCEEDED(rv)) { 797 nsresult rv = mHTMLEditor.PrepareInlineStylesForCaret(); 798 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 799 return Err(NS_ERROR_EDITOR_DESTROYED); 800 } 801 NS_WARNING_ASSERTION( 802 NS_SUCCEEDED(rv), 803 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored"); 804 } 805 } 806 807 // Adjust position based on the first node we are going to insert. 808 const auto candidatePointToInsert = 809 mHTMLEditor.GetFirstSelectionStartPoint<EditorRawDOMPoint>(); 810 if (NS_WARN_IF(!candidatePointToInsert.IsSet()) || 811 NS_WARN_IF( 812 !candidatePointToInsert.GetContainer()->IsInclusiveDescendantOf( 813 &mEditingHost))) { 814 return Err(NS_ERROR_FAILURE); 815 } 816 EditorDOMPoint pointToInsert = 817 HTMLEditUtils::GetBetterInsertionPointFor<EditorDOMPoint>( 818 arrayOfTopMostChildContents[0], 819 mHTMLEditor.GetFirstSelectionStartPoint<EditorRawDOMPoint>()); 820 if (!pointToInsert.IsSet()) { 821 NS_WARNING("HTMLEditor::GetBetterInsertionPointFor() failed"); 822 return Err(NS_ERROR_FAILURE); 823 } 824 Result<EditorDOMPoint, nsresult> pointToInsertOrError = 825 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt( 826 mHTMLEditor, pointToInsert, 827 {WhiteSpaceVisibilityKeeper::NormalizeOption:: 828 StopIfFollowingWhiteSpacesStartsWithNBSP}); 829 if (MOZ_UNLIKELY(pointToInsertOrError.isErr())) { 830 NS_WARNING( 831 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt() failed"); 832 return pointToInsertOrError.propagateErr(); 833 } 834 pointToInsert = pointToInsertOrError.unwrap(); 835 if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc())) { 836 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 837 } 838 839 const bool insertionPointWasInLink = 840 !!HTMLEditor::GetLinkElement(pointToInsert.GetContainer()); 841 842 if (pointToInsert.IsInTextNode()) { 843 Result<SplitNodeResult, nsresult> splitNodeResult = 844 mHTMLEditor.SplitNodeDeepWithTransaction( 845 MOZ_KnownLive(*pointToInsert.ContainerAs<nsIContent>()), 846 pointToInsert, SplitAtEdges::eAllowToCreateEmptyContainer); 847 if (MOZ_UNLIKELY(splitNodeResult.isErr())) { 848 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed"); 849 return splitNodeResult.propagateErr(); 850 } 851 nsresult rv = splitNodeResult.inspect().SuggestCaretPointTo( 852 mHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion, 853 SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); 854 if (NS_FAILED(rv)) { 855 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed"); 856 return Err(rv); 857 } 858 pointToInsert = splitNodeResult.inspect().AtSplitPoint<EditorDOMPoint>(); 859 if (MOZ_UNLIKELY(!pointToInsert.IsSet())) { 860 NS_WARNING( 861 "HTMLEditor::SplitNodeDeepWithTransaction() didn't return split " 862 "point"); 863 return Err(NS_ERROR_FAILURE); 864 } 865 } 866 867 { // Block only for AutoHTMLFragmentBoundariesFixer to hide it from the 868 // following code. Note that it may modify arrayOfTopMostChildContents. 869 AutoHTMLFragmentBoundariesFixer fixPiecesOfTablesAndLists( 870 arrayOfTopMostChildContents); 871 } 872 873 MOZ_ASSERT(pointToInsert.GetContainer()->GetChildAt_Deprecated( 874 pointToInsert.Offset()) == pointToInsert.GetChild()); 875 876 Result<CreateContentResult, nsresult> insertNodeResultOrError = 877 InsertContents(pointToInsert, arrayOfTopMostChildContents, 878 fragmentAsNode); 879 if (MOZ_UNLIKELY(insertNodeResultOrError.isErr())) { 880 NS_WARNING("HTMLWithContextInserter::InsertContents() failed."); 881 return insertNodeResultOrError.propagateErr(); 882 } 883 884 // The inserting content may contain empty container elements. However, it's 885 // intended. Therefore, we should not clean up them in the post-processing. 886 mHTMLEditor.TopLevelEditSubActionDataRef().mNeedsToCleanUpEmptyElements = 887 false; 888 889 CreateContentResult insertNodeResult = insertNodeResultOrError.unwrap(); 890 if (MOZ_UNLIKELY(!insertNodeResult.Handled())) { 891 // Even if we haven't inserted new content nodes, we "handled" to insert 892 // them so that return "handled" state. 893 return EditActionResult::HandledResult(); 894 } 895 896 if (MOZ_LIKELY(insertNodeResult.GetNewNode()->IsInComposedDoc())) { 897 const auto afterLastInsertedContent = 898 EditorRawDOMPoint(insertNodeResult.GetNewNode()) 899 .NextPointOrAfterContainer<EditorDOMPoint>(); 900 if (MOZ_LIKELY(afterLastInsertedContent.IsInContentNode())) { 901 nsresult rv = mHTMLEditor.EnsureNoFollowingUnnecessaryLineBreak( 902 afterLastInsertedContent); 903 if (NS_FAILED(rv)) { 904 NS_WARNING( 905 "HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed"); 906 return Err(rv); 907 } 908 } 909 } 910 911 MOZ_ASSERT(insertNodeResult.HasCaretPointSuggestion()); 912 rv = insertNodeResult.SuggestCaretPointTo( 913 mHTMLEditor, {SuggestCaret::AndIgnoreTrivialError}); 914 if (NS_FAILED(rv)) { 915 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); 916 return Err(rv); 917 } 918 NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 919 "CaretPoint::SuggestCaretPointTo() failed, but ignored"); 920 921 // If we didn't start from an `<a href>` element, we should not keep 922 // caret in the link to make users type something outside the link. 923 if (insertionPointWasInLink) { 924 return EditActionResult::HandledResult(); 925 } 926 if (Element* const parentElement = 927 insertNodeResult.GetNewNode()->GetParentElement()) { 928 const RefPtr<Element> linkElement = GetLinkElement(parentElement); 929 if (MOZ_LIKELY(!linkElement)) { 930 return EditActionResult::HandledResult(); 931 } 932 933 nsresult rv = 934 MoveCaretOutsideOfLink(*linkElement, insertNodeResult.CaretPointRef()); 935 if (NS_FAILED(rv)) { 936 NS_WARNING( 937 "HTMLEditor::HTMLWithContextInserter::MoveCaretOutsideOfLink() " 938 "failed"); 939 return Err(rv); 940 } 941 } 942 943 return EditActionResult::HandledResult(); 944 } 945 946 Result<CreateContentResult, nsresult> 947 HTMLEditor::HTMLWithContextInserter::InsertContents( 948 const EditorDOMPoint& aPointToInsert, 949 nsTArray<OwningNonNull<nsIContent>>& aArrayOfTopMostChildContents, 950 const nsINode* aFragmentAsNode) { 951 MOZ_ASSERT(aPointToInsert.IsSetAndValidInComposedDoc()); 952 953 // Loop over the node list and paste the nodes: 954 const RefPtr<const Element> maybeNonEditableBlockElement = 955 aPointToInsert.IsInContentNode() 956 ? HTMLEditUtils::GetInclusiveAncestorElement( 957 *aPointToInsert.ContainerAs<nsIContent>(), 958 HTMLEditUtils::ClosestBlockElement, 959 BlockInlineCheck::UseComputedDisplayOutsideStyle) 960 : nullptr; 961 962 nsCOMPtr<nsIContent> insertedContextParentContent; 963 RefPtr<nsIContent> lastInsertedContent; 964 for (const OwningNonNull<nsIContent>& content : 965 aArrayOfTopMostChildContents) { 966 if (NS_WARN_IF(content == aFragmentAsNode) || 967 NS_WARN_IF(content->IsHTMLElement(nsGkAtoms::body))) { 968 return Err(NS_ERROR_FAILURE); 969 } 970 971 if (insertedContextParentContent) { 972 // If we had to insert something higher up in the paste hierarchy, 973 // we want to skip any further paste nodes that descend from that. 974 // Else we will paste twice. 975 // XXX This check may be really expensive. Cannot we check whether 976 // the node's `ownerDocument` is the `aFragmentAsNode` or not? 977 if (EditorUtils::IsDescendantOf(*content, 978 *insertedContextParentContent)) { 979 continue; 980 } 981 // Okay, now, we finished moving nodes in insertedContextParentContent. 982 // We can forget it now to skip the expensive check. 983 insertedContextParentContent = nullptr; 984 } 985 986 // In the most cases, we want to move `content` into the DOM as-is. However, 987 // in some cases, we don't want to insert content but do want to insert its 988 // children into the existing proper container. Therefore, we will check 989 // the `content` type and insertion point's container below. However, even 990 // in such case, we may not be able to move only its children. Then, we 991 // need to fall it back to the default behavior. Therefore, let's wrap the 992 // default behavior into this lambda. 993 const auto InsertCurrentContentToNextInsertionPoint = 994 [&](const EditorDOMPoint& aPointToInsertContent) 995 MOZ_CAN_RUN_SCRIPT MOZ_NEVER_INLINE_DEBUG -> Result<Ok, nsresult> { 996 // MOZ_KnownLive(content) because 'aArrayOfTopMostChildContents' 997 // guarantees its lifetime. 998 Result<CreateContentResult, nsresult> moveContentResult = 999 mHTMLEditor.InsertNodeIntoProperAncestorWithTransaction<nsIContent>( 1000 MOZ_KnownLive(content), aPointToInsertContent, 1001 SplitAtEdges::eDoNotCreateEmptyContainer); 1002 if (MOZ_LIKELY(moveContentResult.isOk())) { 1003 moveContentResult.inspect().IgnoreCaretPointSuggestion(); 1004 if (MOZ_UNLIKELY(!moveContentResult.inspect().Handled())) { 1005 MOZ_ASSERT(aPointToInsertContent.IsSetAndValidInComposedDoc()); 1006 MOZ_ASSERT_IF(lastInsertedContent, 1007 lastInsertedContent->IsInComposedDoc()); 1008 return Ok{}; 1009 } 1010 lastInsertedContent = content; 1011 MOZ_ASSERT(lastInsertedContent->IsInComposedDoc()); 1012 return Ok{}; 1013 } 1014 // If we got unexpected DOM tree, let's abort. 1015 if (NS_WARN_IF(moveContentResult.inspectErr() == 1016 NS_ERROR_EDITOR_DESTROYED) || 1017 NS_WARN_IF(moveContentResult.inspectErr() == 1018 NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE)) { 1019 return moveContentResult.propagateErr(); 1020 } 1021 // If we the next insertion point becomes invalid, it means that we 1022 // got unexpected DOM tree which couldn't be detected by 1023 // InsertNodeIntoProperAncestorWithTransaction(). Let's abort to 1024 // avoid to move the node into unexpected position/documents. 1025 if (NS_WARN_IF(!aPointToInsertContent.IsSetAndValidInComposedDoc())) { 1026 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1027 } 1028 // Assume failure means no legal parent in the document hierarchy, 1029 // try again with the parent of content in the paste hierarchy. 1030 // FYI: We cannot use `InclusiveAncestorOfType` here because of 1031 // calling `InsertNodeIntoProperAncestorWithTransaction()`. 1032 EditorDOMPoint pointToInsert = aPointToInsertContent; 1033 for (nsCOMPtr<nsIContent> parent = content->GetParent(); parent; 1034 parent = parent->GetParent()) { 1035 if (NS_WARN_IF(parent->IsHTMLElement(nsGkAtoms::body))) { 1036 break; // for the inner `for` loop 1037 } 1038 Result<CreateContentResult, nsresult> moveParentResult = 1039 mHTMLEditor.InsertNodeIntoProperAncestorWithTransaction<nsIContent>( 1040 *parent, pointToInsert, 1041 SplitAtEdges::eDoNotCreateEmptyContainer); 1042 if (MOZ_UNLIKELY(moveParentResult.isErr())) { 1043 // If we got unexpected DOM tree, let's abort. 1044 if (NS_WARN_IF(moveParentResult.inspectErr() == 1045 NS_ERROR_EDITOR_DESTROYED) || 1046 NS_WARN_IF(moveParentResult.inspectErr() == 1047 NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE)) { 1048 return moveParentResult.propagateErr(); 1049 } 1050 // If we the next insertion point becomes invalid, it means that we 1051 // got unexpected DOM tree which couldn't be detected by 1052 // InsertNodeIntoProperAncestorWithTransaction(). Let's abort to 1053 // avoid to move the node into unexpected position/documents. 1054 if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc())) { 1055 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1056 } 1057 // If the parent cannot be inserted into the DOM tree, the node may be 1058 // an element to make a specific structure like a table. Then, we can 1059 // insert one of its ancestors to the inserting position. So, let's 1060 // retry with its parent. 1061 continue; // the inner `for` loop 1062 } 1063 moveParentResult.inspect().IgnoreCaretPointSuggestion(); 1064 if (MOZ_UNLIKELY(!moveParentResult.inspect().Handled())) { 1065 MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); 1066 MOZ_ASSERT_IF(lastInsertedContent, 1067 lastInsertedContent->IsInComposedDoc()); 1068 continue; 1069 } 1070 pointToInsert = EditorDOMPoint::After(*parent); 1071 lastInsertedContent = parent; 1072 MOZ_ASSERT(lastInsertedContent->IsInComposedDoc()); 1073 insertedContextParentContent = std::move(parent); 1074 break; // from the inner `for` loop 1075 } // end of the inner `for` loop iterating ancestors of content 1076 return Ok{}; 1077 }; 1078 1079 // If a `<table>` or `<tr>` element on the clipboard, and pasting it into 1080 // a `<table>` or `<tr>` element, insert only the appropriate children 1081 // instead. 1082 if (HTMLEditUtils::IsTableRowElement(*content)) { 1083 EditorDOMPoint pointToInsert = 1084 lastInsertedContent ? EditorDOMPoint::After(*lastInsertedContent) 1085 : aPointToInsert; 1086 if (HTMLEditUtils::IsTableRowElement( 1087 pointToInsert.GetContainerAs<nsIContent>()) && 1088 (content->IsHTMLElement(nsGkAtoms::table) || 1089 pointToInsert.IsContainerHTMLElement(nsGkAtoms::table))) { 1090 MOZ_ASSERT(!content->IsInComposedDoc()); 1091 bool inserted = false; 1092 for (RefPtr<nsIContent> child = content->GetFirstChild(); child; 1093 child = content->GetFirstChild()) { 1094 Result<CreateContentResult, nsresult> moveChildResult = 1095 mHTMLEditor 1096 .InsertNodeIntoProperAncestorWithTransaction<nsIContent>( 1097 *child, pointToInsert, 1098 SplitAtEdges::eDoNotCreateEmptyContainer); 1099 if (MOZ_UNLIKELY(moveChildResult.isErr())) { 1100 // If we got unexpected DOM tree, let's abort. 1101 if (NS_WARN_IF(moveChildResult.inspectErr() == 1102 NS_ERROR_EDITOR_DESTROYED) || 1103 NS_WARN_IF(moveChildResult.inspectErr() == 1104 NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE)) { 1105 return moveChildResult.propagateErr(); 1106 } 1107 // If we the next insertion point becomes invalid, it means that 1108 // we got unexpected DOM tree which couldn't be detected by 1109 // InsertNodeIntoProperAncestorWithTransaction(). Let's abort to 1110 // avoid to move the node into unexpected position/documents. 1111 if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc())) { 1112 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1113 } 1114 NS_WARNING( 1115 "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction(" 1116 "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but maybe " 1117 "ignored"); 1118 break; // from the inner `for` loop 1119 } 1120 moveChildResult.inspect().IgnoreCaretPointSuggestion(); 1121 if (MOZ_UNLIKELY(!moveChildResult.inspect().Handled())) { 1122 MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); 1123 MOZ_ASSERT_IF(lastInsertedContent, 1124 lastInsertedContent->IsInComposedDoc()); 1125 continue; 1126 } 1127 inserted = true; 1128 pointToInsert = EditorDOMPoint::After(*child); 1129 MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); 1130 lastInsertedContent = std::move(child); 1131 MOZ_ASSERT(lastInsertedContent->IsInComposedDoc()); 1132 } // end of the inner `for` loop iterating children of `content` 1133 if (!inserted) { 1134 Result<Ok, nsresult> moveContentOrParentResultOrError = 1135 InsertCurrentContentToNextInsertionPoint(pointToInsert); 1136 if (MOZ_UNLIKELY(moveContentOrParentResultOrError.isErr())) { 1137 NS_WARNING("InsertCurrentContentToNextInsertionPoint() failed"); 1138 return moveContentOrParentResultOrError.propagateErr(); 1139 } 1140 } 1141 continue; 1142 } 1143 } // if <tr> 1144 1145 // If a list element on the clipboard, and pasting it into a list or 1146 // list item element, insert the appropriate children instead. I.e., 1147 // merge the list elements instead of pasting as a sublist. 1148 if (HTMLEditUtils::IsListElement(*content)) { 1149 EditorDOMPoint pointToInsert = 1150 lastInsertedContent ? EditorDOMPoint::After(*lastInsertedContent) 1151 : aPointToInsert; 1152 if (HTMLEditUtils::IsListElement( 1153 pointToInsert.GetContainerAs<nsIContent>()) || 1154 HTMLEditUtils::IsListItemElement( 1155 pointToInsert.GetContainerAs<nsIContent>())) { 1156 MOZ_ASSERT(!content->IsInComposedDoc()); 1157 bool inserted = false; 1158 for (RefPtr<nsIContent> child = content->GetFirstChild(); child; 1159 child = content->GetFirstChild()) { 1160 // Ignore invisible nodes like `Comment` or white-space only `Text` 1161 // and invalid children of the list element. 1162 // XXX Although we should not construct invalid structure, but 1163 // shouldn't we preserve invalid children for avoiding dataloss? 1164 if (!HTMLEditUtils::IsListItemElement(*child) && 1165 !HTMLEditUtils::IsListElement(*child)) { 1166 continue; 1167 } 1168 // If we're pasting into empty list item, we should remove it 1169 // and past current node into the parent list directly. 1170 // XXX This creates invalid structure if current list item element 1171 // is not proper child of the parent element, or current node 1172 // is a list element. 1173 if (HTMLEditUtils::IsListItemElement( 1174 pointToInsert.GetContainerAs<nsIContent>()) && 1175 HTMLEditUtils::IsRemovableNode( 1176 *pointToInsert.ContainerAs<Element>()) && 1177 HTMLEditUtils::IsEmptyNode( 1178 *pointToInsert.ContainerAs<Element>(), 1179 {EmptyCheckOption::TreatNonEditableContentAsInvisible})) { 1180 const OwningNonNull<Element> emptyListItemElement = 1181 *pointToInsert.ContainerAs<Element>(); 1182 nsCOMPtr<nsINode> parentNode = 1183 emptyListItemElement->GetParentNode(); 1184 MOZ_ASSERT(parentNode); 1185 nsCOMPtr<nsIContent> nextSibling = 1186 emptyListItemElement->GetNextSibling(); 1187 nsresult rv = 1188 mHTMLEditor.DeleteNodeWithTransaction(*emptyListItemElement); 1189 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { 1190 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 1191 return Err(NS_ERROR_EDITOR_DESTROYED); 1192 } 1193 if (NS_WARN_IF(!parentNode->IsInComposedDoc()) || 1194 NS_WARN_IF(nextSibling && 1195 nextSibling->GetParentNode() != parentNode)) { 1196 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1197 } 1198 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1199 "EditorBase::DeleteNodeWithTransaction() " 1200 "failed, but ignored"); 1201 pointToInsert = 1202 nextSibling ? EditorDOMPoint(std::move(nextSibling)) 1203 : EditorDOMPoint::AtEndOf(std::move(parentNode)); 1204 MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); 1205 } 1206 NS_WARNING(nsPrintfCString("%s into %s", ToString(*child).c_str(), 1207 ToString(pointToInsert).c_str()) 1208 .get()); 1209 Result<CreateContentResult, nsresult> moveChildResult = 1210 mHTMLEditor 1211 .InsertNodeIntoProperAncestorWithTransaction<nsIContent>( 1212 *child, pointToInsert, 1213 SplitAtEdges::eDoNotCreateEmptyContainer); 1214 if (MOZ_UNLIKELY(moveChildResult.isErr())) { 1215 // If we got unexpected DOM tree, let's abort. 1216 if (NS_WARN_IF(moveChildResult.inspectErr() == 1217 NS_ERROR_EDITOR_DESTROYED) || 1218 NS_WARN_IF(moveChildResult.inspectErr() == 1219 NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE)) { 1220 return moveChildResult.propagateErr(); 1221 } 1222 // If we the next insertion point becomes invalid, it means that 1223 // we got unexpected DOM tree which couldn't be detected by 1224 // InsertNodeIntoProperAncestorWithTransaction(). Let's abort to 1225 // avoid to move the node into unexpected position/documents. 1226 if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc())) { 1227 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1228 } 1229 NS_WARNING( 1230 "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction(" 1231 "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but maybe " 1232 "ignored"); 1233 break; // from the inner `for` loop 1234 } 1235 moveChildResult.inspect().IgnoreCaretPointSuggestion(); 1236 if (MOZ_UNLIKELY(!moveChildResult.inspect().Handled())) { 1237 MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); 1238 MOZ_ASSERT_IF(lastInsertedContent, 1239 lastInsertedContent->IsInComposedDoc()); 1240 continue; 1241 } 1242 inserted = true; 1243 pointToInsert = EditorDOMPoint::After(*child); 1244 MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); 1245 lastInsertedContent = std::move(child); 1246 MOZ_ASSERT(lastInsertedContent->IsInComposedDoc()); 1247 } // end of the inner `for` loop iterating children of `content` 1248 if (!inserted) { 1249 Result<Ok, nsresult> moveContentOrParentResultOrError = 1250 InsertCurrentContentToNextInsertionPoint(pointToInsert); 1251 if (MOZ_UNLIKELY(moveContentOrParentResultOrError.isErr())) { 1252 NS_WARNING("InsertCurrentContentToNextInsertionPoint() failed"); 1253 return moveContentOrParentResultOrError.propagateErr(); 1254 } 1255 } 1256 continue; 1257 } 1258 } // if <ul>, <ol> or <dl> 1259 1260 // If pasting into a `<pre>` element and current node is a `<pre>` element, 1261 // move only its children. 1262 if (maybeNonEditableBlockElement && 1263 maybeNonEditableBlockElement->IsHTMLElement(nsGkAtoms::pre) && 1264 content->IsHTMLElement(nsGkAtoms::pre)) { 1265 MOZ_ASSERT(!content->IsInComposedDoc()); 1266 EditorDOMPoint pointToInsert = 1267 lastInsertedContent ? EditorDOMPoint::After(*lastInsertedContent) 1268 : aPointToInsert; 1269 bool inserted = false; 1270 for (RefPtr<nsIContent> child = content->GetFirstChild(); child; 1271 child = content->GetFirstChild()) { 1272 Result<CreateContentResult, nsresult> moveChildResult = 1273 mHTMLEditor.InsertNodeIntoProperAncestorWithTransaction<nsIContent>( 1274 *child, pointToInsert, 1275 SplitAtEdges::eDoNotCreateEmptyContainer); 1276 if (MOZ_UNLIKELY(moveChildResult.isErr())) { 1277 // If we got unexpected DOM tree, let's abort. 1278 if (NS_WARN_IF(moveChildResult.inspectErr() == 1279 NS_ERROR_EDITOR_DESTROYED) || 1280 NS_WARN_IF(moveChildResult.inspectErr() == 1281 NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE)) { 1282 return moveChildResult.propagateErr(); 1283 } 1284 // If we the next insertion point becomes invalid, it means that we 1285 // got unexpected DOM tree which couldn't be detected by 1286 // InsertNodeIntoProperAncestorWithTransaction(). Let's abort to 1287 // avoid to move the node into unexpected position/documents. 1288 if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc())) { 1289 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1290 } 1291 NS_WARNING( 1292 "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction(" 1293 "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but maybe " 1294 "ignored"); 1295 break; // from the inner `for` loop 1296 } 1297 moveChildResult.inspect().IgnoreCaretPointSuggestion(); 1298 if (MOZ_UNLIKELY(!moveChildResult.inspect().Handled())) { 1299 MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); 1300 MOZ_ASSERT_IF(lastInsertedContent, 1301 lastInsertedContent->IsInComposedDoc()); 1302 continue; 1303 } 1304 inserted = true; 1305 pointToInsert = EditorDOMPoint::After(*child); 1306 MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); 1307 lastInsertedContent = std::move(child); 1308 MOZ_ASSERT(lastInsertedContent->IsInComposedDoc()); 1309 } // end of the inner `for` loop iterating children of `content` 1310 if (!inserted) { 1311 Result<Ok, nsresult> moveContentOrParentResultOrError = 1312 InsertCurrentContentToNextInsertionPoint(pointToInsert); 1313 if (MOZ_UNLIKELY(moveContentOrParentResultOrError.isErr())) { 1314 NS_WARNING("InsertCurrentContentToNextInsertionPoint() failed"); 1315 return moveContentOrParentResultOrError.propagateErr(); 1316 } 1317 } 1318 continue; 1319 } // if <pre> and inserting into a connected <pre> 1320 1321 // By default, we should move `content` into the DOM. 1322 Result<Ok, nsresult> moveContentOrParentResultOrError = 1323 InsertCurrentContentToNextInsertionPoint( 1324 lastInsertedContent ? EditorDOMPoint::After(*lastInsertedContent) 1325 : aPointToInsert); 1326 if (MOZ_UNLIKELY(moveContentOrParentResultOrError.isErr())) { 1327 NS_WARNING("InsertCurrentContentToNextInsertionPoint() failed"); 1328 return moveContentOrParentResultOrError.propagateErr(); 1329 } 1330 } // end of the `for` loop iterating aArrayOfTopMostChildContents 1331 1332 if (!lastInsertedContent) { 1333 return CreateContentResult::NotHandled(); 1334 } 1335 EditorDOMPoint pointToPutCaret = 1336 GetNewCaretPointAfterInsertingHTML(EditorDOMPoint(lastInsertedContent)); 1337 return CreateContentResult(std::move(lastInsertedContent), 1338 std::move(pointToPutCaret)); 1339 } 1340 1341 nsresult HTMLEditor::HTMLWithContextInserter::MoveCaretOutsideOfLink( 1342 Element& aLinkElement, const EditorDOMPoint& aPointToPutCaret) { 1343 MOZ_ASSERT(HTMLEditUtils::IsHyperlinkElement(aLinkElement)); 1344 1345 // The reason why do that instead of just moving caret after it is, the 1346 // link might have ended in an invisible `<br>` element. If so, the code 1347 // above just placed selection inside that. So we need to split it instead. 1348 // XXX Sounds like that it's not really expensive comparing with the reason 1349 // to use SplitNodeDeepWithTransaction() here. 1350 Result<SplitNodeResult, nsresult> splitLinkResult = 1351 mHTMLEditor.SplitNodeDeepWithTransaction( 1352 aLinkElement, aPointToPutCaret, 1353 SplitAtEdges::eDoNotCreateEmptyContainer); 1354 if (MOZ_UNLIKELY(splitLinkResult.isErr())) { 1355 if (splitLinkResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) { 1356 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed"); 1357 return NS_ERROR_EDITOR_DESTROYED; 1358 } 1359 NS_WARNING( 1360 "HTMLEditor::SplitNodeDeepWithTransaction() failed, but ignored"); 1361 } 1362 1363 if (nsIContent* previousContentOfSplitPoint = 1364 splitLinkResult.inspect().GetPreviousContent()) { 1365 splitLinkResult.inspect().IgnoreCaretPointSuggestion(); 1366 nsresult rv = mHTMLEditor.CollapseSelectionTo( 1367 EditorRawDOMPoint::After(*previousContentOfSplitPoint)); 1368 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 1369 return NS_ERROR_EDITOR_DESTROYED; 1370 } 1371 NS_WARNING_ASSERTION( 1372 NS_SUCCEEDED(rv), 1373 "EditorBase::CollapseSelectionTo() failed, but ignored"); 1374 return NS_OK; 1375 } 1376 1377 nsresult rv = splitLinkResult.inspect().SuggestCaretPointTo( 1378 mHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion, 1379 SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); 1380 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1381 "SplitNodeResult::SuggestCaretPointTo() failed"); 1382 return rv; 1383 } 1384 1385 // static 1386 Element* HTMLEditor::GetLinkElement(nsINode* aNode) { 1387 if (NS_WARN_IF(!aNode)) { 1388 return nullptr; 1389 } 1390 nsINode* node = aNode; 1391 while (node) { 1392 if (node->IsElement() && 1393 HTMLEditUtils::IsHyperlinkElement(*node->AsElement())) { 1394 return node->AsElement(); 1395 } 1396 node = node->GetParentNode(); 1397 } 1398 return nullptr; 1399 } 1400 1401 // static 1402 nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator:: 1403 RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces( 1404 nsIContent& aNode, NodesToRemove aNodesToRemove) { 1405 if (aNode.TextIsOnlyWhitespace()) { 1406 nsCOMPtr<nsINode> parent = aNode.GetParentNode(); 1407 // TODO: presumably, if the parent is a `<pre>` element, the node 1408 // shouldn't be removed. 1409 if (parent) { 1410 if (aNodesToRemove == NodesToRemove::eAll || 1411 HTMLEditUtils::IsListElement(nsIContent::FromNode(parent))) { 1412 ErrorResult error; 1413 parent->RemoveChild(aNode, error); 1414 NS_WARNING_ASSERTION(!error.Failed(), "nsINode::RemoveChild() failed"); 1415 return error.StealNSResult(); 1416 } 1417 return NS_OK; 1418 } 1419 } 1420 1421 if (!aNode.IsHTMLElement(nsGkAtoms::pre)) { 1422 nsCOMPtr<nsIContent> child = aNode.GetLastChild(); 1423 while (child) { 1424 nsCOMPtr<nsIContent> previous = child->GetPreviousSibling(); 1425 nsresult rv = FragmentFromPasteCreator:: 1426 RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces( 1427 *child, aNodesToRemove); 1428 if (NS_FAILED(rv)) { 1429 NS_WARNING( 1430 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::" 1431 "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces" 1432 "() " 1433 "failed"); 1434 return rv; 1435 } 1436 child = std::move(previous); 1437 } 1438 } 1439 return NS_OK; 1440 } 1441 1442 class MOZ_STACK_CLASS HTMLEditor::HTMLTransferablePreparer { 1443 public: 1444 HTMLTransferablePreparer(const HTMLEditor& aHTMLEditor, 1445 nsITransferable** aTransferable, 1446 const Element* aEditingHost); 1447 1448 nsresult Run(); 1449 1450 private: 1451 void AddDataFlavorsInBestOrder(nsITransferable& aTransferable) const; 1452 1453 const HTMLEditor& mHTMLEditor; 1454 const Element* const mEditingHost; 1455 nsITransferable** mTransferable; 1456 }; 1457 1458 HTMLEditor::HTMLTransferablePreparer::HTMLTransferablePreparer( 1459 const HTMLEditor& aHTMLEditor, nsITransferable** aTransferable, 1460 const Element* aEditingHost) 1461 : mHTMLEditor{aHTMLEditor}, 1462 mEditingHost(aEditingHost), 1463 mTransferable{aTransferable} { 1464 MOZ_ASSERT(mTransferable); 1465 MOZ_ASSERT(!*mTransferable); 1466 } 1467 1468 nsresult HTMLEditor::PrepareHTMLTransferable( 1469 nsITransferable** aTransferable, const Element* aEditingHost) const { 1470 HTMLTransferablePreparer htmlTransferablePreparer{*this, aTransferable, 1471 aEditingHost}; 1472 return htmlTransferablePreparer.Run(); 1473 } 1474 1475 nsresult HTMLEditor::HTMLTransferablePreparer::Run() { 1476 // Create generic Transferable for getting the data 1477 nsresult rv; 1478 RefPtr<nsITransferable> transferable = 1479 do_CreateInstance("@mozilla.org/widget/transferable;1", &rv); 1480 if (NS_FAILED(rv)) { 1481 NS_WARNING("do_CreateInstance() failed to create nsITransferable instance"); 1482 return rv; 1483 } 1484 1485 if (!transferable) { 1486 NS_WARNING("do_CreateInstance() returned nullptr, but ignored"); 1487 return NS_OK; 1488 } 1489 1490 // Get the nsITransferable interface for getting the data from the clipboard 1491 RefPtr<Document> destdoc = mHTMLEditor.GetDocument(); 1492 nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr; 1493 DebugOnly<nsresult> rvIgnored = transferable->Init(loadContext); 1494 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1495 "nsITransferable::Init() failed, but ignored"); 1496 1497 // See `HTMLEditor::InsertFromTransferableAtSelection`. 1498 AddDataFlavorsInBestOrder(*transferable); 1499 1500 transferable.forget(mTransferable); 1501 1502 return NS_OK; 1503 } 1504 1505 void HTMLEditor::HTMLTransferablePreparer::AddDataFlavorsInBestOrder( 1506 nsITransferable& aTransferable) const { 1507 // Create the desired DataFlavor for the type of data 1508 // we want to get out of the transferable 1509 // This should only happen in html editors, not plaintext 1510 // Note that if you add more flavors here you will need to add them 1511 // to DataTransfer::GetExternalClipboardFormats as well. 1512 if (!mHTMLEditor.IsPlaintextMailComposer() && 1513 !(mEditingHost && mEditingHost->IsContentEditablePlainTextOnly())) { 1514 DebugOnly<nsresult> rvIgnored = 1515 aTransferable.AddDataFlavor(kNativeHTMLMime); 1516 NS_WARNING_ASSERTION( 1517 NS_SUCCEEDED(rvIgnored), 1518 "nsITransferable::AddDataFlavor(kNativeHTMLMime) failed, but ignored"); 1519 rvIgnored = aTransferable.AddDataFlavor(kHTMLMime); 1520 NS_WARNING_ASSERTION( 1521 NS_SUCCEEDED(rvIgnored), 1522 "nsITransferable::AddDataFlavor(kHTMLMime) failed, but ignored"); 1523 rvIgnored = aTransferable.AddDataFlavor(kFileMime); 1524 NS_WARNING_ASSERTION( 1525 NS_SUCCEEDED(rvIgnored), 1526 "nsITransferable::AddDataFlavor(kFileMime) failed, but ignored"); 1527 1528 switch (Preferences::GetInt("clipboard.paste_image_type", 1)) { 1529 case 0: // prefer JPEG over PNG over GIF encoding 1530 rvIgnored = aTransferable.AddDataFlavor(kJPEGImageMime); 1531 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1532 "nsITransferable::AddDataFlavor(kJPEGImageMime) " 1533 "failed, but ignored"); 1534 rvIgnored = aTransferable.AddDataFlavor(kJPGImageMime); 1535 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1536 "nsITransferable::AddDataFlavor(kJPGImageMime) " 1537 "failed, but ignored"); 1538 rvIgnored = aTransferable.AddDataFlavor(kPNGImageMime); 1539 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1540 "nsITransferable::AddDataFlavor(kPNGImageMime) " 1541 "failed, but ignored"); 1542 rvIgnored = aTransferable.AddDataFlavor(kGIFImageMime); 1543 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1544 "nsITransferable::AddDataFlavor(kGIFImageMime) " 1545 "failed, but ignored"); 1546 break; 1547 case 1: // prefer PNG over JPEG over GIF encoding (default) 1548 default: 1549 rvIgnored = aTransferable.AddDataFlavor(kPNGImageMime); 1550 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1551 "nsITransferable::AddDataFlavor(kPNGImageMime) " 1552 "failed, but ignored"); 1553 rvIgnored = aTransferable.AddDataFlavor(kJPEGImageMime); 1554 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1555 "nsITransferable::AddDataFlavor(kJPEGImageMime) " 1556 "failed, but ignored"); 1557 rvIgnored = aTransferable.AddDataFlavor(kJPGImageMime); 1558 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1559 "nsITransferable::AddDataFlavor(kJPGImageMime) " 1560 "failed, but ignored"); 1561 rvIgnored = aTransferable.AddDataFlavor(kGIFImageMime); 1562 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1563 "nsITransferable::AddDataFlavor(kGIFImageMime) " 1564 "failed, but ignored"); 1565 break; 1566 case 2: // prefer GIF over JPEG over PNG encoding 1567 rvIgnored = aTransferable.AddDataFlavor(kGIFImageMime); 1568 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1569 "nsITransferable::AddDataFlavor(kGIFImageMime) " 1570 "failed, but ignored"); 1571 rvIgnored = aTransferable.AddDataFlavor(kJPEGImageMime); 1572 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1573 "nsITransferable::AddDataFlavor(kJPEGImageMime) " 1574 "failed, but ignored"); 1575 rvIgnored = aTransferable.AddDataFlavor(kJPGImageMime); 1576 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1577 "nsITransferable::AddDataFlavor(kJPGImageMime) " 1578 "failed, but ignored"); 1579 rvIgnored = aTransferable.AddDataFlavor(kPNGImageMime); 1580 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1581 "nsITransferable::AddDataFlavor(kPNGImageMime) " 1582 "failed, but ignored"); 1583 break; 1584 } 1585 } 1586 DebugOnly<nsresult> rvIgnored = aTransferable.AddDataFlavor(kTextMime); 1587 NS_WARNING_ASSERTION( 1588 NS_SUCCEEDED(rvIgnored), 1589 "nsITransferable::AddDataFlavor(kTextMime) failed, but ignored"); 1590 rvIgnored = aTransferable.AddDataFlavor(kMozTextInternal); 1591 NS_WARNING_ASSERTION( 1592 NS_SUCCEEDED(rvIgnored), 1593 "nsITransferable::AddDataFlavor(kMozTextInternal) failed, but ignored"); 1594 } 1595 1596 bool FindIntegerAfterString(const char* aLeadingString, const nsCString& aCStr, 1597 int32_t& foundNumber) { 1598 // first obtain offsets from cfhtml str 1599 int32_t numFront = aCStr.Find(aLeadingString); 1600 if (numFront == -1) { 1601 return false; 1602 } 1603 numFront += strlen(aLeadingString); 1604 1605 int32_t numBack = aCStr.FindCharInSet(CRLF, numFront); 1606 if (numBack == -1) { 1607 return false; 1608 } 1609 1610 nsAutoCString numStr(Substring(aCStr, numFront, numBack - numFront)); 1611 nsresult errorCode; 1612 foundNumber = numStr.ToInteger(&errorCode); 1613 return true; 1614 } 1615 1616 void RemoveFragComments(nsCString& aStr) { 1617 // remove the StartFragment/EndFragment comments from the str, if present 1618 int32_t startCommentIndx = aStr.Find("<!--StartFragment"); 1619 if (startCommentIndx >= 0) { 1620 int32_t startCommentEnd = aStr.Find("-->", startCommentIndx); 1621 if (startCommentEnd > startCommentIndx) { 1622 aStr.Cut(startCommentIndx, (startCommentEnd + 3) - startCommentIndx); 1623 } 1624 } 1625 int32_t endCommentIndx = aStr.Find("<!--EndFragment"); 1626 if (endCommentIndx >= 0) { 1627 int32_t endCommentEnd = aStr.Find("-->", endCommentIndx); 1628 if (endCommentEnd > endCommentIndx) { 1629 aStr.Cut(endCommentIndx, (endCommentEnd + 3) - endCommentIndx); 1630 } 1631 } 1632 } 1633 1634 nsresult HTMLEditor::ParseCFHTML(const nsCString& aCfhtml, 1635 char16_t** aStuffToPaste, 1636 char16_t** aCfcontext) { 1637 // First obtain offsets from cfhtml str. 1638 int32_t startHTML, endHTML, startFragment, endFragment; 1639 if (!FindIntegerAfterString("StartHTML:", aCfhtml, startHTML) || 1640 startHTML < -1) { 1641 return NS_ERROR_FAILURE; 1642 } 1643 if (!FindIntegerAfterString("EndHTML:", aCfhtml, endHTML) || endHTML < -1) { 1644 return NS_ERROR_FAILURE; 1645 } 1646 if (!FindIntegerAfterString("StartFragment:", aCfhtml, startFragment) || 1647 startFragment < 0) { 1648 return NS_ERROR_FAILURE; 1649 } 1650 if (!FindIntegerAfterString("EndFragment:", aCfhtml, endFragment) || 1651 startFragment < 0) { 1652 return NS_ERROR_FAILURE; 1653 } 1654 1655 // The StartHTML and EndHTML markers are allowed to be -1 to include 1656 // everything. 1657 // See Reference: MSDN doc entitled "HTML Clipboard Format" 1658 // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx#unknown_854 1659 if (startHTML == -1) { 1660 startHTML = aCfhtml.Find("<!--StartFragment-->"); 1661 if (startHTML == -1) { 1662 return NS_OK; 1663 } 1664 } 1665 if (endHTML == -1) { 1666 const char endFragmentMarker[] = "<!--EndFragment-->"; 1667 endHTML = aCfhtml.Find(endFragmentMarker); 1668 if (endHTML == -1) { 1669 return NS_OK; 1670 } 1671 endHTML += std::size(endFragmentMarker) - 1; 1672 } 1673 1674 // create context string 1675 nsAutoCString contextUTF8( 1676 Substring(aCfhtml, startHTML, startFragment - startHTML) + 1677 "<!--" kInsertCookie "-->"_ns + 1678 Substring(aCfhtml, endFragment, endHTML - endFragment)); 1679 1680 // validate startFragment 1681 // make sure it's not in the middle of a HTML tag 1682 // see bug #228879 for more details 1683 int32_t curPos = startFragment; 1684 while (curPos > startHTML) { 1685 if (aCfhtml[curPos] == '>') { 1686 // working backwards, the first thing we see is the end of a tag 1687 // so StartFragment is good, so do nothing. 1688 break; 1689 } 1690 if (aCfhtml[curPos] == '<') { 1691 // if we are at the start, then we want to see the '<' 1692 if (curPos != startFragment) { 1693 // working backwards, the first thing we see is the start of a tag 1694 // so StartFragment is bad, so we need to update it. 1695 NS_ERROR( 1696 "StartFragment byte count in the clipboard looks bad, see bug " 1697 "#228879"); 1698 startFragment = curPos - 1; 1699 } 1700 break; 1701 } 1702 curPos--; 1703 } 1704 1705 // create fragment string 1706 nsAutoCString fragmentUTF8( 1707 Substring(aCfhtml, startFragment, endFragment - startFragment)); 1708 1709 // remove the StartFragment/EndFragment comments from the fragment, if present 1710 RemoveFragComments(fragmentUTF8); 1711 1712 // remove the StartFragment/EndFragment comments from the context, if present 1713 RemoveFragComments(contextUTF8); 1714 1715 // convert both strings to usc2 1716 const nsString& fragUcs2Str = NS_ConvertUTF8toUTF16(fragmentUTF8); 1717 const nsString& cntxtUcs2Str = NS_ConvertUTF8toUTF16(contextUTF8); 1718 1719 // translate platform linebreaks for fragment 1720 int32_t oldLengthInChars = 1721 fragUcs2Str.Length() + 1; // +1 to include null terminator 1722 int32_t newLengthInChars = 0; 1723 *aStuffToPaste = nsLinebreakConverter::ConvertUnicharLineBreaks( 1724 fragUcs2Str.get(), nsLinebreakConverter::eLinebreakAny, 1725 nsLinebreakConverter::eLinebreakContent, oldLengthInChars, 1726 &newLengthInChars); 1727 if (!*aStuffToPaste) { 1728 NS_WARNING("nsLinebreakConverter::ConvertUnicharLineBreaks() failed"); 1729 return NS_ERROR_FAILURE; 1730 } 1731 1732 // translate platform linebreaks for context 1733 oldLengthInChars = 1734 cntxtUcs2Str.Length() + 1; // +1 to include null terminator 1735 newLengthInChars = 0; 1736 *aCfcontext = nsLinebreakConverter::ConvertUnicharLineBreaks( 1737 cntxtUcs2Str.get(), nsLinebreakConverter::eLinebreakAny, 1738 nsLinebreakConverter::eLinebreakContent, oldLengthInChars, 1739 &newLengthInChars); 1740 // it's ok for context to be empty. frag might be whole doc and contain all 1741 // its context. 1742 1743 // we're done! 1744 return NS_OK; 1745 } 1746 1747 static nsresult ImgFromData(const nsACString& aType, const nsACString& aData, 1748 nsString& aOutput) { 1749 aOutput.AssignLiteral("<IMG src=\"data:"); 1750 AppendUTF8toUTF16(aType, aOutput); 1751 aOutput.AppendLiteral(";base64,"); 1752 nsresult rv = Base64EncodeAppend(aData, aOutput); 1753 if (NS_FAILED(rv)) { 1754 NS_WARNING("Base64Encode() failed"); 1755 return rv; 1756 } 1757 aOutput.AppendLiteral("\" alt=\"\" >"); 1758 return NS_OK; 1759 } 1760 1761 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLEditor::BlobReader) 1762 1763 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HTMLEditor::BlobReader) 1764 NS_IMPL_CYCLE_COLLECTION_UNLINK(mBlob) 1765 NS_IMPL_CYCLE_COLLECTION_UNLINK(mHTMLEditor) 1766 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPointToInsert) 1767 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 1768 1769 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(HTMLEditor::BlobReader) 1770 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBlob) 1771 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHTMLEditor) 1772 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPointToInsert) 1773 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 1774 1775 HTMLEditor::BlobReader::BlobReader(BlobImpl* aBlob, HTMLEditor* aHTMLEditor, 1776 SafeToInsertData aSafeToInsertData, 1777 const EditorDOMPoint& aPointToInsert, 1778 DeleteSelectedContent aDeleteSelectedContent, 1779 const Element& aEditingHost) 1780 : mBlob(aBlob), 1781 mHTMLEditor(aHTMLEditor), 1782 mEditingHost(&aEditingHost), 1783 // "beforeinput" event should've been dispatched before we read blob, 1784 // but anyway, we need to clone dataTransfer for "input" event. 1785 mDataTransfer(mHTMLEditor->GetInputEventDataTransfer()), 1786 mPointToInsert(aPointToInsert), 1787 mEditAction(aHTMLEditor->GetEditAction()), 1788 mSafeToInsertData(aSafeToInsertData), 1789 mDeleteSelectedContent(aDeleteSelectedContent), 1790 mNeedsToDispatchBeforeInputEvent( 1791 !mHTMLEditor->HasTriedToDispatchBeforeInputEvent()) { 1792 MOZ_ASSERT(mBlob); 1793 MOZ_ASSERT(mHTMLEditor); 1794 MOZ_ASSERT(mHTMLEditor->IsEditActionDataAvailable()); 1795 MOZ_ASSERT(mDataTransfer); 1796 1797 // Take only offset here since it's our traditional behavior. 1798 if (mPointToInsert.IsSet()) { 1799 AutoEditorDOMPointChildInvalidator storeOnlyWithOffset(mPointToInsert); 1800 } 1801 } 1802 1803 nsresult HTMLEditor::BlobReader::OnResult(const nsACString& aResult) { 1804 if (NS_WARN_IF(!mEditingHost)) { 1805 return NS_ERROR_FAILURE; 1806 } 1807 AutoEditActionDataSetter editActionData(*mHTMLEditor, mEditAction); 1808 editActionData.InitializeDataTransfer(mDataTransfer); 1809 if (NS_WARN_IF(!editActionData.CanHandle())) { 1810 return NS_ERROR_FAILURE; 1811 } 1812 1813 if (NS_WARN_IF(mNeedsToDispatchBeforeInputEvent)) { 1814 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent(); 1815 if (NS_FAILED(rv)) { 1816 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 1817 "MaybeDispatchBeforeInputEvent(), failed"); 1818 return EditorBase::ToGenericNSResult(rv); 1819 } 1820 } else { 1821 editActionData.MarkAsBeforeInputHasBeenDispatched(); 1822 } 1823 1824 nsString blobType; 1825 mBlob->GetType(blobType); 1826 1827 // TODO: This does not work well. 1828 // * If the data is not an image file, this inserts <img> element with odd 1829 // data URI (bug 1610220). 1830 // * If the data is valid image file data, an <img> file is inserted with 1831 // data URI, but it's not loaded (bug 1610219). 1832 NS_ConvertUTF16toUTF8 type(blobType); 1833 nsAutoString stuffToPaste; 1834 nsresult rv = ImgFromData(type, aResult, stuffToPaste); 1835 if (NS_FAILED(rv)) { 1836 NS_WARNING("ImgFormData() failed"); 1837 return EditorBase::ToGenericNSResult(rv); 1838 } 1839 1840 RefPtr<HTMLEditor> htmlEditor = std::move(mHTMLEditor); 1841 AutoPlaceholderBatch treatAsOneTransaction( 1842 *htmlEditor, ScrollSelectionIntoView::Yes, __FUNCTION__); 1843 EditorDOMPoint pointToInsert = std::move(mPointToInsert); 1844 const RefPtr<const Element> editingHost = std::move(mEditingHost); 1845 rv = htmlEditor->InsertHTMLWithContextAsSubAction( 1846 stuffToPaste, u""_ns, u""_ns, NS_LITERAL_STRING_FROM_CSTRING(kFileMime), 1847 mSafeToInsertData, pointToInsert, mDeleteSelectedContent, 1848 InlineStylesAtInsertionPoint::Preserve, *editingHost); 1849 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1850 "HTMLEditor::InsertHTMLWithContextAsSubAction(" 1851 "InlineStylesAtInsertionPoint::Preserve) failed"); 1852 return EditorBase::ToGenericNSResult(rv); 1853 } 1854 1855 nsresult HTMLEditor::BlobReader::OnError(const nsAString& aError) { 1856 AutoTArray<nsString, 1> error; 1857 error.AppendElement(aError); 1858 nsContentUtils::ReportToConsole( 1859 nsIScriptError::warningFlag, "Editor"_ns, mHTMLEditor->GetDocument(), 1860 nsContentUtils::eDOM_PROPERTIES, "EditorFileDropFailed", error); 1861 return NS_OK; 1862 } 1863 1864 class SlurpBlobEventListener final : public nsIDOMEventListener { 1865 public: 1866 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 1867 NS_DECL_CYCLE_COLLECTION_CLASS(SlurpBlobEventListener) 1868 1869 explicit SlurpBlobEventListener(HTMLEditor::BlobReader* aListener) 1870 : mListener(aListener) {} 1871 1872 MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override; 1873 1874 private: 1875 ~SlurpBlobEventListener() = default; 1876 1877 RefPtr<HTMLEditor::BlobReader> mListener; 1878 }; 1879 1880 NS_IMPL_CYCLE_COLLECTION(SlurpBlobEventListener, mListener) 1881 1882 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SlurpBlobEventListener) 1883 NS_INTERFACE_MAP_ENTRY(nsISupports) 1884 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) 1885 NS_INTERFACE_MAP_END 1886 1887 NS_IMPL_CYCLE_COLLECTING_ADDREF(SlurpBlobEventListener) 1888 NS_IMPL_CYCLE_COLLECTING_RELEASE(SlurpBlobEventListener) 1889 1890 NS_IMETHODIMP SlurpBlobEventListener::HandleEvent(Event* aEvent) { 1891 EventTarget* target = aEvent->GetTarget(); 1892 if (!target || !mListener) { 1893 return NS_OK; 1894 } 1895 1896 RefPtr<FileReader> reader = do_QueryObject(target); 1897 if (!reader) { 1898 return NS_OK; 1899 } 1900 1901 EventMessage message = aEvent->WidgetEventPtr()->mMessage; 1902 1903 RefPtr<HTMLEditor::BlobReader> listener(mListener); 1904 if (message == eLoad) { 1905 MOZ_ASSERT(reader->DataFormat() == FileReader::FILE_AS_BINARY); 1906 1907 // The original data has been converted from Latin1 to UTF-16, this just 1908 // undoes that conversion. 1909 DebugOnly<nsresult> rvIgnored = 1910 listener->OnResult(NS_LossyConvertUTF16toASCII(reader->Result())); 1911 NS_WARNING_ASSERTION( 1912 NS_SUCCEEDED(rvIgnored), 1913 "HTMLEditor::BlobReader::OnResult() failed, but ignored"); 1914 return NS_OK; 1915 } 1916 1917 if (message == eLoadError) { 1918 nsAutoString errorMessage; 1919 reader->GetError()->GetErrorMessage(errorMessage); 1920 DebugOnly<nsresult> rvIgnored = listener->OnError(errorMessage); 1921 NS_WARNING_ASSERTION( 1922 NS_SUCCEEDED(rvIgnored), 1923 "HTMLEditor::BlobReader::OnError() failed, but ignored"); 1924 return NS_OK; 1925 } 1926 1927 return NS_OK; 1928 } 1929 1930 // static 1931 nsresult HTMLEditor::SlurpBlob(Blob* aBlob, nsIGlobalObject* aGlobal, 1932 BlobReader* aBlobReader) { 1933 MOZ_ASSERT(aBlob); 1934 MOZ_ASSERT(aGlobal); 1935 MOZ_ASSERT(aBlobReader); 1936 1937 RefPtr<WeakWorkerRef> workerRef; 1938 RefPtr<FileReader> reader = new FileReader(aGlobal, workerRef); 1939 1940 RefPtr<SlurpBlobEventListener> eventListener = 1941 new SlurpBlobEventListener(aBlobReader); 1942 1943 nsresult rv = reader->AddEventListener(u"load"_ns, eventListener, false); 1944 if (NS_FAILED(rv)) { 1945 NS_WARNING("FileReader::AddEventListener(load) failed"); 1946 return rv; 1947 } 1948 1949 rv = reader->AddEventListener(u"error"_ns, eventListener, false); 1950 if (NS_FAILED(rv)) { 1951 NS_WARNING("FileReader::AddEventListener(error) failed"); 1952 return rv; 1953 } 1954 1955 ErrorResult error; 1956 reader->ReadAsBinaryString(*aBlob, error); 1957 NS_WARNING_ASSERTION(!error.Failed(), 1958 "FileReader::ReadAsBinaryString() failed"); 1959 return error.StealNSResult(); 1960 } 1961 1962 nsresult HTMLEditor::InsertObject(const nsACString& aType, nsISupports* aObject, 1963 SafeToInsertData aSafeToInsertData, 1964 const EditorDOMPoint& aPointToInsert, 1965 DeleteSelectedContent aDeleteSelectedContent, 1966 const Element& aEditingHost) { 1967 MOZ_ASSERT(IsEditActionDataAvailable()); 1968 1969 // Check to see if we the file is actually an image. 1970 nsAutoCString type(aType); 1971 if (type.EqualsLiteral(kFileMime)) { 1972 if (nsCOMPtr<nsIFile> file = do_QueryInterface(aObject)) { 1973 nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1"); 1974 if (NS_WARN_IF(!mime)) { 1975 return NS_ERROR_FAILURE; 1976 } 1977 1978 nsresult rv = mime->GetTypeFromFile(file, type); 1979 if (NS_FAILED(rv)) { 1980 NS_WARNING("nsIMIMEService::GetTypeFromFile() failed"); 1981 return rv; 1982 } 1983 } 1984 } 1985 1986 nsCOMPtr<nsISupports> object = aObject; 1987 if (type.EqualsLiteral(kJPEGImageMime) || type.EqualsLiteral(kJPGImageMime) || 1988 type.EqualsLiteral(kPNGImageMime) || type.EqualsLiteral(kGIFImageMime)) { 1989 if (nsCOMPtr<nsIFile> file = do_QueryInterface(object)) { 1990 object = new FileBlobImpl(file); 1991 // Fallthrough to BlobImpl code below. 1992 } else if (RefPtr<Blob> blob = do_QueryObject(object)) { 1993 object = blob->Impl(); 1994 // Fallthrough. 1995 } else if (nsCOMPtr<nsIInputStream> imageStream = 1996 do_QueryInterface(object)) { 1997 nsCString imageData; 1998 nsresult rv = NS_ConsumeStream(imageStream, UINT32_MAX, imageData); 1999 if (NS_FAILED(rv)) { 2000 NS_WARNING("NS_ConsumeStream() failed"); 2001 return rv; 2002 } 2003 2004 rv = imageStream->Close(); 2005 if (NS_FAILED(rv)) { 2006 NS_WARNING("nsIInputStream::Close() failed"); 2007 return rv; 2008 } 2009 2010 nsAutoString stuffToPaste; 2011 rv = ImgFromData(type, imageData, stuffToPaste); 2012 if (NS_FAILED(rv)) { 2013 NS_WARNING("ImgFromData() failed"); 2014 return rv; 2015 } 2016 2017 AutoPlaceholderBatch treatAsOneTransaction( 2018 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 2019 rv = InsertHTMLWithContextAsSubAction( 2020 stuffToPaste, u""_ns, u""_ns, 2021 NS_LITERAL_STRING_FROM_CSTRING(kFileMime), aSafeToInsertData, 2022 aPointToInsert, aDeleteSelectedContent, 2023 InlineStylesAtInsertionPoint::Preserve, aEditingHost); 2024 NS_WARNING_ASSERTION( 2025 NS_SUCCEEDED(rv), 2026 "HTMLEditor::InsertHTMLWithContextAsSubAction(" 2027 "InlineStylesAtInsertionPoint::Preserve) failed, but ignored"); 2028 return NS_OK; 2029 } else { 2030 NS_WARNING("HTMLEditor::InsertObject: Unexpected type for image mime"); 2031 return NS_OK; 2032 } 2033 } 2034 2035 // We always try to insert BlobImpl even without a known image mime. 2036 nsCOMPtr<BlobImpl> blob = do_QueryInterface(object); 2037 if (!blob) { 2038 return NS_OK; 2039 } 2040 2041 RefPtr<BlobReader> br = 2042 new BlobReader(blob, this, aSafeToInsertData, aPointToInsert, 2043 aDeleteSelectedContent, aEditingHost); 2044 2045 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow(); 2046 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(inner); 2047 if (!global) { 2048 NS_WARNING("Could not get global"); 2049 return NS_ERROR_FAILURE; 2050 } 2051 2052 RefPtr<Blob> domBlob = Blob::Create(global, blob); 2053 if (!domBlob) { 2054 NS_WARNING("Blob::Create() failed"); 2055 return NS_ERROR_FAILURE; 2056 } 2057 2058 nsresult rv = SlurpBlob(domBlob, global, br); 2059 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::::SlurpBlob() failed"); 2060 return rv; 2061 } 2062 2063 static bool GetString(nsISupports* aData, nsAString& aText) { 2064 if (nsCOMPtr<nsISupportsString> str = do_QueryInterface(aData)) { 2065 DebugOnly<nsresult> rvIgnored = str->GetData(aText); 2066 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 2067 "nsISupportsString::GetData() failed, but ignored"); 2068 return !aText.IsEmpty(); 2069 } 2070 2071 return false; 2072 } 2073 2074 static bool GetCString(nsISupports* aData, nsACString& aText) { 2075 if (nsCOMPtr<nsISupportsCString> str = do_QueryInterface(aData)) { 2076 DebugOnly<nsresult> rvIgnored = str->GetData(aText); 2077 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 2078 "nsISupportsString::GetData() failed, but ignored"); 2079 return !aText.IsEmpty(); 2080 } 2081 2082 return false; 2083 } 2084 2085 nsresult HTMLEditor::InsertFromTransferableAtSelection( 2086 nsITransferable* aTransferable, const nsAString& aContextStr, 2087 const nsAString& aInfoStr, HavePrivateHTMLFlavor aHavePrivateHTMLFlavor, 2088 const Element& aEditingHost) { 2089 nsAutoCString bestFlavor; 2090 nsCOMPtr<nsISupports> genericDataObj; 2091 2092 // See `HTMLTransferablePreparer::AddDataFlavorsInBestOrder`. 2093 nsresult rv = aTransferable->GetAnyTransferData( 2094 bestFlavor, getter_AddRefs(genericDataObj)); 2095 NS_WARNING_ASSERTION( 2096 NS_SUCCEEDED(rv), 2097 "nsITransferable::GetAnyTransferData() failed, but ignored"); 2098 if (NS_SUCCEEDED(rv)) { 2099 AutoTransactionsConserveSelection dontChangeMySelection(*this); 2100 nsAutoString flavor; 2101 CopyASCIItoUTF16(bestFlavor, flavor); 2102 const SafeToInsertData safeToInsertData = IsSafeToInsertData(nullptr); 2103 2104 if (bestFlavor.EqualsLiteral(kFileMime) || 2105 bestFlavor.EqualsLiteral(kJPEGImageMime) || 2106 bestFlavor.EqualsLiteral(kJPGImageMime) || 2107 bestFlavor.EqualsLiteral(kPNGImageMime) || 2108 bestFlavor.EqualsLiteral(kGIFImageMime)) { 2109 nsresult rv = InsertObject(bestFlavor, genericDataObj, safeToInsertData, 2110 EditorDOMPoint(), DeleteSelectedContent::Yes, 2111 aEditingHost); 2112 if (NS_FAILED(rv)) { 2113 NS_WARNING("HTMLEditor::InsertObject() failed"); 2114 return rv; 2115 } 2116 } else if (bestFlavor.EqualsLiteral(kNativeHTMLMime)) { 2117 // note cf_html uses utf8 2118 nsAutoCString cfhtml; 2119 if (GetCString(genericDataObj, cfhtml)) { 2120 // cfselection left emtpy for now. 2121 nsString cfcontext, cffragment, cfselection; 2122 nsresult rv = ParseCFHTML(cfhtml, getter_Copies(cffragment), 2123 getter_Copies(cfcontext)); 2124 if (NS_SUCCEEDED(rv) && !cffragment.IsEmpty()) { 2125 // If we have our private HTML flavor, we will only use the fragment 2126 // from the CF_HTML. The rest comes from the clipboard. 2127 if (aHavePrivateHTMLFlavor == HavePrivateHTMLFlavor::Yes) { 2128 AutoPlaceholderBatch treatAsOneTransaction( 2129 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 2130 rv = InsertHTMLWithContextAsSubAction( 2131 cffragment, aContextStr, aInfoStr, flavor, safeToInsertData, 2132 EditorDOMPoint(), DeleteSelectedContent::Yes, 2133 InlineStylesAtInsertionPoint::Clear, aEditingHost); 2134 if (NS_FAILED(rv)) { 2135 NS_WARNING( 2136 "HTMLEditor::InsertHTMLWithContextAsSubAction(" 2137 "DeleteSelectedContent::Yes, " 2138 "InlineStylesAtInsertionPoint::Clear) failed"); 2139 return rv; 2140 } 2141 } else { 2142 AutoPlaceholderBatch treatAsOneTransaction( 2143 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 2144 rv = InsertHTMLWithContextAsSubAction( 2145 cffragment, cfcontext, cfselection, flavor, safeToInsertData, 2146 EditorDOMPoint(), DeleteSelectedContent::Yes, 2147 InlineStylesAtInsertionPoint::Clear, aEditingHost); 2148 if (NS_FAILED(rv)) { 2149 NS_WARNING( 2150 "HTMLEditor::InsertHTMLWithContextAsSubAction(" 2151 "DeleteSelectedContent::Yes, " 2152 "InlineStylesAtInsertionPoint::Clear) failed"); 2153 return rv; 2154 } 2155 } 2156 } else { 2157 // In some platforms (like Linux), the clipboard might return data 2158 // requested for unknown flavors (for example: 2159 // application/x-moz-nativehtml). In this case, treat the data 2160 // to be pasted as mere HTML to get the best chance of pasting it 2161 // correctly. 2162 bestFlavor.AssignLiteral(kHTMLMime); 2163 // Fall through the next case 2164 } 2165 } 2166 } 2167 if (bestFlavor.EqualsLiteral(kHTMLMime) || 2168 bestFlavor.EqualsLiteral(kTextMime) || 2169 bestFlavor.EqualsLiteral(kMozTextInternal)) { 2170 nsAutoString stuffToPaste; 2171 if (!GetString(genericDataObj, stuffToPaste)) { 2172 nsAutoCString text; 2173 if (GetCString(genericDataObj, text)) { 2174 CopyUTF8toUTF16(text, stuffToPaste); 2175 } 2176 } 2177 2178 if (!stuffToPaste.IsEmpty()) { 2179 if (bestFlavor.EqualsLiteral(kHTMLMime)) { 2180 AutoPlaceholderBatch treatAsOneTransaction( 2181 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 2182 nsresult rv = InsertHTMLWithContextAsSubAction( 2183 stuffToPaste, aContextStr, aInfoStr, flavor, safeToInsertData, 2184 EditorDOMPoint(), DeleteSelectedContent::Yes, 2185 InlineStylesAtInsertionPoint::Clear, aEditingHost); 2186 if (NS_FAILED(rv)) { 2187 NS_WARNING( 2188 "HTMLEditor::InsertHTMLWithContextAsSubAction(" 2189 "DeleteSelectedContent::Yes, " 2190 "InlineStylesAtInsertionPoint::Clear) failed"); 2191 return rv; 2192 } 2193 } else { 2194 AutoPlaceholderBatch treatAsOneTransaction( 2195 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 2196 nsresult rv = 2197 InsertTextAsSubAction(stuffToPaste, InsertTextFor::NormalText); 2198 if (NS_FAILED(rv)) { 2199 NS_WARNING("EditorBase::InsertTextAsSubAction() failed"); 2200 return rv; 2201 } 2202 } 2203 } 2204 } 2205 } 2206 2207 // Try to scroll the selection into view if the paste succeeded 2208 DebugOnly<nsresult> rvIgnored = ScrollSelectionFocusIntoView(); 2209 NS_WARNING_ASSERTION( 2210 NS_SUCCEEDED(rvIgnored), 2211 "EditorBase::ScrollSelectionFocusIntoView() failed, but ignored"); 2212 return NS_OK; 2213 } 2214 2215 static void GetStringFromDataTransfer(const DataTransfer* aDataTransfer, 2216 const nsAString& aType, uint32_t aIndex, 2217 nsString& aOutputString) { 2218 nsCOMPtr<nsIVariant> variant; 2219 DebugOnly<nsresult> rvIgnored = aDataTransfer->GetDataAtNoSecurityCheck( 2220 aType, aIndex, getter_AddRefs(variant)); 2221 if (!variant) { 2222 MOZ_ASSERT(aOutputString.IsEmpty()); 2223 return; 2224 } 2225 NS_WARNING_ASSERTION( 2226 NS_SUCCEEDED(rvIgnored), 2227 "DataTransfer::GetDataAtNoSecurityCheck() failed, but ignored"); 2228 variant->GetAsAString(aOutputString); 2229 nsContentUtils::PlatformToDOMLineBreaks(aOutputString); 2230 } 2231 2232 nsresult HTMLEditor::InsertFromDataTransfer( 2233 const DataTransfer* aDataTransfer, uint32_t aIndex, 2234 nsIPrincipal* aSourcePrincipal, const EditorDOMPoint& aDroppedAt, 2235 DeleteSelectedContent aDeleteSelectedContent, const Element& aEditingHost) { 2236 MOZ_ASSERT(GetEditAction() == EditAction::eDrop || 2237 GetEditAction() == EditAction::ePaste); 2238 MOZ_ASSERT(mPlaceholderBatch, 2239 "HTMLEditor::InsertFromDataTransfer() should be called by " 2240 "HandleDropEvent() or paste action and there should've already " 2241 "been placeholder transaction"); 2242 MOZ_ASSERT_IF(GetEditAction() == EditAction::eDrop, aDroppedAt.IsSet()); 2243 2244 ErrorResult error; 2245 RefPtr<DOMStringList> types = aDataTransfer->MozTypesAt(aIndex, error); 2246 if (error.Failed()) { 2247 NS_WARNING("DataTransfer::MozTypesAt() failed"); 2248 return error.StealNSResult(); 2249 } 2250 2251 const bool hasPrivateHTMLFlavor = 2252 types->Contains(NS_LITERAL_STRING_FROM_CSTRING(kHTMLContext)); 2253 2254 const bool isPlaintextEditor = IsPlaintextMailComposer() || 2255 aEditingHost.IsContentEditablePlainTextOnly(); 2256 const SafeToInsertData safeToInsertData = 2257 IsSafeToInsertData(aSourcePrincipal); 2258 2259 uint32_t length = types->Length(); 2260 for (uint32_t i = 0; i < length; i++) { 2261 nsAutoString type; 2262 types->Item(i, type); 2263 2264 if (!isPlaintextEditor) { 2265 if (type.EqualsLiteral(kFileMime) || type.EqualsLiteral(kJPEGImageMime) || 2266 type.EqualsLiteral(kJPGImageMime) || 2267 type.EqualsLiteral(kPNGImageMime) || 2268 type.EqualsLiteral(kGIFImageMime)) { 2269 nsCOMPtr<nsIVariant> variant; 2270 DebugOnly<nsresult> rvIgnored = aDataTransfer->GetDataAtNoSecurityCheck( 2271 type, aIndex, getter_AddRefs(variant)); 2272 if (variant) { 2273 NS_WARNING_ASSERTION( 2274 NS_SUCCEEDED(rvIgnored), 2275 "DataTransfer::GetDataAtNoSecurityCheck() failed, but ignored"); 2276 nsCOMPtr<nsISupports> object; 2277 rvIgnored = variant->GetAsISupports(getter_AddRefs(object)); 2278 NS_WARNING_ASSERTION( 2279 NS_SUCCEEDED(rvIgnored), 2280 "nsIVariant::GetAsISupports() failed, but ignored"); 2281 nsresult rv = InsertObject(NS_ConvertUTF16toUTF8(type), object, 2282 safeToInsertData, aDroppedAt, 2283 aDeleteSelectedContent, aEditingHost); 2284 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2285 "HTMLEditor::InsertObject() failed"); 2286 return rv; 2287 } 2288 } else if (type.EqualsLiteral(kNativeHTMLMime)) { 2289 // Windows only clipboard parsing. 2290 nsAutoString text; 2291 GetStringFromDataTransfer(aDataTransfer, type, aIndex, text); 2292 NS_ConvertUTF16toUTF8 cfhtml(text); 2293 2294 nsString cfcontext, cffragment, 2295 cfselection; // cfselection left emtpy for now 2296 2297 nsresult rv = ParseCFHTML(cfhtml, getter_Copies(cffragment), 2298 getter_Copies(cfcontext)); 2299 if (NS_SUCCEEDED(rv) && !cffragment.IsEmpty()) { 2300 if (hasPrivateHTMLFlavor) { 2301 // If we have our private HTML flavor, we will only use the fragment 2302 // from the CF_HTML. The rest comes from the clipboard. 2303 nsAutoString contextString, infoString; 2304 GetStringFromDataTransfer( 2305 aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kHTMLContext), 2306 aIndex, contextString); 2307 GetStringFromDataTransfer(aDataTransfer, 2308 NS_LITERAL_STRING_FROM_CSTRING(kHTMLInfo), 2309 aIndex, infoString); 2310 AutoPlaceholderBatch treatAsOneTransaction( 2311 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 2312 nsresult rv = InsertHTMLWithContextAsSubAction( 2313 cffragment, contextString, infoString, type, safeToInsertData, 2314 aDroppedAt, aDeleteSelectedContent, 2315 InlineStylesAtInsertionPoint::Clear, aEditingHost); 2316 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2317 "HTMLEditor::InsertHTMLWithContextAsSubAction(" 2318 "InlineStylesAtInsertionPoint::Clear) failed"); 2319 return rv; 2320 } 2321 AutoPlaceholderBatch treatAsOneTransaction( 2322 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 2323 nsresult rv = InsertHTMLWithContextAsSubAction( 2324 cffragment, cfcontext, cfselection, type, safeToInsertData, 2325 aDroppedAt, aDeleteSelectedContent, 2326 InlineStylesAtInsertionPoint::Clear, aEditingHost); 2327 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2328 "HTMLEditor::InsertHTMLWithContextAsSubAction(" 2329 "InlineStylesAtInsertionPoint::Clear) failed"); 2330 return rv; 2331 } 2332 } else if (type.EqualsLiteral(kHTMLMime)) { 2333 nsAutoString text, contextString, infoString; 2334 GetStringFromDataTransfer(aDataTransfer, type, aIndex, text); 2335 GetStringFromDataTransfer(aDataTransfer, 2336 NS_LITERAL_STRING_FROM_CSTRING(kHTMLContext), 2337 aIndex, contextString); 2338 GetStringFromDataTransfer(aDataTransfer, 2339 NS_LITERAL_STRING_FROM_CSTRING(kHTMLInfo), 2340 aIndex, infoString); 2341 if (type.EqualsLiteral(kHTMLMime)) { 2342 AutoPlaceholderBatch treatAsOneTransaction( 2343 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 2344 nsresult rv = InsertHTMLWithContextAsSubAction( 2345 text, contextString, infoString, type, safeToInsertData, 2346 aDroppedAt, aDeleteSelectedContent, 2347 InlineStylesAtInsertionPoint::Clear, aEditingHost); 2348 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2349 "HTMLEditor::InsertHTMLWithContextAsSubAction(" 2350 "InlineStylesAtInsertionPoint::Clear) failed"); 2351 return rv; 2352 } 2353 } 2354 } 2355 2356 if (type.EqualsLiteral(kTextMime) || type.EqualsLiteral(kMozTextInternal)) { 2357 nsAutoString text; 2358 GetStringFromDataTransfer(aDataTransfer, type, aIndex, text); 2359 AutoPlaceholderBatch treatAsOneTransaction( 2360 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 2361 nsresult rv = InsertTextAt(text, aDroppedAt, aDeleteSelectedContent); 2362 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2363 "EditorBase::InsertTextAt() failed"); 2364 return rv; 2365 } 2366 } 2367 2368 return NS_OK; 2369 } 2370 2371 // static 2372 HTMLEditor::HavePrivateHTMLFlavor 2373 HTMLEditor::DataTransferOrClipboardHasPrivateHTMLFlavor( 2374 DataTransfer* aDataTransfer, nsIClipboard* aClipboard) { 2375 nsresult rv; 2376 if (aDataTransfer) { 2377 return aDataTransfer->HasPrivateHTMLFlavor() ? HavePrivateHTMLFlavor::Yes 2378 : HavePrivateHTMLFlavor::No; 2379 } 2380 // otherwise, fall back to clipboard 2381 if (NS_WARN_IF(!aClipboard)) { 2382 return HavePrivateHTMLFlavor::No; 2383 } 2384 2385 // check the clipboard for our special kHTMLContext flavor. If that is there, 2386 // we know we have our own internal html format on clipboard. 2387 bool hasPrivateHTMLFlavor = false; 2388 AutoTArray<nsCString, 1> flavArray = {nsDependentCString(kHTMLContext)}; 2389 rv = aClipboard->HasDataMatchingFlavors( 2390 flavArray, nsIClipboard::kGlobalClipboard, &hasPrivateHTMLFlavor); 2391 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2392 "nsIClipboard::HasDataMatchingFlavors(nsIClipboard::" 2393 "kGlobalClipboard) failed"); 2394 return NS_SUCCEEDED(rv) && hasPrivateHTMLFlavor ? HavePrivateHTMLFlavor::Yes 2395 : HavePrivateHTMLFlavor::No; 2396 } 2397 2398 nsresult HTMLEditor::HandlePaste(AutoEditActionDataSetter& aEditActionData, 2399 nsIClipboard::ClipboardType aClipboardType, 2400 DataTransfer* aDataTransfer) { 2401 aEditActionData.InitializeDataTransferWithClipboard( 2402 SettingDataTransfer::eWithFormat, aDataTransfer, aClipboardType); 2403 nsresult rv = aEditActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 2404 if (NS_FAILED(rv)) { 2405 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 2406 "CanHandleAndMaybeDispatchBeforeInputEvent() failed"); 2407 return rv; 2408 } 2409 const RefPtr<Element> editingHost = 2410 ComputeEditingHost(LimitInBodyElement::No); 2411 if (NS_WARN_IF(!editingHost)) { 2412 return NS_ERROR_FAILURE; 2413 } 2414 rv = PasteInternal(aClipboardType, aDataTransfer, *editingHost); 2415 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::PasteInternal() failed"); 2416 return rv; 2417 } 2418 2419 nsresult HTMLEditor::PasteInternal(nsIClipboard::ClipboardType aClipboardType, 2420 DataTransfer* aDataTransfer, 2421 const Element& aEditingHost) { 2422 MOZ_ASSERT(IsEditActionDataAvailable()); 2423 2424 if (MOZ_UNLIKELY(!IsModifiable())) { 2425 return NS_OK; 2426 } 2427 2428 // Get Clipboard Service 2429 nsresult rv = NS_OK; 2430 nsCOMPtr<nsIClipboard> clipboard = 2431 do_GetService("@mozilla.org/widget/clipboard;1", &rv); 2432 if (NS_FAILED(rv)) { 2433 NS_WARNING("Failed to get nsIClipboard service"); 2434 return rv; 2435 } 2436 2437 // Get the nsITransferable interface for getting the data from the clipboard 2438 nsCOMPtr<nsITransferable> transferable; 2439 rv = PrepareHTMLTransferable(getter_AddRefs(transferable), &aEditingHost); 2440 if (NS_FAILED(rv)) { 2441 NS_WARNING("HTMLEditor::PrepareHTMLTransferable() failed"); 2442 return rv; 2443 } 2444 if (!transferable) { 2445 NS_WARNING("HTMLEditor::PrepareHTMLTransferable() returned nullptr"); 2446 return NS_ERROR_FAILURE; 2447 } 2448 // Get the Data from the clipboard 2449 rv = GetDataFromDataTransferOrClipboard(aDataTransfer, transferable, 2450 aClipboardType); 2451 if (NS_FAILED(rv)) { 2452 NS_WARNING("EditorBase::GetDataFromDataTransferOrClipboard() failed"); 2453 return rv; 2454 } 2455 2456 // also get additional html copy hints, if present 2457 nsAutoString contextStr, infoStr; 2458 2459 // If we have our internal html flavor on the clipboard, there is special 2460 // context to use instead of cfhtml context. 2461 const HavePrivateHTMLFlavor clipboardHasPrivateHTMLFlavor = 2462 DataTransferOrClipboardHasPrivateHTMLFlavor(aDataTransfer, clipboard); 2463 if (clipboardHasPrivateHTMLFlavor == HavePrivateHTMLFlavor::Yes) { 2464 nsCOMPtr<nsITransferable> contextTransferable = 2465 do_CreateInstance("@mozilla.org/widget/transferable;1"); 2466 if (!contextTransferable) { 2467 NS_WARNING( 2468 "do_CreateInstance() failed to create nsITransferable instance"); 2469 return NS_ERROR_FAILURE; 2470 } 2471 DebugOnly<nsresult> rvIgnored = contextTransferable->Init(nullptr); 2472 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 2473 "nsITransferable::Init() failed, but ignored"); 2474 contextTransferable->SetIsPrivateData(transferable->GetIsPrivateData()); 2475 rvIgnored = contextTransferable->AddDataFlavor(kHTMLContext); 2476 NS_WARNING_ASSERTION( 2477 NS_SUCCEEDED(rvIgnored), 2478 "nsITransferable::AddDataFlavor(kHTMLContext) failed, but ignored"); 2479 GetDataFromDataTransferOrClipboard(aDataTransfer, contextTransferable, 2480 aClipboardType); 2481 nsCOMPtr<nsISupports> contextDataObj; 2482 rv = contextTransferable->GetTransferData(kHTMLContext, 2483 getter_AddRefs(contextDataObj)); 2484 if (NS_SUCCEEDED(rv) && contextDataObj) { 2485 if (nsCOMPtr<nsISupportsString> str = do_QueryInterface(contextDataObj)) { 2486 DebugOnly<nsresult> rvIgnored = str->GetData(contextStr); 2487 NS_WARNING_ASSERTION( 2488 NS_SUCCEEDED(rvIgnored), 2489 "nsISupportsString::GetData() failed, but ignored"); 2490 } 2491 } 2492 2493 nsCOMPtr<nsITransferable> infoTransferable = 2494 do_CreateInstance("@mozilla.org/widget/transferable;1"); 2495 if (!infoTransferable) { 2496 NS_WARNING( 2497 "do_CreateInstance() failed to create nsITransferable instance"); 2498 return NS_ERROR_FAILURE; 2499 } 2500 rvIgnored = infoTransferable->Init(nullptr); 2501 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2502 "nsITransferable::Init() failed, but ignored"); 2503 contextTransferable->SetIsPrivateData(transferable->GetIsPrivateData()); 2504 rvIgnored = infoTransferable->AddDataFlavor(kHTMLInfo); 2505 NS_WARNING_ASSERTION( 2506 NS_SUCCEEDED(rvIgnored), 2507 "nsITransferable::AddDataFlavor(kHTMLInfo) failed, but ignored"); 2508 2509 GetDataFromDataTransferOrClipboard(aDataTransfer, infoTransferable, 2510 aClipboardType); 2511 nsCOMPtr<nsISupports> infoDataObj; 2512 rv = infoTransferable->GetTransferData(kHTMLInfo, 2513 getter_AddRefs(infoDataObj)); 2514 if (NS_SUCCEEDED(rv) && infoDataObj) { 2515 if (nsCOMPtr<nsISupportsString> str = do_QueryInterface(infoDataObj)) { 2516 DebugOnly<nsresult> rvIgnored = str->GetData(infoStr); 2517 NS_WARNING_ASSERTION( 2518 NS_SUCCEEDED(rvIgnored), 2519 "nsISupportsString::GetData() failed, but ignored"); 2520 } 2521 } 2522 } 2523 2524 rv = InsertFromTransferableAtSelection(transferable, contextStr, infoStr, 2525 clipboardHasPrivateHTMLFlavor, 2526 aEditingHost); 2527 NS_WARNING_ASSERTION( 2528 NS_SUCCEEDED(rv), 2529 "HTMLEditor::InsertFromTransferableAtSelection() failed"); 2530 return rv; 2531 } 2532 2533 nsresult HTMLEditor::HandlePasteTransferable( 2534 AutoEditActionDataSetter& aEditActionData, nsITransferable& aTransferable) { 2535 // InitializeDataTransfer may fetch input stream in aTransferable, so it 2536 // may be invalid after calling this. 2537 aEditActionData.InitializeDataTransfer(&aTransferable); 2538 2539 nsresult rv = aEditActionData.MaybeDispatchBeforeInputEvent(); 2540 if (NS_FAILED(rv)) { 2541 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 2542 "MaybeDispatchBeforeInputEvent(), failed"); 2543 return rv; 2544 } 2545 2546 const RefPtr<Element> editingHost = 2547 ComputeEditingHost(LimitInBodyElement::No); 2548 if (NS_WARN_IF(!editingHost)) { 2549 return NS_ERROR_FAILURE; 2550 } 2551 2552 RefPtr<DataTransfer> dataTransfer = GetInputEventDataTransfer(); 2553 if (dataTransfer->HasFile() && dataTransfer->MozItemCount() > 0) { 2554 // Now aTransferable has moved to DataTransfer. Use DataTransfer. 2555 AutoPlaceholderBatch treatAsOneTransaction( 2556 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 2557 2558 rv = InsertFromDataTransfer(dataTransfer, 0, nullptr, EditorDOMPoint(), 2559 DeleteSelectedContent::Yes, *editingHost); 2560 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2561 "HTMLEditor::InsertFromDataTransfer(" 2562 "DeleteSelectedContent::Yes) failed"); 2563 return rv; 2564 } 2565 2566 nsAutoString contextStr, infoStr; 2567 rv = InsertFromTransferableAtSelection(&aTransferable, contextStr, infoStr, 2568 HavePrivateHTMLFlavor::No, 2569 *editingHost); 2570 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2571 "HTMLEditor::InsertFromTransferableAtSelection(" 2572 "HavePrivateHTMLFlavor::No) failed"); 2573 return rv; 2574 } 2575 2576 nsresult HTMLEditor::PasteNoFormattingAsAction( 2577 nsIClipboard::ClipboardType aClipboardType, 2578 DispatchPasteEvent aDispatchPasteEvent, 2579 DataTransfer* aDataTransfer /* = nullptr */, 2580 nsIPrincipal* aPrincipal /* = nullptr */) { 2581 if (IsReadonly()) { 2582 return NS_OK; 2583 } 2584 // Create the same DataTransfer object here so we can share it between 2585 // the clipboard event and its data with the call to 2586 // InsertFromTransferableWithSelection below. This prevents 2587 // race conditions with Content Analysis on like we see in bug 1918027. 2588 RefPtr<DataTransfer> dataTransfer = 2589 aDataTransfer ? RefPtr<DataTransfer>(aDataTransfer) 2590 : RefPtr<DataTransfer>(CreateDataTransferForPaste( 2591 ePasteNoFormatting, aClipboardType)); 2592 2593 auto clearDataTransfer = MakeScopeExit([&] { 2594 // If the caller passed in aDataTransfer, they are responsible for clearing 2595 // this. 2596 if (!aDataTransfer && dataTransfer) { 2597 dataTransfer->ClearForPaste(); 2598 } 2599 }); 2600 2601 AutoEditActionDataSetter editActionData(*this, EditAction::ePaste, 2602 aPrincipal); 2603 if (NS_WARN_IF(!editActionData.CanHandle())) { 2604 return NS_ERROR_NOT_INITIALIZED; 2605 } 2606 editActionData.InitializeDataTransferWithClipboard( 2607 SettingDataTransfer::eWithoutFormat, dataTransfer, aClipboardType); 2608 2609 if (aDispatchPasteEvent == DispatchPasteEvent::Yes) { 2610 RefPtr<nsFocusManager> focusManager = nsFocusManager::GetFocusManager(); 2611 if (NS_WARN_IF(!focusManager)) { 2612 return NS_ERROR_UNEXPECTED; 2613 } 2614 const RefPtr<Element> focusedElement = focusManager->GetFocusedElement(); 2615 2616 Result<ClipboardEventResult, nsresult> ret = Err(NS_ERROR_FAILURE); 2617 { 2618 // This method is not set up to pass back the new aDataTransfer 2619 // if it changes. If we need this in the future, we can change 2620 // aDataTransfer to be a RefPtr<DataTransfer>*. 2621 MOZ_ASSERT(!aDataTransfer); 2622 AutoTrackDataTransferForPaste trackDataTransfer(*this, dataTransfer); 2623 2624 ret = DispatchClipboardEventAndUpdateClipboard( 2625 ePasteNoFormatting, Some(aClipboardType), dataTransfer); 2626 if (MOZ_UNLIKELY(ret.isErr())) { 2627 NS_WARNING( 2628 "EditorBase::DispatchClipboardEventAndUpdateClipboard(" 2629 "ePasteNoFormatting) failed"); 2630 return EditorBase::ToGenericNSResult(ret.unwrapErr()); 2631 } 2632 } 2633 switch (ret.inspect()) { 2634 case ClipboardEventResult::DoDefault: 2635 break; 2636 case ClipboardEventResult::DefaultPreventedOfPaste: 2637 case ClipboardEventResult::IgnoredOrError: 2638 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED); 2639 case ClipboardEventResult::CopyOrCutHandled: 2640 MOZ_ASSERT_UNREACHABLE("Invalid result for ePaste"); 2641 } 2642 2643 // If focus is changed by a "paste" event listener, we should keep handling 2644 // the "pasting" in new focused editor because Chrome works as so. 2645 const RefPtr<Element> newFocusedElement = focusManager->GetFocusedElement(); 2646 if (MOZ_UNLIKELY(focusedElement != newFocusedElement)) { 2647 // For the privacy reason, let's top handling it if new focused element is 2648 // in different document. 2649 if (focusManager->GetFocusedWindow() != GetWindow()) { 2650 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED); 2651 } 2652 RefPtr<EditorBase> editorBase = 2653 nsContentUtils::GetActiveEditor(GetPresContext()); 2654 if (!editorBase || (editorBase->IsHTMLEditor() && 2655 !editorBase->AsHTMLEditor()->IsActiveInDOMWindow())) { 2656 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED); 2657 } 2658 if (editorBase != this) { 2659 if (editorBase->IsHTMLEditor()) { 2660 nsresult rv = MOZ_KnownLive(editorBase->AsHTMLEditor()) 2661 ->PasteNoFormattingAsAction( 2662 aClipboardType, DispatchPasteEvent::No, 2663 dataTransfer, aPrincipal); 2664 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2665 "HTMLEditor::PasteNoFormattingAsAction(" 2666 "DispatchPasteEvent::No) failed"); 2667 return EditorBase::ToGenericNSResult(rv); 2668 } 2669 nsresult rv = editorBase->PasteAsAction( 2670 aClipboardType, DispatchPasteEvent::No, dataTransfer, aPrincipal); 2671 NS_WARNING_ASSERTION( 2672 NS_SUCCEEDED(rv), 2673 "EditorBase::PasteAsAction(DispatchPasteEvent::No) failed"); 2674 return EditorBase::ToGenericNSResult(rv); 2675 } 2676 } 2677 } 2678 2679 const RefPtr<Element> editingHost = 2680 ComputeEditingHost(LimitInBodyElement::No); 2681 if (NS_WARN_IF(!editingHost)) { 2682 return NS_ERROR_FAILURE; 2683 } 2684 2685 // Dispatch "beforeinput" event after "paste" event. And perhaps, before 2686 // committing composition because if pasting is canceled, we don't need to 2687 // commit the active composition. 2688 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent(); 2689 if (NS_FAILED(rv)) { 2690 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 2691 "MaybeDispatchBeforeInputEvent(), failed"); 2692 return EditorBase::ToGenericNSResult(rv); 2693 } 2694 2695 DebugOnly<nsresult> rvIgnored = CommitComposition(); 2696 if (NS_WARN_IF(Destroyed())) { 2697 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED); 2698 } 2699 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2700 "EditorBase::CommitComposition() failed, but ignored"); 2701 if (MOZ_UNLIKELY(!IsModifiable())) { 2702 return NS_OK; 2703 } 2704 2705 Result<nsCOMPtr<nsITransferable>, nsresult> maybeTransferable = 2706 EditorUtils::CreateTransferableForPlainText(*GetDocument()); 2707 if (maybeTransferable.isErr()) { 2708 NS_WARNING("EditorUtils::CreateTransferableForPlainText() failed"); 2709 return EditorBase::ToGenericNSResult(maybeTransferable.unwrapErr()); 2710 } 2711 nsCOMPtr<nsITransferable> transferable(maybeTransferable.unwrap()); 2712 if (!transferable) { 2713 NS_WARNING( 2714 "EditorUtils::CreateTransferableForPlainText() returned nullptr, but " 2715 "ignored"); 2716 return NS_OK; 2717 } 2718 rv = GetDataFromDataTransferOrClipboard(dataTransfer, transferable, 2719 aClipboardType); 2720 if (NS_FAILED(rv)) { 2721 NS_WARNING("EditorBase::GetDataFromDataTransferOrClipboard() failed"); 2722 return rv; 2723 } 2724 2725 rv = InsertFromTransferableAtSelection( 2726 transferable, u""_ns, u""_ns, HavePrivateHTMLFlavor::No, *editingHost); 2727 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2728 "HTMLEditor::InsertFromTransferableAtSelection(" 2729 "HavePrivateHTMLFlavor::No) failed"); 2730 return EditorBase::ToGenericNSResult(rv); 2731 } 2732 2733 // The following arrays contain the MIME types that we can paste. The arrays 2734 // are used by CanPaste() and CanPasteTransferable() below. 2735 2736 static const char* textEditorFlavors[] = {kTextMime}; 2737 static const char* textHtmlEditorFlavors[] = {kTextMime, kHTMLMime, 2738 kJPEGImageMime, kJPGImageMime, 2739 kPNGImageMime, kGIFImageMime}; 2740 2741 bool HTMLEditor::CanPaste(nsIClipboard::ClipboardType aClipboardType) const { 2742 if (AreClipboardCommandsUnconditionallyEnabled()) { 2743 return true; 2744 } 2745 2746 // can't paste if readonly 2747 if (!IsModifiable()) { 2748 return false; 2749 } 2750 2751 const RefPtr<Element> editingHost = 2752 ComputeEditingHost(LimitInBodyElement::No); 2753 if (!editingHost) { 2754 return false; 2755 } 2756 2757 nsresult rv; 2758 nsCOMPtr<nsIClipboard> clipboard( 2759 do_GetService("@mozilla.org/widget/clipboard;1", &rv)); 2760 if (NS_FAILED(rv)) { 2761 NS_WARNING("Failed to get nsIClipboard service"); 2762 return false; 2763 } 2764 2765 // Use the flavors depending on the current editor mask 2766 if (IsPlaintextMailComposer() || 2767 editingHost->IsContentEditablePlainTextOnly()) { 2768 AutoTArray<nsCString, std::size(textEditorFlavors)> flavors; 2769 flavors.AppendElements<const char*>(Span<const char*>(textEditorFlavors)); 2770 bool haveFlavors; 2771 nsresult rv = clipboard->HasDataMatchingFlavors(flavors, aClipboardType, 2772 &haveFlavors); 2773 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2774 "nsIClipboard::HasDataMatchingFlavors() failed"); 2775 return NS_SUCCEEDED(rv) && haveFlavors; 2776 } 2777 2778 AutoTArray<nsCString, std::size(textHtmlEditorFlavors)> flavors; 2779 flavors.AppendElements<const char*>(Span<const char*>(textHtmlEditorFlavors)); 2780 bool haveFlavors; 2781 rv = clipboard->HasDataMatchingFlavors(flavors, aClipboardType, &haveFlavors); 2782 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2783 "nsIClipboard::HasDataMatchingFlavors() failed"); 2784 return NS_SUCCEEDED(rv) && haveFlavors; 2785 } 2786 2787 bool HTMLEditor::CanPasteTransferable(nsITransferable* aTransferable) { 2788 // can't paste if readonly 2789 if (!IsModifiable()) { 2790 return false; 2791 } 2792 2793 const RefPtr<Element> editingHost = 2794 ComputeEditingHost(LimitInBodyElement::No); 2795 if (!editingHost) { 2796 return false; 2797 } 2798 2799 // If |aTransferable| is null, assume that a paste will succeed. 2800 if (!aTransferable) { 2801 return true; 2802 } 2803 2804 // Peek in |aTransferable| to see if it contains a supported MIME type. 2805 2806 // Use the flavors depending on the current editor mask 2807 const char** flavors; 2808 size_t length; 2809 if (IsPlaintextMailComposer() || 2810 editingHost->IsContentEditablePlainTextOnly()) { 2811 flavors = textEditorFlavors; 2812 length = std::size(textEditorFlavors); 2813 } else { 2814 flavors = textHtmlEditorFlavors; 2815 length = std::size(textHtmlEditorFlavors); 2816 } 2817 2818 for (size_t i = 0; i < length; i++, flavors++) { 2819 nsCOMPtr<nsISupports> data; 2820 nsresult rv = 2821 aTransferable->GetTransferData(*flavors, getter_AddRefs(data)); 2822 if (NS_SUCCEEDED(rv) && data) { 2823 return true; 2824 } 2825 } 2826 2827 return false; 2828 } 2829 2830 nsresult HTMLEditor::HandlePasteAsQuotation( 2831 AutoEditActionDataSetter& aEditActionData, 2832 nsIClipboard::ClipboardType aClipboardType, DataTransfer* aDataTransfer) { 2833 MOZ_ASSERT(aClipboardType == nsIClipboard::kGlobalClipboard || 2834 aClipboardType == nsIClipboard::kSelectionClipboard); 2835 aEditActionData.InitializeDataTransferWithClipboard( 2836 SettingDataTransfer::eWithFormat, aDataTransfer, aClipboardType); 2837 if (NS_WARN_IF(!aEditActionData.CanHandle())) { 2838 return NS_ERROR_NOT_INITIALIZED; 2839 } 2840 2841 nsresult rv = aEditActionData.MaybeDispatchBeforeInputEvent(); 2842 if (NS_FAILED(rv)) { 2843 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 2844 "MaybeDispatchBeforeInputEvent(), failed"); 2845 return rv; 2846 } 2847 2848 const RefPtr<Element> editingHost = 2849 ComputeEditingHost(LimitInBodyElement::No); 2850 if (NS_WARN_IF(!editingHost)) { 2851 return NS_ERROR_FAILURE; 2852 } 2853 2854 if (IsPlaintextMailComposer() || 2855 editingHost->IsContentEditablePlainTextOnly()) { 2856 nsresult rv = 2857 PasteAsPlaintextQuotation(aClipboardType, aDataTransfer, *editingHost); 2858 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2859 "HTMLEditor::PasteAsPlaintextQuotation() failed"); 2860 return rv; 2861 } 2862 2863 // If it's not in plain text edit mode, paste text into new 2864 // <blockquote type="cite"> element after removing selection. 2865 2866 { 2867 // XXX Why don't we test these first? 2868 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(); 2869 if (MOZ_UNLIKELY(result.isErr())) { 2870 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); 2871 return result.unwrapErr(); 2872 } 2873 if (result.inspect().Canceled()) { 2874 return NS_OK; 2875 } 2876 } 2877 2878 UndefineCaretBidiLevel(); 2879 2880 AutoPlaceholderBatch treatAsOneTransaction( 2881 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 2882 IgnoredErrorResult ignoredError; 2883 AutoEditSubActionNotifier startToHandleEditSubAction( 2884 *this, EditSubAction::eInsertQuotation, nsIEditor::eNext, ignoredError); 2885 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 2886 return ignoredError.StealNSResult(); 2887 } 2888 NS_WARNING_ASSERTION( 2889 !ignoredError.Failed(), 2890 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 2891 2892 rv = EnsureNoPaddingBRElementForEmptyEditor(); 2893 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 2894 return NS_ERROR_EDITOR_DESTROYED; 2895 } 2896 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2897 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() " 2898 "failed, but ignored"); 2899 2900 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) { 2901 nsresult rv = EnsureCaretNotAfterInvisibleBRElement(*editingHost); 2902 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 2903 return NS_ERROR_EDITOR_DESTROYED; 2904 } 2905 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2906 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() " 2907 "failed, but ignored"); 2908 if (NS_SUCCEEDED(rv)) { 2909 nsresult rv = PrepareInlineStylesForCaret(); 2910 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 2911 return NS_ERROR_EDITOR_DESTROYED; 2912 } 2913 NS_WARNING_ASSERTION( 2914 NS_SUCCEEDED(rv), 2915 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored"); 2916 } 2917 } 2918 2919 // Remove Selection and create `<blockquote type="cite">` now. 2920 // XXX Why don't we insert the `<blockquote>` into the DOM tree after 2921 // pasting the content in clipboard into it? 2922 Result<RefPtr<Element>, nsresult> blockquoteElementOrError = 2923 DeleteSelectionAndCreateElement( 2924 *nsGkAtoms::blockquote, 2925 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868 2926 [](HTMLEditor&, Element& aBlockquoteElement, const EditorDOMPoint&) 2927 MOZ_CAN_RUN_SCRIPT_BOUNDARY { 2928 DebugOnly<nsresult> rvIgnored = aBlockquoteElement.SetAttr( 2929 kNameSpaceID_None, nsGkAtoms::type, u"cite"_ns, 2930 aBlockquoteElement.IsInComposedDoc()); 2931 NS_WARNING_ASSERTION( 2932 NS_SUCCEEDED(rvIgnored), 2933 nsPrintfCString( 2934 "Element::SetAttr(nsGkAtoms::type, \"cite\", %s) " 2935 "failed, but ignored", 2936 aBlockquoteElement.IsInComposedDoc() ? "true" : "false") 2937 .get()); 2938 return NS_OK; 2939 }); 2940 if (MOZ_UNLIKELY(blockquoteElementOrError.isErr()) || 2941 NS_WARN_IF(Destroyed())) { 2942 NS_WARNING( 2943 "HTMLEditor::DeleteSelectionAndCreateElement(nsGkAtoms::blockquote) " 2944 "failed"); 2945 return Destroyed() ? NS_ERROR_EDITOR_DESTROYED 2946 : blockquoteElementOrError.unwrapErr(); 2947 } 2948 MOZ_ASSERT(blockquoteElementOrError.inspect()); 2949 2950 // Collapse Selection in the new `<blockquote>` element. 2951 rv = CollapseSelectionToStartOf( 2952 MOZ_KnownLive(*blockquoteElementOrError.inspect())); 2953 if (NS_FAILED(rv)) { 2954 NS_WARNING("EditorBase::CollapseSelectionToStartOf() failed"); 2955 return rv; 2956 } 2957 2958 rv = PasteInternal(aClipboardType, aDataTransfer, *editingHost); 2959 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::PasteInternal() failed"); 2960 return rv; 2961 } 2962 2963 nsresult HTMLEditor::PasteAsPlaintextQuotation( 2964 nsIClipboard::ClipboardType aSelectionType, DataTransfer* aDataTransfer, 2965 const Element& aEditingHost) { 2966 nsresult rv; 2967 // Create generic Transferable for getting the data 2968 nsCOMPtr<nsITransferable> transferable = 2969 do_CreateInstance("@mozilla.org/widget/transferable;1", &rv); 2970 if (NS_FAILED(rv)) { 2971 NS_WARNING("do_CreateInstance() failed to create nsITransferable instance"); 2972 return rv; 2973 } 2974 if (!transferable) { 2975 NS_WARNING("do_CreateInstance() returned nullptr"); 2976 return NS_ERROR_FAILURE; 2977 } 2978 2979 RefPtr<Document> destdoc = GetDocument(); 2980 auto* windowContext = GetDocument()->GetWindowContext(); 2981 if (!windowContext) { 2982 NS_WARNING("Editor didn't have document window context"); 2983 return NS_ERROR_FAILURE; 2984 } 2985 2986 nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr; 2987 DebugOnly<nsresult> rvIgnored = transferable->Init(loadContext); 2988 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 2989 "nsITransferable::Init() failed, but ignored"); 2990 2991 // We only handle plaintext pastes here 2992 rvIgnored = transferable->AddDataFlavor(kTextMime); 2993 NS_WARNING_ASSERTION( 2994 NS_SUCCEEDED(rvIgnored), 2995 "nsITransferable::AddDataFlavor(kTextMime) failed, but ignored"); 2996 2997 // Get the Data from the clipboard 2998 GetDataFromDataTransferOrClipboard(aDataTransfer, transferable, 2999 aSelectionType); 3000 3001 // Now we ask the transferable for the data 3002 // it still owns the data, we just have a pointer to it. 3003 // If it can't support a "text" output of the data the call will fail 3004 nsCOMPtr<nsISupports> genericDataObj; 3005 nsAutoCString flavor; 3006 rv = transferable->GetAnyTransferData(flavor, getter_AddRefs(genericDataObj)); 3007 if (NS_FAILED(rv)) { 3008 NS_WARNING("nsITransferable::GetAnyTransferData() failed"); 3009 return rv; 3010 } 3011 3012 if (!flavor.EqualsLiteral(kTextMime)) { 3013 return NS_OK; 3014 } 3015 3016 nsAutoString stuffToPaste; 3017 if (!GetString(genericDataObj, stuffToPaste)) { 3018 return NS_OK; 3019 } 3020 3021 AutoPlaceholderBatch treatAsOneTransaction( 3022 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 3023 rv = InsertAsPlaintextQuotation(stuffToPaste, AddCites::Yes, aEditingHost); 3024 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3025 "HTMLEditor::InsertAsPlaintextQuotation() failed"); 3026 return rv; 3027 } 3028 3029 nsresult HTMLEditor::InsertWithQuotationsAsSubAction( 3030 const nsAString& aQuotedText) { 3031 MOZ_ASSERT(IsEditActionDataAvailable()); 3032 3033 const RefPtr<Element> editingHost = 3034 ComputeEditingHost(LimitInBodyElement::No); 3035 if (NS_WARN_IF(!editingHost)) { 3036 return NS_ERROR_FAILURE; 3037 } 3038 3039 { 3040 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(); 3041 if (MOZ_UNLIKELY(result.isErr())) { 3042 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); 3043 return result.unwrapErr(); 3044 } 3045 if (result.inspect().Canceled()) { 3046 return NS_OK; 3047 } 3048 } 3049 3050 UndefineCaretBidiLevel(); 3051 3052 // Let the citer quote it for us: 3053 nsString quotedStuff; 3054 InternetCiter::GetCiteString(aQuotedText, quotedStuff); 3055 3056 // It's best to put a blank line after the quoted text so that mails 3057 // written without thinking won't be so ugly. 3058 if (!aQuotedText.IsEmpty() && 3059 (aQuotedText.Last() != HTMLEditUtils::kNewLine)) { 3060 quotedStuff.Append(HTMLEditUtils::kNewLine); 3061 } 3062 3063 IgnoredErrorResult ignoredError; 3064 AutoEditSubActionNotifier startToHandleEditSubAction( 3065 *this, EditSubAction::eInsertText, nsIEditor::eNext, ignoredError); 3066 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 3067 return ignoredError.StealNSResult(); 3068 } 3069 NS_WARNING_ASSERTION( 3070 !ignoredError.Failed(), 3071 "OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 3072 3073 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor(); 3074 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 3075 return NS_ERROR_EDITOR_DESTROYED; 3076 } 3077 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3078 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() " 3079 "failed, but ignored"); 3080 3081 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) { 3082 nsresult rv = EnsureCaretNotAfterInvisibleBRElement(*editingHost); 3083 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 3084 return NS_ERROR_EDITOR_DESTROYED; 3085 } 3086 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3087 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() " 3088 "failed, but ignored"); 3089 if (NS_SUCCEEDED(rv)) { 3090 nsresult rv = PrepareInlineStylesForCaret(); 3091 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 3092 return NS_ERROR_EDITOR_DESTROYED; 3093 } 3094 NS_WARNING_ASSERTION( 3095 NS_SUCCEEDED(rv), 3096 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored"); 3097 } 3098 } 3099 3100 rv = InsertTextAsSubAction(quotedStuff, InsertTextFor::NormalText); 3101 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3102 "EditorBase::InsertTextAsSubAction() failed"); 3103 return rv; 3104 } 3105 3106 NS_IMETHODIMP HTMLEditor::InsertTextWithQuotations( 3107 const nsAString& aStringToInsert) { 3108 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertText); 3109 MOZ_ASSERT(!aStringToInsert.IsVoid()); 3110 editActionData.SetData(aStringToInsert); 3111 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 3112 if (NS_FAILED(rv)) { 3113 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 3114 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 3115 return EditorBase::ToGenericNSResult(rv); 3116 } 3117 if (aStringToInsert.IsEmpty()) { 3118 return NS_OK; 3119 } 3120 3121 const RefPtr<Element> editingHost = 3122 ComputeEditingHost(LimitInBodyElement::No); 3123 if (NS_WARN_IF(!editingHost)) { 3124 return NS_ERROR_FAILURE; 3125 } 3126 3127 // The whole operation should be undoable in one transaction: 3128 // XXX Why isn't enough to use only AutoPlaceholderBatch here? 3129 AutoTransactionBatch bundleAllTransactions(*this, __FUNCTION__); 3130 AutoPlaceholderBatch treatAsOneTransaction( 3131 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 3132 3133 rv = InsertTextWithQuotationsInternal(aStringToInsert, *editingHost); 3134 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3135 "HTMLEditor::InsertTextWithQuotationsInternal() failed"); 3136 return EditorBase::ToGenericNSResult(rv); 3137 } 3138 3139 nsresult HTMLEditor::InsertTextWithQuotationsInternal( 3140 const nsAString& aStringToInsert, const Element& aEditingHost) { 3141 MOZ_ASSERT(!aStringToInsert.IsEmpty()); 3142 // We're going to loop over the string, collecting up a "hunk" 3143 // that's all the same type (quoted or not), 3144 // Whenever the quotedness changes (or we reach the string's end) 3145 // we will insert the hunk all at once, quoted or non. 3146 static const char16_t cite('>'); 3147 bool curHunkIsQuoted = (aStringToInsert.First() == cite); 3148 3149 nsAString::const_iterator hunkStart, strEnd; 3150 aStringToInsert.BeginReading(hunkStart); 3151 aStringToInsert.EndReading(strEnd); 3152 3153 // In the loop below, we only look for DOM newlines (\n), 3154 // because we don't have a FindChars method that can look 3155 // for both \r and \n. \r is illegal in the dom anyway, 3156 // but in debug builds, let's take the time to verify that 3157 // there aren't any there: 3158 #ifdef DEBUG 3159 nsAString::const_iterator dbgStart(hunkStart); 3160 if (FindCharInReadable(HTMLEditUtils::kCarriageReturn, dbgStart, strEnd)) { 3161 NS_ASSERTION( 3162 false, 3163 "Return characters in DOM! InsertTextWithQuotations may be wrong"); 3164 } 3165 #endif /* DEBUG */ 3166 3167 // Loop over lines: 3168 nsresult rv = NS_OK; 3169 nsAString::const_iterator lineStart(hunkStart); 3170 // We will break from inside when we run out of newlines. 3171 for (;;) { 3172 // Search for the end of this line (dom newlines, see above): 3173 bool found = FindCharInReadable(HTMLEditUtils::kNewLine, lineStart, strEnd); 3174 bool quoted = false; 3175 if (found) { 3176 // if there's another newline, lineStart now points there. 3177 // Loop over any consecutive newline chars: 3178 nsAString::const_iterator firstNewline(lineStart); 3179 while (*lineStart == HTMLEditUtils::kNewLine) { 3180 ++lineStart; 3181 } 3182 quoted = (*lineStart == cite); 3183 if (quoted == curHunkIsQuoted) { 3184 continue; 3185 } 3186 // else we're changing state, so we need to insert 3187 // from curHunk to lineStart then loop around. 3188 3189 // But if the current hunk is quoted, then we want to make sure 3190 // that any extra newlines on the end do not get included in 3191 // the quoted section: blank lines flaking a quoted section 3192 // should be considered unquoted, so that if the user clicks 3193 // there and starts typing, the new text will be outside of 3194 // the quoted block. 3195 if (curHunkIsQuoted) { 3196 lineStart = firstNewline; 3197 3198 // 'firstNewline' points to the first '\n'. We want to 3199 // ensure that this first newline goes into the hunk 3200 // since quoted hunks can be displayed as blocks 3201 // (and the newline should become invisible in this case). 3202 // So the next line needs to start at the next character. 3203 lineStart++; 3204 } 3205 } 3206 3207 // If no newline found, lineStart is now strEnd and we can finish up, 3208 // inserting from curHunk to lineStart then returning. 3209 const nsAString& curHunk = Substring(hunkStart, lineStart); 3210 if (curHunkIsQuoted) { 3211 rv = InsertAsPlaintextQuotation(curHunk, AddCites::No, aEditingHost); 3212 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 3213 return NS_ERROR_EDITOR_DESTROYED; 3214 } 3215 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3216 "HTMLEditor::InsertAsPlaintextQuotation() failed, " 3217 "but might be ignored"); 3218 } else { 3219 rv = InsertTextAsSubAction(curHunk, InsertTextFor::NormalText); 3220 NS_WARNING_ASSERTION( 3221 NS_SUCCEEDED(rv), 3222 "EditorBase::InsertTextAsSubAction() failed, but might be ignored"); 3223 } 3224 if (!found) { 3225 break; 3226 } 3227 curHunkIsQuoted = quoted; 3228 hunkStart = lineStart; 3229 } 3230 3231 // XXX This returns the last result of InsertAsPlaintextQuotation() or 3232 // InsertTextAsSubAction() in the loop. This must be a bug. 3233 return rv; 3234 } 3235 3236 nsresult HTMLEditor::InsertAsQuotation(const nsAString& aQuotedText, 3237 nsINode** aNodeInserted) { 3238 const RefPtr<Element> editingHost = 3239 ComputeEditingHost(LimitInBodyElement::No); 3240 if (NS_WARN_IF(!editingHost)) { 3241 return NS_ERROR_FAILURE; 3242 } 3243 3244 if (IsPlaintextMailComposer() || 3245 editingHost->IsContentEditablePlainTextOnly()) { 3246 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertText); 3247 MOZ_ASSERT(!aQuotedText.IsVoid()); 3248 editActionData.SetData(aQuotedText); 3249 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 3250 if (NS_FAILED(rv)) { 3251 NS_WARNING_ASSERTION( 3252 rv == NS_ERROR_EDITOR_ACTION_CANCELED, 3253 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 3254 return EditorBase::ToGenericNSResult(rv); 3255 } 3256 AutoPlaceholderBatch treatAsOneTransaction( 3257 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 3258 rv = InsertAsPlaintextQuotation(aQuotedText, AddCites::Yes, *editingHost, 3259 aNodeInserted); 3260 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3261 "HTMLEditor::InsertAsPlaintextQuotation() failed"); 3262 return EditorBase::ToGenericNSResult(rv); 3263 } 3264 3265 AutoEditActionDataSetter editActionData(*this, 3266 EditAction::eInsertBlockquoteElement); 3267 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 3268 if (NS_FAILED(rv)) { 3269 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 3270 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 3271 return EditorBase::ToGenericNSResult(rv); 3272 } 3273 3274 AutoPlaceholderBatch treatAsOneTransaction( 3275 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 3276 nsAutoString citation; 3277 rv = InsertAsCitedQuotationInternal(aQuotedText, citation, false, 3278 *editingHost, aNodeInserted); 3279 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3280 "HTMLEditor::InsertAsCitedQuotationInternal() failed"); 3281 return EditorBase::ToGenericNSResult(rv); 3282 } 3283 3284 // Insert plaintext as a quotation, with cite marks (e.g. "> "). 3285 // This differs from its corresponding method in TextEditor 3286 // in that here, quoted material is enclosed in a <pre> tag 3287 // in order to preserve the original line wrapping. 3288 nsresult HTMLEditor::InsertAsPlaintextQuotation(const nsAString& aQuotedText, 3289 AddCites aAddCites, 3290 const Element& aEditingHost, 3291 nsINode** aNodeInserted) { 3292 MOZ_ASSERT(IsEditActionDataAvailable()); 3293 3294 if (aNodeInserted) { 3295 *aNodeInserted = nullptr; 3296 } 3297 3298 { 3299 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(); 3300 if (MOZ_UNLIKELY(result.isErr())) { 3301 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); 3302 return result.unwrapErr(); 3303 } 3304 if (result.inspect().Canceled()) { 3305 return NS_OK; 3306 } 3307 } 3308 3309 UndefineCaretBidiLevel(); 3310 3311 IgnoredErrorResult ignoredError; 3312 AutoEditSubActionNotifier startToHandleEditSubAction( 3313 *this, EditSubAction::eInsertQuotation, nsIEditor::eNext, ignoredError); 3314 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 3315 return ignoredError.StealNSResult(); 3316 } 3317 NS_WARNING_ASSERTION( 3318 !ignoredError.Failed(), 3319 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 3320 3321 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor(); 3322 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 3323 return NS_ERROR_EDITOR_DESTROYED; 3324 } 3325 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3326 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() " 3327 "failed, but ignored"); 3328 3329 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) { 3330 nsresult rv = EnsureCaretNotAfterInvisibleBRElement(aEditingHost); 3331 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 3332 return NS_ERROR_EDITOR_DESTROYED; 3333 } 3334 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3335 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() " 3336 "failed, but ignored"); 3337 if (NS_SUCCEEDED(rv)) { 3338 nsresult rv = PrepareInlineStylesForCaret(); 3339 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 3340 return NS_ERROR_EDITOR_DESTROYED; 3341 } 3342 NS_WARNING_ASSERTION( 3343 NS_SUCCEEDED(rv), 3344 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored"); 3345 } 3346 } 3347 3348 RefPtr<Element> containerSpanElement; 3349 if (!aEditingHost.IsContentEditablePlainTextOnly()) { 3350 // Wrap the inserted quote in a <span> so we can distinguish it. If we're 3351 // inserting into the <body>, we use a <span> which is displayed as a block 3352 // and sized to the screen using 98 viewport width units. 3353 // We could use 100vw, but 98vw avoids a horizontal scroll bar where 3354 // possible. All this is done to wrap overlong lines to the screen and not 3355 // to the container element, the width-restricted body. 3356 // XXX I think that we don't need to do this in the web. This should be 3357 // done only for Thunderbird. 3358 Result<RefPtr<Element>, nsresult> spanElementOrError = 3359 DeleteSelectionAndCreateElement( 3360 *nsGkAtoms::span, [](HTMLEditor&, Element& aSpanElement, 3361 const EditorDOMPoint& aPointToInsert) { 3362 // Add an attribute on the pre node so we'll know it's a 3363 // quotation. 3364 DebugOnly<nsresult> rvIgnored = aSpanElement.SetAttr( 3365 kNameSpaceID_None, nsGkAtoms::mozquote, u"true"_ns, 3366 aSpanElement.IsInComposedDoc()); 3367 NS_WARNING_ASSERTION( 3368 NS_SUCCEEDED(rvIgnored), 3369 nsPrintfCString( 3370 "Element::SetAttr(nsGkAtoms::mozquote, \"true\", %s) " 3371 "failed", 3372 aSpanElement.IsInComposedDoc() ? "true" : "false") 3373 .get()); 3374 // Allow wrapping on spans so long lines get wrapped to the 3375 // screen. 3376 if (aPointToInsert.IsContainerHTMLElement(nsGkAtoms::body)) { 3377 DebugOnly<nsresult> rvIgnored = aSpanElement.SetAttr( 3378 kNameSpaceID_None, nsGkAtoms::style, 3379 nsLiteralString(u"white-space: pre-wrap; display: block; " 3380 u"width: 98vw;"), 3381 false); 3382 NS_WARNING_ASSERTION( 3383 NS_SUCCEEDED(rvIgnored), 3384 "Element::SetAttr(nsGkAtoms::style, \"pre-wrap, block\", " 3385 "false) failed, but ignored"); 3386 } else { 3387 DebugOnly<nsresult> rvIgnored = 3388 aSpanElement.SetAttr(kNameSpaceID_None, nsGkAtoms::style, 3389 u"white-space: pre-wrap;"_ns, false); 3390 NS_WARNING_ASSERTION( 3391 NS_SUCCEEDED(rvIgnored), 3392 "Element::SetAttr(nsGkAtoms::style, " 3393 "\"pre-wrap\", false) failed, but ignored"); 3394 } 3395 return NS_OK; 3396 }); 3397 if (MOZ_UNLIKELY(spanElementOrError.isErr())) { 3398 NS_WARNING( 3399 "HTMLEditor::DeleteSelectionAndCreateElement(nsGkAtoms::span) " 3400 "failed"); 3401 return NS_OK; 3402 } 3403 // If this succeeded, then set selection inside the pre 3404 // so the inserted text will end up there. 3405 // If it failed, we don't care what the return value was, 3406 // but we'll fall through and try to insert the text anyway. 3407 MOZ_ASSERT(spanElementOrError.inspect()); 3408 nsresult rv = CollapseSelectionToStartOf( 3409 MOZ_KnownLive(*spanElementOrError.inspect())); 3410 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { 3411 NS_WARNING( 3412 "EditorBase::CollapseSelectionToStartOf() caused destroying the " 3413 "editor"); 3414 return NS_ERROR_EDITOR_DESTROYED; 3415 } 3416 NS_WARNING_ASSERTION( 3417 NS_SUCCEEDED(rv), 3418 "EditorBase::CollapseSelectionToStartOf() failed, but ignored"); 3419 containerSpanElement = spanElementOrError.unwrap(); 3420 } 3421 3422 // TODO: We should insert text at specific point rather than at selection. 3423 // Then, we can do this before inserting the <span> element. 3424 if (aAddCites == AddCites::Yes) { 3425 nsresult rv = InsertWithQuotationsAsSubAction(aQuotedText); 3426 if (NS_FAILED(rv)) { 3427 NS_WARNING("HTMLEditor::InsertWithQuotationsAsSubAction() failed"); 3428 return rv; 3429 } 3430 } else { 3431 nsresult rv = InsertTextAsSubAction(aQuotedText, InsertTextFor::NormalText); 3432 if (NS_FAILED(rv)) { 3433 NS_WARNING("EditorBase::InsertTextAsSubAction() failed"); 3434 return rv; 3435 } 3436 } 3437 3438 // Set the selection to after the <span> if and only if we wrap the text into 3439 // it. 3440 if (containerSpanElement) { 3441 EditorRawDOMPoint afterNewSpanElement( 3442 EditorRawDOMPoint::After(*containerSpanElement)); 3443 NS_WARNING_ASSERTION( 3444 afterNewSpanElement.IsSet(), 3445 "Failed to set after the new <span> element, but ignored"); 3446 if (afterNewSpanElement.IsSet()) { 3447 nsresult rv = CollapseSelectionTo(afterNewSpanElement); 3448 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { 3449 NS_WARNING( 3450 "EditorBase::CollapseSelectionTo() caused destroying the editor"); 3451 return NS_ERROR_EDITOR_DESTROYED; 3452 } 3453 NS_WARNING_ASSERTION( 3454 NS_SUCCEEDED(rv), 3455 "EditorBase::CollapseSelectionTo() failed, but ignored"); 3456 } 3457 3458 // Note that if !aAddCites, aNodeInserted isn't set. 3459 // That's okay because the routines that use aAddCites 3460 // don't need to know the inserted node. 3461 if (aNodeInserted) { 3462 containerSpanElement.forget(aNodeInserted); 3463 } 3464 } 3465 3466 return NS_OK; 3467 } 3468 3469 NS_IMETHODIMP HTMLEditor::Rewrap(bool aRespectNewlines) { 3470 AutoEditActionDataSetter editActionData(*this, EditAction::eRewrap); 3471 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 3472 if (NS_FAILED(rv)) { 3473 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 3474 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 3475 return EditorBase::ToGenericNSResult(rv); 3476 } 3477 3478 const RefPtr<Element> editingHost = 3479 ComputeEditingHost(LimitInBodyElement::No); 3480 if (NS_WARN_IF(!editingHost)) { 3481 return NS_ERROR_FAILURE; 3482 } 3483 3484 // Rewrap makes no sense if there's no wrap column; default to 72. 3485 int32_t wrapWidth = WrapWidth(); 3486 if (wrapWidth <= 0) { 3487 wrapWidth = 72; 3488 } 3489 3490 nsAutoString current; 3491 const bool isCollapsed = SelectionRef().IsCollapsed(); 3492 uint32_t flags = nsIDocumentEncoder::OutputFormatted | 3493 nsIDocumentEncoder::OutputLFLineBreak; 3494 if (!isCollapsed) { 3495 flags |= nsIDocumentEncoder::OutputSelectionOnly; 3496 } 3497 rv = ComputeValueInternal(u"text/plain"_ns, flags, current); 3498 if (NS_FAILED(rv)) { 3499 NS_WARNING("EditorBase::ComputeValueInternal(text/plain) failed"); 3500 return EditorBase::ToGenericNSResult(rv); 3501 } 3502 3503 if (current.IsEmpty()) { 3504 return NS_OK; 3505 } 3506 3507 nsString wrapped; 3508 uint32_t firstLineOffset = 0; // XXX need to reset this if there is a 3509 // selection 3510 InternetCiter::Rewrap(current, wrapWidth, firstLineOffset, aRespectNewlines, 3511 wrapped); 3512 3513 if (wrapped.IsEmpty()) { 3514 return NS_OK; 3515 } 3516 3517 if (isCollapsed) { 3518 DebugOnly<nsresult> rvIgnored = SelectAllInternal(); 3519 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 3520 "HTMLEditor::SelectAllInternal() failed"); 3521 } 3522 3523 // The whole operation in InsertTextWithQuotationsInternal() should be 3524 // undoable in one transaction. 3525 // XXX Why isn't enough to use only AutoPlaceholderBatch here? 3526 AutoTransactionBatch bundleAllTransactions(*this, __FUNCTION__); 3527 AutoPlaceholderBatch treatAsOneTransaction( 3528 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 3529 rv = InsertTextWithQuotationsInternal(wrapped, *editingHost); 3530 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3531 "HTMLEditor::InsertTextWithQuotationsInternal() failed"); 3532 return EditorBase::ToGenericNSResult(rv); 3533 } 3534 3535 NS_IMETHODIMP HTMLEditor::InsertAsCitedQuotation(const nsAString& aQuotedText, 3536 const nsAString& aCitation, 3537 bool aInsertHTML, 3538 nsINode** aNodeInserted) { 3539 const RefPtr<Element> editingHost = 3540 ComputeEditingHost(LimitInBodyElement::No); 3541 if (NS_WARN_IF(!editingHost)) { 3542 return NS_ERROR_FAILURE; 3543 } 3544 3545 // Don't let anyone insert HTML when we're in plaintext mode. 3546 if (IsPlaintextMailComposer() || 3547 editingHost->IsContentEditablePlainTextOnly()) { 3548 NS_ASSERTION( 3549 !aInsertHTML, 3550 "InsertAsCitedQuotation: trying to insert html into plaintext editor"); 3551 3552 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertText); 3553 MOZ_ASSERT(!aQuotedText.IsVoid()); 3554 editActionData.SetData(aQuotedText); 3555 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 3556 if (NS_FAILED(rv)) { 3557 NS_WARNING_ASSERTION( 3558 rv == NS_ERROR_EDITOR_ACTION_CANCELED, 3559 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 3560 return EditorBase::ToGenericNSResult(rv); 3561 } 3562 3563 AutoPlaceholderBatch treatAsOneTransaction( 3564 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 3565 rv = InsertAsPlaintextQuotation(aQuotedText, AddCites::Yes, *editingHost, 3566 aNodeInserted); 3567 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3568 "HTMLEditor::InsertAsPlaintextQuotation() failed"); 3569 return EditorBase::ToGenericNSResult(rv); 3570 } 3571 3572 AutoEditActionDataSetter editActionData(*this, 3573 EditAction::eInsertBlockquoteElement); 3574 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 3575 if (NS_FAILED(rv)) { 3576 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 3577 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 3578 return EditorBase::ToGenericNSResult(rv); 3579 } 3580 3581 AutoPlaceholderBatch treatAsOneTransaction( 3582 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 3583 rv = InsertAsCitedQuotationInternal(aQuotedText, aCitation, aInsertHTML, 3584 *editingHost, aNodeInserted); 3585 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3586 "HTMLEditor::InsertAsCitedQuotationInternal() failed"); 3587 return EditorBase::ToGenericNSResult(rv); 3588 } 3589 3590 nsresult HTMLEditor::InsertAsCitedQuotationInternal( 3591 const nsAString& aQuotedText, const nsAString& aCitation, bool aInsertHTML, 3592 const Element& aEditingHost, nsINode** aNodeInserted) { 3593 MOZ_ASSERT(IsEditActionDataAvailable()); 3594 MOZ_ASSERT(!IsPlaintextMailComposer()); 3595 3596 { 3597 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(); 3598 if (MOZ_UNLIKELY(result.isErr())) { 3599 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); 3600 return result.unwrapErr(); 3601 } 3602 if (result.inspect().Canceled()) { 3603 return NS_OK; 3604 } 3605 } 3606 3607 UndefineCaretBidiLevel(); 3608 3609 IgnoredErrorResult ignoredError; 3610 AutoEditSubActionNotifier startToHandleEditSubAction( 3611 *this, EditSubAction::eInsertQuotation, nsIEditor::eNext, ignoredError); 3612 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 3613 return ignoredError.StealNSResult(); 3614 } 3615 NS_WARNING_ASSERTION( 3616 !ignoredError.Failed(), 3617 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 3618 3619 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor(); 3620 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 3621 return NS_ERROR_EDITOR_DESTROYED; 3622 } 3623 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3624 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() " 3625 "failed, but ignored"); 3626 3627 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) { 3628 nsresult rv = EnsureCaretNotAfterInvisibleBRElement(aEditingHost); 3629 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 3630 return NS_ERROR_EDITOR_DESTROYED; 3631 } 3632 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3633 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() " 3634 "failed, but ignored"); 3635 if (NS_SUCCEEDED(rv)) { 3636 nsresult rv = PrepareInlineStylesForCaret(); 3637 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 3638 return NS_ERROR_EDITOR_DESTROYED; 3639 } 3640 NS_WARNING_ASSERTION( 3641 NS_SUCCEEDED(rv), 3642 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored"); 3643 } 3644 } 3645 3646 Result<RefPtr<Element>, nsresult> blockquoteElementOrError = 3647 DeleteSelectionAndCreateElement( 3648 *nsGkAtoms::blockquote, 3649 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868 3650 [&aCitation](HTMLEditor&, Element& aBlockquoteElement, 3651 const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY { 3652 // Try to set type=cite. Ignore it if this fails. 3653 DebugOnly<nsresult> rvIgnored = aBlockquoteElement.SetAttr( 3654 kNameSpaceID_None, nsGkAtoms::type, u"cite"_ns, 3655 aBlockquoteElement.IsInComposedDoc()); 3656 NS_WARNING_ASSERTION( 3657 NS_SUCCEEDED(rvIgnored), 3658 nsPrintfCString( 3659 "Element::SetAttr(nsGkAtoms::type, \"cite\", %s) failed, " 3660 "but ignored", 3661 aBlockquoteElement.IsInComposedDoc() ? "true" : "false") 3662 .get()); 3663 if (!aCitation.IsEmpty()) { 3664 DebugOnly<nsresult> rvIgnored = aBlockquoteElement.SetAttr( 3665 kNameSpaceID_None, nsGkAtoms::cite, aCitation, 3666 aBlockquoteElement.IsInComposedDoc()); 3667 NS_WARNING_ASSERTION( 3668 NS_SUCCEEDED(rvIgnored), 3669 nsPrintfCString( 3670 "Element::SetAttr(nsGkAtoms::cite, \"...\", %s) failed, " 3671 "but ignored", 3672 aBlockquoteElement.IsInComposedDoc() ? "true" : "false") 3673 .get()); 3674 } 3675 return NS_OK; 3676 }); 3677 if (MOZ_UNLIKELY(blockquoteElementOrError.isErr() || 3678 NS_WARN_IF(Destroyed()))) { 3679 NS_WARNING( 3680 "HTMLEditor::DeleteSelectionAndCreateElement(nsGkAtoms::blockquote) " 3681 "failed"); 3682 return Destroyed() ? NS_ERROR_EDITOR_DESTROYED 3683 : blockquoteElementOrError.unwrapErr(); 3684 } 3685 MOZ_ASSERT(blockquoteElementOrError.inspect()); 3686 3687 // Set the selection inside the blockquote so aQuotedText will go there: 3688 rv = CollapseSelectionTo( 3689 EditorRawDOMPoint(blockquoteElementOrError.inspect(), 0u)); 3690 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { 3691 NS_WARNING( 3692 "EditorBase::CollapseSelectionTo() caused destroying the editor"); 3693 return NS_ERROR_EDITOR_DESTROYED; 3694 } 3695 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3696 "EditorBase::CollapseSelectionTo() failed, but ignored"); 3697 3698 // TODO: We should insert text at specific point rather than at selection. 3699 // Then, we can do this before inserting the <blockquote> element. 3700 if (aInsertHTML) { 3701 rv = LoadHTML(aQuotedText); 3702 if (NS_WARN_IF(Destroyed())) { 3703 return NS_ERROR_EDITOR_DESTROYED; 3704 } 3705 if (NS_FAILED(rv)) { 3706 NS_WARNING("HTMLEditor::LoadHTML() failed"); 3707 return rv; 3708 } 3709 } else { 3710 rv = InsertTextAsSubAction( 3711 aQuotedText, InsertTextFor::NormalText); // XXX ignore charset 3712 if (NS_WARN_IF(Destroyed())) { 3713 return NS_ERROR_EDITOR_DESTROYED; 3714 } 3715 if (NS_FAILED(rv)) { 3716 NS_WARNING("HTMLEditor::LoadHTML() failed"); 3717 return rv; 3718 } 3719 } 3720 3721 // Set the selection to just after the inserted node: 3722 EditorRawDOMPoint afterNewBlockquoteElement( 3723 EditorRawDOMPoint::After(blockquoteElementOrError.inspect())); 3724 NS_WARNING_ASSERTION( 3725 afterNewBlockquoteElement.IsSet(), 3726 "Failed to set after new <blockquote> element, but ignored"); 3727 if (afterNewBlockquoteElement.IsSet()) { 3728 nsresult rv = CollapseSelectionTo(afterNewBlockquoteElement); 3729 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { 3730 NS_WARNING( 3731 "EditorBase::CollapseSelectionTo() caused destroying the editor"); 3732 return NS_ERROR_EDITOR_DESTROYED; 3733 } 3734 NS_WARNING_ASSERTION( 3735 NS_SUCCEEDED(rv), 3736 "EditorBase::CollapseSelectionTo() failed, but ignored"); 3737 } 3738 3739 if (aNodeInserted) { 3740 blockquoteElementOrError.unwrap().forget(aNodeInserted); 3741 } 3742 3743 return NS_OK; 3744 } 3745 3746 void HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator:: 3747 RemoveHeadChildAndStealBodyChildsChildren(nsINode& aNode) { 3748 nsCOMPtr<nsIContent> body, head; 3749 // find the body and head nodes if any. 3750 // look only at immediate children of aNode. 3751 for (nsCOMPtr<nsIContent> child = aNode.GetFirstChild(); child; 3752 child = child->GetNextSibling()) { 3753 if (child->IsHTMLElement(nsGkAtoms::body)) { 3754 body = child; 3755 } else if (child->IsHTMLElement(nsGkAtoms::head)) { 3756 head = child; 3757 } 3758 } 3759 if (head) { 3760 ErrorResult ignored; 3761 aNode.RemoveChild(*head, ignored); 3762 } 3763 if (body) { 3764 nsCOMPtr<nsIContent> child = body->GetFirstChild(); 3765 while (child) { 3766 ErrorResult ignored; 3767 aNode.InsertBefore(*child, body, ignored); 3768 child = body->GetFirstChild(); 3769 } 3770 3771 ErrorResult ignored; 3772 aNode.RemoveChild(*body, ignored); 3773 } 3774 } 3775 3776 // static 3777 void HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator:: 3778 RemoveIncompleteDescendantsFromInsertingFragment(nsINode& aNode) { 3779 nsIContent* child = aNode.GetFirstChild(); 3780 while (child) { 3781 bool isEmptyNodeShouldNotInserted = false; 3782 if (HTMLEditUtils::IsListElement(*child)) { 3783 // Current limitation of HTMLEditor: 3784 // Cannot put caret in a list element which does not have list item 3785 // element even as a descendant. I.e., HTMLEditor does not support 3786 // editing in such empty list element, and does not support to delete 3787 // it from outside. Therefore, HTMLWithContextInserter should not 3788 // insert empty list element. 3789 isEmptyNodeShouldNotInserted = HTMLEditUtils::IsEmptyNode( 3790 *child, 3791 { 3792 // Although we don't check relation between list item element 3793 // and parent list element, but it should not be a problem in the 3794 // wild because appearing such invalid list element is an edge 3795 // case and anyway HTMLEditor supports editing in them. 3796 EmptyCheckOption::TreatListItemAsVisible, 3797 // A non-editable list item element may make the list element 3798 // visible. Although HTMLEditor does not support to edit list 3799 // elements which have only non-editable list item elements, but 3800 // it should be deleted from outside. Therefore, don't treat 3801 // non-editable things as invisible. 3802 // TODO: Currently, HTMLEditor does not support deleting such list 3803 // element with Backspace. We should fix this issue. 3804 }); 3805 } 3806 // TODO: Perhaps, we should delete <table>s if they have no <td>/<th> 3807 // element, or something other elements which must have specific 3808 // children but they don't. 3809 if (isEmptyNodeShouldNotInserted) { 3810 nsIContent* nextChild = child->GetNextSibling(); 3811 OwningNonNull<nsIContent> removingChild(*child); 3812 removingChild->Remove(); 3813 child = nextChild; 3814 continue; 3815 } 3816 if (child->HasChildNodes()) { 3817 RemoveIncompleteDescendantsFromInsertingFragment(*child); 3818 } 3819 child = child->GetNextSibling(); 3820 } 3821 } 3822 3823 // static 3824 bool HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator:: 3825 IsInsertionCookie(const nsIContent& aContent) { 3826 // Is this child the magical cookie? 3827 if (const auto* comment = Comment::FromNode(&aContent)) { 3828 nsAutoString data; 3829 comment->GetData(data); 3830 3831 return data.EqualsLiteral(kInsertCookie); 3832 } 3833 3834 return false; 3835 } 3836 3837 /** 3838 * This function finds the target node that we will be pasting into. aStart is 3839 * the context that we're given and aResult will be the target. Initially, 3840 * *aResult must be nullptr. 3841 * 3842 * The target for a paste is found by either finding the node that contains 3843 * the magical comment node containing kInsertCookie or, failing that, the 3844 * firstChild of the firstChild (until we reach a leaf). 3845 */ 3846 bool HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator:: 3847 FindTargetNodeOfContextForPastedHTMLAndRemoveInsertionCookie( 3848 nsINode& aStart, nsCOMPtr<nsINode>& aResult) { 3849 nsIContent* firstChild = aStart.GetFirstChild(); 3850 if (!firstChild) { 3851 // If the current result is nullptr, then aStart is a leaf, and is the 3852 // fallback result. 3853 if (!aResult) { 3854 aResult = &aStart; 3855 } 3856 return false; 3857 } 3858 3859 for (nsCOMPtr<nsIContent> child = firstChild; child; 3860 child = child->GetNextSibling()) { 3861 if (FragmentFromPasteCreator::IsInsertionCookie(*child)) { 3862 // Yes it is! Return an error so we bubble out and short-circuit the 3863 // search. 3864 aResult = &aStart; 3865 3866 child->Remove(); 3867 3868 return true; 3869 } 3870 3871 if (FindTargetNodeOfContextForPastedHTMLAndRemoveInsertionCookie(*child, 3872 aResult)) { 3873 return true; 3874 } 3875 } 3876 3877 return false; 3878 } 3879 3880 class MOZ_STACK_CLASS HTMLEditor::HTMLWithContextInserter::FragmentParser 3881 final { 3882 public: 3883 FragmentParser(const Document& aDocument, SafeToInsertData aSafeToInsertData); 3884 3885 [[nodiscard]] nsresult ParseContext(const nsAString& aContextString, 3886 DocumentFragment** aFragment); 3887 3888 [[nodiscard]] nsresult ParsePastedHTML(const nsAString& aInputString, 3889 nsAtom* aContextLocalNameAtom, 3890 DocumentFragment** aFragment); 3891 3892 private: 3893 static nsresult ParseFragment(const nsAString& aStr, 3894 nsAtom* aContextLocalName, 3895 const Document* aTargetDoc, 3896 dom::DocumentFragment** aFragment, 3897 SafeToInsertData aSafeToInsertData); 3898 3899 const Document& mDocument; 3900 const SafeToInsertData mSafeToInsertData; 3901 }; 3902 3903 HTMLEditor::HTMLWithContextInserter::FragmentParser::FragmentParser( 3904 const Document& aDocument, SafeToInsertData aSafeToInsertData) 3905 : mDocument{aDocument}, mSafeToInsertData{aSafeToInsertData} {} 3906 3907 nsresult HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseContext( 3908 const nsAString& aContextStr, DocumentFragment** aFragment) { 3909 return FragmentParser::ParseFragment(aContextStr, nullptr, &mDocument, 3910 aFragment, mSafeToInsertData); 3911 } 3912 3913 nsresult HTMLEditor::HTMLWithContextInserter::FragmentParser::ParsePastedHTML( 3914 const nsAString& aInputString, nsAtom* aContextLocalNameAtom, 3915 DocumentFragment** aFragment) { 3916 return FragmentParser::ParseFragment(aInputString, aContextLocalNameAtom, 3917 &mDocument, aFragment, 3918 mSafeToInsertData); 3919 } 3920 3921 nsresult HTMLEditor::HTMLWithContextInserter::CreateDOMFragmentFromPaste( 3922 const nsAString& aInputString, const nsAString& aContextStr, 3923 const nsAString& aInfoStr, nsCOMPtr<nsINode>* aOutFragNode, 3924 nsCOMPtr<nsINode>* aOutStartNode, nsCOMPtr<nsINode>* aOutEndNode, 3925 uint32_t* aOutStartOffset, uint32_t* aOutEndOffset, 3926 SafeToInsertData aSafeToInsertData) const { 3927 if (NS_WARN_IF(!aOutFragNode) || NS_WARN_IF(!aOutStartNode) || 3928 NS_WARN_IF(!aOutEndNode) || NS_WARN_IF(!aOutStartOffset) || 3929 NS_WARN_IF(!aOutEndOffset)) { 3930 return NS_ERROR_INVALID_ARG; 3931 } 3932 3933 RefPtr<const Document> document = mHTMLEditor.GetDocument(); 3934 if (NS_WARN_IF(!document)) { 3935 return NS_ERROR_FAILURE; 3936 } 3937 3938 FragmentFromPasteCreator fragmentFromPasteCreator; 3939 3940 const nsresult rv = fragmentFromPasteCreator.Run( 3941 *document, aInputString, aContextStr, aInfoStr, aOutFragNode, 3942 aOutStartNode, aOutEndNode, aSafeToInsertData); 3943 3944 *aOutStartOffset = 0; 3945 *aOutEndOffset = (*aOutEndNode)->Length(); 3946 3947 return rv; 3948 } 3949 3950 // static 3951 nsAtom* HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator:: 3952 DetermineContextLocalNameForParsingPastedHTML( 3953 const nsIContent* aParentContentOfPastedHTMLInContext) { 3954 if (!aParentContentOfPastedHTMLInContext) { 3955 return nsGkAtoms::body; 3956 } 3957 3958 nsAtom* contextLocalNameAtom = 3959 aParentContentOfPastedHTMLInContext->NodeInfo()->NameAtom(); 3960 3961 return (aParentContentOfPastedHTMLInContext->IsHTMLElement(nsGkAtoms::html)) 3962 ? nsGkAtoms::body 3963 : contextLocalNameAtom; 3964 } 3965 3966 // static 3967 nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator:: 3968 MergeAndPostProcessFragmentsForPastedHTMLAndContext( 3969 DocumentFragment& aDocumentFragmentForPastedHTML, 3970 DocumentFragment& aDocumentFragmentForContext, 3971 nsIContent& aTargetContentOfContextForPastedHTML) { 3972 FragmentFromPasteCreator::RemoveHeadChildAndStealBodyChildsChildren( 3973 aDocumentFragmentForPastedHTML); 3974 3975 FragmentFromPasteCreator::RemoveIncompleteDescendantsFromInsertingFragment( 3976 aDocumentFragmentForPastedHTML); 3977 3978 // unite the two trees 3979 IgnoredErrorResult ignoredError; 3980 aTargetContentOfContextForPastedHTML.AppendChild( 3981 aDocumentFragmentForPastedHTML, ignoredError); 3982 NS_WARNING_ASSERTION(!ignoredError.Failed(), 3983 "nsINode::AppendChild() failed, but ignored"); 3984 const nsresult rv = FragmentFromPasteCreator:: 3985 RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces( 3986 aDocumentFragmentForContext, NodesToRemove::eOnlyListItems); 3987 3988 if (NS_FAILED(rv)) { 3989 NS_WARNING( 3990 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::" 3991 "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces()" 3992 " failed"); 3993 return rv; 3994 } 3995 3996 return rv; 3997 } 3998 3999 // static 4000 nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator:: 4001 PostProcessFragmentForPastedHTMLWithoutContext( 4002 DocumentFragment& aDocumentFragmentForPastedHTML) { 4003 FragmentFromPasteCreator::RemoveHeadChildAndStealBodyChildsChildren( 4004 aDocumentFragmentForPastedHTML); 4005 4006 FragmentFromPasteCreator::RemoveIncompleteDescendantsFromInsertingFragment( 4007 aDocumentFragmentForPastedHTML); 4008 4009 const nsresult rv = FragmentFromPasteCreator:: 4010 RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces( 4011 aDocumentFragmentForPastedHTML, NodesToRemove::eOnlyListItems); 4012 4013 if (NS_FAILED(rv)) { 4014 NS_WARNING( 4015 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::" 4016 "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces() " 4017 "failed"); 4018 return rv; 4019 } 4020 4021 return rv; 4022 } 4023 4024 // static 4025 nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator:: 4026 PreProcessContextDocumentFragmentForMerging( 4027 DocumentFragment& aDocumentFragmentForContext) { 4028 // The context is expected to contain text nodes only in block level 4029 // elements. Hence, if they contain only whitespace, they're invisible. 4030 const nsresult rv = FragmentFromPasteCreator:: 4031 RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces( 4032 aDocumentFragmentForContext, NodesToRemove::eAll); 4033 if (NS_FAILED(rv)) { 4034 NS_WARNING( 4035 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::" 4036 "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces() " 4037 "failed"); 4038 return rv; 4039 } 4040 4041 FragmentFromPasteCreator::RemoveHeadChildAndStealBodyChildsChildren( 4042 aDocumentFragmentForContext); 4043 4044 return rv; 4045 } 4046 4047 nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator:: 4048 CreateDocumentFragmentAndGetParentOfPastedHTMLInContext( 4049 const Document& aDocument, const nsAString& aInputString, 4050 const nsAString& aContextStr, SafeToInsertData aSafeToInsertData, 4051 nsCOMPtr<nsINode>& aParentNodeOfPastedHTMLInContext, 4052 RefPtr<DocumentFragment>& aDocumentFragmentToInsert) const { 4053 // if we have context info, create a fragment for that 4054 RefPtr<DocumentFragment> documentFragmentForContext; 4055 4056 FragmentParser fragmentParser{aDocument, aSafeToInsertData}; 4057 if (!aContextStr.IsEmpty()) { 4058 nsresult rv = fragmentParser.ParseContext( 4059 aContextStr, getter_AddRefs(documentFragmentForContext)); 4060 if (NS_FAILED(rv)) { 4061 NS_WARNING( 4062 "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseContext() " 4063 "failed"); 4064 return rv; 4065 } 4066 if (!documentFragmentForContext) { 4067 NS_WARNING( 4068 "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseContext() " 4069 "returned nullptr"); 4070 return NS_ERROR_FAILURE; 4071 } 4072 4073 rv = FragmentFromPasteCreator::PreProcessContextDocumentFragmentForMerging( 4074 *documentFragmentForContext); 4075 if (NS_FAILED(rv)) { 4076 NS_WARNING( 4077 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::" 4078 "PreProcessContextDocumentFragmentForMerging() failed."); 4079 return rv; 4080 } 4081 4082 FragmentFromPasteCreator:: 4083 FindTargetNodeOfContextForPastedHTMLAndRemoveInsertionCookie( 4084 *documentFragmentForContext, aParentNodeOfPastedHTMLInContext); 4085 MOZ_ASSERT(aParentNodeOfPastedHTMLInContext); 4086 } 4087 4088 nsCOMPtr<nsIContent> parentContentOfPastedHTMLInContext = 4089 nsIContent::FromNodeOrNull(aParentNodeOfPastedHTMLInContext); 4090 MOZ_ASSERT_IF(aParentNodeOfPastedHTMLInContext, 4091 parentContentOfPastedHTMLInContext); 4092 4093 nsAtom* contextLocalNameAtom = 4094 FragmentFromPasteCreator::DetermineContextLocalNameForParsingPastedHTML( 4095 parentContentOfPastedHTMLInContext); 4096 RefPtr<DocumentFragment> documentFragmentForPastedHTML; 4097 nsresult rv = fragmentParser.ParsePastedHTML( 4098 aInputString, contextLocalNameAtom, 4099 getter_AddRefs(documentFragmentForPastedHTML)); 4100 if (NS_FAILED(rv)) { 4101 NS_WARNING( 4102 "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParsePastedHTML()" 4103 " failed"); 4104 return rv; 4105 } 4106 if (!documentFragmentForPastedHTML) { 4107 NS_WARNING( 4108 "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParsePastedHTML()" 4109 " returned nullptr"); 4110 return NS_ERROR_FAILURE; 4111 } 4112 4113 if (aParentNodeOfPastedHTMLInContext) { 4114 const nsresult rv = FragmentFromPasteCreator:: 4115 MergeAndPostProcessFragmentsForPastedHTMLAndContext( 4116 *documentFragmentForPastedHTML, *documentFragmentForContext, 4117 *parentContentOfPastedHTMLInContext); 4118 if (NS_FAILED(rv)) { 4119 NS_WARNING( 4120 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::" 4121 "MergeAndPostProcessFragmentsForPastedHTMLAndContext() failed."); 4122 return rv; 4123 } 4124 aDocumentFragmentToInsert = std::move(documentFragmentForContext); 4125 } else { 4126 const nsresult rv = PostProcessFragmentForPastedHTMLWithoutContext( 4127 *documentFragmentForPastedHTML); 4128 if (NS_FAILED(rv)) { 4129 NS_WARNING( 4130 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::" 4131 "PostProcessFragmentForPastedHTMLWithoutContext() failed."); 4132 return rv; 4133 } 4134 4135 aDocumentFragmentToInsert = std::move(documentFragmentForPastedHTML); 4136 } 4137 4138 return rv; 4139 } 4140 4141 nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::Run( 4142 const Document& aDocument, const nsAString& aInputString, 4143 const nsAString& aContextStr, const nsAString& aInfoStr, 4144 nsCOMPtr<nsINode>* aOutFragNode, nsCOMPtr<nsINode>* aOutStartNode, 4145 nsCOMPtr<nsINode>* aOutEndNode, SafeToInsertData aSafeToInsertData) const { 4146 MOZ_ASSERT(aOutFragNode); 4147 MOZ_ASSERT(aOutStartNode); 4148 MOZ_ASSERT(aOutEndNode); 4149 4150 nsCOMPtr<nsINode> parentNodeOfPastedHTMLInContext; 4151 RefPtr<DocumentFragment> documentFragmentToInsert; 4152 nsresult rv = CreateDocumentFragmentAndGetParentOfPastedHTMLInContext( 4153 aDocument, aInputString, aContextStr, aSafeToInsertData, 4154 parentNodeOfPastedHTMLInContext, documentFragmentToInsert); 4155 if (NS_FAILED(rv)) { 4156 NS_WARNING( 4157 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::" 4158 "CreateDocumentFragmentAndGetParentOfPastedHTMLInContext() failed."); 4159 return rv; 4160 } 4161 4162 // If there was no context, then treat all of the data we did get as the 4163 // pasted data. 4164 if (parentNodeOfPastedHTMLInContext) { 4165 *aOutEndNode = *aOutStartNode = parentNodeOfPastedHTMLInContext; 4166 } else { 4167 *aOutEndNode = *aOutStartNode = documentFragmentToInsert; 4168 } 4169 4170 *aOutFragNode = std::move(documentFragmentToInsert); 4171 4172 if (!aInfoStr.IsEmpty()) { 4173 const nsresult rv = 4174 FragmentFromPasteCreator::MoveStartAndEndAccordingToHTMLInfo( 4175 aInfoStr, aOutStartNode, aOutEndNode); 4176 if (NS_FAILED(rv)) { 4177 NS_WARNING( 4178 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::" 4179 "MoveStartAndEndAccordingToHTMLInfo() failed"); 4180 return rv; 4181 } 4182 } 4183 4184 return NS_OK; 4185 } 4186 4187 // static 4188 nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator:: 4189 MoveStartAndEndAccordingToHTMLInfo(const nsAString& aInfoStr, 4190 nsCOMPtr<nsINode>* aOutStartNode, 4191 nsCOMPtr<nsINode>* aOutEndNode) { 4192 int32_t sep = aInfoStr.FindChar((char16_t)','); 4193 nsAutoString numstr1(Substring(aInfoStr, 0, sep)); 4194 nsAutoString numstr2( 4195 Substring(aInfoStr, sep + 1, aInfoStr.Length() - (sep + 1))); 4196 4197 // Move the start and end children. 4198 nsresult rvIgnored; 4199 int32_t num = numstr1.ToInteger(&rvIgnored); 4200 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 4201 "nsAString::ToInteger() failed, but ignored"); 4202 while (num--) { 4203 nsINode* tmp = (*aOutStartNode)->GetFirstChild(); 4204 if (!tmp) { 4205 NS_WARNING("aOutStartNode did not have children"); 4206 return NS_ERROR_FAILURE; 4207 } 4208 *aOutStartNode = tmp; 4209 } 4210 4211 num = numstr2.ToInteger(&rvIgnored); 4212 while (num--) { 4213 nsINode* tmp = (*aOutEndNode)->GetLastChild(); 4214 if (!tmp) { 4215 NS_WARNING("aOutEndNode did not have children"); 4216 return NS_ERROR_FAILURE; 4217 } 4218 *aOutEndNode = tmp; 4219 } 4220 4221 return NS_OK; 4222 } 4223 4224 // static 4225 nsresult HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseFragment( 4226 const nsAString& aFragStr, nsAtom* aContextLocalName, 4227 const Document* aTargetDocument, DocumentFragment** aFragment, 4228 SafeToInsertData aSafeToInsertData) { 4229 nsAutoScriptBlockerSuppressNodeRemoved autoBlocker; 4230 4231 nsCOMPtr<Document> doc = 4232 nsContentUtils::CreateInertHTMLDocument(aTargetDocument); 4233 if (!doc) { 4234 return NS_ERROR_FAILURE; 4235 } 4236 RefPtr<DocumentFragment> fragment = 4237 new (doc->NodeInfoManager()) DocumentFragment(doc->NodeInfoManager()); 4238 nsresult rv = nsContentUtils::ParseFragmentHTML( 4239 aFragStr, fragment, 4240 aContextLocalName ? aContextLocalName : nsGkAtoms::body, 4241 kNameSpaceID_XHTML, false, true); 4242 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 4243 "nsContentUtils::ParseFragmentHTML() failed"); 4244 if (aSafeToInsertData == SafeToInsertData::No) { 4245 nsTreeSanitizer sanitizer(aContextLocalName 4246 ? nsIParserUtils::SanitizerAllowStyle 4247 : nsIParserUtils::SanitizerAllowComments); 4248 sanitizer.Sanitize(fragment); 4249 } 4250 fragment.forget(aFragment); 4251 return rv; 4252 } 4253 4254 // static 4255 void HTMLEditor::HTMLWithContextInserter:: 4256 CollectTopMostChildContentsCompletelyInRange( 4257 const EditorRawDOMPoint& aStartPoint, 4258 const EditorRawDOMPoint& aEndPoint, 4259 nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents) { 4260 MOZ_ASSERT(aStartPoint.IsSetAndValid()); 4261 MOZ_ASSERT(aEndPoint.IsSetAndValid()); 4262 4263 RefPtr<nsRange> range = 4264 nsRange::Create(aStartPoint.ToRawRangeBoundary(), 4265 aEndPoint.ToRawRangeBoundary(), IgnoreErrors()); 4266 if (!range) { 4267 NS_WARNING("nsRange::Create() failed"); 4268 return; 4269 } 4270 DOMSubtreeIterator iter; 4271 if (NS_FAILED(iter.Init(*range))) { 4272 NS_WARNING("DOMSubtreeIterator::Init() failed, but ignored"); 4273 return; 4274 } 4275 4276 iter.AppendAllNodesToArray(aOutArrayOfContents); 4277 } 4278 4279 /****************************************************************************** 4280 * HTMLEditor::AutoHTMLFragmentBoundariesFixer 4281 ******************************************************************************/ 4282 4283 HTMLEditor::AutoHTMLFragmentBoundariesFixer::AutoHTMLFragmentBoundariesFixer( 4284 nsTArray<OwningNonNull<nsIContent>>& aArrayOfTopMostChildContents) { 4285 EnsureBeginsOrEndsWithValidContent(StartOrEnd::start, 4286 aArrayOfTopMostChildContents); 4287 EnsureBeginsOrEndsWithValidContent(StartOrEnd::end, 4288 aArrayOfTopMostChildContents); 4289 } 4290 4291 // static 4292 void HTMLEditor::AutoHTMLFragmentBoundariesFixer:: 4293 CollectTableAndAnyListElementsOfInclusiveAncestorsAt( 4294 nsIContent& aContent, 4295 nsTArray<OwningNonNull<Element>>& aOutArrayOfListAndTableElements) { 4296 for (Element* element = aContent.GetAsElementOrParentElement(); element; 4297 element = element->GetParentElement()) { 4298 if (HTMLEditUtils::IsListElement(*element) || 4299 element->IsHTMLElement(nsGkAtoms::table)) { 4300 aOutArrayOfListAndTableElements.AppendElement(*element); 4301 } 4302 } 4303 } 4304 4305 // static 4306 Element* HTMLEditor::AutoHTMLFragmentBoundariesFixer:: 4307 GetMostDistantAncestorListOrTableElement( 4308 const nsTArray<OwningNonNull<nsIContent>>& aArrayOfTopMostChildContents, 4309 const nsTArray<OwningNonNull<Element>>& 4310 aInclusiveAncestorsTableOrListElements) { 4311 Element* lastFoundAncestorListOrTableElement = nullptr; 4312 for (const OwningNonNull<nsIContent>& content : 4313 aArrayOfTopMostChildContents) { 4314 if (HTMLEditUtils::IsAnyTableElementExceptTableElementAndColumElement( 4315 content)) { 4316 Element* tableElement = 4317 HTMLEditUtils::GetClosestAncestorTableElement(*content); 4318 if (!tableElement) { 4319 continue; 4320 } 4321 // If we find a `<table>` element which is an ancestor of a table 4322 // related element and is not an acestor of first nor last of 4323 // aArrayOfNodes, return the last found list or `<table>` element. 4324 // XXX Is that really expected that this returns a list element in this 4325 // case? 4326 if (!aInclusiveAncestorsTableOrListElements.Contains(tableElement)) { 4327 return lastFoundAncestorListOrTableElement; 4328 } 4329 // If we find a `<table>` element which is topmost list or `<table>` 4330 // element at first or last of aArrayOfNodes, return it. 4331 if (aInclusiveAncestorsTableOrListElements.LastElement().get() == 4332 tableElement) { 4333 return tableElement; 4334 } 4335 // Otherwise, store the `<table>` element which is an ancestor but 4336 // not topmost ancestor of first or last of aArrayOfNodes. 4337 lastFoundAncestorListOrTableElement = tableElement; 4338 continue; 4339 } 4340 4341 if (!HTMLEditUtils::IsListItemElement(*content)) { 4342 continue; 4343 } 4344 Element* listElement = 4345 HTMLEditUtils::GetClosestAncestorAnyListElement(*content); 4346 if (!listElement) { 4347 continue; 4348 } 4349 // If we find a list element which is ancestor of a list item element and 4350 // is not an acestor of first nor last of aArrayOfNodes, return the last 4351 // found list or `<table>` element. 4352 // XXX Is that really expected that this returns a `<table>` element in 4353 // this case? 4354 if (!aInclusiveAncestorsTableOrListElements.Contains(listElement)) { 4355 return lastFoundAncestorListOrTableElement; 4356 } 4357 // If we find a list element which is topmost list or `<table>` element at 4358 // first or last of aArrayOfNodes, return it. 4359 if (aInclusiveAncestorsTableOrListElements.LastElement().get() == 4360 listElement) { 4361 return listElement; 4362 } 4363 // Otherwise, store the list element which is an ancestor but not topmost 4364 // ancestor of first or last of aArrayOfNodes. 4365 lastFoundAncestorListOrTableElement = listElement; 4366 } 4367 4368 // If we find only non-topmost list or `<table>` element, returns the last 4369 // found one (meaning bottommost one). Otherwise, nullptr. 4370 return lastFoundAncestorListOrTableElement; 4371 } 4372 4373 Element* 4374 HTMLEditor::AutoHTMLFragmentBoundariesFixer::FindReplaceableTableElement( 4375 Element& aTableElement, nsIContent& aContentMaybeInTableElement) const { 4376 MOZ_ASSERT(aTableElement.IsHTMLElement(nsGkAtoms::table)); 4377 // Perhaps, this is designed for climbing up the DOM tree from 4378 // aContentMaybeInTableElement to aTableElement and making sure that 4379 // aContentMaybeInTableElement itself or its ancestor is a `<td>`, `<th>`, 4380 // `<tr>`, `<thead>`, `<tbody>`, `<tfoot>` or `<caption>`. 4381 // But this looks really buggy because this loop may skip aTableElement 4382 // as the following NS_ASSERTION. We should write automated tests and 4383 // check right behavior. 4384 for (Element* element = 4385 aContentMaybeInTableElement.GetAsElementOrParentElement(); 4386 element; element = element->GetParentElement()) { 4387 if (!HTMLEditUtils::IsAnyTableElementExceptColumnElement(*element) || 4388 element->IsHTMLElement(nsGkAtoms::table)) { 4389 // XXX Perhaps, the original developer of this method assumed that 4390 // aTableElement won't be skipped because if it's assumed, we can 4391 // stop climbing up the tree in that case. 4392 NS_ASSERTION(element != &aTableElement, 4393 "The table element which is looking for is ignored"); 4394 continue; 4395 } 4396 Element* tableElement = nullptr; 4397 for (Element* maybeTableElement = element->GetParentElement(); 4398 maybeTableElement; 4399 maybeTableElement = maybeTableElement->GetParentElement()) { 4400 if (maybeTableElement->IsHTMLElement(nsGkAtoms::table)) { 4401 tableElement = maybeTableElement; 4402 break; 4403 } 4404 } 4405 if (tableElement == &aTableElement) { 4406 return element; 4407 } 4408 // XXX If we find another `<table>` element, why don't we keep searching 4409 // from its parent? 4410 } 4411 return nullptr; 4412 } 4413 4414 bool HTMLEditor::AutoHTMLFragmentBoundariesFixer::IsReplaceableListElement( 4415 Element& aListElement, nsIContent& aContentMaybeInListElement) const { 4416 MOZ_ASSERT(HTMLEditUtils::IsListElement(aListElement)); 4417 // Perhaps, this is designed for climbing up the DOM tree from 4418 // aContentMaybeInListElement to aListElement and making sure that 4419 // aContentMaybeInListElement itself or its ancestor is an list item. 4420 // But this looks really buggy because this loop may skip aListElement 4421 // as the following NS_ASSERTION. We should write automated tests and 4422 // check right behavior. 4423 for (Element* element = 4424 aContentMaybeInListElement.GetAsElementOrParentElement(); 4425 element; element = element->GetParentElement()) { 4426 if (!HTMLEditUtils::IsListItemElement(*element)) { 4427 // XXX Perhaps, the original developer of this method assumed that 4428 // aListElement won't be skipped because if it's assumed, we can 4429 // stop climbing up the tree in that case. 4430 NS_ASSERTION(element != &aListElement, 4431 "The list element which is looking for is ignored"); 4432 continue; 4433 } 4434 Element* listElement = 4435 HTMLEditUtils::GetClosestAncestorAnyListElement(*element); 4436 if (listElement == &aListElement) { 4437 return true; 4438 } 4439 // XXX If we find another list element, why don't we keep searching 4440 // from its parent? 4441 } 4442 return false; 4443 } 4444 4445 void HTMLEditor::AutoHTMLFragmentBoundariesFixer:: 4446 EnsureBeginsOrEndsWithValidContent(StartOrEnd aStartOrEnd, 4447 nsTArray<OwningNonNull<nsIContent>>& 4448 aArrayOfTopMostChildContents) const { 4449 MOZ_ASSERT(!aArrayOfTopMostChildContents.IsEmpty()); 4450 4451 // Collect list elements and table related elements at first or last node 4452 // in aArrayOfTopMostChildContents. 4453 AutoTArray<OwningNonNull<Element>, 4> inclusiveAncestorsListOrTableElements; 4454 CollectTableAndAnyListElementsOfInclusiveAncestorsAt( 4455 aStartOrEnd == StartOrEnd::end 4456 ? aArrayOfTopMostChildContents.LastElement() 4457 : aArrayOfTopMostChildContents[0], 4458 inclusiveAncestorsListOrTableElements); 4459 if (inclusiveAncestorsListOrTableElements.IsEmpty()) { 4460 return; 4461 } 4462 4463 // Get most ancestor list or `<table>` element in 4464 // inclusiveAncestorsListOrTableElements which contains earlier 4465 // node in aArrayOfTopMostChildContents as far as possible. 4466 // XXX With inclusiveAncestorsListOrTableElements, this returns a 4467 // list or `<table>` element which contains first or last node of 4468 // aArrayOfTopMostChildContents. However, this seems slow when 4469 // aStartOrEnd is StartOrEnd::end and only the last node is in 4470 // different list or `<table>`. But I'm not sure whether it's 4471 // possible case or not. We need to add tests to 4472 // test_content_iterator_subtree.html for checking how 4473 // SubtreeContentIterator works. 4474 Element* listOrTableElement = GetMostDistantAncestorListOrTableElement( 4475 aArrayOfTopMostChildContents, inclusiveAncestorsListOrTableElements); 4476 if (!listOrTableElement) { 4477 return; 4478 } 4479 4480 // If we have pieces of tables or lists to be inserted, let's force the 4481 // insertion to deal with table elements right away, so that it doesn't 4482 // orphan some table or list contents outside the table or list. 4483 4484 OwningNonNull<nsIContent>& firstOrLastChildContent = 4485 aStartOrEnd == StartOrEnd::end 4486 ? aArrayOfTopMostChildContents.LastElement() 4487 : aArrayOfTopMostChildContents[0]; 4488 4489 // Find substructure of list or table that must be included in paste. 4490 Element* replaceElement; 4491 if (HTMLEditUtils::IsListElement(*listOrTableElement)) { 4492 if (!IsReplaceableListElement(*listOrTableElement, 4493 firstOrLastChildContent)) { 4494 return; 4495 } 4496 replaceElement = listOrTableElement; 4497 } else { 4498 MOZ_ASSERT(listOrTableElement->IsHTMLElement(nsGkAtoms::table)); 4499 replaceElement = FindReplaceableTableElement(*listOrTableElement, 4500 firstOrLastChildContent); 4501 if (!replaceElement) { 4502 return; 4503 } 4504 } 4505 4506 // If we can replace the given list element or found a table related element 4507 // in the `<table>` element, insert it into aArrayOfTopMostChildContents which 4508 // is tompost children to be inserted instead of descendants of them in 4509 // aArrayOfTopMostChildContents. 4510 for (size_t i = 0; i < aArrayOfTopMostChildContents.Length();) { 4511 OwningNonNull<nsIContent>& content = aArrayOfTopMostChildContents[i]; 4512 if (content == replaceElement) { 4513 // If the element is n aArrayOfTopMostChildContents, its descendants must 4514 // not be in the array. Therefore, we don't need to optimize this case. 4515 // XXX Perhaps, we can break this loop right now. 4516 aArrayOfTopMostChildContents.RemoveElementAt(i); 4517 continue; 4518 } 4519 if (!EditorUtils::IsDescendantOf(content, *replaceElement)) { 4520 i++; 4521 continue; 4522 } 4523 // For saving number of calls of EditorUtils::IsDescendantOf(), we should 4524 // remove its siblings in the array. 4525 nsIContent* parent = content->GetParent(); 4526 aArrayOfTopMostChildContents.RemoveElementAt(i); 4527 while (i < aArrayOfTopMostChildContents.Length() && 4528 aArrayOfTopMostChildContents[i]->GetParent() == parent) { 4529 aArrayOfTopMostChildContents.RemoveElementAt(i); 4530 } 4531 } 4532 4533 // Now replace the removed nodes with the structural parent 4534 if (aStartOrEnd == StartOrEnd::end) { 4535 aArrayOfTopMostChildContents.AppendElement(*replaceElement); 4536 } else { 4537 aArrayOfTopMostChildContents.InsertElementAt(0, *replaceElement); 4538 } 4539 } 4540 4541 } // namespace mozilla