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