Bug 1735777 [wpt PR 31236] - webrtc wpt: fix invalid unicode indexOf method call...
[gecko.git] / editor / libeditor / HTMLEditorDataTransfer.cpp
blobbdb90d9f32adde576e44206911d9e1b4a60149e7
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=78: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "HTMLEditor.h"
9 #include <string.h>
11 #include "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"
40 #include "nsCOMPtr.h"
41 #include "nsCRTGlue.h" // for CRLF
42 #include "nsComponentManagerUtils.h"
43 #include "nsIScriptError.h"
44 #include "nsContentUtils.h"
45 #include "nsDebug.h"
46 #include "nsDependentSubstring.h"
47 #include "nsError.h"
48 #include "nsGkAtoms.h"
49 #include "nsIClipboard.h"
50 #include "nsIContent.h"
51 #include "nsIDocumentEncoder.h"
52 #include "nsIFile.h"
53 #include "nsIInputStream.h"
54 #include "nsNameSpaceManager.h"
55 #include "nsINode.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"
66 #include "nsRange.h"
67 #include "nsReadableUtils.h"
68 #include "nsServiceManagerUtils.h"
69 #include "nsStreamUtils.h"
70 #include "nsString.h"
71 #include "nsStringFwd.h"
72 #include "nsStringIterator.h"
73 #include "nsTreeSanitizer.h"
74 #include "nsXPCOM.h"
75 #include "nscore.h"
76 #include "nsContentUtils.h"
77 #include "nsQueryObject.h"
79 class nsAtom;
80 class nsILoadContext;
82 namespace mozilla {
84 using namespace dom;
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();
114 if (NS_FAILED(rv)) {
115 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
116 "MaybeDispatchBeforeInputEvent() failed");
117 return rv;
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())) {
124 return NS_OK;
126 NS_WARNING_ASSERTION(
127 NS_SUCCEEDED(rvIgnored),
128 "HTMLEditor::InsertFromDataTransfer() failed, but ignored");
130 return NS_OK;
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();
161 if (NS_FAILED(rv)) {
162 NS_WARNING("EditorBase::EnsureNoPaddingBRElementForEmptyEditor() failed");
163 return rv;
166 // Delete Selection, but only if it isn't collapsed, see bug #106269
167 if (!SelectionRef().IsCollapsed()) {
168 nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip);
169 if (NS_FAILED(rv)) {
170 NS_WARNING(
171 "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
172 return rv;
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.
183 ErrorResult error;
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);
201 if (NS_FAILED(rv)) {
202 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
203 return rv;
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
212 // overflown.
213 pointToInsert.SetToEndOf(pointToInsert.GetContainer());
217 return NS_OK;
220 NS_IMETHODIMP HTMLEditor::InsertHTML(const nsAString& aInString) {
221 nsresult rv = InsertHTMLAsAction(aInString);
222 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
223 "HTMLEditor::InsertHTMLAsAction() failed");
224 return rv;
227 nsresult HTMLEditor::InsertHTMLAsAction(const nsAString& aInString,
228 nsIPrincipal* aPrincipal) {
229 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertHTML,
230 aPrincipal);
231 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
232 if (NS_FAILED(rv)) {
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 {
246 public:
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,
259 bool aClearStyle);
261 private:
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
272 * descendants).
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
277 * the range.
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>
297 InsertContents(
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 {
321 public:
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;
328 private:
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 {
367 eAll,
368 eOnlyListItems /*!< List items are always block-level elements, hence such
369 whitespace-only nodes are always invisible. */
371 static nsresult
372 RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
373 nsIContent& aNode, NodesToRemove aNodesToRemove);
376 HTMLBRElement*
377 HTMLEditor::HTMLWithContextInserter::GetInvisibleBRElementAtPoint(
378 const EditorDOMPoint& aPointToInsert) const {
379 WSRunScanner wsRunScannerAtInsertionPoint(mHTMLEditor.GetActiveEditingHost(),
380 aPointToInsert);
381 if (wsRunScannerAtInsertionPoint.EndsByInvisibleBRElement()) {
382 return wsRunScannerAtInsertionPoint.EndReasonBRElementPtr();
384 return nullptr;
387 EditorDOMPoint
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();
402 maybeTableElement &&
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
410 // table element.
411 if (mostDistantInclusiveAncestorTableElement) {
412 containerContent = mostDistantInclusiveAncestorTableElement;
416 // If we are not in table elements, we should put caret in the last inserted
417 // node.
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.
431 else {
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
438 // `<br>` element.
439 Element* editingHost = mHTMLEditor.GetActiveEditingHost();
440 WSRunScanner wsRunScannerAtCaret(editingHost, pointToPutCaret);
441 if (wsRunScannerAtCaret
442 .ScanPreviousVisibleNodeOrBlockBoundaryFrom(pointToPutCaret)
443 .ReachedInvisibleBRElement()) {
444 WSRunScanner wsRunScannerAtStartReason(
445 editingHost,
446 EditorDOMPoint(wsRunScannerAtCaret.GetStartReasonContent()));
447 WSScanResult backwardScanFromPointToCaretResult =
448 wsRunScannerAtStartReason.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
449 pointToPutCaret);
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);
515 if (NS_FAILED(rv)) {
516 NS_WARNING(
517 "HTMLEditor::HTMLWithContextInserter::CreateDOMFragmentFromPaste() "
518 "failed");
519 return rv;
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);
531 if (NS_FAILED(rv)) {
532 NS_WARNING("EditorBase::PrepareToInsertContent() failed");
533 return rv;
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 =
544 streamStartParent
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) {
568 nsresult rv =
569 MOZ_KnownLive(mHTMLEditor).DeleteSelectionAsSubAction(eNone, eStrip);
570 if (NS_FAILED(rv)) {
571 NS_WARNING(
572 "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
573 return rv;
576 return NS_OK;
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
589 // indicate results.
590 if (!HTMLEditUtils::IsAnyTableElement(arrayOfTopMostChildContents[0])) {
591 cellSelectionMode = false;
595 if (!cellSelectionMode) {
596 rv = MOZ_KnownLive(mHTMLEditor).DeleteSelectionAndPrepareToCreateNode();
597 if (NS_FAILED(rv)) {
598 NS_WARNING("HTMLEditor::DeleteSelectionAndPrepareToCreateNode() failed");
599 return rv;
602 if (aClearStyle) {
603 // pasting does not inherit local inline styles
604 EditResult result =
605 MOZ_KnownLive(mHTMLEditor)
606 .ClearStyleAt(
607 EditorDOMPoint(mHTMLEditor.SelectionRef().AnchorRef()),
608 nullptr, nullptr, SpecifiedStyle::Preserve);
609 if (result.Failed()) {
610 NS_WARNING("HTMLEditor::ClearStyleAt() failed");
611 return result.Rv();
614 } else {
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
619 // it.
621 AutoSelectionRestorer restoreSelectionLater(MOZ_KnownLive(mHTMLEditor));
622 rv = MOZ_KnownLive(mHTMLEditor).DeleteTableCellWithTransaction(1);
623 if (NS_FAILED(rv)) {
624 NS_WARNING("HTMLEditor::DeleteTableCellWithTransaction(1) failed");
625 return rv;
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()) {
637 return NS_OK;
640 EditActionResult result = mHTMLEditor.CanHandleHTMLEditSubAction();
641 if (result.Failed() || result.Canceled()) {
642 NS_WARNING_ASSERTION(result.Succeeded(),
643 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
644 return result.Rv();
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()) {
658 nsresult rv =
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(
672 NS_SUCCEEDED(rv),
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);
699 nsresult rv =
700 MOZ_KnownLive(mHTMLEditor)
701 .DeleteNodeWithTransaction(MOZ_KnownLive(*invisibleBRElement));
702 if (NS_FAILED(rv)) {
703 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed.");
704 return rv;
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()) {
723 NS_WARNING(
724 "HTMLEditor::SplitNodeDeepWithTransaction() didn't return split "
725 "point");
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()) {
747 return NS_OK;
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) {
763 return NS_OK;
765 RefPtr<Element> linkElement = GetLinkElement(pointToPutCaret.GetContainer());
767 if (!linkElement) {
768 return NS_OK;
771 rv = MoveCaretOutsideOfLink(*linkElement, pointToPutCaret);
772 if (NS_FAILED(rv)) {
773 NS_WARNING(
774 "HTMLEditor::HTMLWithContextInserter::MoveCaretOutsideOfLink "
775 "failed.");
776 return rv;
779 return NS_OK;
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)
795 : nullptr;
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)) {
815 continue;
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
821 // instead.
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()) {
836 NS_WARNING(
837 "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction("
838 "SplitAtEdges::eDoNotCreateEmptyContainer) "
839 "failed");
840 break;
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
844 // position.
845 inserted = true;
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()) {
891 NS_WARNING(
892 "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction("
893 "SplitAtEdges::eDoNotCreateEmptyContainer) failed");
894 break;
896 // If moving node is moved to different place, we should ignore
897 // this result and keep trying to insert next content node to
898 // same position.
899 inserted = true;
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");
914 } else {
915 NS_WARNING(
916 "Failed to delete the first child of a list element because the "
917 "list element non-editable");
918 break;
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()) {
935 NS_WARNING(
936 "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction("
937 "SplitAtEdges::eDoNotCreateEmptyContainer) failed");
938 break;
940 // If moving node is moved to different place, we should ignore
941 // this result and keep trying to insert next content node there.
942 inserted = true;
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.
953 if (!inserted) {
954 // MOZ_KnownLive because 'aArrayOfTopMostChildContents' is guaranteed to
955 // keep it alive.
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;
972 } else {
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()) ||
980 NS_WARN_IF(
981 childContent->GetParent()->IsHTMLElement(nsGkAtoms::body))) {
982 break;
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()) {
995 continue;
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;
1003 break;
1007 if (lastInsertedPoint.IsSet()) {
1008 if (lastInsertedPoint.GetContainer() !=
1009 lastInsertedPoint.GetChild()->GetParentNode()) {
1010 NS_WARNING(
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()) {
1042 nsresult rv =
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(
1048 NS_SUCCEEDED(rv),
1049 "HTMLEditor::CollapseSelectionTo() failed, but ignored");
1052 return NS_OK;
1055 // static
1056 Element* HTMLEditor::GetLinkElement(nsINode* aNode) {
1057 if (NS_WARN_IF(!aNode)) {
1058 return nullptr;
1060 nsINode* node = aNode;
1061 while (node) {
1062 if (HTMLEditUtils::IsLink(node)) {
1063 return node->AsElement();
1065 node = node->GetParentNode();
1067 return nullptr;
1070 // static
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.
1078 if (parent) {
1079 if (aNodesToRemove == NodesToRemove::eAll ||
1080 HTMLEditUtils::IsAnyListElement(parent)) {
1081 ErrorResult error;
1082 parent->RemoveChild(aNode, error);
1083 NS_WARNING_ASSERTION(!error.Failed(), "nsINode::RemoveChild() failed");
1084 return error.StealNSResult();
1086 return NS_OK;
1090 if (!aNode.IsHTMLElement(nsGkAtoms::pre)) {
1091 nsCOMPtr<nsIContent> child = aNode.GetLastChild();
1092 while (child) {
1093 nsCOMPtr<nsIContent> previous = child->GetPreviousSibling();
1094 nsresult rv = FragmentFromPasteCreator::
1095 RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
1096 *child, aNodesToRemove);
1097 if (NS_FAILED(rv)) {
1098 NS_WARNING(
1099 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
1100 "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces"
1101 "() "
1102 "failed");
1103 return rv;
1105 child = std::move(previous);
1108 return NS_OK;
1111 class MOZ_STACK_CLASS HTMLEditor::HTMLTransferablePreparer {
1112 public:
1113 HTMLTransferablePreparer(const HTMLEditor& aHTMLEditor,
1114 nsITransferable** aTransferable);
1116 nsresult Run();
1118 private:
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
1140 nsresult rv;
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");
1145 return rv;
1148 if (!transferable) {
1149 NS_WARNING("do_CreateInstance() returned nullptr, but ignored");
1150 return NS_OK;
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);
1165 return NS_OK;
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");
1206 break;
1207 case 1: // prefer PNG over JPEG over GIF encoding (default)
1208 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");
1225 break;
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");
1243 break;
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) {
1261 return false;
1263 numFront += strlen(aLeadingString);
1265 int32_t numBack = aCStr.FindCharInSet(CRLF, numFront);
1266 if (numBack == -1) {
1267 return false;
1270 nsAutoCString numStr(Substring(aCStr, numFront, numBack - numFront));
1271 nsresult errorCode;
1272 foundNumber = numStr.ToInteger(&errorCode);
1273 return true;
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) ||
1300 startHTML < -1) {
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
1316 // everything.
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) {
1322 return NS_OK;
1325 if (endHTML == -1) {
1326 const char endFragmentMarker[] = "<!--EndFragment-->";
1327 endHTML = aCfhtml.Find(endFragmentMarker);
1328 if (endHTML == -1) {
1329 return NS_OK;
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.
1348 break;
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.
1355 NS_ERROR(
1356 "StartFragment byte count in the clipboard looks bad, see bug "
1357 "#228879");
1358 startFragment = curPos - 1;
1360 break;
1362 curPos--;
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,
1386 &newLengthInChars);
1387 if (!*aStuffToPaste) {
1388 NS_WARNING("nsLinebreakConverter::ConvertUnicharLineBreaks() failed");
1389 return NS_ERROR_FAILURE;
1392 // translate platform linebreaks for context
1393 oldLengthInChars =
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,
1399 &newLengthInChars);
1400 // it's ok for context to be empty. frag might be whole doc and contain all
1401 // its context.
1403 // we're done!
1404 return NS_OK;
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");
1415 return rv;
1417 aOutput.AppendLiteral("\" alt=\"\" >");
1418 return NS_OK;
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,
1439 bool aIsSafe,
1440 const EditorDOMPoint& aPointToInsert,
1441 bool aDoDeleteSelection)
1442 : mBlob(aBlob),
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()),
1449 mIsSafe(aIsSafe),
1450 mDoDeleteSelection(aDoDeleteSelection),
1451 mNeedsToDispatchBeforeInputEvent(
1452 !mHTMLEditor->HasTriedToDispatchBeforeInputEvent()) {
1453 MOZ_ASSERT(mBlob);
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);
1477 } else {
1478 editActionData.MarkAsBeforeInputHasBeenDispatched();
1481 nsString blobType;
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,
1504 false);
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);
1517 return NS_OK;
1520 class SlurpBlobEventListener final : public nsIDOMEventListener {
1521 public:
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;
1530 private:
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) {
1549 return NS_OK;
1552 RefPtr<FileReader> reader = do_QueryObject(target);
1553 if (!reader) {
1554 return NS_OK;
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");
1570 return NS_OK;
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");
1580 return NS_OK;
1583 return NS_OK;
1586 // static
1587 nsresult HTMLEditor::SlurpBlob(Blob* aBlob, nsPIDOMWindowOuter* aWindow,
1588 BlobReader* aBlobReader) {
1589 MOZ_ASSERT(aBlob);
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");
1604 return rv;
1607 rv = reader->AddEventListener(u"error"_ns, eventListener, false);
1608 if (NS_FAILED(rv)) {
1609 NS_WARNING("FileReader::AddEventListener(error) failed");
1610 return rv;
1613 ErrorResult error;
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,
1621 bool aIsSafe,
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);
1634 if (!domBlob) {
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");
1642 return rv;
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);
1652 if (fileObj) {
1653 // Accept any image type fed to us
1654 if (nsContentUtils::IsFileImage(fileObj, type)) {
1655 insertAsImage = true;
1656 } else {
1657 // Reset type.
1658 type.AssignLiteral(kFileMime);
1663 if (type.EqualsLiteral(kJPEGImageMime) || type.EqualsLiteral(kJPGImageMime) ||
1664 type.EqualsLiteral(kPNGImageMime) || type.EqualsLiteral(kGIFImageMime) ||
1665 insertAsImage) {
1666 nsCString imageData;
1667 if (insertAsImage) {
1668 nsresult rv = nsContentUtils::SlurpFileToString(fileObj, imageData);
1669 if (NS_FAILED(rv)) {
1670 NS_WARNING("nsContentUtils::SlurpFileToString() failed");
1671 return rv;
1673 } else {
1674 nsCOMPtr<nsIInputStream> imageStream;
1675 if (RefPtr<Blob> blob = do_QueryObject(aObject)) {
1676 RefPtr<File> file = blob->ToFile();
1677 if (!file) {
1678 NS_WARNING("No mozilla::dom::File object");
1679 return NS_ERROR_FAILURE;
1681 ErrorResult error;
1682 file->CreateInputStream(getter_AddRefs(imageStream), error);
1683 if (error.Failed()) {
1684 NS_WARNING("File::CreateInputStream() failed");
1685 return error.StealNSResult();
1687 } else {
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");
1697 return rv;
1700 rv = imageStream->Close();
1701 if (NS_FAILED(rv)) {
1702 NS_WARNING("nsIInputStream::Close() failed");
1703 return rv;
1707 nsAutoString stuffToPaste;
1708 nsresult rv = ImgFromData(type, imageData, stuffToPaste);
1709 if (NS_FAILED(rv)) {
1710 NS_WARNING("ImgFromData() failed");
1711 return rv;
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(
1720 NS_SUCCEEDED(rv),
1721 "HTMLEditor::DoInsertHTMLWithContext() failed, but ignored");
1724 return NS_OK;
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();
1735 return false;
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();
1746 return false;
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(
1761 NS_SUCCEEDED(rv),
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");
1778 return rv;
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");
1799 return rv;
1801 } else {
1802 rv = DoInsertHTMLWithContext(cffragment, cfcontext, cfselection,
1803 flavor, EditorDOMPoint(),
1804 aDoDeleteSelection, isSafe);
1805 if (NS_FAILED(rv)) {
1806 NS_WARNING("HTMLEditor::DoInsertHTMLWithContext() failed");
1807 return rv;
1810 } else {
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
1815 // correctly.
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)) {
1826 nsAutoCString text;
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");
1841 return rv;
1843 } else {
1844 nsresult rv =
1845 InsertTextAsSubAction(stuffToPaste, SelectionHandling::Delete);
1846 if (NS_FAILED(rv)) {
1847 NS_WARNING("EditorBase::InsertTextAsSubAction() failed");
1848 return rv;
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");
1860 return NS_OK;
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));
1869 if (!variant) {
1870 MOZ_ASSERT(aOutputString.IsEmpty());
1871 return;
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,
1881 uint32_t aIndex,
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());
1893 ErrorResult error;
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++) {
1909 nsAutoString type;
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));
1920 if (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");
1933 return rv;
1935 } else if (type.EqualsLiteral(kNativeHTMLMime)) {
1936 // Windows only clipboard parsing.
1937 nsAutoString text;
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(
1961 NS_SUCCEEDED(rv),
1962 "HTMLEditor::DoInsertHTMLWithContext() failed");
1963 return rv;
1965 nsresult rv =
1966 DoInsertHTMLWithContext(cffragment, cfcontext, cfselection, type,
1967 aDroppedAt, aDoDeleteSelection, isSafe);
1968 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1969 "HTMLEditor::DoInsertHTMLWithContext() failed");
1970 return rv;
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)) {
1982 nsresult rv =
1983 DoInsertHTMLWithContext(text, contextString, infoString, type,
1984 aDroppedAt, aDoDeleteSelection, isSafe);
1985 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1986 "HTMLEditor::DoInsertHTMLWithContext() failed");
1987 return rv;
1992 if (type.EqualsLiteral(kTextMime) || type.EqualsLiteral(kMozTextInternal)) {
1993 nsAutoString text;
1994 GetStringFromDataTransfer(aDataTransfer, type, aIndex, text);
1995 nsresult rv = InsertTextAt(text, aDroppedAt, aDoDeleteSelection);
1996 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1997 "EditorBase::InsertTextAt() failed");
1998 return rv;
2002 return NS_OK;
2005 // static
2006 bool HTMLEditor::HavePrivateHTMLFlavor(nsIClipboard* aClipboard) {
2007 if (NS_WARN_IF(!aClipboard)) {
2008 return false;
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,
2027 aPrincipal);
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);
2036 } else {
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");
2063 return rv;
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");
2071 return rv;
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");
2081 return rv;
2084 // XXX Why don't you check this first?
2085 if (!IsModifiable()) {
2086 return NS_OK;
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) {
2099 NS_WARNING(
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) {
2129 NS_WARNING(
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");
2161 return rv;
2164 nsresult HTMLEditor::PasteTransferableAsAction(nsITransferable* aTransferable,
2165 nsIPrincipal* aPrincipal) {
2166 AutoEditActionDataSetter editActionData(*this, EditAction::ePaste,
2167 aPrincipal);
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(),
2197 true);
2198 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2199 "HTMLEditor::InsertFromDataTransfer() failed");
2200 } else {
2201 nsAutoString contextStr, infoStr;
2202 rv =
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,
2213 aPrincipal);
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");
2246 return rv;
2249 if (!GetDocument()) {
2250 NS_WARNING("Editor didn't have document, but ignored");
2251 return NS_OK;
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) {
2262 NS_WARNING(
2263 "EditorUtils::CreateTransferableForPlainText() returned nullptr, but "
2264 "ignored");
2265 return NS_OK;
2268 if (!IsModifiable()) {
2269 return NS_OK;
2272 // Get the Data from the clipboard
2273 rv = clipboard->GetData(transferable, aSelectionType);
2274 if (NS_FAILED(rv)) {
2275 NS_WARNING("nsIClipboard::GetData() failed");
2276 return rv;
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()) {
2295 return true;
2298 // can't paste if readonly
2299 if (!IsModifiable()) {
2300 return false;
2303 nsresult rv;
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");
2308 return false;
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));
2315 bool haveFlavors;
2316 nsresult rv = clipboard->HasDataMatchingFlavors(flavors, aClipboardType,
2317 &haveFlavors);
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));
2325 bool haveFlavors;
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()) {
2335 return false;
2338 // If |aTransferable| is null, assume that a paste will succeed.
2339 if (!aTransferable) {
2340 return true;
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;
2347 size_t length;
2348 if (IsInPlaintextMode()) {
2349 flavors = textEditorFlavors;
2350 length = ArrayLength(textEditorFlavors);
2351 } else {
2352 flavors = textHtmlEditorFlavors;
2353 length = ArrayLength(textHtmlEditorFlavors);
2356 for (size_t i = 0; i < length; i++, flavors++) {
2357 nsCOMPtr<nsISupports> data;
2358 nsresult rv =
2359 aTransferable->GetTransferData(*flavors, getter_AddRefs(data));
2360 if (NS_SUCCEEDED(rv) && data) {
2361 return true;
2365 return false;
2368 nsresult HTMLEditor::PasteAsQuotationAsAction(int32_t aClipboardType,
2369 bool aDispatchPasteEvent,
2370 nsIPrincipal* aPrincipal) {
2371 MOZ_ASSERT(aClipboardType == nsIClipboard::kGlobalClipboard ||
2372 aClipboardType == nsIClipboard::kSelectionClipboard);
2374 if (IsReadonly()) {
2375 return NS_OK;
2378 AutoEditActionDataSetter editActionData(*this, EditAction::ePasteAsQuotation,
2379 aPrincipal);
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);
2390 } else {
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(
2456 NS_SUCCEEDED(rv),
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) {
2467 NS_WARNING(
2468 "HTMLEditor::DeleteSelectionAndCreateElement(nsGkAtoms::blockquote) "
2469 "failed");
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");
2485 return rv;
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
2495 nsresult rv;
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");
2500 return rv;
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");
2508 return rv;
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");
2540 return rv;
2543 if (!flavor.EqualsLiteral(kUnicodeMime)) {
2544 return NS_OK;
2547 nsAutoString stuffToPaste;
2548 if (!GetString(genericDataObj, stuffToPaste)) {
2549 return NS_OK;
2552 AutoPlaceholderBatch treatAsOneTransaction(*this,
2553 ScrollSelectionIntoView::Yes);
2554 rv = InsertAsPlaintextQuotation(stuffToPaste, true, 0);
2555 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2556 "HTMLEditor::InsertAsPlaintextQuotation() failed");
2557 return rv;
2560 nsresult HTMLEditor::InsertWithQuotationsAsSubAction(
2561 const nsAString& aQuotedText) {
2562 MOZ_ASSERT(IsEditActionDataAvailable());
2564 if (IsReadonly()) {
2565 return NS_OK;
2568 EditActionResult result = CanHandleHTMLEditSubAction();
2569 if (result.Failed() || result.Canceled()) {
2570 NS_WARNING_ASSERTION(result.Succeeded(),
2571 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
2572 return result.Rv();
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(
2620 NS_SUCCEEDED(rv),
2621 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
2625 rv = InsertTextAsSubAction(quotedStuff, SelectionHandling::Delete);
2626 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2627 "EditorBase::InsertTextAsSubAction() failed");
2628 return rv;
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()) {
2643 return NS_OK;
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:
2677 #ifdef DEBUG
2678 nsAString::const_iterator dbgStart(hunkStart);
2679 if (FindCharInReadable(HTMLEditUtils::kCarriageReturn, dbgStart, strEnd)) {
2680 NS_ASSERTION(
2681 false,
2682 "Return characters in DOM! InsertTextWithQuotations may be wrong");
2684 #endif /* DEBUG */
2686 // Loop over lines:
2687 nsresult rv = NS_OK;
2688 nsAString::const_iterator lineStart(hunkStart);
2689 // We will break from inside when we run out of newlines.
2690 for (;;) {
2691 // Search for the end of this line (dom newlines, see above):
2692 bool found = FindCharInReadable(HTMLEditUtils::kNewLine, lineStart, strEnd);
2693 bool quoted = false;
2694 if (found) {
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) {
2699 ++lineStart;
2701 quoted = (*lineStart == cite);
2702 if (quoted == curHunkIsQuoted) {
2703 continue;
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.
2722 lineStart++;
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) {
2731 rv =
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");
2739 } else {
2740 rv = InsertTextAsSubAction(curHunk, SelectionHandling::Delete);
2741 NS_WARNING_ASSERTION(
2742 NS_SUCCEEDED(rv),
2743 "EditorBase::InsertTextAsSubAction() failed, but might be ignored");
2745 if (!found) {
2746 break;
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.
2754 return rv;
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,
2791 aNodeInserted);
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,
2802 bool aAddCites,
2803 nsINode** aNodeInserted) {
2804 MOZ_ASSERT(IsEditActionDataAvailable());
2806 if (aNodeInserted) {
2807 *aNodeInserted = nullptr;
2810 if (IsReadonly()) {
2811 return NS_OK;
2814 EditActionResult result = CanHandleHTMLEditSubAction();
2815 if (result.Failed() || result.Canceled()) {
2816 NS_WARNING_ASSERTION(result.Succeeded(),
2817 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
2818 return result.Rv();
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(
2855 NS_SUCCEEDED(rv),
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(
2869 newSpanElement,
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,
2887 nsLiteralString(
2888 u"white-space: pre-wrap; display: block; width: 98vw;"),
2889 true);
2890 NS_WARNING_ASSERTION(
2891 NS_SUCCEEDED(rvIgnored),
2892 "Element::SetAttr(nsGkAtoms::style) failed, but ignored");
2893 } else {
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(
2908 NS_SUCCEEDED(rv),
2909 "HTMLEditor::CollapseSelectionToStartOf() failed, but ignored");
2912 if (aAddCites) {
2913 rv = InsertWithQuotationsAsSubAction(aQuotedText);
2914 if (NS_FAILED(rv)) {
2915 NS_WARNING("HTMLEditor::InsertWithQuotationsAsSubAction() failed");
2916 return rv;
2918 } else {
2919 rv = InsertTextAsSubAction(aQuotedText, SelectionHandling::Delete);
2920 if (NS_FAILED(rv)) {
2921 NS_WARNING("EditorBase::InsertTextAsSubAction() failed");
2922 return rv;
2926 // XXX Why don't we check this before inserting the quoted text?
2927 if (!newSpanElement) {
2928 return NS_OK;
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(
2943 NS_SUCCEEDED(rv),
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);
2954 return NS_OK;
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) {
2969 wrapWidth = 72;
2972 nsAutoString current;
2973 const bool isCollapsed = SelectionRef().IsCollapsed();
2974 uint32_t flags = nsIDocumentEncoder::OutputFormatted |
2975 nsIDocumentEncoder::OutputLFLineBreak;
2976 if (!isCollapsed) {
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()) {
2986 return NS_OK;
2989 nsString wrapped;
2990 uint32_t firstLineOffset = 0; // XXX need to reset this if there is a
2991 // selection
2992 InternetCiter::Rewrap(current, wrapWidth, firstLineOffset, aRespectNewlines,
2993 wrapped);
2995 if (isCollapsed) {
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,
3015 bool aInsertHTML,
3016 nsINode** aNodeInserted) {
3017 // Don't let anyone insert HTML when we're in plaintext mode.
3018 if (IsInPlaintextMode()) {
3019 NS_ASSERTION(
3020 !aInsertHTML,
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,
3054 aNodeInserted);
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());
3066 if (IsReadonly()) {
3067 return NS_OK;
3070 EditActionResult result = CanHandleHTMLEditSubAction();
3071 if (result.Failed() || result.Canceled()) {
3072 NS_WARNING_ASSERTION(result.Succeeded(),
3073 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
3074 return result.Rv();
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(
3111 NS_SUCCEEDED(rv),
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) {
3122 NS_WARNING(
3123 "HTMLEditor::DeleteSelectionAndCreateElement(nsGkAtoms::blockquote) "
3124 "failed");
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");
3157 if (aInsertHTML) {
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");
3164 return rv;
3166 } else {
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");
3174 return rv;
3178 // XXX Why don't we check this before inserting aQuotedText?
3179 if (!newBlockquoteElement) {
3180 return NS_OK;
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(
3195 NS_SUCCEEDED(rv),
3196 "HTMLEditor::CollapseSelectionTo() failed, but ignored");
3199 if (aNodeInserted) {
3200 newBlockquoteElement.forget(aNodeInserted);
3203 return NS_OK;
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)) {
3214 body = child;
3215 } else if (child->IsHTMLElement(nsGkAtoms::head)) {
3216 head = child;
3219 if (head) {
3220 ErrorResult ignored;
3221 aNode.RemoveChild(*head, ignored);
3223 if (body) {
3224 nsCOMPtr<nsIContent> child = body->GetFirstChild();
3225 while (child) {
3226 ErrorResult ignored;
3227 aNode.InsertBefore(*child, body, ignored);
3228 child = body->GetFirstChild();
3231 ErrorResult ignored;
3232 aNode.RemoveChild(*body, ignored);
3236 // static
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)) {
3241 nsAutoString data;
3242 comment->GetData(data);
3244 return data.EqualsLiteral(kInsertCookie);
3247 return false;
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();
3263 if (!firstChild) {
3264 // If the current result is nullptr, then aStart is a leaf, and is the
3265 // fallback result.
3266 if (!aResult) {
3267 aResult = &aStart;
3269 return false;
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
3276 // search.
3277 aResult = &aStart;
3279 child->Remove();
3281 return true;
3284 if (FindTargetNodeOfContextForPastedHTMLAndRemoveInsertionCookie(*child,
3285 aResult)) {
3286 return true;
3290 return false;
3293 class MOZ_STACK_CLASS HTMLEditor::HTMLWithContextInserter::FragmentParser
3294 final {
3295 public:
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);
3305 private:
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();
3359 return rv;
3362 // static
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))
3374 ? nsGkAtoms::body
3375 : contextLocalNameAtom;
3378 // static
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)) {
3398 NS_WARNING(
3399 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
3400 "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces()"
3401 " failed");
3402 return rv;
3405 return rv;
3408 // static
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)) {
3420 NS_WARNING(
3421 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
3422 "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces() "
3423 "failed");
3424 return rv;
3427 return rv;
3430 // static
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)) {
3440 NS_WARNING(
3441 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
3442 "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces() "
3443 "failed");
3444 return rv;
3447 FragmentFromPasteCreator::RemoveHeadChildAndStealBodyChildsChildren(
3448 aDocumentFragmentForContext);
3450 return rv;
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)) {
3467 NS_WARNING(
3468 "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseContext() "
3469 "failed");
3470 return rv;
3472 if (!documentFragmentForContext) {
3473 NS_WARNING(
3474 "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseContext() "
3475 "returned nullptr");
3476 return NS_ERROR_FAILURE;
3479 rv = FragmentFromPasteCreator::PreProcessContextDocumentFragmentForMerging(
3480 *documentFragmentForContext);
3481 if (NS_FAILED(rv)) {
3482 NS_WARNING(
3483 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
3484 "PreProcessContextDocumentFragmentForMerging() failed.");
3485 return rv;
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)) {
3507 NS_WARNING(
3508 "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParsePastedHTML()"
3509 " failed");
3510 return rv;
3512 if (!documentFragmentForPastedHTML) {
3513 NS_WARNING(
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)) {
3525 NS_WARNING(
3526 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
3527 "MergeAndPostProcessFragmentsForPastedHTMLAndContext() failed.");
3528 return rv;
3530 aDocumentFragmentToInsert = std::move(documentFragmentForContext);
3531 } else {
3532 const nsresult rv = PostProcessFragmentForPastedHTMLWithoutContext(
3533 *documentFragmentForPastedHTML);
3534 if (NS_FAILED(rv)) {
3535 NS_WARNING(
3536 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
3537 "PostProcessFragmentForPastedHTMLWithoutContext() failed.");
3538 return rv;
3541 aDocumentFragmentToInsert = std::move(documentFragmentForPastedHTML);
3544 return rv;
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)) {
3562 NS_WARNING(
3563 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
3564 "CreateDocumentFragmentAndGetParentOfPastedHTMLInContext() failed.");
3565 return rv;
3568 // If there was no context, then treat all of the data we did get as the
3569 // pasted data.
3570 if (parentNodeOfPastedHTMLInContext) {
3571 *aOutEndNode = *aOutStartNode = parentNodeOfPastedHTMLInContext;
3572 } else {
3573 *aOutEndNode = *aOutStartNode = documentFragmentToInsert;
3576 *aOutFragNode = std::move(documentFragmentToInsert);
3578 if (!aInfoStr.IsEmpty()) {
3579 const nsresult rv =
3580 FragmentFromPasteCreator::MoveStartAndEndAccordingToHTMLInfo(
3581 aInfoStr, aOutStartNode, aOutEndNode);
3582 if (NS_FAILED(rv)) {
3583 NS_WARNING(
3584 "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
3585 "MoveStartAndEndAccordingToHTMLInfo() failed");
3586 return rv;
3590 return NS_OK;
3593 // static
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.
3604 nsresult rvIgnored;
3605 int32_t num = numstr1.ToInteger(&rvIgnored);
3606 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
3607 "nsAString::ToInteger() failed, but ignored");
3608 while (num--) {
3609 nsINode* tmp = (*aOutStartNode)->GetFirstChild();
3610 if (!tmp) {
3611 NS_WARNING("aOutStartNode did not have children");
3612 return NS_ERROR_FAILURE;
3614 *aOutStartNode = tmp;
3617 num = numstr2.ToInteger(&rvIgnored);
3618 while (num--) {
3619 nsINode* tmp = (*aOutEndNode)->GetLastChild();
3620 if (!tmp) {
3621 NS_WARNING("aOutEndNode did not have children");
3622 return NS_ERROR_FAILURE;
3624 *aOutEndNode = tmp;
3627 return NS_OK;
3630 // static
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);
3639 if (!doc) {
3640 return NS_ERROR_FAILURE;
3642 RefPtr<DocumentFragment> fragment =
3643 new (doc->NodeInfoManager()) DocumentFragment(doc->NodeInfoManager());
3644 nsresult rv = nsContentUtils::ParseFragmentHTML(
3645 aFragStr, fragment,
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);
3657 return rv;
3660 // static
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());
3672 if (!range) {
3673 NS_WARNING("nsRange::Create() failed");
3674 return;
3676 DOMSubtreeIterator iter;
3677 if (NS_FAILED(iter.Init(*range))) {
3678 NS_WARNING("DOMSubtreeIterator::Init() failed, but ignored");
3679 return;
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);
3697 // static
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);
3711 // static
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) {
3723 continue;
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
3729 // case?
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() ==
3736 tableElement) {
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;
3742 continue;
3745 if (!HTMLEditUtils::IsListItem(content)) {
3746 continue;
3748 Element* listElement =
3749 HTMLEditUtils::GetClosestAncestorAnyListElement(*content);
3750 if (!listElement) {
3751 continue;
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
3757 // this case?
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() ==
3764 listElement) {
3765 return listElement;
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;
3777 Element*
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");
3798 continue;
3800 Element* tableElement = nullptr;
3801 for (Element* maybeTableElement = element->GetParentElement();
3802 maybeTableElement;
3803 maybeTableElement = maybeTableElement->GetParentElement()) {
3804 if (maybeTableElement->IsHTMLElement(nsGkAtoms::table)) {
3805 tableElement = maybeTableElement;
3806 break;
3809 if (tableElement == &aTableElement) {
3810 return element;
3812 // XXX If we find another `<table>` element, why don't we keep searching
3813 // from its parent?
3815 return nullptr;
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");
3836 continue;
3838 Element* listElement =
3839 HTMLEditUtils::GetClosestAncestorAnyListElement(*element);
3840 if (listElement == &aListElement) {
3841 return true;
3843 // XXX If we find another list element, why don't we keep searching
3844 // from its parent?
3846 return false;
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()) {
3864 return;
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) {
3881 return;
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)) {
3898 return;
3900 replaceElement = listOrTableElement;
3901 } else {
3902 MOZ_ASSERT(listOrTableElement->IsHTMLElement(nsGkAtoms::table));
3903 replaceElement = FindReplaceableTableElement(*listOrTableElement,
3904 firstOrLastChildContent);
3905 if (!replaceElement) {
3906 return;
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);
3921 continue;
3923 if (!EditorUtils::IsDescendantOf(content, *replaceElement)) {
3924 i++;
3925 continue;
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);
3940 } else {
3941 aArrayOfTopMostChildContents.InsertElementAt(0, *replaceElement);
3945 } // namespace mozilla