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 "HTMLEditUtils.h"
12 #include "InternetCiter.h"
13 #include "WSRunObject.h"
14 #include "mozilla/dom/Comment.h"
15 #include "mozilla/dom/Document.h"
16 #include "mozilla/dom/DataTransfer.h"
17 #include "mozilla/dom/DocumentFragment.h"
18 #include "mozilla/dom/DOMException.h"
19 #include "mozilla/dom/DOMStringList.h"
20 #include "mozilla/dom/DOMStringList.h"
21 #include "mozilla/dom/Event.h"
22 #include "mozilla/dom/FileReader.h"
23 #include "mozilla/dom/Selection.h"
24 #include "mozilla/dom/StaticRange.h"
25 #include "mozilla/dom/WorkerRef.h"
26 #include "mozilla/ArrayUtils.h"
27 #include "mozilla/Attributes.h"
28 #include "mozilla/Base64.h"
29 #include "mozilla/BasicEvents.h"
30 #include "mozilla/DebugOnly.h"
31 #include "mozilla/EditAction.h"
32 #include "mozilla/EditorDOMPoint.h"
33 #include "mozilla/EditorUtils.h"
34 #include "mozilla/OwningNonNull.h"
35 #include "mozilla/Preferences.h"
36 #include "mozilla/Result.h"
37 #include "mozilla/SelectionState.h"
38 #include "mozilla/TypeInState.h"
39 #include "nsAString.h"
41 #include "nsCRTGlue.h" // for CRLF
42 #include "nsComponentManagerUtils.h"
43 #include "nsIScriptError.h"
44 #include "nsContentUtils.h"
46 #include "nsDependentSubstring.h"
48 #include "nsGkAtoms.h"
49 #include "nsIClipboard.h"
50 #include "nsIContent.h"
51 #include "nsIDocumentEncoder.h"
53 #include "nsIInputStream.h"
54 #include "nsNameSpaceManager.h"
56 #include "nsIParserUtils.h"
57 #include "nsIPrincipal.h"
58 #include "nsISupportsImpl.h"
59 #include "nsISupportsPrimitives.h"
60 #include "nsISupportsUtils.h"
61 #include "nsITransferable.h"
62 #include "nsIVariant.h"
63 #include "nsLinebreakConverter.h"
64 #include "nsLiteralString.h"
65 #include "nsNetUtil.h"
67 #include "nsReadableUtils.h"
68 #include "nsServiceManagerUtils.h"
69 #include "nsStreamUtils.h"
71 #include "nsStringFwd.h"
72 #include "nsStringIterator.h"
73 #include "nsTreeSanitizer.h"
76 #include "nsContentUtils.h"
77 #include "nsQueryObject.h"
85 using LeafNodeType
= HTMLEditUtils::LeafNodeType
;
87 #define kInsertCookie "_moz_Insert Here_moz_"
89 // some little helpers
90 static bool FindIntegerAfterString(const char* aLeadingString
,
91 const nsCString
& aCStr
,
92 int32_t& foundNumber
);
93 static void RemoveFragComments(nsCString
& aStr
);
95 nsresult
HTMLEditor::InsertDroppedDataTransferAsAction(
96 AutoEditActionDataSetter
& aEditActionData
, DataTransfer
& aDataTransfer
,
97 const EditorDOMPoint
& aDroppedAt
, nsIPrincipal
* aSourcePrincipal
) {
98 MOZ_ASSERT(aEditActionData
.GetEditAction() == EditAction::eDrop
);
99 MOZ_ASSERT(GetEditAction() == EditAction::eDrop
);
100 MOZ_ASSERT(aDroppedAt
.IsSet());
101 MOZ_ASSERT(aDataTransfer
.MozItemCount() > 0);
103 aEditActionData
.InitializeDataTransfer(&aDataTransfer
);
104 RefPtr
<StaticRange
> targetRange
= StaticRange::Create(
105 aDroppedAt
.GetContainer(), aDroppedAt
.Offset(), aDroppedAt
.GetContainer(),
106 aDroppedAt
.Offset(), IgnoreErrors());
107 NS_WARNING_ASSERTION(targetRange
&& targetRange
->IsPositioned(),
108 "Why did we fail to create collapsed static range at "
109 "dropped position?");
110 if (targetRange
&& targetRange
->IsPositioned()) {
111 aEditActionData
.AppendTargetRange(*targetRange
);
113 nsresult rv
= aEditActionData
.MaybeDispatchBeforeInputEvent();
115 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
116 "MaybeDispatchBeforeInputEvent() failed");
119 uint32_t numItems
= aDataTransfer
.MozItemCount();
120 for (uint32_t i
= 0; i
< numItems
; ++i
) {
121 DebugOnly
<nsresult
> rvIgnored
= InsertFromDataTransfer(
122 &aDataTransfer
, i
, aSourcePrincipal
, aDroppedAt
, false);
123 if (NS_WARN_IF(Destroyed())) {
126 NS_WARNING_ASSERTION(
127 NS_SUCCEEDED(rvIgnored
),
128 "HTMLEditor::InsertFromDataTransfer() failed, but ignored");
133 nsresult
HTMLEditor::LoadHTML(const nsAString
& aInputString
) {
134 MOZ_ASSERT(IsEditActionDataAvailable());
136 if (NS_WARN_IF(!mInitSucceeded
)) {
137 return NS_ERROR_NOT_INITIALIZED
;
140 // force IME commit; set up rules sniffing and batching
141 DebugOnly
<nsresult
> rvIgnored
= CommitComposition();
142 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
143 "EditorBase::CommitComposition() failed, but ignored");
144 if (NS_WARN_IF(Destroyed())) {
145 return NS_ERROR_EDITOR_DESTROYED
;
148 AutoPlaceholderBatch
treatAsOneTransaction(*this,
149 ScrollSelectionIntoView::Yes
);
150 IgnoredErrorResult ignoredError
;
151 AutoEditSubActionNotifier
startToHandleEditSubAction(
152 *this, EditSubAction::eInsertHTMLSource
, nsIEditor::eNext
, ignoredError
);
153 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
154 return ignoredError
.StealNSResult();
156 NS_WARNING_ASSERTION(
157 !ignoredError
.Failed(),
158 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
160 nsresult rv
= EnsureNoPaddingBRElementForEmptyEditor();
162 NS_WARNING("EditorBase::EnsureNoPaddingBRElementForEmptyEditor() failed");
166 // Delete Selection, but only if it isn't collapsed, see bug #106269
167 if (!SelectionRef().IsCollapsed()) {
168 nsresult rv
= DeleteSelectionAsSubAction(eNone
, eStrip
);
171 "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
176 // Get the first range in the selection, for context:
177 RefPtr
<const nsRange
> range
= SelectionRef().GetRangeAt(0);
178 if (NS_WARN_IF(!range
)) {
179 return NS_ERROR_FAILURE
;
182 // Create fragment for pasted HTML.
184 RefPtr
<DocumentFragment
> documentFragment
=
185 range
->CreateContextualFragment(aInputString
, error
);
186 if (error
.Failed()) {
187 NS_WARNING("nsRange::CreateContextualFragment() failed");
188 return error
.StealNSResult();
191 // Put the fragment into the document at start of selection.
192 EditorDOMPoint
pointToInsert(range
->StartRef());
193 // XXX We need to make pointToInsert store offset for keeping traditional
194 // behavior since using only child node to pointing insertion point
195 // changes the behavior when inserted child is moved by mutation
196 // observer. We need to investigate what we should do here.
197 Unused
<< pointToInsert
.Offset();
198 for (nsCOMPtr
<nsIContent
> contentToInsert
= documentFragment
->GetFirstChild();
199 contentToInsert
; contentToInsert
= documentFragment
->GetFirstChild()) {
200 rv
= InsertNodeWithTransaction(*contentToInsert
, pointToInsert
);
202 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
205 // XXX If the inserted node has been moved by mutation observer,
206 // incrementing offset will cause odd result. Next new node
207 // will be inserted after existing node and the offset will be
208 // overflown from the container node.
209 pointToInsert
.Set(pointToInsert
.GetContainer(), pointToInsert
.Offset() + 1);
210 if (NS_WARN_IF(!pointToInsert
.Offset())) {
211 // Append the remaining children to the container if offset is
213 pointToInsert
.SetToEndOf(pointToInsert
.GetContainer());
220 NS_IMETHODIMP
HTMLEditor::InsertHTML(const nsAString
& aInString
) {
221 nsresult rv
= InsertHTMLAsAction(aInString
);
222 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
223 "HTMLEditor::InsertHTMLAsAction() failed");
227 nsresult
HTMLEditor::InsertHTMLAsAction(const nsAString
& aInString
,
228 nsIPrincipal
* aPrincipal
) {
229 AutoEditActionDataSetter
editActionData(*this, EditAction::eInsertHTML
,
231 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
233 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
234 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
235 return EditorBase::ToGenericNSResult(rv
);
238 rv
= DoInsertHTMLWithContext(aInString
, u
""_ns
, u
""_ns
, u
""_ns
,
239 EditorDOMPoint(), true, true, false);
240 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
241 "HTMLEditor::DoInsertHTMLWithContext() failed");
242 return EditorBase::ToGenericNSResult(rv
);
245 class MOZ_STACK_CLASS
HTMLEditor::HTMLWithContextInserter final
{
247 MOZ_CAN_RUN_SCRIPT
explicit HTMLWithContextInserter(HTMLEditor
& aHTMLEditor
)
248 : mHTMLEditor(aHTMLEditor
) {}
250 HTMLWithContextInserter() = delete;
251 HTMLWithContextInserter(const HTMLWithContextInserter
&) = delete;
252 HTMLWithContextInserter(HTMLWithContextInserter
&&) = delete;
254 MOZ_CAN_RUN_SCRIPT nsresult
Run(const nsAString
& aInputString
,
255 const nsAString
& aContextStr
,
256 const nsAString
& aInfoStr
,
257 const EditorDOMPoint
& aPointToInsert
,
258 bool aDoDeleteSelection
, bool aTrustedInput
,
262 class FragmentFromPasteCreator
;
263 class FragmentParser
;
265 * CollectTopMostChildContentsCompletelyInRange() collects topmost child
266 * contents which are completely in the given range.
267 * For example, if the range points a node with its container node, the
268 * result is only the node (meaning does not include its descendants).
269 * If the range starts start of a node and ends end of it, and if the node
270 * does not have children, returns no nodes, otherwise, if the node has
271 * some children, the result includes its all children (not including their
274 * @param aStartPoint Start point of the range.
275 * @param aEndPoint End point of the range.
276 * @param aOutArrayOfContents [Out] Topmost children which are completely in
279 static void CollectTopMostChildContentsCompletelyInRange(
280 const EditorRawDOMPoint
& aStartPoint
, const EditorRawDOMPoint
& aEndPoint
,
281 nsTArray
<OwningNonNull
<nsIContent
>>& aOutArrayOfContents
);
284 * @return nullptr, if there's no invisible `<br>`.
286 HTMLBRElement
* GetInvisibleBRElementAtPoint(
287 const EditorDOMPoint
& aPointToInsert
) const;
289 EditorDOMPoint
GetNewCaretPointAfterInsertingHTML(
290 const EditorDOMPoint
& aLastInsertedPoint
) const;
293 * @return error result or the last inserted point. The latter is only set, if
294 * content was inserted.
296 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditorDOMPoint
, nsresult
>
298 const EditorDOMPoint
& aPointToInsert
,
299 nsTArray
<OwningNonNull
<nsIContent
>>& aArrayOfTopMostChildContents
,
300 const nsINode
* aFragmentAsNode
);
303 * @param aContextStr as indicated by nsITransferable's kHTMLContext.
304 * @param aInfoStr as indicated by nsITransferable's kHTMLInfo.
306 nsresult
CreateDOMFragmentFromPaste(
307 const nsAString
& aInputString
, const nsAString
& aContextStr
,
308 const nsAString
& aInfoStr
, nsCOMPtr
<nsINode
>* aOutFragNode
,
309 nsCOMPtr
<nsINode
>* aOutStartNode
, nsCOMPtr
<nsINode
>* aOutEndNode
,
310 uint32_t* aOutStartOffset
, uint32_t* aOutEndOffset
,
311 bool aTrustedInput
) const;
313 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
MoveCaretOutsideOfLink(
314 Element
& aLinkElement
, const EditorDOMPoint
& aPointToPutCaret
);
316 HTMLEditor
& mHTMLEditor
;
319 class MOZ_STACK_CLASS
320 HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator final
{
322 nsresult
Run(const Document
& aDocument
, const nsAString
& aInputString
,
323 const nsAString
& aContextStr
, const nsAString
& aInfoStr
,
324 nsCOMPtr
<nsINode
>* aOutFragNode
,
325 nsCOMPtr
<nsINode
>* aOutStartNode
, nsCOMPtr
<nsINode
>* aOutEndNode
,
326 bool aTrustedInput
) const;
329 nsresult
CreateDocumentFragmentAndGetParentOfPastedHTMLInContext(
330 const Document
& aDocument
, const nsAString
& aInputString
,
331 const nsAString
& aContextStr
, bool aTrustedInput
,
332 nsCOMPtr
<nsINode
>& aParentNodeOfPastedHTMLInContext
,
333 RefPtr
<DocumentFragment
>& aDocumentFragmentToInsert
) const;
335 static nsAtom
* DetermineContextLocalNameForParsingPastedHTML(
336 const nsIContent
* aParentContentOfPastedHTMLInContext
);
338 static bool FindTargetNodeOfContextForPastedHTMLAndRemoveInsertionCookie(
339 nsINode
& aStart
, nsCOMPtr
<nsINode
>& aResult
);
341 static bool IsInsertionCookie(const nsIContent
& aContent
);
344 * @param aDocumentFragmentForContext contains the merged result.
346 static nsresult
MergeAndPostProcessFragmentsForPastedHTMLAndContext(
347 DocumentFragment
& aDocumentFragmentForPastedHTML
,
348 DocumentFragment
& aDocumentFragmentForContext
,
349 nsIContent
& aTargetContentOfContextForPastedHTML
);
352 * @param aInfoStr as indicated by nsITransferable's kHTMLInfo.
354 [[nodiscard
]] static nsresult
MoveStartAndEndAccordingToHTMLInfo(
355 const nsAString
& aInfoStr
, nsCOMPtr
<nsINode
>* aOutStartNode
,
356 nsCOMPtr
<nsINode
>* aOutEndNode
);
358 static nsresult
PostProcessFragmentForPastedHTMLWithoutContext(
359 DocumentFragment
& aDocumentFragmentForPastedHTML
);
361 static nsresult
PreProcessContextDocumentFragmentForMerging(
362 DocumentFragment
& aDocumentFragmentForContext
);
364 static void RemoveHeadChildAndStealBodyChildsChildren(nsINode
& aNode
);
366 enum class NodesToRemove
{
368 eOnlyListItems
/*!< List items are always block-level elements, hence such
369 whitespace-only nodes are always invisible. */
372 RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
373 nsIContent
& aNode
, NodesToRemove aNodesToRemove
);
377 HTMLEditor::HTMLWithContextInserter::GetInvisibleBRElementAtPoint(
378 const EditorDOMPoint
& aPointToInsert
) const {
379 WSRunScanner
wsRunScannerAtInsertionPoint(mHTMLEditor
.GetActiveEditingHost(),
381 if (wsRunScannerAtInsertionPoint
.EndsByInvisibleBRElement()) {
382 return wsRunScannerAtInsertionPoint
.EndReasonBRElementPtr();
388 HTMLEditor::HTMLWithContextInserter::GetNewCaretPointAfterInsertingHTML(
389 const EditorDOMPoint
& aLastInsertedPoint
) const {
390 EditorDOMPoint pointToPutCaret
;
392 // but don't cross tables
393 nsIContent
* containerContent
= nullptr;
394 if (!HTMLEditUtils::IsTable(aLastInsertedPoint
.GetChild())) {
395 containerContent
= HTMLEditUtils::GetLastLeafContent(
396 *aLastInsertedPoint
.GetChild(), {LeafNodeType::OnlyEditableLeafNode
},
397 aLastInsertedPoint
.GetChild()->GetAsElementOrParentElement());
398 if (containerContent
) {
399 Element
* mostDistantInclusiveAncestorTableElement
= nullptr;
400 for (Element
* maybeTableElement
=
401 containerContent
->GetAsElementOrParentElement();
403 maybeTableElement
!= aLastInsertedPoint
.GetChild();
404 maybeTableElement
= maybeTableElement
->GetParentElement()) {
405 if (HTMLEditUtils::IsTable(maybeTableElement
)) {
406 mostDistantInclusiveAncestorTableElement
= maybeTableElement
;
409 // If we're in table elements, we should put caret into the most ancestor
411 if (mostDistantInclusiveAncestorTableElement
) {
412 containerContent
= mostDistantInclusiveAncestorTableElement
;
416 // If we are not in table elements, we should put caret in the last inserted
418 if (!containerContent
) {
419 containerContent
= aLastInsertedPoint
.GetChild();
422 // If the container is a text node or a container element except `<table>`
423 // element, put caret a end of it.
424 if (containerContent
->IsText() ||
425 (HTMLEditUtils::IsContainerNode(*containerContent
) &&
426 !HTMLEditUtils::IsTable(containerContent
))) {
427 pointToPutCaret
.SetToEndOf(containerContent
);
429 // Otherwise, i.e., it's an atomic element, `<table>` element or data node,
430 // put caret after it.
432 pointToPutCaret
.Set(containerContent
);
433 DebugOnly
<bool> advanced
= pointToPutCaret
.AdvanceOffset();
434 NS_WARNING_ASSERTION(advanced
, "Failed to advance offset from found node");
437 // Make sure we don't end up with selection collapsed after an invisible
439 Element
* editingHost
= mHTMLEditor
.GetActiveEditingHost();
440 WSRunScanner
wsRunScannerAtCaret(editingHost
, pointToPutCaret
);
441 if (wsRunScannerAtCaret
442 .ScanPreviousVisibleNodeOrBlockBoundaryFrom(pointToPutCaret
)
443 .ReachedInvisibleBRElement()) {
444 WSRunScanner
wsRunScannerAtStartReason(
446 EditorDOMPoint(wsRunScannerAtCaret
.GetStartReasonContent()));
447 WSScanResult backwardScanFromPointToCaretResult
=
448 wsRunScannerAtStartReason
.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
450 if (backwardScanFromPointToCaretResult
.InVisibleOrCollapsibleCharacters()) {
451 pointToPutCaret
= backwardScanFromPointToCaretResult
.Point();
452 } else if (backwardScanFromPointToCaretResult
.ReachedSpecialContent()) {
453 // XXX In my understanding, this is odd. The end reason may not be
454 // same as the reached special content because the equality is
455 // guaranteed only when ReachedCurrentBlockBoundary() returns true.
456 // However, looks like that this code assumes that
457 // GetStartReasonContent() returns the content.
458 NS_ASSERTION(wsRunScannerAtStartReason
.GetStartReasonContent() ==
459 backwardScanFromPointToCaretResult
.GetContent(),
460 "Start reason is not the reached special content");
461 pointToPutCaret
.SetAfter(
462 wsRunScannerAtStartReason
.GetStartReasonContent());
466 return pointToPutCaret
;
469 nsresult
HTMLEditor::DoInsertHTMLWithContext(
470 const nsAString
& aInputString
, const nsAString
& aContextStr
,
471 const nsAString
& aInfoStr
, const nsAString
& aFlavor
,
472 const EditorDOMPoint
& aPointToInsert
, bool aDoDeleteSelection
,
473 bool aTrustedInput
, bool aClearStyle
) {
474 HTMLWithContextInserter
htmlWithContextInserter(*this);
476 return htmlWithContextInserter
.Run(aInputString
, aContextStr
, aInfoStr
,
477 aPointToInsert
, aDoDeleteSelection
,
478 aTrustedInput
, aClearStyle
);
481 nsresult
HTMLEditor::HTMLWithContextInserter::Run(
482 const nsAString
& aInputString
, const nsAString
& aContextStr
,
483 const nsAString
& aInfoStr
, const EditorDOMPoint
& aPointToInsert
,
484 bool aDoDeleteSelection
, bool aTrustedInput
, bool aClearStyle
) {
485 MOZ_ASSERT(mHTMLEditor
.IsEditActionDataAvailable());
487 if (NS_WARN_IF(!mHTMLEditor
.mInitSucceeded
)) {
488 return NS_ERROR_NOT_INITIALIZED
;
491 // force IME commit; set up rules sniffing and batching
492 mHTMLEditor
.CommitComposition();
493 AutoPlaceholderBatch
treatAsOneTransaction(mHTMLEditor
,
494 ScrollSelectionIntoView::Yes
);
495 IgnoredErrorResult ignoredError
;
496 AutoEditSubActionNotifier
startToHandleEditSubAction(
497 MOZ_KnownLive(mHTMLEditor
), EditSubAction::ePasteHTMLContent
,
498 nsIEditor::eNext
, ignoredError
);
499 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
500 return ignoredError
.StealNSResult();
502 NS_WARNING_ASSERTION(
503 !ignoredError
.Failed(),
504 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
505 ignoredError
.SuppressException();
507 // create a dom document fragment that represents the structure to paste
508 nsCOMPtr
<nsINode
> fragmentAsNode
, streamStartParent
, streamEndParent
;
509 uint32_t streamStartOffset
= 0, streamEndOffset
= 0;
511 nsresult rv
= CreateDOMFragmentFromPaste(
512 aInputString
, aContextStr
, aInfoStr
, address_of(fragmentAsNode
),
513 address_of(streamStartParent
), address_of(streamEndParent
),
514 &streamStartOffset
, &streamEndOffset
, aTrustedInput
);
517 "HTMLEditor::HTMLWithContextInserter::CreateDOMFragmentFromPaste() "
522 // if we have a destination / target node, we want to insert there
523 // rather than in place of the selection
524 // ignore aDoDeleteSelection here if aPointToInsert is not set since deletion
525 // will also occur later; this block is intended to cover the various
526 // scenarios where we are dropping in an editor (and may want to delete
527 // the selection before collapsing the selection in the new destination)
528 if (aPointToInsert
.IsSet()) {
529 rv
= MOZ_KnownLive(mHTMLEditor
)
530 .PrepareToInsertContent(aPointToInsert
, aDoDeleteSelection
);
532 NS_WARNING("EditorBase::PrepareToInsertContent() failed");
537 // we need to recalculate various things based on potentially new offsets
538 // this is work to be completed at a later date (probably by jfrancis)
540 AutoTArray
<OwningNonNull
<nsIContent
>, 64> arrayOfTopMostChildContents
;
541 // If we have stream start point information, lets use it and end point.
542 // Otherwise, we should make a range all over the document fragment.
543 EditorRawDOMPoint streamStartPoint
=
545 ? EditorRawDOMPoint(streamStartParent
,
546 AssertedCast
<uint32_t>(streamStartOffset
))
547 : EditorRawDOMPoint(fragmentAsNode
, 0);
548 EditorRawDOMPoint streamEndPoint
=
549 streamStartParent
? EditorRawDOMPoint(streamEndParent
, streamEndOffset
)
550 : EditorRawDOMPoint::AtEndOf(fragmentAsNode
);
552 Unused
<< streamStartPoint
;
553 Unused
<< streamEndPoint
;
555 HTMLWithContextInserter::CollectTopMostChildContentsCompletelyInRange(
556 EditorRawDOMPoint(streamStartParent
,
557 AssertedCast
<uint32_t>(streamStartOffset
)),
558 EditorRawDOMPoint(streamEndParent
,
559 AssertedCast
<uint32_t>(streamEndOffset
)),
560 arrayOfTopMostChildContents
);
562 if (arrayOfTopMostChildContents
.IsEmpty()) {
563 // We aren't inserting anything, but if aDoDeleteSelection is set, we do
564 // want to delete everything.
565 // XXX What will this do? We've already called DeleteSelectionAsSubAtion()
566 // above if insertion point is specified.
567 if (aDoDeleteSelection
) {
569 MOZ_KnownLive(mHTMLEditor
).DeleteSelectionAsSubAction(eNone
, eStrip
);
572 "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
579 // Are there any table elements in the list?
580 // check for table cell selection mode
581 bool cellSelectionMode
=
582 HTMLEditUtils::IsInTableCellSelectionMode(mHTMLEditor
.SelectionRef());
584 if (cellSelectionMode
) {
585 // do we have table content to paste? If so, we want to delete
586 // the selected table cells and replace with new table elements;
587 // but if not we want to delete _contents_ of cells and replace
588 // with non-table elements. Use cellSelectionMode bool to
590 if (!HTMLEditUtils::IsAnyTableElement(arrayOfTopMostChildContents
[0])) {
591 cellSelectionMode
= false;
595 if (!cellSelectionMode
) {
596 rv
= MOZ_KnownLive(mHTMLEditor
).DeleteSelectionAndPrepareToCreateNode();
598 NS_WARNING("HTMLEditor::DeleteSelectionAndPrepareToCreateNode() failed");
603 // pasting does not inherit local inline styles
605 MOZ_KnownLive(mHTMLEditor
)
607 EditorDOMPoint(mHTMLEditor
.SelectionRef().AnchorRef()),
608 nullptr, nullptr, SpecifiedStyle::Preserve
);
609 if (result
.Failed()) {
610 NS_WARNING("HTMLEditor::ClearStyleAt() failed");
615 // Delete whole cells: we will replace with new table content.
617 // Braces for artificial block to scope AutoSelectionRestorer.
618 // Save current selection since DeleteTableCellWithTransaction() perturbs
621 AutoSelectionRestorer
restoreSelectionLater(MOZ_KnownLive(mHTMLEditor
));
622 rv
= MOZ_KnownLive(mHTMLEditor
).DeleteTableCellWithTransaction(1);
624 NS_WARNING("HTMLEditor::DeleteTableCellWithTransaction(1) failed");
628 // collapse selection to beginning of deleted table content
629 IgnoredErrorResult ignoredError
;
630 mHTMLEditor
.SelectionRef().CollapseToStart(ignoredError
);
631 NS_WARNING_ASSERTION(!ignoredError
.Failed(),
632 "Selection::Collapse() failed, but ignored");
635 // XXX Why don't we test this first?
636 if (mHTMLEditor
.IsReadonly()) {
640 EditActionResult result
= mHTMLEditor
.CanHandleHTMLEditSubAction();
641 if (result
.Failed() || result
.Canceled()) {
642 NS_WARNING_ASSERTION(result
.Succeeded(),
643 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
647 mHTMLEditor
.UndefineCaretBidiLevel();
649 rv
= MOZ_KnownLive(mHTMLEditor
).EnsureNoPaddingBRElementForEmptyEditor();
650 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
651 return NS_ERROR_EDITOR_DESTROYED
;
653 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
654 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
655 "failed, but ignored");
657 if (NS_SUCCEEDED(rv
) && mHTMLEditor
.SelectionRef().IsCollapsed()) {
659 MOZ_KnownLive(mHTMLEditor
).EnsureCaretNotAfterInvisibleBRElement();
660 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
661 return NS_ERROR_EDITOR_DESTROYED
;
663 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
664 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
665 "failed, but ignored");
666 if (NS_SUCCEEDED(rv
)) {
667 nsresult rv
= MOZ_KnownLive(mHTMLEditor
).PrepareInlineStylesForCaret();
668 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
669 return NS_ERROR_EDITOR_DESTROYED
;
671 NS_WARNING_ASSERTION(
673 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
677 Element
* editingHost
=
678 mHTMLEditor
.GetActiveEditingHost(HTMLEditor::LimitInBodyElement::No
);
679 if (NS_WARN_IF(!editingHost
)) {
680 return NS_ERROR_FAILURE
;
683 // Adjust position based on the first node we are going to insert.
684 EditorDOMPoint pointToInsert
=
685 HTMLEditUtils::GetBetterInsertionPointFor
<EditorDOMPoint
>(
686 arrayOfTopMostChildContents
[0],
687 EditorBase::GetStartPoint(mHTMLEditor
.SelectionRef()), *editingHost
);
688 if (!pointToInsert
.IsSet()) {
689 NS_WARNING("HTMLEditor::GetBetterInsertionPointFor() failed");
690 return NS_ERROR_FAILURE
;
693 // Remove invisible `<br>` element at the point because if there is a `<br>`
694 // element at end of what we paste, it will make the existing invisible
695 // `<br>` element visible.
696 if (HTMLBRElement
* invisibleBRElement
=
697 GetInvisibleBRElementAtPoint(pointToInsert
)) {
698 AutoEditorDOMPointChildInvalidator
lockOffset(pointToInsert
);
700 MOZ_KnownLive(mHTMLEditor
)
701 .DeleteNodeWithTransaction(MOZ_KnownLive(*invisibleBRElement
));
703 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed.");
708 const bool insertionPointWasInLink
=
709 !!HTMLEditor::GetLinkElement(pointToInsert
.GetContainer());
711 if (pointToInsert
.IsInTextNode()) {
712 SplitNodeResult splitNodeResult
=
713 MOZ_KnownLive(mHTMLEditor
)
714 .SplitNodeDeepWithTransaction(
715 MOZ_KnownLive(*pointToInsert
.GetContainerAsContent()),
716 pointToInsert
, SplitAtEdges::eAllowToCreateEmptyContainer
);
717 if (splitNodeResult
.Failed()) {
718 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
719 return splitNodeResult
.Rv();
721 pointToInsert
= splitNodeResult
.SplitPoint();
722 if (!pointToInsert
.IsSet()) {
724 "HTMLEditor::SplitNodeDeepWithTransaction() didn't return split "
726 return NS_ERROR_FAILURE
;
730 { // Block only for AutoHTMLFragmentBoundariesFixer to hide it from the
731 // following code. Note that it may modify arrayOfTopMostChildContents.
732 AutoHTMLFragmentBoundariesFixer
fixPiecesOfTablesAndLists(
733 arrayOfTopMostChildContents
);
736 MOZ_ASSERT(pointToInsert
.GetContainer()->GetChildAt_Deprecated(
737 pointToInsert
.Offset()) == pointToInsert
.GetChild());
739 const Result
<EditorDOMPoint
, nsresult
> lastInsertedPoint
= InsertContents(
740 pointToInsert
, arrayOfTopMostChildContents
, fragmentAsNode
);
741 if (lastInsertedPoint
.isErr()) {
742 NS_WARNING("HTMLWithContextInserter::InsertContents() failed.");
743 return lastInsertedPoint
.inspectErr();
746 if (!lastInsertedPoint
.inspect().IsSet()) {
750 const EditorDOMPoint pointToPutCaret
=
751 GetNewCaretPointAfterInsertingHTML(lastInsertedPoint
.inspect());
752 // Now collapse the selection to the end of what we just inserted.
753 rv
= MOZ_KnownLive(mHTMLEditor
).CollapseSelectionTo(pointToPutCaret
);
754 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
755 return NS_ERROR_EDITOR_DESTROYED
;
757 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
758 "HTMLEditor::CollapseSelectionTo() failed, but ignored");
760 // If we didn't start from an `<a href>` element, we should not keep
761 // caret in the link to make users type something outside the link.
762 if (insertionPointWasInLink
) {
765 RefPtr
<Element
> linkElement
= GetLinkElement(pointToPutCaret
.GetContainer());
771 rv
= MoveCaretOutsideOfLink(*linkElement
, pointToPutCaret
);
774 "HTMLEditor::HTMLWithContextInserter::MoveCaretOutsideOfLink "
782 Result
<EditorDOMPoint
, nsresult
>
783 HTMLEditor::HTMLWithContextInserter::InsertContents(
784 const EditorDOMPoint
& aPointToInsert
,
785 nsTArray
<OwningNonNull
<nsIContent
>>& aArrayOfTopMostChildContents
,
786 const nsINode
* aFragmentAsNode
) {
787 EditorDOMPoint pointToInsert
{aPointToInsert
};
789 // Loop over the node list and paste the nodes:
790 const RefPtr
<const Element
> maybeNonEditableBlockElement
=
791 pointToInsert
.IsInContentNode()
792 ? HTMLEditUtils::GetInclusiveAncestorElement(
793 *pointToInsert
.ContainerAsContent(),
794 HTMLEditUtils::ClosestBlockElement
)
797 EditorDOMPoint lastInsertedPoint
;
798 nsCOMPtr
<nsIContent
> insertedContextParentContent
;
799 for (OwningNonNull
<nsIContent
>& content
: aArrayOfTopMostChildContents
) {
800 if (NS_WARN_IF(content
== aFragmentAsNode
) ||
801 NS_WARN_IF(content
->IsHTMLElement(nsGkAtoms::body
))) {
802 return Err(NS_ERROR_FAILURE
);
805 if (insertedContextParentContent
) {
806 // If we had to insert something higher up in the paste hierarchy,
807 // we want to skip any further paste nodes that descend from that.
808 // Else we will paste twice.
809 // XXX This check may be really expensive. Cannot we check whether
810 // the node's `ownerDocument` is the `aFragmentAsNode` or not?
811 // XXX If content was moved to outside of insertedContextParentContent
812 // by mutation event listeners, we will anyway duplicate it.
813 if (EditorUtils::IsDescendantOf(*content
,
814 *insertedContextParentContent
)) {
819 // If a `<table>` or `<tr>` element on the clipboard, and pasting it into
820 // a `<table>` or `<tr>` element, insert only the appropriate children
822 bool inserted
= false;
823 if (HTMLEditUtils::IsTableRow(content
) &&
824 HTMLEditUtils::IsTableRow(pointToInsert
.GetContainer()) &&
825 (HTMLEditUtils::IsTable(content
) ||
826 HTMLEditUtils::IsTable(pointToInsert
.GetContainer()))) {
827 // Move children of current node to the insertion point.
828 for (nsCOMPtr
<nsIContent
> firstChild
= content
->GetFirstChild();
829 firstChild
; firstChild
= content
->GetFirstChild()) {
830 EditorDOMPoint insertedPoint
=
831 MOZ_KnownLive(mHTMLEditor
)
832 .InsertNodeIntoProperAncestorWithTransaction(
833 *firstChild
, pointToInsert
,
834 SplitAtEdges::eDoNotCreateEmptyContainer
);
835 if (!insertedPoint
.IsSet()) {
837 "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction("
838 "SplitAtEdges::eDoNotCreateEmptyContainer) "
842 // If moving node is moved to different place, we should ignore
843 // this result and keep trying to insert next content node to same
846 if (firstChild
->GetParentElement() == insertedPoint
.GetContainer()) {
847 lastInsertedPoint
.Set(firstChild
);
848 pointToInsert
= insertedPoint
.NextPoint();
849 MOZ_ASSERT(pointToInsert
.IsSet());
853 // If a list element on the clipboard, and pasting it into a list or
854 // list item element, insert the appropriate children instead. I.e.,
855 // merge the list elements instead of pasting as a sublist.
856 else if (HTMLEditUtils::IsAnyListElement(content
) &&
857 (HTMLEditUtils::IsAnyListElement(pointToInsert
.GetContainer()) ||
858 HTMLEditUtils::IsListItem(pointToInsert
.GetContainer()))) {
859 for (nsCOMPtr
<nsIContent
> firstChild
= content
->GetFirstChild();
860 firstChild
; firstChild
= content
->GetFirstChild()) {
861 if (HTMLEditUtils::IsListItem(firstChild
) ||
862 HTMLEditUtils::IsAnyListElement(firstChild
)) {
863 // If we're pasting into empty list item, we should remove it
864 // and past current node into the parent list directly.
865 // XXX This creates invalid structure if current list item element
866 // is not proper child of the parent element, or current node
867 // is a list element.
868 if (HTMLEditUtils::IsListItem(pointToInsert
.GetContainer()) &&
869 HTMLEditUtils::IsEmptyNode(*pointToInsert
.GetContainer())) {
870 NS_WARNING_ASSERTION(pointToInsert
.GetContainerParent(),
871 "Insertion point is out of the DOM tree");
872 if (pointToInsert
.GetContainerParent()) {
873 pointToInsert
.Set(pointToInsert
.GetContainer());
874 MOZ_ASSERT(pointToInsert
.IsSet());
875 AutoEditorDOMPointChildInvalidator
lockOffset(pointToInsert
);
876 DebugOnly
<nsresult
> rvIgnored
=
877 MOZ_KnownLive(mHTMLEditor
)
878 .DeleteNodeWithTransaction(
879 MOZ_KnownLive(*pointToInsert
.GetChild()));
880 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
881 "HTMLEditor::DeleteNodeWithTransaction() "
882 "failed, but ignored");
885 EditorDOMPoint insertedPoint
=
886 MOZ_KnownLive(mHTMLEditor
)
887 .InsertNodeIntoProperAncestorWithTransaction(
888 *firstChild
, pointToInsert
,
889 SplitAtEdges::eDoNotCreateEmptyContainer
);
890 if (!insertedPoint
.IsSet()) {
892 "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction("
893 "SplitAtEdges::eDoNotCreateEmptyContainer) failed");
896 // If moving node is moved to different place, we should ignore
897 // this result and keep trying to insert next content node to
900 if (firstChild
->GetParentElement() == insertedPoint
.GetContainer()) {
901 lastInsertedPoint
.Set(firstChild
);
902 pointToInsert
= insertedPoint
.NextPoint();
903 MOZ_ASSERT(pointToInsert
.IsSet());
906 // If the child of current node is not list item nor list element,
907 // we should remove it from the DOM tree.
908 else if (HTMLEditUtils::IsRemovableNode(*firstChild
)) {
909 AutoEditorDOMPointChildInvalidator
lockOffset(pointToInsert
);
910 IgnoredErrorResult ignoredError
;
911 content
->RemoveChild(*firstChild
, ignoredError
);
912 NS_WARNING_ASSERTION(!ignoredError
.Failed(),
913 "nsINode::RemoveChild() failed, but ignored");
916 "Failed to delete the first child of a list element because the "
917 "list element non-editable");
922 // If pasting into a `<pre>` element and current node is a `<pre>` element,
923 // move only its children.
924 else if (HTMLEditUtils::IsPre(maybeNonEditableBlockElement
) &&
925 HTMLEditUtils::IsPre(content
)) {
926 // Check for pre's going into pre's.
927 for (nsCOMPtr
<nsIContent
> firstChild
= content
->GetFirstChild();
928 firstChild
; firstChild
= content
->GetFirstChild()) {
929 EditorDOMPoint insertedPoint
=
930 MOZ_KnownLive(mHTMLEditor
)
931 .InsertNodeIntoProperAncestorWithTransaction(
932 *firstChild
, pointToInsert
,
933 SplitAtEdges::eDoNotCreateEmptyContainer
);
934 if (!insertedPoint
.IsSet()) {
936 "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction("
937 "SplitAtEdges::eDoNotCreateEmptyContainer) failed");
940 // If moving node is moved to different place, we should ignore
941 // this result and keep trying to insert next content node there.
943 if (firstChild
->GetParentElement() == insertedPoint
.GetContainer()) {
944 lastInsertedPoint
.Set(firstChild
);
945 pointToInsert
= insertedPoint
.NextPoint();
946 MOZ_ASSERT(pointToInsert
.IsSet());
951 // If we haven't inserted current node nor its children, move current node
952 // to the insertion point.
954 // MOZ_KnownLive because 'aArrayOfTopMostChildContents' is guaranteed to
956 EditorDOMPoint insertedPoint
=
957 MOZ_KnownLive(mHTMLEditor
)
958 .InsertNodeIntoProperAncestorWithTransaction(
959 MOZ_KnownLive(content
), pointToInsert
,
960 SplitAtEdges::eDoNotCreateEmptyContainer
);
961 NS_WARNING_ASSERTION(
962 insertedPoint
.IsSet(),
963 "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction("
964 "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but ignored");
965 if (insertedPoint
.IsSet()) {
966 // Moving node is moved to different place, we should keep trying to
967 // insert the next content to same position.
968 if (content
->GetParentElement() == insertedPoint
.GetContainer()) {
969 lastInsertedPoint
.Set(content
);
970 pointToInsert
= insertedPoint
;
973 // Assume failure means no legal parent in the document hierarchy,
974 // try again with the parent of content in the paste hierarchy.
975 // FYI: We cannot use `InclusiveAncestorOfType` here because of
976 // calling `InsertNodeIntoProperAncestorWithTransaction()`.
977 for (nsCOMPtr
<nsIContent
> childContent
= content
; childContent
;
978 childContent
= childContent
->GetParent()) {
979 if (NS_WARN_IF(!childContent
->GetParent()) ||
981 childContent
->GetParent()->IsHTMLElement(nsGkAtoms::body
))) {
984 OwningNonNull
<nsIContent
> oldParentContent(
985 *childContent
->GetParent());
986 insertedPoint
= MOZ_KnownLive(mHTMLEditor
)
987 .InsertNodeIntoProperAncestorWithTransaction(
988 oldParentContent
, pointToInsert
,
989 SplitAtEdges::eDoNotCreateEmptyContainer
);
990 NS_WARNING_ASSERTION(
991 insertedPoint
.IsSet(),
992 "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction("
993 "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but ignored");
994 if (!insertedPoint
.IsSet()) {
997 // Moving node is moved to different place, we should keep trying to
998 // insert the next content to same position.
999 if (oldParentContent
== insertedPoint
.GetContainer()) {
1000 insertedContextParentContent
= oldParentContent
;
1001 pointToInsert
= insertedPoint
;
1007 if (lastInsertedPoint
.IsSet()) {
1008 if (lastInsertedPoint
.GetContainer() !=
1009 lastInsertedPoint
.GetChild()->GetParentNode()) {
1011 "HTMLEditor::DoInsertHTMLWithContext() got lost insertion point");
1012 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
1014 pointToInsert
= lastInsertedPoint
.NextPoint();
1015 MOZ_ASSERT(pointToInsert
.IsSet());
1019 return lastInsertedPoint
;
1022 nsresult
HTMLEditor::HTMLWithContextInserter::MoveCaretOutsideOfLink(
1023 Element
& aLinkElement
, const EditorDOMPoint
& aPointToPutCaret
) {
1024 MOZ_ASSERT(HTMLEditUtils::IsLink(&aLinkElement
));
1026 // The reason why do that instead of just moving caret after it is, the
1027 // link might have ended in an invisible `<br>` element. If so, the code
1028 // above just placed selection inside that. So we need to split it instead.
1029 // XXX Sounds like that it's not really expensive comparing with the reason
1030 // to use SplitNodeDeepWithTransaction() here.
1031 SplitNodeResult splitLinkResult
=
1032 MOZ_KnownLive(mHTMLEditor
)
1033 .SplitNodeDeepWithTransaction(
1034 aLinkElement
, aPointToPutCaret
,
1035 SplitAtEdges::eDoNotCreateEmptyContainer
);
1036 NS_WARNING_ASSERTION(
1037 splitLinkResult
.Succeeded(),
1038 "HTMLEditor::SplitNodeDeepWithTransaction() failed, but ignored");
1039 if (splitLinkResult
.GetPreviousNode()) {
1040 EditorRawDOMPoint
afterLeftLink(splitLinkResult
.GetPreviousNode());
1041 if (afterLeftLink
.AdvanceOffset()) {
1043 MOZ_KnownLive(mHTMLEditor
).CollapseSelectionTo(afterLeftLink
);
1044 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
1045 return NS_ERROR_EDITOR_DESTROYED
;
1047 NS_WARNING_ASSERTION(
1049 "HTMLEditor::CollapseSelectionTo() failed, but ignored");
1056 Element
* HTMLEditor::GetLinkElement(nsINode
* aNode
) {
1057 if (NS_WARN_IF(!aNode
)) {
1060 nsINode
* node
= aNode
;
1062 if (HTMLEditUtils::IsLink(node
)) {
1063 return node
->AsElement();
1065 node
= node
->GetParentNode();
1071 nsresult
HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
1072 RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
1073 nsIContent
& aNode
, NodesToRemove aNodesToRemove
) {
1074 if (aNode
.TextIsOnlyWhitespace()) {
1075 nsCOMPtr
<nsINode
> parent
= aNode
.GetParentNode();
1076 // TODO: presumably, if the parent is a `<pre>` element, the node
1077 // shouldn't be removed.
1079 if (aNodesToRemove
== NodesToRemove::eAll
||
1080 HTMLEditUtils::IsAnyListElement(parent
)) {
1082 parent
->RemoveChild(aNode
, error
);
1083 NS_WARNING_ASSERTION(!error
.Failed(), "nsINode::RemoveChild() failed");
1084 return error
.StealNSResult();
1090 if (!aNode
.IsHTMLElement(nsGkAtoms::pre
)) {
1091 nsCOMPtr
<nsIContent
> child
= aNode
.GetLastChild();
1093 nsCOMPtr
<nsIContent
> previous
= child
->GetPreviousSibling();
1094 nsresult rv
= FragmentFromPasteCreator::
1095 RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
1096 *child
, aNodesToRemove
);
1097 if (NS_FAILED(rv
)) {
1099 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
1100 "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces"
1105 child
= std::move(previous
);
1111 class MOZ_STACK_CLASS
HTMLEditor::HTMLTransferablePreparer
{
1113 HTMLTransferablePreparer(const HTMLEditor
& aHTMLEditor
,
1114 nsITransferable
** aTransferable
);
1119 void AddDataFlavorsInBestOrder(nsITransferable
& aTransferable
) const;
1121 const HTMLEditor
& mHTMLEditor
;
1122 nsITransferable
** mTransferable
;
1125 HTMLEditor::HTMLTransferablePreparer::HTMLTransferablePreparer(
1126 const HTMLEditor
& aHTMLEditor
, nsITransferable
** aTransferable
)
1127 : mHTMLEditor
{aHTMLEditor
}, mTransferable
{aTransferable
} {
1128 MOZ_ASSERT(mTransferable
);
1129 MOZ_ASSERT(!*mTransferable
);
1132 nsresult
HTMLEditor::PrepareHTMLTransferable(
1133 nsITransferable
** aTransferable
) const {
1134 HTMLTransferablePreparer htmlTransferablePreparer
{*this, aTransferable
};
1135 return htmlTransferablePreparer
.Run();
1138 nsresult
HTMLEditor::HTMLTransferablePreparer::Run() {
1139 // Create generic Transferable for getting the data
1141 RefPtr
<nsITransferable
> transferable
=
1142 do_CreateInstance("@mozilla.org/widget/transferable;1", &rv
);
1143 if (NS_FAILED(rv
)) {
1144 NS_WARNING("do_CreateInstance() failed to create nsITransferable instance");
1148 if (!transferable
) {
1149 NS_WARNING("do_CreateInstance() returned nullptr, but ignored");
1153 // Get the nsITransferable interface for getting the data from the clipboard
1154 RefPtr
<Document
> destdoc
= mHTMLEditor
.GetDocument();
1155 nsILoadContext
* loadContext
= destdoc
? destdoc
->GetLoadContext() : nullptr;
1156 DebugOnly
<nsresult
> rvIgnored
= transferable
->Init(loadContext
);
1157 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1158 "nsITransferable::Init() failed, but ignored");
1160 // See `HTMLEditor::InsertFromTransferable`.
1161 AddDataFlavorsInBestOrder(*transferable
);
1163 transferable
.forget(mTransferable
);
1168 void HTMLEditor::HTMLTransferablePreparer::AddDataFlavorsInBestOrder(
1169 nsITransferable
& aTransferable
) const {
1170 // Create the desired DataFlavor for the type of data
1171 // we want to get out of the transferable
1172 // This should only happen in html editors, not plaintext
1173 if (!mHTMLEditor
.IsInPlaintextMode()) {
1174 DebugOnly
<nsresult
> rvIgnored
=
1175 aTransferable
.AddDataFlavor(kNativeHTMLMime
);
1176 NS_WARNING_ASSERTION(
1177 NS_SUCCEEDED(rvIgnored
),
1178 "nsITransferable::AddDataFlavor(kNativeHTMLMime) failed, but ignored");
1179 rvIgnored
= aTransferable
.AddDataFlavor(kHTMLMime
);
1180 NS_WARNING_ASSERTION(
1181 NS_SUCCEEDED(rvIgnored
),
1182 "nsITransferable::AddDataFlavor(kHTMLMime) failed, but ignored");
1183 rvIgnored
= aTransferable
.AddDataFlavor(kFileMime
);
1184 NS_WARNING_ASSERTION(
1185 NS_SUCCEEDED(rvIgnored
),
1186 "nsITransferable::AddDataFlavor(kFileMime) failed, but ignored");
1188 switch (Preferences::GetInt("clipboard.paste_image_type", 1)) {
1189 case 0: // prefer JPEG over PNG over GIF encoding
1190 rvIgnored
= aTransferable
.AddDataFlavor(kJPEGImageMime
);
1191 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1192 "nsITransferable::AddDataFlavor(kJPEGImageMime) "
1193 "failed, but ignored");
1194 rvIgnored
= aTransferable
.AddDataFlavor(kJPGImageMime
);
1195 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1196 "nsITransferable::AddDataFlavor(kJPGImageMime) "
1197 "failed, but ignored");
1198 rvIgnored
= aTransferable
.AddDataFlavor(kPNGImageMime
);
1199 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1200 "nsITransferable::AddDataFlavor(kPNGImageMime) "
1201 "failed, but ignored");
1202 rvIgnored
= aTransferable
.AddDataFlavor(kGIFImageMime
);
1203 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1204 "nsITransferable::AddDataFlavor(kGIFImageMime) "
1205 "failed, but ignored");
1207 case 1: // prefer PNG over JPEG over GIF encoding (default)
1209 rvIgnored
= aTransferable
.AddDataFlavor(kPNGImageMime
);
1210 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1211 "nsITransferable::AddDataFlavor(kPNGImageMime) "
1212 "failed, but ignored");
1213 rvIgnored
= aTransferable
.AddDataFlavor(kJPEGImageMime
);
1214 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1215 "nsITransferable::AddDataFlavor(kJPEGImageMime) "
1216 "failed, but ignored");
1217 rvIgnored
= aTransferable
.AddDataFlavor(kJPGImageMime
);
1218 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1219 "nsITransferable::AddDataFlavor(kJPGImageMime) "
1220 "failed, but ignored");
1221 rvIgnored
= aTransferable
.AddDataFlavor(kGIFImageMime
);
1222 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1223 "nsITransferable::AddDataFlavor(kGIFImageMime) "
1224 "failed, but ignored");
1226 case 2: // prefer GIF over JPEG over PNG encoding
1227 rvIgnored
= aTransferable
.AddDataFlavor(kGIFImageMime
);
1228 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1229 "nsITransferable::AddDataFlavor(kGIFImageMime) "
1230 "failed, but ignored");
1231 rvIgnored
= aTransferable
.AddDataFlavor(kJPEGImageMime
);
1232 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1233 "nsITransferable::AddDataFlavor(kJPEGImageMime) "
1234 "failed, but ignored");
1235 rvIgnored
= aTransferable
.AddDataFlavor(kJPGImageMime
);
1236 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1237 "nsITransferable::AddDataFlavor(kJPGImageMime) "
1238 "failed, but ignored");
1239 rvIgnored
= aTransferable
.AddDataFlavor(kPNGImageMime
);
1240 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1241 "nsITransferable::AddDataFlavor(kPNGImageMime) "
1242 "failed, but ignored");
1246 DebugOnly
<nsresult
> rvIgnored
= aTransferable
.AddDataFlavor(kUnicodeMime
);
1247 NS_WARNING_ASSERTION(
1248 NS_SUCCEEDED(rvIgnored
),
1249 "nsITransferable::AddDataFlavor(kUnicodeMime) failed, but ignored");
1250 rvIgnored
= aTransferable
.AddDataFlavor(kMozTextInternal
);
1251 NS_WARNING_ASSERTION(
1252 NS_SUCCEEDED(rvIgnored
),
1253 "nsITransferable::AddDataFlavor(kMozTextInternal) failed, but ignored");
1256 bool FindIntegerAfterString(const char* aLeadingString
, const nsCString
& aCStr
,
1257 int32_t& foundNumber
) {
1258 // first obtain offsets from cfhtml str
1259 int32_t numFront
= aCStr
.Find(aLeadingString
);
1260 if (numFront
== -1) {
1263 numFront
+= strlen(aLeadingString
);
1265 int32_t numBack
= aCStr
.FindCharInSet(CRLF
, numFront
);
1266 if (numBack
== -1) {
1270 nsAutoCString
numStr(Substring(aCStr
, numFront
, numBack
- numFront
));
1272 foundNumber
= numStr
.ToInteger(&errorCode
);
1276 void RemoveFragComments(nsCString
& aStr
) {
1277 // remove the StartFragment/EndFragment comments from the str, if present
1278 int32_t startCommentIndx
= aStr
.Find("<!--StartFragment");
1279 if (startCommentIndx
>= 0) {
1280 int32_t startCommentEnd
= aStr
.Find("-->", false, startCommentIndx
);
1281 if (startCommentEnd
> startCommentIndx
) {
1282 aStr
.Cut(startCommentIndx
, (startCommentEnd
+ 3) - startCommentIndx
);
1285 int32_t endCommentIndx
= aStr
.Find("<!--EndFragment");
1286 if (endCommentIndx
>= 0) {
1287 int32_t endCommentEnd
= aStr
.Find("-->", false, endCommentIndx
);
1288 if (endCommentEnd
> endCommentIndx
) {
1289 aStr
.Cut(endCommentIndx
, (endCommentEnd
+ 3) - endCommentIndx
);
1294 nsresult
HTMLEditor::ParseCFHTML(const nsCString
& aCfhtml
,
1295 char16_t
** aStuffToPaste
,
1296 char16_t
** aCfcontext
) {
1297 // First obtain offsets from cfhtml str.
1298 int32_t startHTML
, endHTML
, startFragment
, endFragment
;
1299 if (!FindIntegerAfterString("StartHTML:", aCfhtml
, startHTML
) ||
1301 return NS_ERROR_FAILURE
;
1303 if (!FindIntegerAfterString("EndHTML:", aCfhtml
, endHTML
) || endHTML
< -1) {
1304 return NS_ERROR_FAILURE
;
1306 if (!FindIntegerAfterString("StartFragment:", aCfhtml
, startFragment
) ||
1307 startFragment
< 0) {
1308 return NS_ERROR_FAILURE
;
1310 if (!FindIntegerAfterString("EndFragment:", aCfhtml
, endFragment
) ||
1311 startFragment
< 0) {
1312 return NS_ERROR_FAILURE
;
1315 // The StartHTML and EndHTML markers are allowed to be -1 to include
1317 // See Reference: MSDN doc entitled "HTML Clipboard Format"
1318 // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx#unknown_854
1319 if (startHTML
== -1) {
1320 startHTML
= aCfhtml
.Find("<!--StartFragment-->");
1321 if (startHTML
== -1) {
1325 if (endHTML
== -1) {
1326 const char endFragmentMarker
[] = "<!--EndFragment-->";
1327 endHTML
= aCfhtml
.Find(endFragmentMarker
);
1328 if (endHTML
== -1) {
1331 endHTML
+= ArrayLength(endFragmentMarker
) - 1;
1334 // create context string
1335 nsAutoCString
contextUTF8(
1336 Substring(aCfhtml
, startHTML
, startFragment
- startHTML
) +
1337 "<!--" kInsertCookie
"-->"_ns
+
1338 Substring(aCfhtml
, endFragment
, endHTML
- endFragment
));
1340 // validate startFragment
1341 // make sure it's not in the middle of a HTML tag
1342 // see bug #228879 for more details
1343 int32_t curPos
= startFragment
;
1344 while (curPos
> startHTML
) {
1345 if (aCfhtml
[curPos
] == '>') {
1346 // working backwards, the first thing we see is the end of a tag
1347 // so StartFragment is good, so do nothing.
1350 if (aCfhtml
[curPos
] == '<') {
1351 // if we are at the start, then we want to see the '<'
1352 if (curPos
!= startFragment
) {
1353 // working backwards, the first thing we see is the start of a tag
1354 // so StartFragment is bad, so we need to update it.
1356 "StartFragment byte count in the clipboard looks bad, see bug "
1358 startFragment
= curPos
- 1;
1365 // create fragment string
1366 nsAutoCString
fragmentUTF8(
1367 Substring(aCfhtml
, startFragment
, endFragment
- startFragment
));
1369 // remove the StartFragment/EndFragment comments from the fragment, if present
1370 RemoveFragComments(fragmentUTF8
);
1372 // remove the StartFragment/EndFragment comments from the context, if present
1373 RemoveFragComments(contextUTF8
);
1375 // convert both strings to usc2
1376 const nsString
& fragUcs2Str
= NS_ConvertUTF8toUTF16(fragmentUTF8
);
1377 const nsString
& cntxtUcs2Str
= NS_ConvertUTF8toUTF16(contextUTF8
);
1379 // translate platform linebreaks for fragment
1380 int32_t oldLengthInChars
=
1381 fragUcs2Str
.Length() + 1; // +1 to include null terminator
1382 int32_t newLengthInChars
= 0;
1383 *aStuffToPaste
= nsLinebreakConverter::ConvertUnicharLineBreaks(
1384 fragUcs2Str
.get(), nsLinebreakConverter::eLinebreakAny
,
1385 nsLinebreakConverter::eLinebreakContent
, oldLengthInChars
,
1387 if (!*aStuffToPaste
) {
1388 NS_WARNING("nsLinebreakConverter::ConvertUnicharLineBreaks() failed");
1389 return NS_ERROR_FAILURE
;
1392 // translate platform linebreaks for context
1394 cntxtUcs2Str
.Length() + 1; // +1 to include null terminator
1395 newLengthInChars
= 0;
1396 *aCfcontext
= nsLinebreakConverter::ConvertUnicharLineBreaks(
1397 cntxtUcs2Str
.get(), nsLinebreakConverter::eLinebreakAny
,
1398 nsLinebreakConverter::eLinebreakContent
, oldLengthInChars
,
1400 // it's ok for context to be empty. frag might be whole doc and contain all
1407 static nsresult
ImgFromData(const nsACString
& aType
, const nsACString
& aData
,
1408 nsString
& aOutput
) {
1409 aOutput
.AssignLiteral("<IMG src=\"data:");
1410 AppendUTF8toUTF16(aType
, aOutput
);
1411 aOutput
.AppendLiteral(";base64,");
1412 nsresult rv
= Base64EncodeAppend(aData
, aOutput
);
1413 if (NS_FAILED(rv
)) {
1414 NS_WARNING("Base64Encode() failed");
1417 aOutput
.AppendLiteral("\" alt=\"\" >");
1421 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLEditor::BlobReader
)
1423 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HTMLEditor::BlobReader
)
1424 NS_IMPL_CYCLE_COLLECTION_UNLINK(mBlob
)
1425 NS_IMPL_CYCLE_COLLECTION_UNLINK(mHTMLEditor
)
1426 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPointToInsert
)
1427 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1429 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(HTMLEditor::BlobReader
)
1430 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBlob
)
1431 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHTMLEditor
)
1432 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPointToInsert
)
1433 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1435 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(HTMLEditor::BlobReader
, AddRef
)
1436 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(HTMLEditor::BlobReader
, Release
)
1438 HTMLEditor::BlobReader::BlobReader(BlobImpl
* aBlob
, HTMLEditor
* aHTMLEditor
,
1440 const EditorDOMPoint
& aPointToInsert
,
1441 bool aDoDeleteSelection
)
1443 mHTMLEditor(aHTMLEditor
),
1444 // "beforeinput" event should've been dispatched before we read blob,
1445 // but anyway, we need to clone dataTransfer for "input" event.
1446 mDataTransfer(mHTMLEditor
->GetInputEventDataTransfer()),
1447 mPointToInsert(aPointToInsert
),
1448 mEditAction(aHTMLEditor
->GetEditAction()),
1450 mDoDeleteSelection(aDoDeleteSelection
),
1451 mNeedsToDispatchBeforeInputEvent(
1452 !mHTMLEditor
->HasTriedToDispatchBeforeInputEvent()) {
1454 MOZ_ASSERT(mHTMLEditor
);
1455 MOZ_ASSERT(mHTMLEditor
->IsEditActionDataAvailable());
1456 MOZ_ASSERT(aPointToInsert
.IsSet());
1457 MOZ_ASSERT(mDataTransfer
);
1459 // Take only offset here since it's our traditional behavior.
1460 AutoEditorDOMPointChildInvalidator
storeOnlyWithOffset(mPointToInsert
);
1463 nsresult
HTMLEditor::BlobReader::OnResult(const nsACString
& aResult
) {
1464 AutoEditActionDataSetter
editActionData(*mHTMLEditor
, mEditAction
);
1465 editActionData
.InitializeDataTransfer(mDataTransfer
);
1466 if (NS_WARN_IF(!editActionData
.CanHandle())) {
1467 return EditorBase::ToGenericNSResult(NS_ERROR_FAILURE
);
1470 if (NS_WARN_IF(mNeedsToDispatchBeforeInputEvent
)) {
1471 nsresult rv
= editActionData
.MaybeDispatchBeforeInputEvent();
1472 if (NS_FAILED(rv
)) {
1473 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
1474 "MaybeDispatchBeforeInputEvent(), failed");
1475 return EditorBase::ToGenericNSResult(rv
);
1478 editActionData
.MarkAsBeforeInputHasBeenDispatched();
1482 mBlob
->GetType(blobType
);
1484 // TODO: This does not work well.
1485 // * If the data is not an image file, this inserts <img> element with odd
1486 // data URI (bug 1610220).
1487 // * If the data is valid image file data, an <img> file is inserted with
1488 // data URI, but it's not loaded (bug 1610219).
1489 NS_ConvertUTF16toUTF8
type(blobType
);
1490 nsAutoString stuffToPaste
;
1491 nsresult rv
= ImgFromData(type
, aResult
, stuffToPaste
);
1492 if (NS_FAILED(rv
)) {
1493 NS_WARNING("ImgFormData() failed");
1494 return EditorBase::ToGenericNSResult(rv
);
1497 AutoPlaceholderBatch
treatAsOneTransaction(*mHTMLEditor
,
1498 ScrollSelectionIntoView::Yes
);
1499 EditorDOMPoint
pointToInsert(mPointToInsert
);
1500 rv
= MOZ_KnownLive(mHTMLEditor
)
1501 ->DoInsertHTMLWithContext(stuffToPaste
, u
""_ns
, u
""_ns
,
1502 NS_LITERAL_STRING_FROM_CSTRING(kFileMime
),
1503 pointToInsert
, mDoDeleteSelection
, mIsSafe
,
1505 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1506 "HTMLEditor::DoInsertHTMLWithContext() failed");
1507 return EditorBase::ToGenericNSResult(rv
);
1510 nsresult
HTMLEditor::BlobReader::OnError(const nsAString
& aError
) {
1511 AutoTArray
<nsString
, 1> error
;
1512 error
.AppendElement(aError
);
1513 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag
, "Editor"_ns
,
1514 mPointToInsert
.GetContainer()->OwnerDoc(),
1515 nsContentUtils::eDOM_PROPERTIES
,
1516 "EditorFileDropFailed", error
);
1520 class SlurpBlobEventListener final
: public nsIDOMEventListener
{
1522 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
1523 NS_DECL_CYCLE_COLLECTION_CLASS(SlurpBlobEventListener
)
1525 explicit SlurpBlobEventListener(HTMLEditor::BlobReader
* aListener
)
1526 : mListener(aListener
) {}
1528 MOZ_CAN_RUN_SCRIPT NS_IMETHOD
HandleEvent(Event
* aEvent
) override
;
1531 ~SlurpBlobEventListener() = default;
1533 RefPtr
<HTMLEditor::BlobReader
> mListener
;
1536 NS_IMPL_CYCLE_COLLECTION(SlurpBlobEventListener
, mListener
)
1538 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SlurpBlobEventListener
)
1539 NS_INTERFACE_MAP_ENTRY(nsISupports
)
1540 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener
)
1541 NS_INTERFACE_MAP_END
1543 NS_IMPL_CYCLE_COLLECTING_ADDREF(SlurpBlobEventListener
)
1544 NS_IMPL_CYCLE_COLLECTING_RELEASE(SlurpBlobEventListener
)
1546 NS_IMETHODIMP
SlurpBlobEventListener::HandleEvent(Event
* aEvent
) {
1547 EventTarget
* target
= aEvent
->GetTarget();
1548 if (!target
|| !mListener
) {
1552 RefPtr
<FileReader
> reader
= do_QueryObject(target
);
1557 EventMessage message
= aEvent
->WidgetEventPtr()->mMessage
;
1559 RefPtr
<HTMLEditor::BlobReader
> listener(mListener
);
1560 if (message
== eLoad
) {
1561 MOZ_ASSERT(reader
->DataFormat() == FileReader::FILE_AS_BINARY
);
1563 // The original data has been converted from Latin1 to UTF-16, this just
1564 // undoes that conversion.
1565 DebugOnly
<nsresult
> rvIgnored
=
1566 listener
->OnResult(NS_LossyConvertUTF16toASCII(reader
->Result()));
1567 NS_WARNING_ASSERTION(
1568 NS_SUCCEEDED(rvIgnored
),
1569 "HTMLEditor::BlobReader::OnResult() failed, but ignored");
1573 if (message
== eLoadError
) {
1574 nsAutoString errorMessage
;
1575 reader
->GetError()->GetErrorMessage(errorMessage
);
1576 DebugOnly
<nsresult
> rvIgnored
= listener
->OnError(errorMessage
);
1577 NS_WARNING_ASSERTION(
1578 NS_SUCCEEDED(rvIgnored
),
1579 "HTMLEditor::BlobReader::OnError() failed, but ignored");
1587 nsresult
HTMLEditor::SlurpBlob(Blob
* aBlob
, nsPIDOMWindowOuter
* aWindow
,
1588 BlobReader
* aBlobReader
) {
1590 MOZ_ASSERT(aWindow
);
1591 MOZ_ASSERT(aBlobReader
);
1593 nsCOMPtr
<nsPIDOMWindowInner
> inner
= aWindow
->GetCurrentInnerWindow();
1594 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(inner
);
1595 RefPtr
<WeakWorkerRef
> workerRef
;
1596 RefPtr
<FileReader
> reader
= new FileReader(global
, workerRef
);
1598 RefPtr
<SlurpBlobEventListener
> eventListener
=
1599 new SlurpBlobEventListener(aBlobReader
);
1601 nsresult rv
= reader
->AddEventListener(u
"load"_ns
, eventListener
, false);
1602 if (NS_FAILED(rv
)) {
1603 NS_WARNING("FileReader::AddEventListener(load) failed");
1607 rv
= reader
->AddEventListener(u
"error"_ns
, eventListener
, false);
1608 if (NS_FAILED(rv
)) {
1609 NS_WARNING("FileReader::AddEventListener(error) failed");
1614 reader
->ReadAsBinaryString(*aBlob
, error
);
1615 NS_WARNING_ASSERTION(!error
.Failed(),
1616 "FileReader::ReadAsBinaryString() failed");
1617 return error
.StealNSResult();
1620 nsresult
HTMLEditor::InsertObject(const nsACString
& aType
, nsISupports
* aObject
,
1622 const EditorDOMPoint
& aPointToInsert
,
1623 bool aDoDeleteSelection
) {
1624 MOZ_ASSERT(IsEditActionDataAvailable());
1626 if (nsCOMPtr
<BlobImpl
> blob
= do_QueryInterface(aObject
)) {
1627 RefPtr
<BlobReader
> br
=
1628 new BlobReader(blob
, this, aIsSafe
, aPointToInsert
, aDoDeleteSelection
);
1629 // XXX This is not guaranteed.
1630 MOZ_ASSERT(aPointToInsert
.IsSet());
1632 RefPtr
<Blob
> domBlob
=
1633 Blob::Create(aPointToInsert
.GetContainer()->GetOwnerGlobal(), blob
);
1635 NS_WARNING("Blob::Create() failed");
1636 return NS_ERROR_FAILURE
;
1639 nsresult rv
= SlurpBlob(
1640 domBlob
, aPointToInsert
.GetContainer()->OwnerDoc()->GetWindow(), br
);
1641 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "HTMLEditor::::SlurpBlob() failed");
1645 nsAutoCString
type(aType
);
1647 // Check to see if we can insert an image file
1648 bool insertAsImage
= false;
1649 nsCOMPtr
<nsIFile
> fileObj
;
1650 if (type
.EqualsLiteral(kFileMime
)) {
1651 fileObj
= do_QueryInterface(aObject
);
1653 // Accept any image type fed to us
1654 if (nsContentUtils::IsFileImage(fileObj
, type
)) {
1655 insertAsImage
= true;
1658 type
.AssignLiteral(kFileMime
);
1663 if (type
.EqualsLiteral(kJPEGImageMime
) || type
.EqualsLiteral(kJPGImageMime
) ||
1664 type
.EqualsLiteral(kPNGImageMime
) || type
.EqualsLiteral(kGIFImageMime
) ||
1666 nsCString imageData
;
1667 if (insertAsImage
) {
1668 nsresult rv
= nsContentUtils::SlurpFileToString(fileObj
, imageData
);
1669 if (NS_FAILED(rv
)) {
1670 NS_WARNING("nsContentUtils::SlurpFileToString() failed");
1674 nsCOMPtr
<nsIInputStream
> imageStream
;
1675 if (RefPtr
<Blob
> blob
= do_QueryObject(aObject
)) {
1676 RefPtr
<File
> file
= blob
->ToFile();
1678 NS_WARNING("No mozilla::dom::File object");
1679 return NS_ERROR_FAILURE
;
1682 file
->CreateInputStream(getter_AddRefs(imageStream
), error
);
1683 if (error
.Failed()) {
1684 NS_WARNING("File::CreateInputStream() failed");
1685 return error
.StealNSResult();
1688 imageStream
= do_QueryInterface(aObject
);
1689 if (NS_WARN_IF(!imageStream
)) {
1690 return NS_ERROR_FAILURE
;
1694 nsresult rv
= NS_ConsumeStream(imageStream
, UINT32_MAX
, imageData
);
1695 if (NS_FAILED(rv
)) {
1696 NS_WARNING("NS_ConsumeStream() failed");
1700 rv
= imageStream
->Close();
1701 if (NS_FAILED(rv
)) {
1702 NS_WARNING("nsIInputStream::Close() failed");
1707 nsAutoString stuffToPaste
;
1708 nsresult rv
= ImgFromData(type
, imageData
, stuffToPaste
);
1709 if (NS_FAILED(rv
)) {
1710 NS_WARNING("ImgFromData() failed");
1714 AutoPlaceholderBatch
treatAsOneTransaction(*this,
1715 ScrollSelectionIntoView::Yes
);
1716 rv
= DoInsertHTMLWithContext(
1717 stuffToPaste
, u
""_ns
, u
""_ns
, NS_LITERAL_STRING_FROM_CSTRING(kFileMime
),
1718 aPointToInsert
, aDoDeleteSelection
, aIsSafe
, false);
1719 NS_WARNING_ASSERTION(
1721 "HTMLEditor::DoInsertHTMLWithContext() failed, but ignored");
1727 static bool GetString(nsISupports
* aData
, nsAString
& aText
) {
1728 if (nsCOMPtr
<nsISupportsString
> str
= do_QueryInterface(aData
)) {
1729 DebugOnly
<nsresult
> rvIgnored
= str
->GetData(aText
);
1730 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1731 "nsISupportsString::GetData() failed, but ignored");
1732 return !aText
.IsEmpty();
1738 static bool GetCString(nsISupports
* aData
, nsACString
& aText
) {
1739 if (nsCOMPtr
<nsISupportsCString
> str
= do_QueryInterface(aData
)) {
1740 DebugOnly
<nsresult
> rvIgnored
= str
->GetData(aText
);
1741 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1742 "nsISupportsString::GetData() failed, but ignored");
1743 return !aText
.IsEmpty();
1749 nsresult
HTMLEditor::InsertFromTransferable(nsITransferable
* aTransferable
,
1750 const nsAString
& aContextStr
,
1751 const nsAString
& aInfoStr
,
1752 bool aHavePrivateHTMLFlavor
,
1753 bool aDoDeleteSelection
) {
1754 nsAutoCString bestFlavor
;
1755 nsCOMPtr
<nsISupports
> genericDataObj
;
1757 // See `HTMLTransferablePreparer::AddDataFlavorsInBestOrder`.
1758 nsresult rv
= aTransferable
->GetAnyTransferData(
1759 bestFlavor
, getter_AddRefs(genericDataObj
));
1760 NS_WARNING_ASSERTION(
1762 "nsITransferable::GetAnyTransferData() failed, but ignored");
1763 if (NS_SUCCEEDED(rv
)) {
1764 AutoTransactionsConserveSelection
dontChangeMySelection(*this);
1765 nsAutoString flavor
;
1766 CopyASCIItoUTF16(bestFlavor
, flavor
);
1767 bool isSafe
= IsSafeToInsertData(nullptr);
1769 if (bestFlavor
.EqualsLiteral(kFileMime
) ||
1770 bestFlavor
.EqualsLiteral(kJPEGImageMime
) ||
1771 bestFlavor
.EqualsLiteral(kJPGImageMime
) ||
1772 bestFlavor
.EqualsLiteral(kPNGImageMime
) ||
1773 bestFlavor
.EqualsLiteral(kGIFImageMime
)) {
1774 nsresult rv
= InsertObject(bestFlavor
, genericDataObj
, isSafe
,
1775 EditorDOMPoint(), aDoDeleteSelection
);
1776 if (NS_FAILED(rv
)) {
1777 NS_WARNING("HTMLEditor::InsertObject() failed");
1780 } else if (bestFlavor
.EqualsLiteral(kNativeHTMLMime
)) {
1781 // note cf_html uses utf8
1782 nsAutoCString cfhtml
;
1783 if (GetCString(genericDataObj
, cfhtml
)) {
1784 // cfselection left emtpy for now.
1785 nsString cfcontext
, cffragment
, cfselection
;
1786 nsresult rv
= ParseCFHTML(cfhtml
, getter_Copies(cffragment
),
1787 getter_Copies(cfcontext
));
1788 if (NS_SUCCEEDED(rv
) && !cffragment
.IsEmpty()) {
1789 AutoPlaceholderBatch
treatAsOneTransaction(
1790 *this, ScrollSelectionIntoView::Yes
);
1791 // If we have our private HTML flavor, we will only use the fragment
1792 // from the CF_HTML. The rest comes from the clipboard.
1793 if (aHavePrivateHTMLFlavor
) {
1794 rv
= DoInsertHTMLWithContext(cffragment
, aContextStr
, aInfoStr
,
1795 flavor
, EditorDOMPoint(),
1796 aDoDeleteSelection
, isSafe
);
1797 if (NS_FAILED(rv
)) {
1798 NS_WARNING("HTMLEditor::DoInsertHTMLWithContext() failed");
1802 rv
= DoInsertHTMLWithContext(cffragment
, cfcontext
, cfselection
,
1803 flavor
, EditorDOMPoint(),
1804 aDoDeleteSelection
, isSafe
);
1805 if (NS_FAILED(rv
)) {
1806 NS_WARNING("HTMLEditor::DoInsertHTMLWithContext() failed");
1811 // In some platforms (like Linux), the clipboard might return data
1812 // requested for unknown flavors (for example:
1813 // application/x-moz-nativehtml). In this case, treat the data
1814 // to be pasted as mere HTML to get the best chance of pasting it
1816 bestFlavor
.AssignLiteral(kHTMLMime
);
1817 // Fall through the next case
1821 if (bestFlavor
.EqualsLiteral(kHTMLMime
) ||
1822 bestFlavor
.EqualsLiteral(kUnicodeMime
) ||
1823 bestFlavor
.EqualsLiteral(kMozTextInternal
)) {
1824 nsAutoString stuffToPaste
;
1825 if (!GetString(genericDataObj
, stuffToPaste
)) {
1827 if (GetCString(genericDataObj
, text
)) {
1828 CopyUTF8toUTF16(text
, stuffToPaste
);
1832 if (!stuffToPaste
.IsEmpty()) {
1833 AutoPlaceholderBatch
treatAsOneTransaction(
1834 *this, ScrollSelectionIntoView::Yes
);
1835 if (bestFlavor
.EqualsLiteral(kHTMLMime
)) {
1836 nsresult rv
= DoInsertHTMLWithContext(
1837 stuffToPaste
, aContextStr
, aInfoStr
, flavor
, EditorDOMPoint(),
1838 aDoDeleteSelection
, isSafe
);
1839 if (NS_FAILED(rv
)) {
1840 NS_WARNING("HTMLEditor::DoInsertHTMLWithContext() failed");
1845 InsertTextAsSubAction(stuffToPaste
, SelectionHandling::Delete
);
1846 if (NS_FAILED(rv
)) {
1847 NS_WARNING("EditorBase::InsertTextAsSubAction() failed");
1855 // Try to scroll the selection into view if the paste succeeded
1856 DebugOnly
<nsresult
> rvIgnored
= ScrollSelectionFocusIntoView();
1857 NS_WARNING_ASSERTION(
1858 NS_SUCCEEDED(rvIgnored
),
1859 "EditorBase::ScrollSelectionFocusIntoView() failed, but ignored");
1863 static void GetStringFromDataTransfer(const DataTransfer
* aDataTransfer
,
1864 const nsAString
& aType
, uint32_t aIndex
,
1865 nsString
& aOutputString
) {
1866 nsCOMPtr
<nsIVariant
> variant
;
1867 DebugOnly
<nsresult
> rvIgnored
= aDataTransfer
->GetDataAtNoSecurityCheck(
1868 aType
, aIndex
, getter_AddRefs(variant
));
1870 MOZ_ASSERT(aOutputString
.IsEmpty());
1873 NS_WARNING_ASSERTION(
1874 NS_SUCCEEDED(rvIgnored
),
1875 "DataTransfer::GetDataAtNoSecurityCheck() failed, but ignored");
1876 variant
->GetAsAString(aOutputString
);
1877 nsContentUtils::PlatformToDOMLineBreaks(aOutputString
);
1880 nsresult
HTMLEditor::InsertFromDataTransfer(const DataTransfer
* aDataTransfer
,
1882 nsIPrincipal
* aSourcePrincipal
,
1883 const EditorDOMPoint
& aDroppedAt
,
1884 bool aDoDeleteSelection
) {
1885 MOZ_ASSERT(GetEditAction() == EditAction::eDrop
||
1886 GetEditAction() == EditAction::ePaste
);
1887 MOZ_ASSERT(mPlaceholderBatch
,
1888 "HTMLEditor::InsertFromDataTransfer() should be called by "
1889 "HandleDropEvent() or paste action and there should've already "
1890 "been placeholder transaction");
1891 MOZ_ASSERT_IF(GetEditAction() == EditAction::eDrop
, aDroppedAt
.IsSet());
1894 RefPtr
<DOMStringList
> types
=
1895 aDataTransfer
->MozTypesAt(aIndex
, CallerType::System
, error
);
1896 if (error
.Failed()) {
1897 NS_WARNING("DataTransfer::MozTypesAt() failed");
1898 return error
.StealNSResult();
1901 bool hasPrivateHTMLFlavor
=
1902 types
->Contains(NS_LITERAL_STRING_FROM_CSTRING(kHTMLContext
));
1904 bool isPlaintextEditor
= IsInPlaintextMode();
1905 bool isSafe
= IsSafeToInsertData(aSourcePrincipal
);
1907 uint32_t length
= types
->Length();
1908 for (uint32_t i
= 0; i
< length
; i
++) {
1910 types
->Item(i
, type
);
1912 if (!isPlaintextEditor
) {
1913 if (type
.EqualsLiteral(kFileMime
) || type
.EqualsLiteral(kJPEGImageMime
) ||
1914 type
.EqualsLiteral(kJPGImageMime
) ||
1915 type
.EqualsLiteral(kPNGImageMime
) ||
1916 type
.EqualsLiteral(kGIFImageMime
)) {
1917 nsCOMPtr
<nsIVariant
> variant
;
1918 DebugOnly
<nsresult
> rvIgnored
= aDataTransfer
->GetDataAtNoSecurityCheck(
1919 type
, aIndex
, getter_AddRefs(variant
));
1921 NS_WARNING_ASSERTION(
1922 NS_SUCCEEDED(rvIgnored
),
1923 "DataTransfer::GetDataAtNoSecurityCheck() failed, but ignored");
1924 nsCOMPtr
<nsISupports
> object
;
1925 rvIgnored
= variant
->GetAsISupports(getter_AddRefs(object
));
1926 NS_WARNING_ASSERTION(
1927 NS_SUCCEEDED(rvIgnored
),
1928 "nsIVariant::GetAsISupports() failed, but ignored");
1929 nsresult rv
= InsertObject(NS_ConvertUTF16toUTF8(type
), object
,
1930 isSafe
, aDroppedAt
, aDoDeleteSelection
);
1931 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1932 "HTMLEditor::InsertObject() failed");
1935 } else if (type
.EqualsLiteral(kNativeHTMLMime
)) {
1936 // Windows only clipboard parsing.
1938 GetStringFromDataTransfer(aDataTransfer
, type
, aIndex
, text
);
1939 NS_ConvertUTF16toUTF8
cfhtml(text
);
1941 nsString cfcontext
, cffragment
,
1942 cfselection
; // cfselection left emtpy for now
1944 nsresult rv
= ParseCFHTML(cfhtml
, getter_Copies(cffragment
),
1945 getter_Copies(cfcontext
));
1946 if (NS_SUCCEEDED(rv
) && !cffragment
.IsEmpty()) {
1947 if (hasPrivateHTMLFlavor
) {
1948 // If we have our private HTML flavor, we will only use the fragment
1949 // from the CF_HTML. The rest comes from the clipboard.
1950 nsAutoString contextString
, infoString
;
1951 GetStringFromDataTransfer(
1952 aDataTransfer
, NS_LITERAL_STRING_FROM_CSTRING(kHTMLContext
),
1953 aIndex
, contextString
);
1954 GetStringFromDataTransfer(aDataTransfer
,
1955 NS_LITERAL_STRING_FROM_CSTRING(kHTMLInfo
),
1956 aIndex
, infoString
);
1957 nsresult rv
= DoInsertHTMLWithContext(cffragment
, contextString
,
1958 infoString
, type
, aDroppedAt
,
1959 aDoDeleteSelection
, isSafe
);
1960 NS_WARNING_ASSERTION(
1962 "HTMLEditor::DoInsertHTMLWithContext() failed");
1966 DoInsertHTMLWithContext(cffragment
, cfcontext
, cfselection
, type
,
1967 aDroppedAt
, aDoDeleteSelection
, isSafe
);
1968 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1969 "HTMLEditor::DoInsertHTMLWithContext() failed");
1972 } else if (type
.EqualsLiteral(kHTMLMime
)) {
1973 nsAutoString text
, contextString
, infoString
;
1974 GetStringFromDataTransfer(aDataTransfer
, type
, aIndex
, text
);
1975 GetStringFromDataTransfer(aDataTransfer
,
1976 NS_LITERAL_STRING_FROM_CSTRING(kHTMLContext
),
1977 aIndex
, contextString
);
1978 GetStringFromDataTransfer(aDataTransfer
,
1979 NS_LITERAL_STRING_FROM_CSTRING(kHTMLInfo
),
1980 aIndex
, infoString
);
1981 if (type
.EqualsLiteral(kHTMLMime
)) {
1983 DoInsertHTMLWithContext(text
, contextString
, infoString
, type
,
1984 aDroppedAt
, aDoDeleteSelection
, isSafe
);
1985 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1986 "HTMLEditor::DoInsertHTMLWithContext() failed");
1992 if (type
.EqualsLiteral(kTextMime
) || type
.EqualsLiteral(kMozTextInternal
)) {
1994 GetStringFromDataTransfer(aDataTransfer
, type
, aIndex
, text
);
1995 nsresult rv
= InsertTextAt(text
, aDroppedAt
, aDoDeleteSelection
);
1996 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1997 "EditorBase::InsertTextAt() failed");
2006 bool HTMLEditor::HavePrivateHTMLFlavor(nsIClipboard
* aClipboard
) {
2007 if (NS_WARN_IF(!aClipboard
)) {
2011 // check the clipboard for our special kHTMLContext flavor. If that is there,
2012 // we know we have our own internal html format on clipboard.
2013 bool bHavePrivateHTMLFlavor
= false;
2014 AutoTArray
<nsCString
, 1> flavArray
= {nsDependentCString(kHTMLContext
)};
2015 nsresult rv
= aClipboard
->HasDataMatchingFlavors(
2016 flavArray
, nsIClipboard::kGlobalClipboard
, &bHavePrivateHTMLFlavor
);
2017 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2018 "nsIClipboard::HasDataMatchingFlavors(nsIClipboard::"
2019 "kGlobalClipboard) failed");
2020 return NS_SUCCEEDED(rv
) && bHavePrivateHTMLFlavor
;
2023 nsresult
HTMLEditor::PasteAsAction(int32_t aClipboardType
,
2024 bool aDispatchPasteEvent
,
2025 nsIPrincipal
* aPrincipal
) {
2026 AutoEditActionDataSetter
editActionData(*this, EditAction::ePaste
,
2028 if (NS_WARN_IF(!editActionData
.CanHandle())) {
2029 return NS_ERROR_NOT_INITIALIZED
;
2032 if (aDispatchPasteEvent
) {
2033 if (!FireClipboardEvent(ePaste
, aClipboardType
)) {
2034 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED
);
2037 // The caller must already have dispatched a "paste" event.
2038 editActionData
.NotifyOfDispatchingClipboardEvent();
2041 editActionData
.InitializeDataTransferWithClipboard(
2042 SettingDataTransfer::eWithFormat
, aClipboardType
);
2043 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
2044 if (NS_FAILED(rv
)) {
2045 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
2046 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
2047 return EditorBase::ToGenericNSResult(rv
);
2049 rv
= PasteInternal(aClipboardType
);
2050 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "HTMLEditor::PasteInternal() failed");
2051 return EditorBase::ToGenericNSResult(rv
);
2054 nsresult
HTMLEditor::PasteInternal(int32_t aClipboardType
) {
2055 MOZ_ASSERT(IsEditActionDataAvailable());
2057 // Get Clipboard Service
2058 nsresult rv
= NS_OK
;
2059 nsCOMPtr
<nsIClipboard
> clipboard
=
2060 do_GetService("@mozilla.org/widget/clipboard;1", &rv
);
2061 if (NS_FAILED(rv
)) {
2062 NS_WARNING("Failed to get nsIClipboard service");
2066 // Get the nsITransferable interface for getting the data from the clipboard
2067 nsCOMPtr
<nsITransferable
> transferable
;
2068 rv
= PrepareHTMLTransferable(getter_AddRefs(transferable
));
2069 if (NS_FAILED(rv
)) {
2070 NS_WARNING("HTMLEditor::PrepareHTMLTransferable() failed");
2073 if (!transferable
) {
2074 NS_WARNING("HTMLEditor::PrepareHTMLTransferable() returned nullptr");
2075 return NS_ERROR_FAILURE
;
2077 // Get the Data from the clipboard
2078 rv
= clipboard
->GetData(transferable
, aClipboardType
);
2079 if (NS_FAILED(rv
)) {
2080 NS_WARNING("nsIClipboard::GetData() failed");
2084 // XXX Why don't you check this first?
2085 if (!IsModifiable()) {
2089 // also get additional html copy hints, if present
2090 nsAutoString contextStr
, infoStr
;
2092 // If we have our internal html flavor on the clipboard, there is special
2093 // context to use instead of cfhtml context.
2094 bool hasPrivateHTMLFlavor
= HavePrivateHTMLFlavor(clipboard
);
2095 if (hasPrivateHTMLFlavor
) {
2096 nsCOMPtr
<nsITransferable
> contextTransferable
=
2097 do_CreateInstance("@mozilla.org/widget/transferable;1");
2098 if (!contextTransferable
) {
2100 "do_CreateInstance() failed to create nsITransferable instance");
2101 return NS_ERROR_FAILURE
;
2103 DebugOnly
<nsresult
> rvIgnored
= contextTransferable
->Init(nullptr);
2104 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
2105 "nsITransferable::Init() failed, but ignored");
2106 contextTransferable
->SetIsPrivateData(transferable
->GetIsPrivateData());
2107 rvIgnored
= contextTransferable
->AddDataFlavor(kHTMLContext
);
2108 NS_WARNING_ASSERTION(
2109 NS_SUCCEEDED(rvIgnored
),
2110 "nsITransferable::AddDataFlavor(kHTMLContext) failed, but ignored");
2111 rvIgnored
= clipboard
->GetData(contextTransferable
, aClipboardType
);
2112 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
2113 "nsIClipboard::GetData() failed, but ignored");
2114 nsCOMPtr
<nsISupports
> contextDataObj
;
2115 rv
= contextTransferable
->GetTransferData(kHTMLContext
,
2116 getter_AddRefs(contextDataObj
));
2117 if (NS_SUCCEEDED(rv
) && contextDataObj
) {
2118 if (nsCOMPtr
<nsISupportsString
> str
= do_QueryInterface(contextDataObj
)) {
2119 DebugOnly
<nsresult
> rvIgnored
= str
->GetData(contextStr
);
2120 NS_WARNING_ASSERTION(
2121 NS_SUCCEEDED(rvIgnored
),
2122 "nsISupportsString::GetData() failed, but ignored");
2126 nsCOMPtr
<nsITransferable
> infoTransferable
=
2127 do_CreateInstance("@mozilla.org/widget/transferable;1");
2128 if (!infoTransferable
) {
2130 "do_CreateInstance() failed to create nsITransferable instance");
2131 return NS_ERROR_FAILURE
;
2133 rvIgnored
= infoTransferable
->Init(nullptr);
2134 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2135 "nsITransferable::Init() failed, but ignored");
2136 contextTransferable
->SetIsPrivateData(transferable
->GetIsPrivateData());
2137 rvIgnored
= infoTransferable
->AddDataFlavor(kHTMLInfo
);
2138 NS_WARNING_ASSERTION(
2139 NS_SUCCEEDED(rvIgnored
),
2140 "nsITransferable::AddDataFlavor(kHTMLInfo) failed, but ignored");
2141 clipboard
->GetData(infoTransferable
, aClipboardType
);
2142 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
2143 "nsIClipboard::GetData() failed, but ignored");
2144 nsCOMPtr
<nsISupports
> infoDataObj
;
2145 rv
= infoTransferable
->GetTransferData(kHTMLInfo
,
2146 getter_AddRefs(infoDataObj
));
2147 if (NS_SUCCEEDED(rv
) && infoDataObj
) {
2148 if (nsCOMPtr
<nsISupportsString
> str
= do_QueryInterface(infoDataObj
)) {
2149 DebugOnly
<nsresult
> rvIgnored
= str
->GetData(infoStr
);
2150 NS_WARNING_ASSERTION(
2151 NS_SUCCEEDED(rvIgnored
),
2152 "nsISupportsString::GetData() failed, but ignored");
2157 rv
= InsertFromTransferable(transferable
, contextStr
, infoStr
,
2158 hasPrivateHTMLFlavor
, true);
2159 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2160 "HTMLEditor::InsertFromTransferable() failed");
2164 nsresult
HTMLEditor::PasteTransferableAsAction(nsITransferable
* aTransferable
,
2165 nsIPrincipal
* aPrincipal
) {
2166 AutoEditActionDataSetter
editActionData(*this, EditAction::ePaste
,
2168 if (NS_WARN_IF(!editActionData
.CanHandle())) {
2169 return NS_ERROR_NOT_INITIALIZED
;
2171 // InitializeDataTransfer may fetch input stream in aTransferable, so it
2172 // may be invalid after calling this.
2173 editActionData
.InitializeDataTransfer(aTransferable
);
2175 // Use an invalid value for the clipboard type as data comes from
2176 // aTransferable and we don't currently implement a way to put that in the
2177 // data transfer yet.
2178 if (!FireClipboardEvent(ePaste
, nsIClipboard::kGlobalClipboard
)) {
2179 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED
);
2182 // Dispatch "beforeinput" event after "paste" event.
2183 nsresult rv
= editActionData
.MaybeDispatchBeforeInputEvent();
2184 if (NS_FAILED(rv
)) {
2185 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
2186 "MaybeDispatchBeforeInputEvent(), failed");
2187 return EditorBase::ToGenericNSResult(rv
);
2190 RefPtr
<DataTransfer
> dataTransfer
= GetInputEventDataTransfer();
2191 if (dataTransfer
->HasFile() && dataTransfer
->MozItemCount() > 0) {
2192 // Now aTransferable has moved to DataTransfer. Use DataTransfer.
2193 AutoPlaceholderBatch
treatAsOneTransaction(*this,
2194 ScrollSelectionIntoView::Yes
);
2196 rv
= InsertFromDataTransfer(dataTransfer
, 0, nullptr, EditorDOMPoint(),
2198 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2199 "HTMLEditor::InsertFromDataTransfer() failed");
2201 nsAutoString contextStr
, infoStr
;
2203 InsertFromTransferable(aTransferable
, contextStr
, infoStr
, false, true);
2204 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2205 "HTMLEditor::InsertFromTransferable() failed");
2207 return EditorBase::ToGenericNSResult(rv
);
2210 nsresult
HTMLEditor::PasteNoFormattingAsAction(int32_t aSelectionType
,
2211 nsIPrincipal
* aPrincipal
) {
2212 AutoEditActionDataSetter
editActionData(*this, EditAction::ePaste
,
2214 if (NS_WARN_IF(!editActionData
.CanHandle())) {
2215 return NS_ERROR_NOT_INITIALIZED
;
2217 editActionData
.InitializeDataTransferWithClipboard(
2218 SettingDataTransfer::eWithoutFormat
, aSelectionType
);
2220 if (!FireClipboardEvent(ePasteNoFormatting
, aSelectionType
)) {
2221 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED
);
2224 // Dispatch "beforeinput" event after "paste" event. And perhaps, before
2225 // committing composition because if pasting is canceled, we don't need to
2226 // commit the active composition.
2227 nsresult rv
= editActionData
.MaybeDispatchBeforeInputEvent();
2228 if (NS_FAILED(rv
)) {
2229 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
2230 "MaybeDispatchBeforeInputEvent(), failed");
2231 return EditorBase::ToGenericNSResult(rv
);
2234 DebugOnly
<nsresult
> rvIgnored
= CommitComposition();
2235 if (NS_WARN_IF(Destroyed())) {
2236 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED
);
2238 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2239 "EditorBase::CommitComposition() failed, but ignored");
2241 // Get Clipboard Service
2242 nsCOMPtr
<nsIClipboard
> clipboard(
2243 do_GetService("@mozilla.org/widget/clipboard;1", &rv
));
2244 if (NS_FAILED(rv
)) {
2245 NS_WARNING("Failed to get nsIClipboard service");
2249 if (!GetDocument()) {
2250 NS_WARNING("Editor didn't have document, but ignored");
2254 Result
<nsCOMPtr
<nsITransferable
>, nsresult
> maybeTransferable
=
2255 EditorUtils::CreateTransferableForPlainText(*GetDocument());
2256 if (maybeTransferable
.isErr()) {
2257 NS_WARNING("EditorUtils::CreateTransferableForPlainText() failed");
2258 return EditorBase::ToGenericNSResult(maybeTransferable
.unwrapErr());
2260 nsCOMPtr
<nsITransferable
> transferable(maybeTransferable
.unwrap());
2261 if (!transferable
) {
2263 "EditorUtils::CreateTransferableForPlainText() returned nullptr, but "
2268 if (!IsModifiable()) {
2272 // Get the Data from the clipboard
2273 rv
= clipboard
->GetData(transferable
, aSelectionType
);
2274 if (NS_FAILED(rv
)) {
2275 NS_WARNING("nsIClipboard::GetData() failed");
2279 rv
= InsertFromTransferable(transferable
, u
""_ns
, u
""_ns
, false, true);
2280 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2281 "HTMLEditor::InsertFromTransferable() failed");
2282 return EditorBase::ToGenericNSResult(rv
);
2285 // The following arrays contain the MIME types that we can paste. The arrays
2286 // are used by CanPaste() and CanPasteTransferable() below.
2288 static const char* textEditorFlavors
[] = {kUnicodeMime
};
2289 static const char* textHtmlEditorFlavors
[] = {kUnicodeMime
, kHTMLMime
,
2290 kJPEGImageMime
, kJPGImageMime
,
2291 kPNGImageMime
, kGIFImageMime
};
2293 bool HTMLEditor::CanPaste(int32_t aClipboardType
) const {
2294 if (AreClipboardCommandsUnconditionallyEnabled()) {
2298 // can't paste if readonly
2299 if (!IsModifiable()) {
2304 nsCOMPtr
<nsIClipboard
> clipboard(
2305 do_GetService("@mozilla.org/widget/clipboard;1", &rv
));
2306 if (NS_FAILED(rv
)) {
2307 NS_WARNING("Failed to get nsIClipboard service");
2311 // Use the flavors depending on the current editor mask
2312 if (IsInPlaintextMode()) {
2313 AutoTArray
<nsCString
, ArrayLength(textEditorFlavors
)> flavors
;
2314 flavors
.AppendElements
<const char*>(Span
<const char*>(textEditorFlavors
));
2316 nsresult rv
= clipboard
->HasDataMatchingFlavors(flavors
, aClipboardType
,
2318 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2319 "nsIClipboard::HasDataMatchingFlavors() failed");
2320 return NS_SUCCEEDED(rv
) && haveFlavors
;
2323 AutoTArray
<nsCString
, ArrayLength(textHtmlEditorFlavors
)> flavors
;
2324 flavors
.AppendElements
<const char*>(Span
<const char*>(textHtmlEditorFlavors
));
2326 rv
= clipboard
->HasDataMatchingFlavors(flavors
, aClipboardType
, &haveFlavors
);
2327 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2328 "nsIClipboard::HasDataMatchingFlavors() failed");
2329 return NS_SUCCEEDED(rv
) && haveFlavors
;
2332 bool HTMLEditor::CanPasteTransferable(nsITransferable
* aTransferable
) {
2333 // can't paste if readonly
2334 if (!IsModifiable()) {
2338 // If |aTransferable| is null, assume that a paste will succeed.
2339 if (!aTransferable
) {
2343 // Peek in |aTransferable| to see if it contains a supported MIME type.
2345 // Use the flavors depending on the current editor mask
2346 const char** flavors
;
2348 if (IsInPlaintextMode()) {
2349 flavors
= textEditorFlavors
;
2350 length
= ArrayLength(textEditorFlavors
);
2352 flavors
= textHtmlEditorFlavors
;
2353 length
= ArrayLength(textHtmlEditorFlavors
);
2356 for (size_t i
= 0; i
< length
; i
++, flavors
++) {
2357 nsCOMPtr
<nsISupports
> data
;
2359 aTransferable
->GetTransferData(*flavors
, getter_AddRefs(data
));
2360 if (NS_SUCCEEDED(rv
) && data
) {
2368 nsresult
HTMLEditor::PasteAsQuotationAsAction(int32_t aClipboardType
,
2369 bool aDispatchPasteEvent
,
2370 nsIPrincipal
* aPrincipal
) {
2371 MOZ_ASSERT(aClipboardType
== nsIClipboard::kGlobalClipboard
||
2372 aClipboardType
== nsIClipboard::kSelectionClipboard
);
2378 AutoEditActionDataSetter
editActionData(*this, EditAction::ePasteAsQuotation
,
2380 editActionData
.InitializeDataTransferWithClipboard(
2381 SettingDataTransfer::eWithFormat
, aClipboardType
);
2382 if (NS_WARN_IF(!editActionData
.CanHandle())) {
2383 return NS_ERROR_NOT_INITIALIZED
;
2386 if (aDispatchPasteEvent
) {
2387 if (!FireClipboardEvent(ePaste
, aClipboardType
)) {
2388 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED
);
2391 // The caller must already have dispatched a "paste" event.
2392 editActionData
.NotifyOfDispatchingClipboardEvent();
2395 nsresult rv
= editActionData
.MaybeDispatchBeforeInputEvent();
2396 if (NS_FAILED(rv
)) {
2397 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
2398 "MaybeDispatchBeforeInputEvent(), failed");
2399 return EditorBase::ToGenericNSResult(rv
);
2402 if (IsInPlaintextMode()) {
2403 nsresult rv
= PasteAsPlaintextQuotation(aClipboardType
);
2404 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2405 "HTMLEditor::PasteAsPlaintextQuotation() failed");
2406 return EditorBase::ToGenericNSResult(rv
);
2409 // If it's not in plain text edit mode, paste text into new
2410 // <blockquote type="cite"> element after removing selection.
2412 // XXX Why don't we test these first?
2413 EditActionResult result
= CanHandleHTMLEditSubAction();
2414 if (result
.Failed() || result
.Canceled()) {
2415 NS_WARNING_ASSERTION(result
.Succeeded(),
2416 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
2417 return EditorBase::ToGenericNSResult(result
.Rv());
2420 UndefineCaretBidiLevel();
2422 AutoPlaceholderBatch
treatAsOneTransaction(*this,
2423 ScrollSelectionIntoView::Yes
);
2424 IgnoredErrorResult ignoredError
;
2425 AutoEditSubActionNotifier
startToHandleEditSubAction(
2426 *this, EditSubAction::eInsertQuotation
, nsIEditor::eNext
, ignoredError
);
2427 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
2428 return ignoredError
.StealNSResult();
2430 NS_WARNING_ASSERTION(
2431 !ignoredError
.Failed(),
2432 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
2434 rv
= EnsureNoPaddingBRElementForEmptyEditor();
2435 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
2436 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED
);
2438 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2439 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
2440 "failed, but ignored");
2442 if (NS_SUCCEEDED(rv
) && SelectionRef().IsCollapsed()) {
2443 nsresult rv
= EnsureCaretNotAfterInvisibleBRElement();
2444 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
2445 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED
);
2447 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2448 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
2449 "failed, but ignored");
2450 if (NS_SUCCEEDED(rv
)) {
2451 nsresult rv
= PrepareInlineStylesForCaret();
2452 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
2453 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED
);
2455 NS_WARNING_ASSERTION(
2457 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
2461 // Remove Selection and create `<blockquote type="cite">` now.
2462 // XXX Why don't we insert the `<blockquote>` into the DOM tree after
2463 // pasting the content in clipboard into it?
2464 RefPtr
<Element
> newBlockquoteElement
=
2465 DeleteSelectionAndCreateElement(*nsGkAtoms::blockquote
);
2466 if (!newBlockquoteElement
) {
2468 "HTMLEditor::DeleteSelectionAndCreateElement(nsGkAtoms::blockquote) "
2470 return NS_ERROR_FAILURE
;
2472 DebugOnly
<nsresult
> rvIgnored
= newBlockquoteElement
->SetAttr(
2473 kNameSpaceID_None
, nsGkAtoms::type
, u
"cite"_ns
, true);
2474 if (NS_WARN_IF(Destroyed())) {
2475 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED
);
2477 NS_WARNING_ASSERTION(
2478 NS_SUCCEEDED(rvIgnored
),
2479 "Element::SetAttr(nsGkAtoms::type, cite) failed, but ignored");
2481 // Collapse Selection in the new `<blockquote>` element.
2482 rv
= CollapseSelectionToStartOf(*newBlockquoteElement
);
2483 if (NS_FAILED(rv
)) {
2484 NS_WARNING("HTMLEditor::CollapseSelectionToStartOf() failed");
2488 rv
= PasteInternal(aClipboardType
);
2489 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "HTMLEditor::PasteInternal() failed");
2490 return EditorBase::ToGenericNSResult(rv
);
2493 nsresult
HTMLEditor::PasteAsPlaintextQuotation(int32_t aSelectionType
) {
2494 // Get Clipboard Service
2496 nsCOMPtr
<nsIClipboard
> clipboard
=
2497 do_GetService("@mozilla.org/widget/clipboard;1", &rv
);
2498 if (NS_FAILED(rv
)) {
2499 NS_WARNING("Failed to get nsIClipboard service");
2503 // Create generic Transferable for getting the data
2504 nsCOMPtr
<nsITransferable
> transferable
=
2505 do_CreateInstance("@mozilla.org/widget/transferable;1", &rv
);
2506 if (NS_FAILED(rv
)) {
2507 NS_WARNING("do_CreateInstance() failed to create nsITransferable instance");
2510 if (!transferable
) {
2511 NS_WARNING("do_CreateInstance() returned nullptr");
2512 return NS_ERROR_FAILURE
;
2515 RefPtr
<Document
> destdoc
= GetDocument();
2516 nsILoadContext
* loadContext
= destdoc
? destdoc
->GetLoadContext() : nullptr;
2517 DebugOnly
<nsresult
> rvIgnored
= transferable
->Init(loadContext
);
2518 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
2519 "nsITransferable::Init() failed, but ignored");
2521 // We only handle plaintext pastes here
2522 rvIgnored
= transferable
->AddDataFlavor(kUnicodeMime
);
2523 NS_WARNING_ASSERTION(
2524 NS_SUCCEEDED(rvIgnored
),
2525 "nsITransferable::AddDataFlavor(kUnicodeMime) failed, but ignored");
2527 // Get the Data from the clipboard
2528 rvIgnored
= clipboard
->GetData(transferable
, aSelectionType
);
2529 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
2530 "nsIClipboard::GetData() failed, but ignored");
2532 // Now we ask the transferable for the data
2533 // it still owns the data, we just have a pointer to it.
2534 // If it can't support a "text" output of the data the call will fail
2535 nsCOMPtr
<nsISupports
> genericDataObj
;
2536 nsAutoCString flavor
;
2537 rv
= transferable
->GetAnyTransferData(flavor
, getter_AddRefs(genericDataObj
));
2538 if (NS_FAILED(rv
)) {
2539 NS_WARNING("nsITransferable::GetAnyTransferData() failed");
2543 if (!flavor
.EqualsLiteral(kUnicodeMime
)) {
2547 nsAutoString stuffToPaste
;
2548 if (!GetString(genericDataObj
, stuffToPaste
)) {
2552 AutoPlaceholderBatch
treatAsOneTransaction(*this,
2553 ScrollSelectionIntoView::Yes
);
2554 rv
= InsertAsPlaintextQuotation(stuffToPaste
, true, 0);
2555 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2556 "HTMLEditor::InsertAsPlaintextQuotation() failed");
2560 nsresult
HTMLEditor::InsertWithQuotationsAsSubAction(
2561 const nsAString
& aQuotedText
) {
2562 MOZ_ASSERT(IsEditActionDataAvailable());
2568 EditActionResult result
= CanHandleHTMLEditSubAction();
2569 if (result
.Failed() || result
.Canceled()) {
2570 NS_WARNING_ASSERTION(result
.Succeeded(),
2571 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
2575 UndefineCaretBidiLevel();
2577 // Let the citer quote it for us:
2578 nsString quotedStuff
;
2579 InternetCiter::GetCiteString(aQuotedText
, quotedStuff
);
2581 // It's best to put a blank line after the quoted text so that mails
2582 // written without thinking won't be so ugly.
2583 if (!aQuotedText
.IsEmpty() &&
2584 (aQuotedText
.Last() != HTMLEditUtils::kNewLine
)) {
2585 quotedStuff
.Append(HTMLEditUtils::kNewLine
);
2588 IgnoredErrorResult ignoredError
;
2589 AutoEditSubActionNotifier
startToHandleEditSubAction(
2590 *this, EditSubAction::eInsertText
, nsIEditor::eNext
, ignoredError
);
2591 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
2592 return ignoredError
.StealNSResult();
2594 NS_WARNING_ASSERTION(
2595 !ignoredError
.Failed(),
2596 "OnStartToHandleTopLevelEditSubAction() failed, but ignored");
2598 nsresult rv
= EnsureNoPaddingBRElementForEmptyEditor();
2599 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
2600 return NS_ERROR_EDITOR_DESTROYED
;
2602 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2603 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
2604 "failed, but ignored");
2606 if (NS_SUCCEEDED(rv
) && SelectionRef().IsCollapsed()) {
2607 nsresult rv
= EnsureCaretNotAfterInvisibleBRElement();
2608 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
2609 return NS_ERROR_EDITOR_DESTROYED
;
2611 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2612 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
2613 "failed, but ignored");
2614 if (NS_SUCCEEDED(rv
)) {
2615 nsresult rv
= PrepareInlineStylesForCaret();
2616 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
2617 return NS_ERROR_EDITOR_DESTROYED
;
2619 NS_WARNING_ASSERTION(
2621 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
2625 rv
= InsertTextAsSubAction(quotedStuff
, SelectionHandling::Delete
);
2626 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2627 "EditorBase::InsertTextAsSubAction() failed");
2631 nsresult
HTMLEditor::InsertTextWithQuotations(
2632 const nsAString
& aStringToInsert
) {
2633 AutoEditActionDataSetter
editActionData(*this, EditAction::eInsertText
);
2634 MOZ_ASSERT(!aStringToInsert
.IsVoid());
2635 editActionData
.SetData(aStringToInsert
);
2636 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
2637 if (NS_FAILED(rv
)) {
2638 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
2639 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2640 return EditorBase::ToGenericNSResult(rv
);
2642 if (aStringToInsert
.IsEmpty()) {
2646 // The whole operation should be undoable in one transaction:
2647 // XXX Why isn't enough to use only AutoPlaceholderBatch here?
2648 AutoTransactionBatch
bundleAllTransactions(*this);
2649 AutoPlaceholderBatch
treatAsOneTransaction(*this,
2650 ScrollSelectionIntoView::Yes
);
2652 rv
= InsertTextWithQuotationsInternal(aStringToInsert
);
2653 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2654 "HTMLEditor::InsertTextWithQuotationsInternal() failed");
2655 return EditorBase::ToGenericNSResult(rv
);
2658 nsresult
HTMLEditor::InsertTextWithQuotationsInternal(
2659 const nsAString
& aStringToInsert
) {
2660 MOZ_ASSERT(!aStringToInsert
.IsEmpty());
2661 // We're going to loop over the string, collecting up a "hunk"
2662 // that's all the same type (quoted or not),
2663 // Whenever the quotedness changes (or we reach the string's end)
2664 // we will insert the hunk all at once, quoted or non.
2665 static const char16_t
cite('>');
2666 bool curHunkIsQuoted
= (aStringToInsert
.First() == cite
);
2668 nsAString::const_iterator hunkStart
, strEnd
;
2669 aStringToInsert
.BeginReading(hunkStart
);
2670 aStringToInsert
.EndReading(strEnd
);
2672 // In the loop below, we only look for DOM newlines (\n),
2673 // because we don't have a FindChars method that can look
2674 // for both \r and \n. \r is illegal in the dom anyway,
2675 // but in debug builds, let's take the time to verify that
2676 // there aren't any there:
2678 nsAString::const_iterator
dbgStart(hunkStart
);
2679 if (FindCharInReadable(HTMLEditUtils::kCarriageReturn
, dbgStart
, strEnd
)) {
2682 "Return characters in DOM! InsertTextWithQuotations may be wrong");
2687 nsresult rv
= NS_OK
;
2688 nsAString::const_iterator
lineStart(hunkStart
);
2689 // We will break from inside when we run out of newlines.
2691 // Search for the end of this line (dom newlines, see above):
2692 bool found
= FindCharInReadable(HTMLEditUtils::kNewLine
, lineStart
, strEnd
);
2693 bool quoted
= false;
2695 // if there's another newline, lineStart now points there.
2696 // Loop over any consecutive newline chars:
2697 nsAString::const_iterator
firstNewline(lineStart
);
2698 while (*lineStart
== HTMLEditUtils::kNewLine
) {
2701 quoted
= (*lineStart
== cite
);
2702 if (quoted
== curHunkIsQuoted
) {
2705 // else we're changing state, so we need to insert
2706 // from curHunk to lineStart then loop around.
2708 // But if the current hunk is quoted, then we want to make sure
2709 // that any extra newlines on the end do not get included in
2710 // the quoted section: blank lines flaking a quoted section
2711 // should be considered unquoted, so that if the user clicks
2712 // there and starts typing, the new text will be outside of
2713 // the quoted block.
2714 if (curHunkIsQuoted
) {
2715 lineStart
= firstNewline
;
2717 // 'firstNewline' points to the first '\n'. We want to
2718 // ensure that this first newline goes into the hunk
2719 // since quoted hunks can be displayed as blocks
2720 // (and the newline should become invisible in this case).
2721 // So the next line needs to start at the next character.
2726 // If no newline found, lineStart is now strEnd and we can finish up,
2727 // inserting from curHunk to lineStart then returning.
2728 const nsAString
& curHunk
= Substring(hunkStart
, lineStart
);
2729 nsCOMPtr
<nsINode
> dummyNode
;
2730 if (curHunkIsQuoted
) {
2732 InsertAsPlaintextQuotation(curHunk
, false, getter_AddRefs(dummyNode
));
2733 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
2734 return NS_ERROR_EDITOR_DESTROYED
;
2736 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2737 "HTMLEditor::InsertAsPlaintextQuotation() failed, "
2738 "but might be ignored");
2740 rv
= InsertTextAsSubAction(curHunk
, SelectionHandling::Delete
);
2741 NS_WARNING_ASSERTION(
2743 "EditorBase::InsertTextAsSubAction() failed, but might be ignored");
2748 curHunkIsQuoted
= quoted
;
2749 hunkStart
= lineStart
;
2752 // XXX This returns the last result of InsertAsPlaintextQuotation() or
2753 // InsertTextAsSubAction() in the loop. This must be a bug.
2757 nsresult
HTMLEditor::InsertAsQuotation(const nsAString
& aQuotedText
,
2758 nsINode
** aNodeInserted
) {
2759 if (IsInPlaintextMode()) {
2760 AutoEditActionDataSetter
editActionData(*this, EditAction::eInsertText
);
2761 MOZ_ASSERT(!aQuotedText
.IsVoid());
2762 editActionData
.SetData(aQuotedText
);
2763 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
2764 if (NS_FAILED(rv
)) {
2765 NS_WARNING_ASSERTION(
2766 rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
2767 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2768 return EditorBase::ToGenericNSResult(rv
);
2770 AutoPlaceholderBatch
treatAsOneTransaction(*this,
2771 ScrollSelectionIntoView::Yes
);
2772 rv
= InsertAsPlaintextQuotation(aQuotedText
, true, aNodeInserted
);
2773 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2774 "HTMLEditor::InsertAsPlaintextQuotation() failed");
2775 return EditorBase::ToGenericNSResult(rv
);
2778 AutoEditActionDataSetter
editActionData(*this,
2779 EditAction::eInsertBlockquoteElement
);
2780 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
2781 if (NS_FAILED(rv
)) {
2782 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
2783 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2784 return EditorBase::ToGenericNSResult(rv
);
2787 AutoPlaceholderBatch
treatAsOneTransaction(*this,
2788 ScrollSelectionIntoView::Yes
);
2789 nsAutoString citation
;
2790 rv
= InsertAsCitedQuotationInternal(aQuotedText
, citation
, false,
2792 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2793 "HTMLEditor::InsertAsCitedQuotationInternal() failed");
2794 return EditorBase::ToGenericNSResult(rv
);
2797 // Insert plaintext as a quotation, with cite marks (e.g. "> ").
2798 // This differs from its corresponding method in TextEditor
2799 // in that here, quoted material is enclosed in a <pre> tag
2800 // in order to preserve the original line wrapping.
2801 nsresult
HTMLEditor::InsertAsPlaintextQuotation(const nsAString
& aQuotedText
,
2803 nsINode
** aNodeInserted
) {
2804 MOZ_ASSERT(IsEditActionDataAvailable());
2806 if (aNodeInserted
) {
2807 *aNodeInserted
= nullptr;
2814 EditActionResult result
= CanHandleHTMLEditSubAction();
2815 if (result
.Failed() || result
.Canceled()) {
2816 NS_WARNING_ASSERTION(result
.Succeeded(),
2817 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
2821 UndefineCaretBidiLevel();
2823 IgnoredErrorResult ignoredError
;
2824 AutoEditSubActionNotifier
startToHandleEditSubAction(
2825 *this, EditSubAction::eInsertQuotation
, nsIEditor::eNext
, ignoredError
);
2826 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
2827 return ignoredError
.StealNSResult();
2829 NS_WARNING_ASSERTION(
2830 !ignoredError
.Failed(),
2831 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
2833 nsresult rv
= EnsureNoPaddingBRElementForEmptyEditor();
2834 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
2835 return NS_ERROR_EDITOR_DESTROYED
;
2837 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2838 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
2839 "failed, but ignored");
2841 if (NS_SUCCEEDED(rv
) && SelectionRef().IsCollapsed()) {
2842 nsresult rv
= EnsureCaretNotAfterInvisibleBRElement();
2843 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
2844 return NS_ERROR_EDITOR_DESTROYED
;
2846 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2847 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
2848 "failed, but ignored");
2849 if (NS_SUCCEEDED(rv
)) {
2850 nsresult rv
= PrepareInlineStylesForCaret();
2851 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
2852 return NS_ERROR_EDITOR_DESTROYED
;
2854 NS_WARNING_ASSERTION(
2856 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
2860 // Wrap the inserted quote in a <span> so we can distinguish it. If we're
2861 // inserting into the <body>, we use a <span> which is displayed as a block
2862 // and sized to the screen using 98 viewport width units.
2863 // We could use 100vw, but 98vw avoids a horizontal scroll bar where possible.
2864 // All this is done to wrap overlong lines to the screen and not to the
2865 // container element, the width-restricted body.
2866 RefPtr
<Element
> newSpanElement
=
2867 DeleteSelectionAndCreateElement(*nsGkAtoms::span
);
2868 NS_WARNING_ASSERTION(
2870 "HTMLEditor::DeleteSelectionAndCreateElement() failed, but ignored");
2872 // If this succeeded, then set selection inside the pre
2873 // so the inserted text will end up there.
2874 // If it failed, we don't care what the return value was,
2875 // but we'll fall through and try to insert the text anyway.
2876 if (newSpanElement
) {
2877 // Add an attribute on the pre node so we'll know it's a quotation.
2878 DebugOnly
<nsresult
> rvIgnored
= newSpanElement
->SetAttr(
2879 kNameSpaceID_None
, nsGkAtoms::mozquote
, u
"true"_ns
, true);
2880 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
2881 "Element::SetAttr(nsGkAtoms::mozquote, true) failed");
2882 // Allow wrapping on spans so long lines get wrapped to the screen.
2883 nsCOMPtr
<nsINode
> parentNode
= newSpanElement
->GetParentNode();
2884 if (parentNode
&& parentNode
->IsHTMLElement(nsGkAtoms::body
)) {
2885 DebugOnly
<nsresult
> rvIgnored
= newSpanElement
->SetAttr(
2886 kNameSpaceID_None
, nsGkAtoms::style
,
2888 u
"white-space: pre-wrap; display: block; width: 98vw;"),
2890 NS_WARNING_ASSERTION(
2891 NS_SUCCEEDED(rvIgnored
),
2892 "Element::SetAttr(nsGkAtoms::style) failed, but ignored");
2894 DebugOnly
<nsresult
> rvIgnored
=
2895 newSpanElement
->SetAttr(kNameSpaceID_None
, nsGkAtoms::style
,
2896 u
"white-space: pre-wrap;"_ns
, true);
2897 NS_WARNING_ASSERTION(
2898 NS_SUCCEEDED(rvIgnored
),
2899 "Element::SetAttr(nsGkAtoms::style) failed, but ignored");
2902 // and set the selection inside it:
2903 rv
= CollapseSelectionToStartOf(*newSpanElement
);
2904 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
2905 return NS_ERROR_EDITOR_DESTROYED
;
2907 NS_WARNING_ASSERTION(
2909 "HTMLEditor::CollapseSelectionToStartOf() failed, but ignored");
2913 rv
= InsertWithQuotationsAsSubAction(aQuotedText
);
2914 if (NS_FAILED(rv
)) {
2915 NS_WARNING("HTMLEditor::InsertWithQuotationsAsSubAction() failed");
2919 rv
= InsertTextAsSubAction(aQuotedText
, SelectionHandling::Delete
);
2920 if (NS_FAILED(rv
)) {
2921 NS_WARNING("EditorBase::InsertTextAsSubAction() failed");
2926 // XXX Why don't we check this before inserting the quoted text?
2927 if (!newSpanElement
) {
2931 // Set the selection to just after the inserted node:
2932 EditorRawDOMPoint
afterNewSpanElement(
2933 EditorRawDOMPoint::After(*newSpanElement
));
2934 NS_WARNING_ASSERTION(
2935 afterNewSpanElement
.IsSet(),
2936 "Failed to set after the new <span> element, but ignored");
2937 if (afterNewSpanElement
.IsSet()) {
2938 nsresult rv
= CollapseSelectionTo(afterNewSpanElement
);
2939 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
2940 return NS_ERROR_EDITOR_DESTROYED
;
2942 NS_WARNING_ASSERTION(
2944 "HTMLEditor::CollapseSelectionTo() failed, but ignored");
2947 // Note that if !aAddCites, aNodeInserted isn't set.
2948 // That's okay because the routines that use aAddCites
2949 // don't need to know the inserted node.
2950 if (aNodeInserted
) {
2951 newSpanElement
.forget(aNodeInserted
);
2957 NS_IMETHODIMP
HTMLEditor::Rewrap(bool aRespectNewlines
) {
2958 AutoEditActionDataSetter
editActionData(*this, EditAction::eRewrap
);
2959 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
2960 if (NS_FAILED(rv
)) {
2961 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
2962 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2963 return EditorBase::ToGenericNSResult(rv
);
2966 // Rewrap makes no sense if there's no wrap column; default to 72.
2967 int32_t wrapWidth
= WrapWidth();
2968 if (wrapWidth
<= 0) {
2972 nsAutoString current
;
2973 const bool isCollapsed
= SelectionRef().IsCollapsed();
2974 uint32_t flags
= nsIDocumentEncoder::OutputFormatted
|
2975 nsIDocumentEncoder::OutputLFLineBreak
;
2977 flags
|= nsIDocumentEncoder::OutputSelectionOnly
;
2979 rv
= ComputeValueInternal(u
"text/plain"_ns
, flags
, current
);
2980 if (NS_FAILED(rv
)) {
2981 NS_WARNING("EditorBase::ComputeValueInternal(text/plain) failed");
2982 return EditorBase::ToGenericNSResult(rv
);
2985 if (current
.IsEmpty()) {
2990 uint32_t firstLineOffset
= 0; // XXX need to reset this if there is a
2992 InternetCiter::Rewrap(current
, wrapWidth
, firstLineOffset
, aRespectNewlines
,
2996 DebugOnly
<nsresult
> rvIgnored
= SelectAllInternal();
2997 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
2998 "HTMLEditor::SelectAllInternal() failed");
3001 // The whole operation in InsertTextWithQuotationsInternal() should be
3002 // undoable in one transaction.
3003 // XXX Why isn't enough to use only AutoPlaceholderBatch here?
3004 AutoTransactionBatch
bundleAllTransactions(*this);
3005 AutoPlaceholderBatch
treatAsOneTransaction(*this,
3006 ScrollSelectionIntoView::Yes
);
3007 rv
= InsertTextWithQuotationsInternal(wrapped
);
3008 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3009 "HTMLEditor::InsertTextWithQuotationsInternal() failed");
3010 return EditorBase::ToGenericNSResult(rv
);
3013 NS_IMETHODIMP
HTMLEditor::InsertAsCitedQuotation(const nsAString
& aQuotedText
,
3014 const nsAString
& aCitation
,
3016 nsINode
** aNodeInserted
) {
3017 // Don't let anyone insert HTML when we're in plaintext mode.
3018 if (IsInPlaintextMode()) {
3021 "InsertAsCitedQuotation: trying to insert html into plaintext editor");
3023 AutoEditActionDataSetter
editActionData(*this, EditAction::eInsertText
);
3024 MOZ_ASSERT(!aQuotedText
.IsVoid());
3025 editActionData
.SetData(aQuotedText
);
3026 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
3027 if (NS_FAILED(rv
)) {
3028 NS_WARNING_ASSERTION(
3029 rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
3030 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
3031 return EditorBase::ToGenericNSResult(rv
);
3034 AutoPlaceholderBatch
treatAsOneTransaction(*this,
3035 ScrollSelectionIntoView::Yes
);
3036 rv
= InsertAsPlaintextQuotation(aQuotedText
, true, aNodeInserted
);
3037 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3038 "HTMLEditor::InsertAsPlaintextQuotation() failed");
3039 return EditorBase::ToGenericNSResult(rv
);
3042 AutoEditActionDataSetter
editActionData(*this,
3043 EditAction::eInsertBlockquoteElement
);
3044 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
3045 if (NS_FAILED(rv
)) {
3046 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
3047 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
3048 return EditorBase::ToGenericNSResult(rv
);
3051 AutoPlaceholderBatch
treatAsOneTransaction(*this,
3052 ScrollSelectionIntoView::Yes
);
3053 rv
= InsertAsCitedQuotationInternal(aQuotedText
, aCitation
, aInsertHTML
,
3055 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3056 "HTMLEditor::InsertAsCitedQuotationInternal() failed");
3057 return EditorBase::ToGenericNSResult(rv
);
3060 nsresult
HTMLEditor::InsertAsCitedQuotationInternal(
3061 const nsAString
& aQuotedText
, const nsAString
& aCitation
, bool aInsertHTML
,
3062 nsINode
** aNodeInserted
) {
3063 MOZ_ASSERT(IsEditActionDataAvailable());
3064 MOZ_ASSERT(!IsInPlaintextMode());
3070 EditActionResult result
= CanHandleHTMLEditSubAction();
3071 if (result
.Failed() || result
.Canceled()) {
3072 NS_WARNING_ASSERTION(result
.Succeeded(),
3073 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
3077 UndefineCaretBidiLevel();
3079 IgnoredErrorResult ignoredError
;
3080 AutoEditSubActionNotifier
startToHandleEditSubAction(
3081 *this, EditSubAction::eInsertQuotation
, nsIEditor::eNext
, ignoredError
);
3082 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
3083 return ignoredError
.StealNSResult();
3085 NS_WARNING_ASSERTION(
3086 !ignoredError
.Failed(),
3087 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
3089 nsresult rv
= EnsureNoPaddingBRElementForEmptyEditor();
3090 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
3091 return NS_ERROR_EDITOR_DESTROYED
;
3093 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3094 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
3095 "failed, but ignored");
3097 if (NS_SUCCEEDED(rv
) && SelectionRef().IsCollapsed()) {
3098 nsresult rv
= EnsureCaretNotAfterInvisibleBRElement();
3099 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
3100 return NS_ERROR_EDITOR_DESTROYED
;
3102 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3103 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
3104 "failed, but ignored");
3105 if (NS_SUCCEEDED(rv
)) {
3106 nsresult rv
= PrepareInlineStylesForCaret();
3107 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
3108 return NS_ERROR_EDITOR_DESTROYED
;
3110 NS_WARNING_ASSERTION(
3112 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
3116 RefPtr
<Element
> newBlockquoteElement
=
3117 DeleteSelectionAndCreateElement(*nsGkAtoms::blockquote
);
3118 if (NS_WARN_IF(Destroyed())) {
3119 return NS_ERROR_EDITOR_DESTROYED
;
3121 if (!newBlockquoteElement
) {
3123 "HTMLEditor::DeleteSelectionAndCreateElement(nsGkAtoms::blockquote) "
3125 return NS_ERROR_FAILURE
;
3128 // Try to set type=cite. Ignore it if this fails.
3129 DebugOnly
<nsresult
> rvIgnored
= newBlockquoteElement
->SetAttr(
3130 kNameSpaceID_None
, nsGkAtoms::type
, u
"cite"_ns
, true);
3131 if (NS_WARN_IF(Destroyed())) {
3132 return NS_ERROR_EDITOR_DESTROYED
;
3134 NS_WARNING_ASSERTION(
3135 NS_SUCCEEDED(rvIgnored
),
3136 "Element::SetAttr(nsGkAtoms::type, cite) failed, but ignored");
3138 if (!aCitation
.IsEmpty()) {
3139 DebugOnly
<nsresult
> rvIgnored
= newBlockquoteElement
->SetAttr(
3140 kNameSpaceID_None
, nsGkAtoms::cite
, aCitation
, true);
3141 if (NS_WARN_IF(Destroyed())) {
3142 return NS_ERROR_EDITOR_DESTROYED
;
3144 NS_WARNING_ASSERTION(
3145 NS_SUCCEEDED(rvIgnored
),
3146 "Element::SetAttr(nsGkAtoms::cite) failed, but ignored");
3149 // Set the selection inside the blockquote so aQuotedText will go there:
3150 rv
= CollapseSelectionTo(EditorRawDOMPoint(newBlockquoteElement
, 0));
3151 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
3152 return NS_ERROR_EDITOR_DESTROYED
;
3154 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3155 "HTMLEditor::CollapseSelectionTo() failed, but ignored");
3158 rv
= LoadHTML(aQuotedText
);
3159 if (NS_WARN_IF(Destroyed())) {
3160 return NS_ERROR_EDITOR_DESTROYED
;
3162 if (NS_FAILED(rv
)) {
3163 NS_WARNING("HTMLEditor::LoadHTML() failed");
3167 rv
= InsertTextAsSubAction(
3168 aQuotedText
, SelectionHandling::Delete
); // XXX ignore charset
3169 if (NS_WARN_IF(Destroyed())) {
3170 return NS_ERROR_EDITOR_DESTROYED
;
3172 if (NS_FAILED(rv
)) {
3173 NS_WARNING("HTMLEditor::LoadHTML() failed");
3178 // XXX Why don't we check this before inserting aQuotedText?
3179 if (!newBlockquoteElement
) {
3183 // Set the selection to just after the inserted node:
3184 EditorRawDOMPoint
afterNewBlockquoteElement(
3185 EditorRawDOMPoint::After(newBlockquoteElement
));
3186 NS_WARNING_ASSERTION(
3187 afterNewBlockquoteElement
.IsSet(),
3188 "Failed to set after new <blockquote> element, but ignored");
3189 if (afterNewBlockquoteElement
.IsSet()) {
3190 nsresult rv
= CollapseSelectionTo(afterNewBlockquoteElement
);
3191 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
3192 return NS_ERROR_EDITOR_DESTROYED
;
3194 NS_WARNING_ASSERTION(
3196 "HTMLEditor::CollapseSelectionTo() failed, but ignored");
3199 if (aNodeInserted
) {
3200 newBlockquoteElement
.forget(aNodeInserted
);
3206 void HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
3207 RemoveHeadChildAndStealBodyChildsChildren(nsINode
& aNode
) {
3208 nsCOMPtr
<nsIContent
> body
, head
;
3209 // find the body and head nodes if any.
3210 // look only at immediate children of aNode.
3211 for (nsCOMPtr
<nsIContent
> child
= aNode
.GetFirstChild(); child
;
3212 child
= child
->GetNextSibling()) {
3213 if (child
->IsHTMLElement(nsGkAtoms::body
)) {
3215 } else if (child
->IsHTMLElement(nsGkAtoms::head
)) {
3220 ErrorResult ignored
;
3221 aNode
.RemoveChild(*head
, ignored
);
3224 nsCOMPtr
<nsIContent
> child
= body
->GetFirstChild();
3226 ErrorResult ignored
;
3227 aNode
.InsertBefore(*child
, body
, ignored
);
3228 child
= body
->GetFirstChild();
3231 ErrorResult ignored
;
3232 aNode
.RemoveChild(*body
, ignored
);
3237 bool HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
3238 IsInsertionCookie(const nsIContent
& aContent
) {
3239 // Is this child the magical cookie?
3240 if (const auto* comment
= Comment::FromNode(&aContent
)) {
3242 comment
->GetData(data
);
3244 return data
.EqualsLiteral(kInsertCookie
);
3251 * This function finds the target node that we will be pasting into. aStart is
3252 * the context that we're given and aResult will be the target. Initially,
3253 * *aResult must be nullptr.
3255 * The target for a paste is found by either finding the node that contains
3256 * the magical comment node containing kInsertCookie or, failing that, the
3257 * firstChild of the firstChild (until we reach a leaf).
3259 bool HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
3260 FindTargetNodeOfContextForPastedHTMLAndRemoveInsertionCookie(
3261 nsINode
& aStart
, nsCOMPtr
<nsINode
>& aResult
) {
3262 nsIContent
* firstChild
= aStart
.GetFirstChild();
3264 // If the current result is nullptr, then aStart is a leaf, and is the
3272 for (nsCOMPtr
<nsIContent
> child
= firstChild
; child
;
3273 child
= child
->GetNextSibling()) {
3274 if (FragmentFromPasteCreator::IsInsertionCookie(*child
)) {
3275 // Yes it is! Return an error so we bubble out and short-circuit the
3284 if (FindTargetNodeOfContextForPastedHTMLAndRemoveInsertionCookie(*child
,
3293 class MOZ_STACK_CLASS
HTMLEditor::HTMLWithContextInserter::FragmentParser
3296 FragmentParser(const Document
& aDocument
, bool aTrustedInput
);
3298 [[nodiscard
]] nsresult
ParseContext(const nsAString
& aContextString
,
3299 DocumentFragment
** aFragment
);
3301 [[nodiscard
]] nsresult
ParsePastedHTML(const nsAString
& aInputString
,
3302 nsAtom
* aContextLocalNameAtom
,
3303 DocumentFragment
** aFragment
);
3306 static nsresult
ParseFragment(const nsAString
& aStr
,
3307 nsAtom
* aContextLocalName
,
3308 const Document
* aTargetDoc
,
3309 dom::DocumentFragment
** aFragment
,
3310 bool aTrustedInput
);
3312 const Document
& mDocument
;
3313 const bool mTrustedInput
;
3316 HTMLEditor::HTMLWithContextInserter::FragmentParser::FragmentParser(
3317 const Document
& aDocument
, bool aTrustedInput
)
3318 : mDocument
{aDocument
}, mTrustedInput
{aTrustedInput
} {}
3320 nsresult
HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseContext(
3321 const nsAString
& aContextStr
, DocumentFragment
** aFragment
) {
3322 return FragmentParser::ParseFragment(aContextStr
, nullptr, &mDocument
,
3323 aFragment
, mTrustedInput
);
3326 nsresult
HTMLEditor::HTMLWithContextInserter::FragmentParser::ParsePastedHTML(
3327 const nsAString
& aInputString
, nsAtom
* aContextLocalNameAtom
,
3328 DocumentFragment
** aFragment
) {
3329 return FragmentParser::ParseFragment(aInputString
, aContextLocalNameAtom
,
3330 &mDocument
, aFragment
, mTrustedInput
);
3333 nsresult
HTMLEditor::HTMLWithContextInserter::CreateDOMFragmentFromPaste(
3334 const nsAString
& aInputString
, const nsAString
& aContextStr
,
3335 const nsAString
& aInfoStr
, nsCOMPtr
<nsINode
>* aOutFragNode
,
3336 nsCOMPtr
<nsINode
>* aOutStartNode
, nsCOMPtr
<nsINode
>* aOutEndNode
,
3337 uint32_t* aOutStartOffset
, uint32_t* aOutEndOffset
,
3338 bool aTrustedInput
) const {
3339 if (NS_WARN_IF(!aOutFragNode
) || NS_WARN_IF(!aOutStartNode
) ||
3340 NS_WARN_IF(!aOutEndNode
) || NS_WARN_IF(!aOutStartOffset
) ||
3341 NS_WARN_IF(!aOutEndOffset
)) {
3342 return NS_ERROR_INVALID_ARG
;
3345 RefPtr
<const Document
> document
= mHTMLEditor
.GetDocument();
3346 if (NS_WARN_IF(!document
)) {
3347 return NS_ERROR_FAILURE
;
3350 FragmentFromPasteCreator fragmentFromPasteCreator
;
3352 const nsresult rv
= fragmentFromPasteCreator
.Run(
3353 *document
, aInputString
, aContextStr
, aInfoStr
, aOutFragNode
,
3354 aOutStartNode
, aOutEndNode
, aTrustedInput
);
3356 *aOutStartOffset
= 0;
3357 *aOutEndOffset
= (*aOutEndNode
)->Length();
3363 nsAtom
* HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
3364 DetermineContextLocalNameForParsingPastedHTML(
3365 const nsIContent
* aParentContentOfPastedHTMLInContext
) {
3366 if (!aParentContentOfPastedHTMLInContext
) {
3367 return nsGkAtoms::body
;
3370 nsAtom
* contextLocalNameAtom
=
3371 aParentContentOfPastedHTMLInContext
->NodeInfo()->NameAtom();
3373 return (aParentContentOfPastedHTMLInContext
->IsHTMLElement(nsGkAtoms::html
))
3375 : contextLocalNameAtom
;
3379 nsresult
HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
3380 MergeAndPostProcessFragmentsForPastedHTMLAndContext(
3381 DocumentFragment
& aDocumentFragmentForPastedHTML
,
3382 DocumentFragment
& aDocumentFragmentForContext
,
3383 nsIContent
& aTargetContentOfContextForPastedHTML
) {
3384 FragmentFromPasteCreator::RemoveHeadChildAndStealBodyChildsChildren(
3385 aDocumentFragmentForPastedHTML
);
3387 // unite the two trees
3388 IgnoredErrorResult ignoredError
;
3389 aTargetContentOfContextForPastedHTML
.AppendChild(
3390 aDocumentFragmentForPastedHTML
, ignoredError
);
3391 NS_WARNING_ASSERTION(!ignoredError
.Failed(),
3392 "nsINode::AppendChild() failed, but ignored");
3393 const nsresult rv
= FragmentFromPasteCreator::
3394 RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
3395 aDocumentFragmentForContext
, NodesToRemove::eOnlyListItems
);
3397 if (NS_FAILED(rv
)) {
3399 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
3400 "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces()"
3409 nsresult
HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
3410 PostProcessFragmentForPastedHTMLWithoutContext(
3411 DocumentFragment
& aDocumentFragmentForPastedHTML
) {
3412 FragmentFromPasteCreator::RemoveHeadChildAndStealBodyChildsChildren(
3413 aDocumentFragmentForPastedHTML
);
3415 const nsresult rv
= FragmentFromPasteCreator::
3416 RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
3417 aDocumentFragmentForPastedHTML
, NodesToRemove::eOnlyListItems
);
3419 if (NS_FAILED(rv
)) {
3421 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
3422 "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces() "
3431 nsresult
HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
3432 PreProcessContextDocumentFragmentForMerging(
3433 DocumentFragment
& aDocumentFragmentForContext
) {
3434 // The context is expected to contain text nodes only in block level
3435 // elements. Hence, if they contain only whitespace, they're invisible.
3436 const nsresult rv
= FragmentFromPasteCreator::
3437 RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
3438 aDocumentFragmentForContext
, NodesToRemove::eAll
);
3439 if (NS_FAILED(rv
)) {
3441 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
3442 "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces() "
3447 FragmentFromPasteCreator::RemoveHeadChildAndStealBodyChildsChildren(
3448 aDocumentFragmentForContext
);
3453 nsresult
HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
3454 CreateDocumentFragmentAndGetParentOfPastedHTMLInContext(
3455 const Document
& aDocument
, const nsAString
& aInputString
,
3456 const nsAString
& aContextStr
, bool aTrustedInput
,
3457 nsCOMPtr
<nsINode
>& aParentNodeOfPastedHTMLInContext
,
3458 RefPtr
<DocumentFragment
>& aDocumentFragmentToInsert
) const {
3459 // if we have context info, create a fragment for that
3460 RefPtr
<DocumentFragment
> documentFragmentForContext
;
3462 FragmentParser fragmentParser
{aDocument
, aTrustedInput
};
3463 if (!aContextStr
.IsEmpty()) {
3464 nsresult rv
= fragmentParser
.ParseContext(
3465 aContextStr
, getter_AddRefs(documentFragmentForContext
));
3466 if (NS_FAILED(rv
)) {
3468 "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseContext() "
3472 if (!documentFragmentForContext
) {
3474 "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseContext() "
3475 "returned nullptr");
3476 return NS_ERROR_FAILURE
;
3479 rv
= FragmentFromPasteCreator::PreProcessContextDocumentFragmentForMerging(
3480 *documentFragmentForContext
);
3481 if (NS_FAILED(rv
)) {
3483 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
3484 "PreProcessContextDocumentFragmentForMerging() failed.");
3488 FragmentFromPasteCreator::
3489 FindTargetNodeOfContextForPastedHTMLAndRemoveInsertionCookie(
3490 *documentFragmentForContext
, aParentNodeOfPastedHTMLInContext
);
3491 MOZ_ASSERT(aParentNodeOfPastedHTMLInContext
);
3494 nsCOMPtr
<nsIContent
> parentContentOfPastedHTMLInContext
=
3495 nsIContent::FromNodeOrNull(aParentNodeOfPastedHTMLInContext
);
3496 MOZ_ASSERT_IF(aParentNodeOfPastedHTMLInContext
,
3497 parentContentOfPastedHTMLInContext
);
3499 nsAtom
* contextLocalNameAtom
=
3500 FragmentFromPasteCreator::DetermineContextLocalNameForParsingPastedHTML(
3501 parentContentOfPastedHTMLInContext
);
3502 RefPtr
<DocumentFragment
> documentFragmentForPastedHTML
;
3503 nsresult rv
= fragmentParser
.ParsePastedHTML(
3504 aInputString
, contextLocalNameAtom
,
3505 getter_AddRefs(documentFragmentForPastedHTML
));
3506 if (NS_FAILED(rv
)) {
3508 "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParsePastedHTML()"
3512 if (!documentFragmentForPastedHTML
) {
3514 "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParsePastedHTML()"
3515 " returned nullptr");
3516 return NS_ERROR_FAILURE
;
3519 if (aParentNodeOfPastedHTMLInContext
) {
3520 const nsresult rv
= FragmentFromPasteCreator::
3521 MergeAndPostProcessFragmentsForPastedHTMLAndContext(
3522 *documentFragmentForPastedHTML
, *documentFragmentForContext
,
3523 *parentContentOfPastedHTMLInContext
);
3524 if (NS_FAILED(rv
)) {
3526 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
3527 "MergeAndPostProcessFragmentsForPastedHTMLAndContext() failed.");
3530 aDocumentFragmentToInsert
= std::move(documentFragmentForContext
);
3532 const nsresult rv
= PostProcessFragmentForPastedHTMLWithoutContext(
3533 *documentFragmentForPastedHTML
);
3534 if (NS_FAILED(rv
)) {
3536 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
3537 "PostProcessFragmentForPastedHTMLWithoutContext() failed.");
3541 aDocumentFragmentToInsert
= std::move(documentFragmentForPastedHTML
);
3547 nsresult
HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::Run(
3548 const Document
& aDocument
, const nsAString
& aInputString
,
3549 const nsAString
& aContextStr
, const nsAString
& aInfoStr
,
3550 nsCOMPtr
<nsINode
>* aOutFragNode
, nsCOMPtr
<nsINode
>* aOutStartNode
,
3551 nsCOMPtr
<nsINode
>* aOutEndNode
, bool aTrustedInput
) const {
3552 MOZ_ASSERT(aOutFragNode
);
3553 MOZ_ASSERT(aOutStartNode
);
3554 MOZ_ASSERT(aOutEndNode
);
3556 nsCOMPtr
<nsINode
> parentNodeOfPastedHTMLInContext
;
3557 RefPtr
<DocumentFragment
> documentFragmentToInsert
;
3558 nsresult rv
= CreateDocumentFragmentAndGetParentOfPastedHTMLInContext(
3559 aDocument
, aInputString
, aContextStr
, aTrustedInput
,
3560 parentNodeOfPastedHTMLInContext
, documentFragmentToInsert
);
3561 if (NS_FAILED(rv
)) {
3563 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
3564 "CreateDocumentFragmentAndGetParentOfPastedHTMLInContext() failed.");
3568 // If there was no context, then treat all of the data we did get as the
3570 if (parentNodeOfPastedHTMLInContext
) {
3571 *aOutEndNode
= *aOutStartNode
= parentNodeOfPastedHTMLInContext
;
3573 *aOutEndNode
= *aOutStartNode
= documentFragmentToInsert
;
3576 *aOutFragNode
= std::move(documentFragmentToInsert
);
3578 if (!aInfoStr
.IsEmpty()) {
3580 FragmentFromPasteCreator::MoveStartAndEndAccordingToHTMLInfo(
3581 aInfoStr
, aOutStartNode
, aOutEndNode
);
3582 if (NS_FAILED(rv
)) {
3584 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
3585 "MoveStartAndEndAccordingToHTMLInfo() failed");
3594 nsresult
HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
3595 MoveStartAndEndAccordingToHTMLInfo(const nsAString
& aInfoStr
,
3596 nsCOMPtr
<nsINode
>* aOutStartNode
,
3597 nsCOMPtr
<nsINode
>* aOutEndNode
) {
3598 int32_t sep
= aInfoStr
.FindChar((char16_t
)',');
3599 nsAutoString
numstr1(Substring(aInfoStr
, 0, sep
));
3600 nsAutoString
numstr2(
3601 Substring(aInfoStr
, sep
+ 1, aInfoStr
.Length() - (sep
+ 1)));
3603 // Move the start and end children.
3605 int32_t num
= numstr1
.ToInteger(&rvIgnored
);
3606 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
3607 "nsAString::ToInteger() failed, but ignored");
3609 nsINode
* tmp
= (*aOutStartNode
)->GetFirstChild();
3611 NS_WARNING("aOutStartNode did not have children");
3612 return NS_ERROR_FAILURE
;
3614 *aOutStartNode
= tmp
;
3617 num
= numstr2
.ToInteger(&rvIgnored
);
3619 nsINode
* tmp
= (*aOutEndNode
)->GetLastChild();
3621 NS_WARNING("aOutEndNode did not have children");
3622 return NS_ERROR_FAILURE
;
3631 nsresult
HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseFragment(
3632 const nsAString
& aFragStr
, nsAtom
* aContextLocalName
,
3633 const Document
* aTargetDocument
, DocumentFragment
** aFragment
,
3634 bool aTrustedInput
) {
3635 nsAutoScriptBlockerSuppressNodeRemoved autoBlocker
;
3637 nsCOMPtr
<Document
> doc
=
3638 nsContentUtils::CreateInertHTMLDocument(aTargetDocument
);
3640 return NS_ERROR_FAILURE
;
3642 RefPtr
<DocumentFragment
> fragment
=
3643 new (doc
->NodeInfoManager()) DocumentFragment(doc
->NodeInfoManager());
3644 nsresult rv
= nsContentUtils::ParseFragmentHTML(
3646 aContextLocalName
? aContextLocalName
: nsGkAtoms::body
,
3647 kNameSpaceID_XHTML
, false, true);
3648 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3649 "nsContentUtils::ParseFragmentHTML() failed");
3650 if (!aTrustedInput
) {
3651 nsTreeSanitizer
sanitizer(aContextLocalName
3652 ? nsIParserUtils::SanitizerAllowStyle
3653 : nsIParserUtils::SanitizerAllowComments
);
3654 sanitizer
.Sanitize(fragment
);
3656 fragment
.forget(aFragment
);
3661 void HTMLEditor::HTMLWithContextInserter::
3662 CollectTopMostChildContentsCompletelyInRange(
3663 const EditorRawDOMPoint
& aStartPoint
,
3664 const EditorRawDOMPoint
& aEndPoint
,
3665 nsTArray
<OwningNonNull
<nsIContent
>>& aOutArrayOfContents
) {
3666 MOZ_ASSERT(aStartPoint
.IsSetAndValid());
3667 MOZ_ASSERT(aEndPoint
.IsSetAndValid());
3669 RefPtr
<nsRange
> range
=
3670 nsRange::Create(aStartPoint
.ToRawRangeBoundary(),
3671 aEndPoint
.ToRawRangeBoundary(), IgnoreErrors());
3673 NS_WARNING("nsRange::Create() failed");
3676 DOMSubtreeIterator iter
;
3677 if (NS_FAILED(iter
.Init(*range
))) {
3678 NS_WARNING("DOMSubtreeIterator::Init() failed, but ignored");
3682 iter
.AppendAllNodesToArray(aOutArrayOfContents
);
3685 /******************************************************************************
3686 * HTMLEditor::AutoHTMLFragmentBoundariesFixer
3687 ******************************************************************************/
3689 HTMLEditor::AutoHTMLFragmentBoundariesFixer::AutoHTMLFragmentBoundariesFixer(
3690 nsTArray
<OwningNonNull
<nsIContent
>>& aArrayOfTopMostChildContents
) {
3691 EnsureBeginsOrEndsWithValidContent(StartOrEnd::start
,
3692 aArrayOfTopMostChildContents
);
3693 EnsureBeginsOrEndsWithValidContent(StartOrEnd::end
,
3694 aArrayOfTopMostChildContents
);
3698 void HTMLEditor::AutoHTMLFragmentBoundariesFixer::
3699 CollectTableAndAnyListElementsOfInclusiveAncestorsAt(
3700 nsIContent
& aContent
,
3701 nsTArray
<OwningNonNull
<Element
>>& aOutArrayOfListAndTableElements
) {
3702 for (Element
* element
= aContent
.GetAsElementOrParentElement(); element
;
3703 element
= element
->GetParentElement()) {
3704 if (HTMLEditUtils::IsAnyListElement(element
) ||
3705 HTMLEditUtils::IsTable(element
)) {
3706 aOutArrayOfListAndTableElements
.AppendElement(*element
);
3712 Element
* HTMLEditor::AutoHTMLFragmentBoundariesFixer::
3713 GetMostDistantAncestorListOrTableElement(
3714 const nsTArray
<OwningNonNull
<nsIContent
>>& aArrayOfTopMostChildContents
,
3715 const nsTArray
<OwningNonNull
<Element
>>&
3716 aInclusiveAncestorsTableOrListElements
) {
3717 Element
* lastFoundAncestorListOrTableElement
= nullptr;
3718 for (auto& content
: aArrayOfTopMostChildContents
) {
3719 if (HTMLEditUtils::IsAnyTableElementButNotTable(content
)) {
3720 Element
* tableElement
=
3721 HTMLEditUtils::GetClosestAncestorTableElement(*content
);
3722 if (!tableElement
) {
3725 // If we find a `<table>` element which is an ancestor of a table
3726 // related element and is not an acestor of first nor last of
3727 // aArrayOfNodes, return the last found list or `<table>` element.
3728 // XXX Is that really expected that this returns a list element in this
3730 if (!aInclusiveAncestorsTableOrListElements
.Contains(tableElement
)) {
3731 return lastFoundAncestorListOrTableElement
;
3733 // If we find a `<table>` element which is topmost list or `<table>`
3734 // element at first or last of aArrayOfNodes, return it.
3735 if (aInclusiveAncestorsTableOrListElements
.LastElement().get() ==
3737 return tableElement
;
3739 // Otherwise, store the `<table>` element which is an ancestor but
3740 // not topmost ancestor of first or last of aArrayOfNodes.
3741 lastFoundAncestorListOrTableElement
= tableElement
;
3745 if (!HTMLEditUtils::IsListItem(content
)) {
3748 Element
* listElement
=
3749 HTMLEditUtils::GetClosestAncestorAnyListElement(*content
);
3753 // If we find a list element which is ancestor of a list item element and
3754 // is not an acestor of first nor last of aArrayOfNodes, return the last
3755 // found list or `<table>` element.
3756 // XXX Is that really expected that this returns a `<table>` element in
3758 if (!aInclusiveAncestorsTableOrListElements
.Contains(listElement
)) {
3759 return lastFoundAncestorListOrTableElement
;
3761 // If we find a list element which is topmost list or `<table>` element at
3762 // first or last of aArrayOfNodes, return it.
3763 if (aInclusiveAncestorsTableOrListElements
.LastElement().get() ==
3767 // Otherwise, store the list element which is an ancestor but not topmost
3768 // ancestor of first or last of aArrayOfNodes.
3769 lastFoundAncestorListOrTableElement
= listElement
;
3772 // If we find only non-topmost list or `<table>` element, returns the last
3773 // found one (meaning bottommost one). Otherwise, nullptr.
3774 return lastFoundAncestorListOrTableElement
;
3778 HTMLEditor::AutoHTMLFragmentBoundariesFixer::FindReplaceableTableElement(
3779 Element
& aTableElement
, nsIContent
& aContentMaybeInTableElement
) const {
3780 MOZ_ASSERT(aTableElement
.IsHTMLElement(nsGkAtoms::table
));
3781 // Perhaps, this is designed for climbing up the DOM tree from
3782 // aContentMaybeInTableElement to aTableElement and making sure that
3783 // aContentMaybeInTableElement itself or its ancestor is a `<td>`, `<th>`,
3784 // `<tr>`, `<thead>`, `<tbody>`, `<tfoot>` or `<caption>`.
3785 // But this looks really buggy because this loop may skip aTableElement
3786 // as the following NS_ASSERTION. We should write automated tests and
3787 // check right behavior.
3788 for (Element
* element
=
3789 aContentMaybeInTableElement
.GetAsElementOrParentElement();
3790 element
; element
= element
->GetParentElement()) {
3791 if (!HTMLEditUtils::IsAnyTableElement(element
) ||
3792 element
->IsHTMLElement(nsGkAtoms::table
)) {
3793 // XXX Perhaps, the original developer of this method assumed that
3794 // aTableElement won't be skipped because if it's assumed, we can
3795 // stop climbing up the tree in that case.
3796 NS_ASSERTION(element
!= &aTableElement
,
3797 "The table element which is looking for is ignored");
3800 Element
* tableElement
= nullptr;
3801 for (Element
* maybeTableElement
= element
->GetParentElement();
3803 maybeTableElement
= maybeTableElement
->GetParentElement()) {
3804 if (maybeTableElement
->IsHTMLElement(nsGkAtoms::table
)) {
3805 tableElement
= maybeTableElement
;
3809 if (tableElement
== &aTableElement
) {
3812 // XXX If we find another `<table>` element, why don't we keep searching
3818 bool HTMLEditor::AutoHTMLFragmentBoundariesFixer::IsReplaceableListElement(
3819 Element
& aListElement
, nsIContent
& aContentMaybeInListElement
) const {
3820 MOZ_ASSERT(HTMLEditUtils::IsAnyListElement(&aListElement
));
3821 // Perhaps, this is designed for climbing up the DOM tree from
3822 // aContentMaybeInListElement to aListElement and making sure that
3823 // aContentMaybeInListElement itself or its ancestor is an list item.
3824 // But this looks really buggy because this loop may skip aListElement
3825 // as the following NS_ASSERTION. We should write automated tests and
3826 // check right behavior.
3827 for (Element
* element
=
3828 aContentMaybeInListElement
.GetAsElementOrParentElement();
3829 element
; element
= element
->GetParentElement()) {
3830 if (!HTMLEditUtils::IsListItem(element
)) {
3831 // XXX Perhaps, the original developer of this method assumed that
3832 // aListElement won't be skipped because if it's assumed, we can
3833 // stop climbing up the tree in that case.
3834 NS_ASSERTION(element
!= &aListElement
,
3835 "The list element which is looking for is ignored");
3838 Element
* listElement
=
3839 HTMLEditUtils::GetClosestAncestorAnyListElement(*element
);
3840 if (listElement
== &aListElement
) {
3843 // XXX If we find another list element, why don't we keep searching
3849 void HTMLEditor::AutoHTMLFragmentBoundariesFixer::
3850 EnsureBeginsOrEndsWithValidContent(StartOrEnd aStartOrEnd
,
3851 nsTArray
<OwningNonNull
<nsIContent
>>&
3852 aArrayOfTopMostChildContents
) const {
3853 MOZ_ASSERT(!aArrayOfTopMostChildContents
.IsEmpty());
3855 // Collect list elements and table related elements at first or last node
3856 // in aArrayOfTopMostChildContents.
3857 AutoTArray
<OwningNonNull
<Element
>, 4> inclusiveAncestorsListOrTableElements
;
3858 CollectTableAndAnyListElementsOfInclusiveAncestorsAt(
3859 aStartOrEnd
== StartOrEnd::end
3860 ? aArrayOfTopMostChildContents
.LastElement()
3861 : aArrayOfTopMostChildContents
[0],
3862 inclusiveAncestorsListOrTableElements
);
3863 if (inclusiveAncestorsListOrTableElements
.IsEmpty()) {
3867 // Get most ancestor list or `<table>` element in
3868 // inclusiveAncestorsListOrTableElements which contains earlier
3869 // node in aArrayOfTopMostChildContents as far as possible.
3870 // XXX With inclusiveAncestorsListOrTableElements, this returns a
3871 // list or `<table>` element which contains first or last node of
3872 // aArrayOfTopMostChildContents. However, this seems slow when
3873 // aStartOrEnd is StartOrEnd::end and only the last node is in
3874 // different list or `<table>`. But I'm not sure whether it's
3875 // possible case or not. We need to add tests to
3876 // test_content_iterator_subtree.html for checking how
3877 // SubtreeContentIterator works.
3878 Element
* listOrTableElement
= GetMostDistantAncestorListOrTableElement(
3879 aArrayOfTopMostChildContents
, inclusiveAncestorsListOrTableElements
);
3880 if (!listOrTableElement
) {
3884 // If we have pieces of tables or lists to be inserted, let's force the
3885 // insertion to deal with table elements right away, so that it doesn't
3886 // orphan some table or list contents outside the table or list.
3888 OwningNonNull
<nsIContent
>& firstOrLastChildContent
=
3889 aStartOrEnd
== StartOrEnd::end
3890 ? aArrayOfTopMostChildContents
.LastElement()
3891 : aArrayOfTopMostChildContents
[0];
3893 // Find substructure of list or table that must be included in paste.
3894 Element
* replaceElement
;
3895 if (HTMLEditUtils::IsAnyListElement(listOrTableElement
)) {
3896 if (!IsReplaceableListElement(*listOrTableElement
,
3897 firstOrLastChildContent
)) {
3900 replaceElement
= listOrTableElement
;
3902 MOZ_ASSERT(listOrTableElement
->IsHTMLElement(nsGkAtoms::table
));
3903 replaceElement
= FindReplaceableTableElement(*listOrTableElement
,
3904 firstOrLastChildContent
);
3905 if (!replaceElement
) {
3910 // If we can replace the given list element or found a table related element
3911 // in the `<table>` element, insert it into aArrayOfTopMostChildContents which
3912 // is tompost children to be inserted instead of descendants of them in
3913 // aArrayOfTopMostChildContents.
3914 for (size_t i
= 0; i
< aArrayOfTopMostChildContents
.Length();) {
3915 OwningNonNull
<nsIContent
>& content
= aArrayOfTopMostChildContents
[i
];
3916 if (content
== replaceElement
) {
3917 // If the element is n aArrayOfTopMostChildContents, its descendants must
3918 // not be in the array. Therefore, we don't need to optimize this case.
3919 // XXX Perhaps, we can break this loop right now.
3920 aArrayOfTopMostChildContents
.RemoveElementAt(i
);
3923 if (!EditorUtils::IsDescendantOf(content
, *replaceElement
)) {
3927 // For saving number of calls of EditorUtils::IsDescendantOf(), we should
3928 // remove its siblings in the array.
3929 nsIContent
* parent
= content
->GetParent();
3930 aArrayOfTopMostChildContents
.RemoveElementAt(i
);
3931 while (i
< aArrayOfTopMostChildContents
.Length() &&
3932 aArrayOfTopMostChildContents
[i
]->GetParent() == parent
) {
3933 aArrayOfTopMostChildContents
.RemoveElementAt(i
);
3937 // Now replace the removed nodes with the structural parent
3938 if (aStartOrEnd
== StartOrEnd::end
) {
3939 aArrayOfTopMostChildContents
.AppendElement(*replaceElement
);
3941 aArrayOfTopMostChildContents
.InsertElementAt(0, *replaceElement
);
3945 } // namespace mozilla