/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/TextEditor.h" #include "mozilla/ArrayUtils.h" #include "mozilla/HTMLEditor.h" #include "mozilla/MouseEvents.h" #include "mozilla/SelectionState.h" #include "mozilla/TextControlElement.h" #include "mozilla/dom/DataTransfer.h" #include "mozilla/dom/DocumentInlines.h" #include "mozilla/dom/DragEvent.h" #include "mozilla/dom/Selection.h" #include "mozilla/dom/StaticRange.h" #include "nsAString.h" #include "nsCOMPtr.h" #include "nsComponentManagerUtils.h" #include "nsContentUtils.h" #include "nsDebug.h" #include "nsError.h" #include "nsFocusManager.h" #include "nsIClipboard.h" #include "nsIContent.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/Document.h" #include "nsIDragService.h" #include "nsIDragSession.h" #include "nsIDocShell.h" #include "nsIDocShellTreeItem.h" #include "nsIPrincipal.h" #include "nsIFormControl.h" #include "nsISupportsPrimitives.h" #include "nsITransferable.h" #include "nsIVariant.h" #include "nsLiteralString.h" #include "nsRange.h" #include "nsServiceManagerUtils.h" #include "nsString.h" #include "nsXPCOM.h" #include "nscore.h" class nsILoadContext; class nsISupports; namespace mozilla { using namespace dom; nsresult TextEditor::PrepareTransferable(nsITransferable** aOutTransferable) { MOZ_ASSERT(aOutTransferable); MOZ_ASSERT(!*aOutTransferable); // Create generic Transferable for getting the data nsresult rv; nsCOMPtr transferable = do_CreateInstance("@mozilla.org/widget/transferable;1", &rv); if (NS_FAILED(rv)) { NS_WARNING("do_CreateInstance() failed to create nsITransferable instance"); return rv; } if (!transferable) { NS_WARNING("do_CreateInstance() returned nullptr, but ignored"); return NS_OK; } RefPtr document = GetDocument(); nsILoadContext* loadContext = document ? document->GetLoadContext() : nullptr; DebugOnly rvIgnored = transferable->Init(loadContext); NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "nsITransferable::Init() failed, but ignored"); rvIgnored = transferable->AddDataFlavor(kUnicodeMime); NS_WARNING_ASSERTION( NS_SUCCEEDED(rvIgnored), "nsITransferable::AddDataFlavor(kUnicodeMime) failed, but ignored"); rvIgnored = transferable->AddDataFlavor(kMozTextInternal); NS_WARNING_ASSERTION( NS_SUCCEEDED(rvIgnored), "nsITransferable::AddDataFlavor(kMozTextInternal) failed, but ignored"); transferable.forget(aOutTransferable); return NS_OK; } nsresult TextEditor::PrepareToInsertContent( const EditorDOMPoint& aPointToInsert, bool aDoDeleteSelection) { // TODO: Move this method to `EditorBase`. MOZ_ASSERT(IsEditActionDataAvailable()); MOZ_ASSERT(aPointToInsert.IsSet()); EditorDOMPoint pointToInsert(aPointToInsert); if (aDoDeleteSelection) { AutoTrackDOMPoint tracker(RangeUpdaterRef(), &pointToInsert); nsresult rv = DeleteSelectionAsSubAction( nsIEditor::eNone, IsTextEditor() ? nsIEditor::eNoStrip : nsIEditor::eStrip); if (NS_FAILED(rv)) { NS_WARNING("EditorBase::DeleteSelectionAsSubAction(eNone) failed"); return rv; } } IgnoredErrorResult error; MOZ_KnownLive(SelectionRefPtr())->CollapseInLimiter(pointToInsert, error); if (NS_WARN_IF(Destroyed())) { return NS_ERROR_EDITOR_DESTROYED; } NS_WARNING_ASSERTION(!error.Failed(), "Selection::CollapseInLimiter() failed"); return error.StealNSResult(); } nsresult TextEditor::InsertTextAt(const nsAString& aStringToInsert, const EditorDOMPoint& aPointToInsert, bool aDoDeleteSelection) { MOZ_ASSERT(IsEditActionDataAvailable()); MOZ_ASSERT(aPointToInsert.IsSet()); nsresult rv = PrepareToInsertContent(aPointToInsert, aDoDeleteSelection); if (NS_FAILED(rv)) { NS_WARNING("TextEditor::PrepareToInsertContent() failed"); return rv; } rv = InsertTextAsSubAction(aStringToInsert); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::InsertTextAsSubAction() failed"); return rv; } nsresult TextEditor::InsertTextFromTransferable( nsITransferable* aTransferable) { MOZ_ASSERT(IsEditActionDataAvailable()); MOZ_ASSERT(!AsHTMLEditor()); nsAutoCString bestFlavor; nsCOMPtr genericDataObj; nsresult rv = aTransferable->GetAnyTransferData( bestFlavor, getter_AddRefs(genericDataObj)); NS_WARNING_ASSERTION( NS_SUCCEEDED(rv), "nsITransferable::GetAnyDataTransferData() failed, but ignored"); if (NS_SUCCEEDED(rv) && (bestFlavor.EqualsLiteral(kUnicodeMime) || bestFlavor.EqualsLiteral(kMozTextInternal))) { AutoTransactionsConserveSelection dontChangeMySelection(*this); nsAutoString stuffToPaste; if (nsCOMPtr text = do_QueryInterface(genericDataObj)) { text->GetData(stuffToPaste); } MOZ_ASSERT(GetEditAction() == EditAction::ePaste); // Use native line breaks for compatibility with Chrome. // XXX Although, somebody has already converted native line breaks to // XP line breaks. UpdateEditActionData(stuffToPaste); nsresult rv = MaybeDispatchBeforeInputEvent(); if (NS_FAILED(rv)) { NS_WARNING_ASSERTION( rv == NS_ERROR_EDITOR_ACTION_CANCELED, "EditorBase::MaybeDispatchBeforeInputEvent() failed"); return rv; } if (!stuffToPaste.IsEmpty()) { // Sanitize possible carriage returns in the string to be inserted nsContentUtils::PlatformToDOMLineBreaks(stuffToPaste); AutoPlaceholderBatch treatAsOneTransaction(*this, ScrollSelectionIntoView::Yes); nsresult rv = InsertTextAsSubAction(stuffToPaste); if (NS_FAILED(rv)) { NS_WARNING("EditorBase::InsertTextAsSubAction() failed"); return rv; } } } // Try to scroll the selection into view if the paste/drop succeeded rv = ScrollSelectionFocusIntoView(); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::ScrollSelectionFocusIntoView() failed"); return rv; } nsresult TextEditor::OnDrop(DragEvent* aDropEvent) { if (NS_WARN_IF(!aDropEvent)) { return NS_ERROR_INVALID_ARG; } DebugOnly rvIgnored = CommitComposition(); NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "EditorBase::CommitComposition() failed, but ignored"); AutoEditActionDataSetter editActionData(*this, EditAction::eDrop); // We need to initialize data or dataTransfer later. Therefore, we cannot // dispatch "beforeinput" event until then. if (NS_WARN_IF(!editActionData.CanHandle())) { return NS_ERROR_NOT_INITIALIZED; } RefPtr dataTransfer = aDropEvent->GetDataTransfer(); if (NS_WARN_IF(!dataTransfer)) { return NS_ERROR_FAILURE; } nsCOMPtr dragSession = nsContentUtils::GetDragSession(); if (NS_WARN_IF(!dragSession)) { return NS_ERROR_FAILURE; } nsCOMPtr sourceNode = dataTransfer->GetMozSourceNode(); RefPtr srcdoc; if (sourceNode) { srcdoc = sourceNode->OwnerDoc(); } if (nsContentUtils::CheckForSubFrameDrop( dragSession, aDropEvent->WidgetEventPtr()->AsDragEvent())) { // Don't allow drags from subframe documents with different origins than // the drop destination. if (srcdoc && !IsSafeToInsertData(srcdoc)) { return NS_OK; } } // Current doc is destination RefPtr document = GetDocument(); if (NS_WARN_IF(!document)) { return NS_ERROR_NOT_INITIALIZED; } const uint32_t numItems = dataTransfer->MozItemCount(); if (NS_WARN_IF(!numItems)) { return NS_ERROR_FAILURE; // Nothing to drop? } // We have to figure out whether to delete and relocate caret only once // Parent and offset are under the mouse cursor. int32_t dropOffset = -1; nsCOMPtr dropParentContent = aDropEvent->GetRangeParentContentAndOffset(&dropOffset); EditorDOMPoint droppedAt(dropParentContent, dropOffset); if (NS_WARN_IF(!droppedAt.IsSet()) || NS_WARN_IF(!droppedAt.GetContainerAsContent())) { return NS_ERROR_FAILURE; } // Check if dropping into a selected range. If so and the source comes from // same document, jump through some hoops to determine if mouse is over // selection (bail) and whether user wants to copy selection or delete it. if (sourceNode && sourceNode->IsEditable() && srcdoc == document) { bool isPointInSelection = EditorUtils::IsPointInSelection( *SelectionRefPtr(), *droppedAt.GetContainer(), droppedAt.Offset()); if (isPointInSelection) { // If source document and destination document is same and dropping // into one of selected ranges, we don't need to do nothing. // XXX If the source comes from outside of this editor, this check // means that we don't allow to drop the item in the selected // range. However, the selection is hidden until the or //