123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480 |
- /* -*- 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/EditorUtils.h"
- #include "mozilla/MouseEvents.h"
- #include "mozilla/SelectionState.h"
- #include "mozilla/dom/Selection.h"
- #include "nsAString.h"
- #include "nsCOMPtr.h"
- #include "nsComponentManagerUtils.h"
- #include "nsContentUtils.h"
- #include "nsDebug.h"
- #include "nsError.h"
- #include "nsIClipboard.h"
- #include "nsIContent.h"
- #include "nsIDOMDataTransfer.h"
- #include "nsIDOMDocument.h"
- #include "nsIDOMDragEvent.h"
- #include "nsIDOMEvent.h"
- #include "nsIDOMNode.h"
- #include "nsIDOMUIEvent.h"
- #include "nsIDocument.h"
- #include "nsIDragService.h"
- #include "nsIDragSession.h"
- #include "nsIEditor.h"
- #include "nsIEditorIMESupport.h"
- #include "nsIDocShell.h"
- #include "nsIDocShellTreeItem.h"
- #include "nsIPrincipal.h"
- #include "nsIFormControl.h"
- #include "nsIPlaintextEditor.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;
- NS_IMETHODIMP
- TextEditor::PrepareTransferable(nsITransferable** transferable)
- {
- // Create generic Transferable for getting the data
- nsresult rv = CallCreateInstance("@mozilla.org/widget/transferable;1", transferable);
- NS_ENSURE_SUCCESS(rv, rv);
- // Get the nsITransferable interface for getting the data from the clipboard
- if (transferable) {
- nsCOMPtr<nsIDocument> destdoc = GetDocument();
- nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr;
- (*transferable)->Init(loadContext);
- (*transferable)->AddDataFlavor(kUnicodeMime);
- (*transferable)->AddDataFlavor(kMozTextInternal);
- };
- return NS_OK;
- }
- nsresult
- TextEditor::InsertTextAt(const nsAString& aStringToInsert,
- nsIDOMNode* aDestinationNode,
- int32_t aDestOffset,
- bool aDoDeleteSelection)
- {
- if (aDestinationNode) {
- RefPtr<Selection> selection = GetSelection();
- NS_ENSURE_STATE(selection);
- nsCOMPtr<nsIDOMNode> targetNode = aDestinationNode;
- int32_t targetOffset = aDestOffset;
- if (aDoDeleteSelection) {
- // Use an auto tracker so that our drop point is correctly
- // positioned after the delete.
- AutoTrackDOMPoint tracker(mRangeUpdater, &targetNode, &targetOffset);
- nsresult rv = DeleteSelection(eNone, eStrip);
- NS_ENSURE_SUCCESS(rv, rv);
- }
- nsresult rv = selection->Collapse(targetNode, targetOffset);
- NS_ENSURE_SUCCESS(rv, rv);
- }
- return InsertText(aStringToInsert);
- }
- NS_IMETHODIMP
- TextEditor::InsertTextFromTransferable(nsITransferable* aTransferable,
- nsIDOMNode* aDestinationNode,
- int32_t aDestOffset,
- bool aDoDeleteSelection)
- {
- nsresult rv = NS_OK;
- nsAutoCString bestFlavor;
- nsCOMPtr<nsISupports> genericDataObj;
- uint32_t len = 0;
- if (NS_SUCCEEDED(
- aTransferable->GetAnyTransferData(bestFlavor,
- getter_AddRefs(genericDataObj),
- &len)) &&
- (bestFlavor.EqualsLiteral(kUnicodeMime) ||
- bestFlavor.EqualsLiteral(kMozTextInternal))) {
- AutoTransactionsConserveSelection dontSpazMySelection(this);
- nsCOMPtr<nsISupportsString> textDataObj ( do_QueryInterface(genericDataObj) );
- if (textDataObj && len > 0) {
- nsAutoString stuffToPaste;
- textDataObj->GetData(stuffToPaste);
- NS_ASSERTION(stuffToPaste.Length() <= (len/2), "Invalid length!");
- // Sanitize possible carriage returns in the string to be inserted
- nsContentUtils::PlatformToDOMLineBreaks(stuffToPaste);
- AutoEditBatch beginBatching(this);
- rv = InsertTextAt(stuffToPaste, aDestinationNode, aDestOffset, aDoDeleteSelection);
- }
- }
- // Try to scroll the selection into view if the paste/drop succeeded
- if (NS_SUCCEEDED(rv)) {
- ScrollSelectionIntoView(false);
- }
- return rv;
- }
- nsresult
- TextEditor::InsertFromDataTransfer(DataTransfer* aDataTransfer,
- int32_t aIndex,
- nsIDOMDocument* aSourceDoc,
- nsIDOMNode* aDestinationNode,
- int32_t aDestOffset,
- bool aDoDeleteSelection)
- {
- nsCOMPtr<nsIVariant> data;
- DataTransfer::Cast(aDataTransfer)->GetDataAtNoSecurityCheck(NS_LITERAL_STRING("text/plain"), aIndex,
- getter_AddRefs(data));
- if (data) {
- nsAutoString insertText;
- data->GetAsAString(insertText);
- nsContentUtils::PlatformToDOMLineBreaks(insertText);
- AutoEditBatch beginBatching(this);
- return InsertTextAt(insertText, aDestinationNode, aDestOffset, aDoDeleteSelection);
- }
- return NS_OK;
- }
- nsresult
- TextEditor::InsertFromDrop(nsIDOMEvent* aDropEvent)
- {
- ForceCompositionEnd();
- nsCOMPtr<nsIDOMDragEvent> dragEvent(do_QueryInterface(aDropEvent));
- NS_ENSURE_TRUE(dragEvent, NS_ERROR_FAILURE);
- nsCOMPtr<nsIDOMDataTransfer> domDataTransfer;
- dragEvent->GetDataTransfer(getter_AddRefs(domDataTransfer));
- nsCOMPtr<DataTransfer> dataTransfer = do_QueryInterface(domDataTransfer);
- NS_ENSURE_TRUE(dataTransfer, NS_ERROR_FAILURE);
- nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
- NS_ASSERTION(dragSession, "No drag session");
- nsCOMPtr<nsIDOMNode> sourceNode;
- dataTransfer->GetMozSourceNode(getter_AddRefs(sourceNode));
- nsCOMPtr<nsIDOMDocument> srcdomdoc;
- if (sourceNode) {
- sourceNode->GetOwnerDocument(getter_AddRefs(srcdomdoc));
- NS_ENSURE_TRUE(sourceNode, NS_ERROR_FAILURE);
- }
- if (nsContentUtils::CheckForSubFrameDrop(dragSession,
- aDropEvent->WidgetEventPtr()->AsDragEvent())) {
- // Don't allow drags from subframe documents with different origins than
- // the drop destination.
- if (srcdomdoc && !IsSafeToInsertData(srcdomdoc)) {
- return NS_OK;
- }
- }
- // Current doc is destination
- nsCOMPtr<nsIDOMDocument> destdomdoc = GetDOMDocument();
- NS_ENSURE_TRUE(destdomdoc, NS_ERROR_NOT_INITIALIZED);
- uint32_t numItems = 0;
- nsresult rv = dataTransfer->GetMozItemCount(&numItems);
- NS_ENSURE_SUCCESS(rv, rv);
- if (numItems < 1) {
- return NS_ERROR_FAILURE; // Nothing to drop?
- }
- // Combine any deletion and drop insertion into one transaction
- AutoEditBatch beginBatching(this);
- bool deleteSelection = false;
- // We have to figure out whether to delete and relocate caret only once
- // Parent and offset are under the mouse cursor
- nsCOMPtr<nsIDOMUIEvent> uiEvent = do_QueryInterface(aDropEvent);
- NS_ENSURE_TRUE(uiEvent, NS_ERROR_FAILURE);
- nsCOMPtr<nsIDOMNode> newSelectionParent;
- rv = uiEvent->GetRangeParent(getter_AddRefs(newSelectionParent));
- NS_ENSURE_SUCCESS(rv, rv);
- NS_ENSURE_TRUE(newSelectionParent, NS_ERROR_FAILURE);
- int32_t newSelectionOffset;
- rv = uiEvent->GetRangeOffset(&newSelectionOffset);
- NS_ENSURE_SUCCESS(rv, rv);
- RefPtr<Selection> selection = GetSelection();
- NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
- bool isCollapsed = selection->Collapsed();
- // Only the HTMLEditor::FindUserSelectAllNode returns a node.
- nsCOMPtr<nsIDOMNode> userSelectNode = FindUserSelectAllNode(newSelectionParent);
- if (userSelectNode) {
- // The drop is happening over a "-moz-user-select: all"
- // subtree so make sure the content we insert goes before
- // the root of the subtree.
- //
- // XXX: Note that inserting before the subtree matches the
- // current behavior when dropping on top of an image.
- // The decision for dropping before or after the
- // subtree should really be done based on coordinates.
- newSelectionParent = GetNodeLocation(userSelectNode, &newSelectionOffset);
- NS_ENSURE_TRUE(newSelectionParent, NS_ERROR_FAILURE);
- }
- // Check if mouse is in the selection
- // if so, jump through some hoops to determine if mouse is over selection (bail)
- // and whether user wants to copy selection or delete it
- if (!isCollapsed) {
- // We never have to delete if selection is already collapsed
- bool cursorIsInSelection = false;
- int32_t rangeCount;
- rv = selection->GetRangeCount(&rangeCount);
- NS_ENSURE_SUCCESS(rv, rv);
- for (int32_t j = 0; j < rangeCount; j++) {
- RefPtr<nsRange> range = selection->GetRangeAt(j);
- if (!range) {
- // don't bail yet, iterate through them all
- continue;
- }
- rv = range->IsPointInRange(newSelectionParent, newSelectionOffset, &cursorIsInSelection);
- if (cursorIsInSelection) {
- break;
- }
- }
- if (cursorIsInSelection) {
- // Dragging within same doc can't drop on itself -- leave!
- if (srcdomdoc == destdomdoc) {
- return NS_OK;
- }
- // Dragging from another window onto a selection
- // XXX Decision made to NOT do this,
- // note that 4.x does replace if dropped on
- //deleteSelection = true;
- } else {
- // We are NOT over the selection
- if (srcdomdoc == destdomdoc) {
- // Within the same doc: delete if user doesn't want to copy
- uint32_t dropEffect;
- dataTransfer->GetDropEffectInt(&dropEffect);
- deleteSelection = !(dropEffect & nsIDragService::DRAGDROP_ACTION_COPY);
- } else {
- // Different source doc: Don't delete
- deleteSelection = false;
- }
- }
- }
- if (IsPlaintextEditor()) {
- nsCOMPtr<nsIContent> content = do_QueryInterface(newSelectionParent);
- while (content) {
- nsCOMPtr<nsIFormControl> formControl(do_QueryInterface(content));
- if (formControl && !formControl->AllowDrop()) {
- // Don't allow dropping into a form control that doesn't allow being
- // dropped into.
- return NS_OK;
- }
- content = content->GetParent();
- }
- }
- for (uint32_t i = 0; i < numItems; ++i) {
- InsertFromDataTransfer(dataTransfer, i, srcdomdoc, newSelectionParent,
- newSelectionOffset, deleteSelection);
- }
- if (NS_SUCCEEDED(rv)) {
- ScrollSelectionIntoView(false);
- }
- return rv;
- }
- NS_IMETHODIMP
- TextEditor::Paste(int32_t aSelectionType)
- {
- if (!FireClipboardEvent(ePaste, aSelectionType)) {
- return NS_OK;
- }
- // Get Clipboard Service
- nsresult rv;
- nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
- if (NS_FAILED(rv)) {
- return rv;
- }
- // Get the nsITransferable interface for getting the data from the clipboard
- nsCOMPtr<nsITransferable> trans;
- rv = PrepareTransferable(getter_AddRefs(trans));
- if (NS_SUCCEEDED(rv) && trans) {
- // Get the Data from the clipboard
- if (NS_SUCCEEDED(clipboard->GetData(trans, aSelectionType)) &&
- IsModifiable()) {
- // handle transferable hooks
- nsCOMPtr<nsIDOMDocument> domdoc = GetDOMDocument();
- if (!EditorHookUtils::DoInsertionHook(domdoc, nullptr, trans)) {
- return NS_OK;
- }
- rv = InsertTextFromTransferable(trans, nullptr, 0, true);
- }
- }
- return rv;
- }
- NS_IMETHODIMP
- TextEditor::PasteTransferable(nsITransferable* aTransferable)
- {
- // Use an invalid value for the clipboard type as data comes from aTransferable
- // and we don't currently implement a way to put that in the data transfer yet.
- if (!FireClipboardEvent(ePaste, -1)) {
- return NS_OK;
- }
- if (!IsModifiable()) {
- return NS_OK;
- }
- // handle transferable hooks
- nsCOMPtr<nsIDOMDocument> domdoc = GetDOMDocument();
- if (!EditorHookUtils::DoInsertionHook(domdoc, nullptr, aTransferable)) {
- return NS_OK;
- }
- return InsertTextFromTransferable(aTransferable, nullptr, 0, true);
- }
- NS_IMETHODIMP
- TextEditor::CanPaste(int32_t aSelectionType,
- bool* aCanPaste)
- {
- NS_ENSURE_ARG_POINTER(aCanPaste);
- *aCanPaste = false;
- // Always enable the paste command when inside of a HTML or XHTML document.
- nsCOMPtr<nsIDocument> doc = GetDocument();
- if (doc && doc->IsHTMLOrXHTML()) {
- *aCanPaste = true;
- return NS_OK;
- }
- // can't paste if readonly
- if (!IsModifiable()) {
- return NS_OK;
- }
- nsresult rv;
- nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
- NS_ENSURE_SUCCESS(rv, rv);
- // the flavors that we can deal with
- const char* textEditorFlavors[] = { kUnicodeMime };
- bool haveFlavors;
- rv = clipboard->HasDataMatchingFlavors(textEditorFlavors,
- ArrayLength(textEditorFlavors),
- aSelectionType, &haveFlavors);
- NS_ENSURE_SUCCESS(rv, rv);
- *aCanPaste = haveFlavors;
- return NS_OK;
- }
- NS_IMETHODIMP
- TextEditor::CanPasteTransferable(nsITransferable* aTransferable,
- bool* aCanPaste)
- {
- NS_ENSURE_ARG_POINTER(aCanPaste);
- // can't paste if readonly
- if (!IsModifiable()) {
- *aCanPaste = false;
- return NS_OK;
- }
- // If |aTransferable| is null, assume that a paste will succeed.
- if (!aTransferable) {
- *aCanPaste = true;
- return NS_OK;
- }
- nsCOMPtr<nsISupports> data;
- uint32_t dataLen;
- nsresult rv = aTransferable->GetTransferData(kUnicodeMime,
- getter_AddRefs(data),
- &dataLen);
- if (NS_SUCCEEDED(rv) && data) {
- *aCanPaste = true;
- } else {
- *aCanPaste = false;
- }
- return NS_OK;
- }
- bool
- TextEditor::IsSafeToInsertData(nsIDOMDocument* aSourceDoc)
- {
- // Try to determine whether we should use a sanitizing fragment sink
- bool isSafe = false;
- nsCOMPtr<nsIDocument> destdoc = GetDocument();
- NS_ASSERTION(destdoc, "Where is our destination doc?");
- nsCOMPtr<nsIDocShellTreeItem> dsti = destdoc->GetDocShell();
- nsCOMPtr<nsIDocShellTreeItem> root;
- if (dsti) {
- dsti->GetRootTreeItem(getter_AddRefs(root));
- }
- nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(root);
- uint32_t appType;
- if (docShell && NS_SUCCEEDED(docShell->GetAppType(&appType))) {
- isSafe = appType == nsIDocShell::APP_TYPE_EDITOR;
- }
- if (!isSafe && aSourceDoc) {
- nsCOMPtr<nsIDocument> srcdoc = do_QueryInterface(aSourceDoc);
- NS_ASSERTION(srcdoc, "Where is our source doc?");
- nsIPrincipal* srcPrincipal = srcdoc->NodePrincipal();
- nsIPrincipal* destPrincipal = destdoc->NodePrincipal();
- NS_ASSERTION(srcPrincipal && destPrincipal, "How come we don't have a principal?");
- srcPrincipal->Subsumes(destPrincipal, &isSafe);
- }
- return isSafe;
- }
- } // namespace mozilla
|