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"
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"
51 #include "nsCRTGlue.h" // for CRLF
52 #include "nsComponentManagerUtils.h"
53 #include "nsIScriptError.h"
54 #include "nsContentUtils.h"
56 #include "nsDependentSubstring.h"
58 #include "nsFocusManager.h"
59 #include "nsGkAtoms.h"
60 #include "nsIClipboard.h"
61 #include "nsIContent.h"
62 #include "nsIDocumentEncoder.h"
64 #include "nsIInputStream.h"
65 #include "nsIMIMEService.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"
80 #include "nsReadableUtils.h"
81 #include "nsServiceManagerUtils.h"
82 #include "nsStreamUtils.h"
84 #include "nsStringFwd.h"
85 #include "nsStringIterator.h"
86 #include "nsTreeSanitizer.h"
89 #include "nsContentUtils.h"
90 #include "nsQueryObject.h"
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);
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();
133 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
134 "MaybeDispatchBeforeInputEvent() failed");
138 if (MOZ_UNLIKELY(!aDroppedAt
.IsInContentNode())) {
139 NS_WARNING("Dropped into non-content node");
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");
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())) {
158 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
159 "HTMLEditor::InsertFromDataTransfer("
160 "DeleteSelectedContent::No) failed, but ignored");
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();
194 NS_WARNING("EditorBase::EnsureNoPaddingBRElementForEmptyEditor() failed");
198 // Delete Selection, but only if it isn't collapsed, see bug #106269
199 if (!SelectionRef().IsCollapsed()) {
200 nsresult rv
= DeleteSelectionAsSubAction(eNone
, eStrip
);
203 "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
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.
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
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(
265 "EditorBase::CollapseSelectionTo() failed, but ignored");
271 NS_IMETHODIMP
HTMLEditor::InsertHTML(const nsAString
& aInString
) {
272 nsresult rv
= InsertHTMLAsAction(aInString
);
273 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
274 "HTMLEditor::InsertHTMLAsAction() failed");
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.
286 AutoEditActionDataSetter
editActionData(*this, EditAction::eInsertHTML
,
288 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
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*/);
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
,
318 mComposition
->CanRequsetIMEToCommitOrCancelComposition()) {
319 EnsureAutoPlaceholderBatch();
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
{
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
);
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
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
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
>
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
{
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;
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,
486 * @param aNode Should not be a node whose mutation may be observed by
489 static void RemoveIncompleteDescendantsFromInsertingFragment(nsINode
& aNode
);
491 enum class NodesToRemove
{
493 eOnlyListItems
/*!< List items are always block-level elements, hence such
494 whitespace-only nodes are always invisible. */
497 RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
498 nsIContent
& aNode
, NodesToRemove aNodesToRemove
);
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();
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();
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
538 if (mostDistantInclusiveAncestorTableElement
) {
539 containerContent
= mostDistantInclusiveAncestorTableElement
;
543 // If we are not in table elements, we should put caret in the last inserted
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.
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
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(
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
;
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()) {
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()) {
645 PrepareToInsertContent(aPointToInsert
, aDeleteSelectedContent
);
647 NS_WARNING("EditorBase::PrepareToInsertContent() failed");
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
);
669 "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
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
);
692 "HTMLEditor::HTMLWithContextInserter::CreateDOMFragmentFromPaste() "
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
=
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
737 if (!HTMLEditUtils::IsAnyTableElement(arrayOfTopMostChildContents
[0])) {
738 cellSelectionMode
= false;
742 if (!cellSelectionMode
) {
743 rv
= mHTMLEditor
.DeleteSelectionAndPrepareToCreateNode();
745 NS_WARNING("HTMLEditor::DeleteSelectionAndPrepareToCreateNode() failed");
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
,
756 if (MOZ_UNLIKELY(pointToPutCaretOrError
.isErr())) {
757 NS_WARNING("HTMLEditor::ClearStyleAt() failed");
758 return pointToPutCaretOrError
.propagateErr();
760 if (pointToPutCaretOrError
.inspect().IsSetAndValid()) {
762 mHTMLEditor
.CollapseSelectionTo(pointToPutCaretOrError
.unwrap());
764 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
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
776 AutoSelectionRestorer
restoreSelectionLater(&mHTMLEditor
);
777 rv
= mHTMLEditor
.DeleteTableCellWithTransaction(1);
779 NS_WARNING("HTMLEditor::DeleteTableCellWithTransaction(1) failed");
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");
797 if (result
.inspect().Canceled()) {
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()) {
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(
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()) ||
837 !candidatePointToInsert
.GetContainer()->IsInclusiveDescendantOf(
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
});
866 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
869 pointToInsert
= splitNodeResult
.inspect().AtSplitPoint
<EditorDOMPoint
>();
870 if (MOZ_UNLIKELY(!pointToInsert
.IsSet())) {
872 "HTMLEditor::SplitNodeDeepWithTransaction() didn't return split "
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
=
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
);
909 "HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed");
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
)) {
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());
935 return EditActionResult::HandledResult();
938 rv
= MoveCaretOutsideOfLink(*linkElement
, pointToPutCaret
);
941 "HTMLEditor::HTMLWithContextInserter::MoveCaretOutsideOfLink "
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
)
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
)) {
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
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
1011 if (moveChildResult
.inspectErr() ==
1012 NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
) {
1014 continue; // the inner `for` loop
1017 "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction("
1018 "SplitAtEdges::eDoNotCreateEmptyContainer) failed, maybe "
1020 break; // from the inner `for` loop
1022 if (MOZ_UNLIKELY(!moveChildResult
.inspect().Handled())) {
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
)) {
1040 "EditorBase::CollapseSelectionTo() caused destroying the editor");
1041 return Err(NS_ERROR_EDITOR_DESTROYED
);
1043 NS_WARNING_ASSERTION(
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
=
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
1096 if (moveChildResult
.inspectErr() ==
1097 NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
) {
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
);
1106 "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction("
1107 "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but maybe "
1109 break; // from the inner `for` loop
1111 if (MOZ_UNLIKELY(!moveChildResult
.inspect().Handled())) {
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())) {
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");
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
)) {
1154 "EditorBase::CollapseSelectionTo() caused destroying the editor");
1155 return Err(NS_ERROR_EDITOR_DESTROYED
);
1157 NS_WARNING_ASSERTION(
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
) {
1182 continue; // the inner `for` loop
1184 if (NS_WARN_IF(moveChildResult
.inspectErr() ==
1185 NS_ERROR_EDITOR_DESTROYED
)) {
1186 return moveChildResult
.propagateErr();
1189 "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction("
1190 "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but maybe "
1192 break; // from the inner `for` loop
1194 if (MOZ_UNLIKELY(!moveChildResult
.inspect().Handled())) {
1197 CreateContentResult unwrappedMoveChildResult
= moveChildResult
.unwrap();
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
)) {
1212 "EditorBase::CollapseSelectionTo() caused destroying the editor");
1213 return Err(NS_ERROR_EDITOR_DESTROYED
);
1215 NS_WARNING_ASSERTION(
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.
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())) {
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");
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.
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()) ||
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
=
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
);
1288 "HTMLEditor::InsertNodeInToProperAncestorWithTransaction("
1289 "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but "
1291 continue; // the inner `for` loop
1293 if (MOZ_UNLIKELY(!moveParentResult
.inspect().Handled())) {
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");
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())) {
1318 "HTMLEditor::InsertHTMLWithContextAsSubAction() got lost insertion "
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
;
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(
1362 "EditorBase::CollapseSelectionTo() failed, but ignored");
1366 nsresult rv
= splitLinkResult
.inspect().SuggestCaretPointTo(
1367 mHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
,
1368 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
1369 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1370 "SplitNodeResult::SuggestCaretPointTo() failed");
1375 Element
* HTMLEditor::GetLinkElement(nsINode
* aNode
) {
1376 if (NS_WARN_IF(!aNode
)) {
1379 nsINode
* node
= aNode
;
1381 if (HTMLEditUtils::IsLink(node
)) {
1382 return node
->AsElement();
1384 node
= node
->GetParentNode();
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.
1398 if (aNodesToRemove
== NodesToRemove::eAll
||
1399 HTMLEditUtils::IsAnyListElement(parent
)) {
1401 parent
->RemoveChild(aNode
, error
);
1402 NS_WARNING_ASSERTION(!error
.Failed(), "nsINode::RemoveChild() failed");
1403 return error
.StealNSResult();
1409 if (!aNode
.IsHTMLElement(nsGkAtoms::pre
)) {
1410 nsCOMPtr
<nsIContent
> child
= aNode
.GetLastChild();
1412 nsCOMPtr
<nsIContent
> previous
= child
->GetPreviousSibling();
1413 nsresult rv
= FragmentFromPasteCreator::
1414 RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
1415 *child
, aNodesToRemove
);
1416 if (NS_FAILED(rv
)) {
1418 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
1419 "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces"
1424 child
= std::move(previous
);
1430 class MOZ_STACK_CLASS
HTMLEditor::HTMLTransferablePreparer
{
1432 HTMLTransferablePreparer(const HTMLEditor
& aHTMLEditor
,
1433 nsITransferable
** aTransferable
,
1434 const Element
* aEditingHost
);
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
,
1460 return htmlTransferablePreparer
.Run();
1463 nsresult
HTMLEditor::HTMLTransferablePreparer::Run() {
1464 // Create generic Transferable for getting the data
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");
1473 if (!transferable
) {
1474 NS_WARNING("do_CreateInstance() returned nullptr, but ignored");
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
);
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");
1535 case 1: // prefer PNG over JPEG over GIF encoding (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");
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");
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) {
1591 numFront
+= strlen(aLeadingString
);
1593 int32_t numBack
= aCStr
.FindCharInSet(CRLF
, numFront
);
1594 if (numBack
== -1) {
1598 nsAutoCString
numStr(Substring(aCStr
, numFront
, numBack
- numFront
));
1600 foundNumber
= numStr
.ToInteger(&errorCode
);
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
) ||
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
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) {
1653 if (endHTML
== -1) {
1654 const char endFragmentMarker
[] = "<!--EndFragment-->";
1655 endHTML
= aCfhtml
.Find(endFragmentMarker
);
1656 if (endHTML
== -1) {
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.
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.
1684 "StartFragment byte count in the clipboard looks bad, see bug "
1686 startFragment
= curPos
- 1;
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
,
1715 if (!*aStuffToPaste
) {
1716 NS_WARNING("nsLinebreakConverter::ConvertUnicharLineBreaks() failed");
1717 return NS_ERROR_FAILURE
;
1720 // translate platform linebreaks for context
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
,
1728 // it's ok for context to be empty. frag might be whole doc and contain all
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");
1745 aOutput
.AppendLiteral("\" alt=\"\" >");
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
)
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()) {
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
);
1809 editActionData
.MarkAsBeforeInputHasBeenDispatched();
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
);
1852 class SlurpBlobEventListener final
: public nsIDOMEventListener
{
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
;
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
) {
1884 RefPtr
<FileReader
> reader
= do_QueryObject(target
);
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");
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");
1919 nsresult
HTMLEditor::SlurpBlob(Blob
* aBlob
, nsIGlobalObject
* aGlobal
,
1920 BlobReader
* aBlobReader
) {
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");
1937 rv
= reader
->AddEventListener(u
"error"_ns
, eventListener
, false);
1938 if (NS_FAILED(rv
)) {
1939 NS_WARNING("FileReader::AddEventListener(error) failed");
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");
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();
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");
1992 rv
= imageStream
->Close();
1993 if (NS_FAILED(rv
)) {
1994 NS_WARNING("nsIInputStream::Close() failed");
1998 nsAutoString stuffToPaste
;
1999 rv
= ImgFromData(type
, imageData
, stuffToPaste
);
2000 if (NS_FAILED(rv
)) {
2001 NS_WARNING("ImgFromData() failed");
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(
2014 "HTMLEditor::InsertHTMLWithContextAsSubAction("
2015 "InlineStylesAtInsertionPoint::Preserve) failed, but ignored");
2018 NS_WARNING("HTMLEditor::InsertObject: Unexpected type for image mime");
2023 // We always try to insert BlobImpl even without a known image mime.
2024 nsCOMPtr
<BlobImpl
> blob
= do_QueryInterface(object
);
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
);
2036 NS_WARNING("Could not get global");
2037 return NS_ERROR_FAILURE
;
2040 RefPtr
<Blob
> domBlob
= Blob::Create(global
, blob
);
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");
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();
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();
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(
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
,
2100 if (NS_FAILED(rv
)) {
2101 NS_WARNING("HTMLEditor::InsertObject() failed");
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
)) {
2124 "HTMLEditor::InsertHTMLWithContextAsSubAction("
2125 "DeleteSelectedContent::Yes, "
2126 "InlineStylesAtInsertionPoint::Clear) failed");
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
)) {
2138 "HTMLEditor::InsertHTMLWithContextAsSubAction("
2139 "DeleteSelectedContent::Yes, "
2140 "InlineStylesAtInsertionPoint::Clear) failed");
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
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
)) {
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
)) {
2176 "HTMLEditor::InsertHTMLWithContextAsSubAction("
2177 "DeleteSelectedContent::Yes, "
2178 "InlineStylesAtInsertionPoint::Clear) failed");
2182 AutoPlaceholderBatch
treatAsOneTransaction(
2183 *this, ScrollSelectionIntoView::Yes
, __FUNCTION__
);
2185 InsertTextAsSubAction(stuffToPaste
, SelectionHandling::Delete
);
2186 if (NS_FAILED(rv
)) {
2187 NS_WARNING("EditorBase::InsertTextAsSubAction() failed");
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");
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
));
2210 MOZ_ASSERT(aOutputString
.IsEmpty());
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());
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
++) {
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
));
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");
2276 } else if (type
.EqualsLiteral(kNativeHTMLMime
)) {
2277 // Windows only clipboard parsing.
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");
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");
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");
2344 if (type
.EqualsLiteral(kTextMime
) || type
.EqualsLiteral(kMozTextInternal
)) {
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");
2360 HTMLEditor::HavePrivateHTMLFlavor
2361 HTMLEditor::DataTransferOrClipboardHasPrivateHTMLFlavor(
2362 DataTransfer
* aDataTransfer
, nsIClipboard
* aClipboard
) {
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");
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");
2407 nsresult
HTMLEditor::PasteInternal(nsIClipboard::ClipboardType aClipboardType
,
2408 DataTransfer
* aDataTransfer
,
2409 const Element
& aEditingHost
) {
2410 MOZ_ASSERT(IsEditActionDataAvailable());
2412 if (MOZ_UNLIKELY(!IsModifiable())) {
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");
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");
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
,
2439 if (NS_FAILED(rv
)) {
2440 NS_WARNING("EditorBase::GetDataFromDataTransferOrClipboard() failed");
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
) {
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
,
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
) {
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
,
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
,
2515 NS_WARNING_ASSERTION(
2517 "HTMLEditor::InsertFromTransferableAtSelection() failed");
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");
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");
2554 nsAutoString contextStr
, infoStr
;
2555 rv
= InsertFromTransferableAtSelection(&aTransferable
, contextStr
, infoStr
,
2556 HavePrivateHTMLFlavor::No
,
2558 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2559 "HTMLEditor::InsertFromTransferableAtSelection("
2560 "HavePrivateHTMLFlavor::No) failed");
2564 nsresult
HTMLEditor::PasteNoFormattingAsAction(
2565 nsIClipboard::ClipboardType aClipboardType
,
2566 DispatchPasteEvent aDispatchPasteEvent
,
2567 DataTransfer
* aDataTransfer
/* = nullptr */,
2568 nsIPrincipal
* aPrincipal
/* = nullptr */) {
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
2584 if (!aDataTransfer
&& dataTransfer
) {
2585 dataTransfer
->ClearForPaste();
2589 AutoEditActionDataSetter
editActionData(*this, EditAction::ePaste
,
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())) {
2616 "EditorBase::DispatchClipboardEventAndUpdateClipboard("
2617 "ePasteNoFormatting) failed");
2618 return EditorBase::ToGenericNSResult(ret
.unwrapErr());
2621 switch (ret
.inspect()) {
2622 case ClipboardEventResult::DoDefault
:
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(
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())) {
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
) {
2702 "EditorUtils::CreateTransferableForPlainText() returned nullptr, but "
2706 rv
= GetDataFromDataTransferOrClipboard(dataTransfer
, transferable
,
2708 if (NS_FAILED(rv
)) {
2709 NS_WARNING("EditorBase::GetDataFromDataTransferOrClipboard() failed");
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()) {
2734 // can't paste if readonly
2735 if (!IsModifiable()) {
2739 const RefPtr
<Element
> editingHost
=
2740 ComputeEditingHost(LimitInBodyElement::No
);
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");
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
));
2759 nsresult rv
= clipboard
->HasDataMatchingFlavors(flavors
, aClipboardType
,
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
));
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()) {
2781 const RefPtr
<Element
> editingHost
=
2782 ComputeEditingHost(LimitInBodyElement::No
);
2787 // If |aTransferable| is null, assume that a paste will succeed.
2788 if (!aTransferable
) {
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
;
2797 if (IsPlaintextMailComposer() ||
2798 editingHost
->IsContentEditablePlainTextOnly()) {
2799 flavors
= textEditorFlavors
;
2800 length
= std::size(textEditorFlavors
);
2802 flavors
= textHtmlEditorFlavors
;
2803 length
= std::size(textHtmlEditorFlavors
);
2806 for (size_t i
= 0; i
< length
; i
++, flavors
++) {
2807 nsCOMPtr
<nsISupports
> data
;
2809 aTransferable
->GetTransferData(*flavors
, getter_AddRefs(data
));
2810 if (NS_SUCCEEDED(rv
) && data
) {
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");
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()) {
2845 PasteAsPlaintextQuotation(aClipboardType
, aDataTransfer
, *editingHost
);
2846 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2847 "HTMLEditor::PasteAsPlaintextQuotation() failed");
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()) {
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(
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
),
2922 "Element::SetAttr(nsGkAtoms::type, \"cite\", %s) "
2923 "failed, but ignored",
2924 aBlockquoteElement
.IsInComposedDoc() ? "true" : "false")
2928 if (MOZ_UNLIKELY(blockquoteElementOrError
.isErr()) ||
2929 NS_WARN_IF(Destroyed())) {
2931 "HTMLEditor::DeleteSelectionAndCreateElement(nsGkAtoms::blockquote) "
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");
2946 rv
= PasteInternal(aClipboardType
, aDataTransfer
, *editingHost
);
2947 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "HTMLEditor::PasteInternal() failed");
2951 nsresult
HTMLEditor::PasteAsPlaintextQuotation(
2952 nsIClipboard::ClipboardType aSelectionType
, DataTransfer
* aDataTransfer
,
2953 const Element
& aEditingHost
) {
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");
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
,
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");
3000 if (!flavor
.EqualsLiteral(kTextMime
)) {
3004 nsAutoString stuffToPaste
;
3005 if (!GetString(genericDataObj
, stuffToPaste
)) {
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");
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()) {
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(
3084 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
3088 rv
= InsertTextAsSubAction(quotedStuff
, SelectionHandling::Delete
);
3089 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3090 "EditorBase::InsertTextAsSubAction() failed");
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()) {
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:
3147 nsAString::const_iterator
dbgStart(hunkStart
);
3148 if (FindCharInReadable(HTMLEditUtils::kCarriageReturn
, dbgStart
, strEnd
)) {
3151 "Return characters in DOM! InsertTextWithQuotations may be wrong");
3156 nsresult rv
= NS_OK
;
3157 nsAString::const_iterator
lineStart(hunkStart
);
3158 // We will break from inside when we run out of newlines.
3160 // Search for the end of this line (dom newlines, see above):
3161 bool found
= FindCharInReadable(HTMLEditUtils::kNewLine
, lineStart
, strEnd
);
3162 bool quoted
= false;
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
) {
3170 quoted
= (*lineStart
== cite
);
3171 if (quoted
== curHunkIsQuoted
) {
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.
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");
3207 rv
= InsertTextAsSubAction(curHunk
, SelectionHandling::Delete
);
3208 NS_WARNING_ASSERTION(
3210 "EditorBase::InsertTextAsSubAction() failed, but might be ignored");
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.
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
,
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
,
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()) {
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(
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
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
),
3358 "Element::SetAttr(nsGkAtoms::mozquote, \"true\", %s) "
3360 aSpanElement
.IsInComposedDoc() ? "true" : "false")
3362 // Allow wrapping on spans so long lines get wrapped to the
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; "
3370 NS_WARNING_ASSERTION(
3371 NS_SUCCEEDED(rvIgnored
),
3372 "Element::SetAttr(nsGkAtoms::style, \"pre-wrap, block\", "
3373 "false) failed, but ignored");
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");
3385 if (MOZ_UNLIKELY(spanElementOrError
.isErr())) {
3387 "HTMLEditor::DeleteSelectionAndCreateElement(nsGkAtoms::span) "
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
)) {
3400 "EditorBase::CollapseSelectionToStartOf() caused destroying the "
3402 return NS_ERROR_EDITOR_DESTROYED
;
3404 NS_WARNING_ASSERTION(
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");
3419 nsresult rv
= InsertTextAsSubAction(aQuotedText
, SelectionHandling::Delete
);
3420 if (NS_FAILED(rv
)) {
3421 NS_WARNING("EditorBase::InsertTextAsSubAction() failed");
3426 // Set the selection to after the <span> if and only if we wrap the text into
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
)) {
3438 "EditorBase::CollapseSelectionTo() caused destroying the editor");
3439 return NS_ERROR_EDITOR_DESTROYED
;
3441 NS_WARNING_ASSERTION(
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
);
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) {
3478 nsAutoString current
;
3479 const bool isCollapsed
= SelectionRef().IsCollapsed();
3480 uint32_t flags
= nsIDocumentEncoder::OutputFormatted
|
3481 nsIDocumentEncoder::OutputLFLineBreak
;
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()) {
3496 uint32_t firstLineOffset
= 0; // XXX need to reset this if there is a
3498 InternetCiter::Rewrap(current
, wrapWidth
, firstLineOffset
, aRespectNewlines
,
3501 if (wrapped
.IsEmpty()) {
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
,
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()) {
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
,
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()) {
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(
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
),
3647 "Element::SetAttr(nsGkAtoms::type, \"cite\", %s) failed, "
3649 aBlockquoteElement
.IsInComposedDoc() ? "true" : "false")
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
),
3658 "Element::SetAttr(nsGkAtoms::cite, \"...\", %s) failed, "
3660 aBlockquoteElement
.IsInComposedDoc() ? "true" : "false")
3665 if (MOZ_UNLIKELY(blockquoteElementOrError
.isErr() ||
3666 NS_WARN_IF(Destroyed()))) {
3668 "HTMLEditor::DeleteSelectionAndCreateElement(nsGkAtoms::blockquote) "
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
)) {
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.
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");
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");
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
)) {
3719 "EditorBase::CollapseSelectionTo() caused destroying the editor");
3720 return NS_ERROR_EDITOR_DESTROYED
;
3722 NS_WARNING_ASSERTION(
3724 "EditorBase::CollapseSelectionTo() failed, but ignored");
3727 if (aNodeInserted
) {
3728 blockquoteElementOrError
.unwrap().forget(aNodeInserted
);
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
)) {
3743 } else if (child
->IsHTMLElement(nsGkAtoms::head
)) {
3748 ErrorResult ignored
;
3749 aNode
.RemoveChild(*head
, ignored
);
3752 nsCOMPtr
<nsIContent
> child
= body
->GetFirstChild();
3754 ErrorResult ignored
;
3755 aNode
.InsertBefore(*child
, body
, ignored
);
3756 child
= body
->GetFirstChild();
3759 ErrorResult ignored
;
3760 aNode
.RemoveChild(*body
, ignored
);
3765 void HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
3766 RemoveIncompleteDescendantsFromInsertingFragment(nsINode
& aNode
) {
3767 nsIContent
* child
= aNode
.GetFirstChild();
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(
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();
3804 if (child
->HasChildNodes()) {
3805 RemoveIncompleteDescendantsFromInsertingFragment(*child
);
3807 child
= child
->GetNextSibling();
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
)) {
3817 comment
->GetData(data
);
3819 return data
.EqualsLiteral(kInsertCookie
);
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();
3839 // If the current result is nullptr, then aStart is a leaf, and is the
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
3859 if (FindTargetNodeOfContextForPastedHTMLAndRemoveInsertionCookie(*child
,
3868 class MOZ_STACK_CLASS
HTMLEditor::HTMLWithContextInserter::FragmentParser
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
);
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
,
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();
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
))
3951 : contextLocalNameAtom
;
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
)) {
3978 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
3979 "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces()"
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
)) {
4003 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
4004 "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces() "
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
)) {
4023 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
4024 "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces() "
4029 FragmentFromPasteCreator::RemoveHeadChildAndStealBodyChildsChildren(
4030 aDocumentFragmentForContext
);
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
)) {
4050 "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseContext() "
4054 if (!documentFragmentForContext
) {
4056 "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseContext() "
4057 "returned nullptr");
4058 return NS_ERROR_FAILURE
;
4061 rv
= FragmentFromPasteCreator::PreProcessContextDocumentFragmentForMerging(
4062 *documentFragmentForContext
);
4063 if (NS_FAILED(rv
)) {
4065 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
4066 "PreProcessContextDocumentFragmentForMerging() failed.");
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
)) {
4090 "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParsePastedHTML()"
4094 if (!documentFragmentForPastedHTML
) {
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
)) {
4108 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
4109 "MergeAndPostProcessFragmentsForPastedHTMLAndContext() failed.");
4112 aDocumentFragmentToInsert
= std::move(documentFragmentForContext
);
4114 const nsresult rv
= PostProcessFragmentForPastedHTMLWithoutContext(
4115 *documentFragmentForPastedHTML
);
4116 if (NS_FAILED(rv
)) {
4118 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
4119 "PostProcessFragmentForPastedHTMLWithoutContext() failed.");
4123 aDocumentFragmentToInsert
= std::move(documentFragmentForPastedHTML
);
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
)) {
4145 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
4146 "CreateDocumentFragmentAndGetParentOfPastedHTMLInContext() failed.");
4150 // If there was no context, then treat all of the data we did get as the
4152 if (parentNodeOfPastedHTMLInContext
) {
4153 *aOutEndNode
= *aOutStartNode
= parentNodeOfPastedHTMLInContext
;
4155 *aOutEndNode
= *aOutStartNode
= documentFragmentToInsert
;
4158 *aOutFragNode
= std::move(documentFragmentToInsert
);
4160 if (!aInfoStr
.IsEmpty()) {
4162 FragmentFromPasteCreator::MoveStartAndEndAccordingToHTMLInfo(
4163 aInfoStr
, aOutStartNode
, aOutEndNode
);
4164 if (NS_FAILED(rv
)) {
4166 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
4167 "MoveStartAndEndAccordingToHTMLInfo() failed");
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.
4187 int32_t num
= numstr1
.ToInteger(&rvIgnored
);
4188 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
4189 "nsAString::ToInteger() failed, but ignored");
4191 nsINode
* tmp
= (*aOutStartNode
)->GetFirstChild();
4193 NS_WARNING("aOutStartNode did not have children");
4194 return NS_ERROR_FAILURE
;
4196 *aOutStartNode
= tmp
;
4199 num
= numstr2
.ToInteger(&rvIgnored
);
4201 nsINode
* tmp
= (*aOutEndNode
)->GetLastChild();
4203 NS_WARNING("aOutEndNode did not have children");
4204 return NS_ERROR_FAILURE
;
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
);
4222 return NS_ERROR_FAILURE
;
4224 RefPtr
<DocumentFragment
> fragment
=
4225 new (doc
->NodeInfoManager()) DocumentFragment(doc
->NodeInfoManager());
4226 nsresult rv
= nsContentUtils::ParseFragmentHTML(
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
);
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());
4255 NS_WARNING("nsRange::Create() failed");
4258 DOMSubtreeIterator iter
;
4259 if (NS_FAILED(iter
.Init(*range
))) {
4260 NS_WARNING("DOMSubtreeIterator::Init() failed, but ignored");
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
);
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
);
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
) {
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
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() ==
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
;
4327 if (!HTMLEditUtils::IsListItem(content
)) {
4330 Element
* listElement
=
4331 HTMLEditUtils::GetClosestAncestorAnyListElement(*content
);
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
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() ==
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
;
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");
4382 Element
* tableElement
= nullptr;
4383 for (Element
* maybeTableElement
= element
->GetParentElement();
4385 maybeTableElement
= maybeTableElement
->GetParentElement()) {
4386 if (maybeTableElement
->IsHTMLElement(nsGkAtoms::table
)) {
4387 tableElement
= maybeTableElement
;
4391 if (tableElement
== &aTableElement
) {
4394 // XXX If we find another `<table>` element, why don't we keep searching
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");
4420 Element
* listElement
=
4421 HTMLEditUtils::GetClosestAncestorAnyListElement(*element
);
4422 if (listElement
== &aListElement
) {
4425 // XXX If we find another list element, why don't we keep searching
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()) {
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
) {
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
)) {
4482 replaceElement
= listOrTableElement
;
4484 MOZ_ASSERT(listOrTableElement
->IsHTMLElement(nsGkAtoms::table
));
4485 replaceElement
= FindReplaceableTableElement(*listOrTableElement
,
4486 firstOrLastChildContent
);
4487 if (!replaceElement
) {
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
);
4505 if (!EditorUtils::IsDescendantOf(content
, *replaceElement
)) {
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
);
4523 aArrayOfTopMostChildContents
.InsertElementAt(0, *replaceElement
);
4527 } // namespace mozilla