TextEditorDataTransfer.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. #include "mozilla/TextEditor.h"
  6. #include "mozilla/ArrayUtils.h"
  7. #include "mozilla/EditorUtils.h"
  8. #include "mozilla/MouseEvents.h"
  9. #include "mozilla/SelectionState.h"
  10. #include "mozilla/dom/Selection.h"
  11. #include "nsAString.h"
  12. #include "nsCOMPtr.h"
  13. #include "nsComponentManagerUtils.h"
  14. #include "nsContentUtils.h"
  15. #include "nsDebug.h"
  16. #include "nsError.h"
  17. #include "nsIClipboard.h"
  18. #include "nsIContent.h"
  19. #include "nsIDOMDataTransfer.h"
  20. #include "nsIDOMDocument.h"
  21. #include "nsIDOMDragEvent.h"
  22. #include "nsIDOMEvent.h"
  23. #include "nsIDOMNode.h"
  24. #include "nsIDOMUIEvent.h"
  25. #include "nsIDocument.h"
  26. #include "nsIDragService.h"
  27. #include "nsIDragSession.h"
  28. #include "nsIEditor.h"
  29. #include "nsIEditorIMESupport.h"
  30. #include "nsIDocShell.h"
  31. #include "nsIDocShellTreeItem.h"
  32. #include "nsIPrincipal.h"
  33. #include "nsIFormControl.h"
  34. #include "nsIPlaintextEditor.h"
  35. #include "nsISupportsPrimitives.h"
  36. #include "nsITransferable.h"
  37. #include "nsIVariant.h"
  38. #include "nsLiteralString.h"
  39. #include "nsRange.h"
  40. #include "nsServiceManagerUtils.h"
  41. #include "nsString.h"
  42. #include "nsXPCOM.h"
  43. #include "nscore.h"
  44. class nsILoadContext;
  45. class nsISupports;
  46. namespace mozilla {
  47. using namespace dom;
  48. NS_IMETHODIMP
  49. TextEditor::PrepareTransferable(nsITransferable** transferable)
  50. {
  51. // Create generic Transferable for getting the data
  52. nsresult rv = CallCreateInstance("@mozilla.org/widget/transferable;1", transferable);
  53. NS_ENSURE_SUCCESS(rv, rv);
  54. // Get the nsITransferable interface for getting the data from the clipboard
  55. if (transferable) {
  56. nsCOMPtr<nsIDocument> destdoc = GetDocument();
  57. nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr;
  58. (*transferable)->Init(loadContext);
  59. (*transferable)->AddDataFlavor(kUnicodeMime);
  60. (*transferable)->AddDataFlavor(kMozTextInternal);
  61. };
  62. return NS_OK;
  63. }
  64. nsresult
  65. TextEditor::InsertTextAt(const nsAString& aStringToInsert,
  66. nsIDOMNode* aDestinationNode,
  67. int32_t aDestOffset,
  68. bool aDoDeleteSelection)
  69. {
  70. if (aDestinationNode) {
  71. RefPtr<Selection> selection = GetSelection();
  72. NS_ENSURE_STATE(selection);
  73. nsCOMPtr<nsIDOMNode> targetNode = aDestinationNode;
  74. int32_t targetOffset = aDestOffset;
  75. if (aDoDeleteSelection) {
  76. // Use an auto tracker so that our drop point is correctly
  77. // positioned after the delete.
  78. AutoTrackDOMPoint tracker(mRangeUpdater, &targetNode, &targetOffset);
  79. nsresult rv = DeleteSelection(eNone, eStrip);
  80. NS_ENSURE_SUCCESS(rv, rv);
  81. }
  82. nsresult rv = selection->Collapse(targetNode, targetOffset);
  83. NS_ENSURE_SUCCESS(rv, rv);
  84. }
  85. return InsertText(aStringToInsert);
  86. }
  87. NS_IMETHODIMP
  88. TextEditor::InsertTextFromTransferable(nsITransferable* aTransferable,
  89. nsIDOMNode* aDestinationNode,
  90. int32_t aDestOffset,
  91. bool aDoDeleteSelection)
  92. {
  93. nsresult rv = NS_OK;
  94. nsAutoCString bestFlavor;
  95. nsCOMPtr<nsISupports> genericDataObj;
  96. uint32_t len = 0;
  97. if (NS_SUCCEEDED(
  98. aTransferable->GetAnyTransferData(bestFlavor,
  99. getter_AddRefs(genericDataObj),
  100. &len)) &&
  101. (bestFlavor.EqualsLiteral(kUnicodeMime) ||
  102. bestFlavor.EqualsLiteral(kMozTextInternal))) {
  103. AutoTransactionsConserveSelection dontSpazMySelection(this);
  104. nsCOMPtr<nsISupportsString> textDataObj ( do_QueryInterface(genericDataObj) );
  105. if (textDataObj && len > 0) {
  106. nsAutoString stuffToPaste;
  107. textDataObj->GetData(stuffToPaste);
  108. NS_ASSERTION(stuffToPaste.Length() <= (len/2), "Invalid length!");
  109. // Sanitize possible carriage returns in the string to be inserted
  110. nsContentUtils::PlatformToDOMLineBreaks(stuffToPaste);
  111. AutoEditBatch beginBatching(this);
  112. rv = InsertTextAt(stuffToPaste, aDestinationNode, aDestOffset, aDoDeleteSelection);
  113. }
  114. }
  115. // Try to scroll the selection into view if the paste/drop succeeded
  116. if (NS_SUCCEEDED(rv)) {
  117. ScrollSelectionIntoView(false);
  118. }
  119. return rv;
  120. }
  121. nsresult
  122. TextEditor::InsertFromDataTransfer(DataTransfer* aDataTransfer,
  123. int32_t aIndex,
  124. nsIDOMDocument* aSourceDoc,
  125. nsIDOMNode* aDestinationNode,
  126. int32_t aDestOffset,
  127. bool aDoDeleteSelection)
  128. {
  129. nsCOMPtr<nsIVariant> data;
  130. DataTransfer::Cast(aDataTransfer)->GetDataAtNoSecurityCheck(NS_LITERAL_STRING("text/plain"), aIndex,
  131. getter_AddRefs(data));
  132. if (data) {
  133. nsAutoString insertText;
  134. data->GetAsAString(insertText);
  135. nsContentUtils::PlatformToDOMLineBreaks(insertText);
  136. AutoEditBatch beginBatching(this);
  137. return InsertTextAt(insertText, aDestinationNode, aDestOffset, aDoDeleteSelection);
  138. }
  139. return NS_OK;
  140. }
  141. nsresult
  142. TextEditor::InsertFromDrop(nsIDOMEvent* aDropEvent)
  143. {
  144. ForceCompositionEnd();
  145. nsCOMPtr<nsIDOMDragEvent> dragEvent(do_QueryInterface(aDropEvent));
  146. NS_ENSURE_TRUE(dragEvent, NS_ERROR_FAILURE);
  147. nsCOMPtr<nsIDOMDataTransfer> domDataTransfer;
  148. dragEvent->GetDataTransfer(getter_AddRefs(domDataTransfer));
  149. nsCOMPtr<DataTransfer> dataTransfer = do_QueryInterface(domDataTransfer);
  150. NS_ENSURE_TRUE(dataTransfer, NS_ERROR_FAILURE);
  151. nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
  152. NS_ASSERTION(dragSession, "No drag session");
  153. nsCOMPtr<nsIDOMNode> sourceNode;
  154. dataTransfer->GetMozSourceNode(getter_AddRefs(sourceNode));
  155. nsCOMPtr<nsIDOMDocument> srcdomdoc;
  156. if (sourceNode) {
  157. sourceNode->GetOwnerDocument(getter_AddRefs(srcdomdoc));
  158. NS_ENSURE_TRUE(sourceNode, NS_ERROR_FAILURE);
  159. }
  160. if (nsContentUtils::CheckForSubFrameDrop(dragSession,
  161. aDropEvent->WidgetEventPtr()->AsDragEvent())) {
  162. // Don't allow drags from subframe documents with different origins than
  163. // the drop destination.
  164. if (srcdomdoc && !IsSafeToInsertData(srcdomdoc)) {
  165. return NS_OK;
  166. }
  167. }
  168. // Current doc is destination
  169. nsCOMPtr<nsIDOMDocument> destdomdoc = GetDOMDocument();
  170. NS_ENSURE_TRUE(destdomdoc, NS_ERROR_NOT_INITIALIZED);
  171. uint32_t numItems = 0;
  172. nsresult rv = dataTransfer->GetMozItemCount(&numItems);
  173. NS_ENSURE_SUCCESS(rv, rv);
  174. if (numItems < 1) {
  175. return NS_ERROR_FAILURE; // Nothing to drop?
  176. }
  177. // Combine any deletion and drop insertion into one transaction
  178. AutoEditBatch beginBatching(this);
  179. bool deleteSelection = false;
  180. // We have to figure out whether to delete and relocate caret only once
  181. // Parent and offset are under the mouse cursor
  182. nsCOMPtr<nsIDOMUIEvent> uiEvent = do_QueryInterface(aDropEvent);
  183. NS_ENSURE_TRUE(uiEvent, NS_ERROR_FAILURE);
  184. nsCOMPtr<nsIDOMNode> newSelectionParent;
  185. rv = uiEvent->GetRangeParent(getter_AddRefs(newSelectionParent));
  186. NS_ENSURE_SUCCESS(rv, rv);
  187. NS_ENSURE_TRUE(newSelectionParent, NS_ERROR_FAILURE);
  188. int32_t newSelectionOffset;
  189. rv = uiEvent->GetRangeOffset(&newSelectionOffset);
  190. NS_ENSURE_SUCCESS(rv, rv);
  191. RefPtr<Selection> selection = GetSelection();
  192. NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
  193. bool isCollapsed = selection->Collapsed();
  194. // Only the HTMLEditor::FindUserSelectAllNode returns a node.
  195. nsCOMPtr<nsIDOMNode> userSelectNode = FindUserSelectAllNode(newSelectionParent);
  196. if (userSelectNode) {
  197. // The drop is happening over a "-moz-user-select: all"
  198. // subtree so make sure the content we insert goes before
  199. // the root of the subtree.
  200. //
  201. // XXX: Note that inserting before the subtree matches the
  202. // current behavior when dropping on top of an image.
  203. // The decision for dropping before or after the
  204. // subtree should really be done based on coordinates.
  205. newSelectionParent = GetNodeLocation(userSelectNode, &newSelectionOffset);
  206. NS_ENSURE_TRUE(newSelectionParent, NS_ERROR_FAILURE);
  207. }
  208. // Check if mouse is in the selection
  209. // if so, jump through some hoops to determine if mouse is over selection (bail)
  210. // and whether user wants to copy selection or delete it
  211. if (!isCollapsed) {
  212. // We never have to delete if selection is already collapsed
  213. bool cursorIsInSelection = false;
  214. int32_t rangeCount;
  215. rv = selection->GetRangeCount(&rangeCount);
  216. NS_ENSURE_SUCCESS(rv, rv);
  217. for (int32_t j = 0; j < rangeCount; j++) {
  218. RefPtr<nsRange> range = selection->GetRangeAt(j);
  219. if (!range) {
  220. // don't bail yet, iterate through them all
  221. continue;
  222. }
  223. rv = range->IsPointInRange(newSelectionParent, newSelectionOffset, &cursorIsInSelection);
  224. if (cursorIsInSelection) {
  225. break;
  226. }
  227. }
  228. if (cursorIsInSelection) {
  229. // Dragging within same doc can't drop on itself -- leave!
  230. if (srcdomdoc == destdomdoc) {
  231. return NS_OK;
  232. }
  233. // Dragging from another window onto a selection
  234. // XXX Decision made to NOT do this,
  235. // note that 4.x does replace if dropped on
  236. //deleteSelection = true;
  237. } else {
  238. // We are NOT over the selection
  239. if (srcdomdoc == destdomdoc) {
  240. // Within the same doc: delete if user doesn't want to copy
  241. uint32_t dropEffect;
  242. dataTransfer->GetDropEffectInt(&dropEffect);
  243. deleteSelection = !(dropEffect & nsIDragService::DRAGDROP_ACTION_COPY);
  244. } else {
  245. // Different source doc: Don't delete
  246. deleteSelection = false;
  247. }
  248. }
  249. }
  250. if (IsPlaintextEditor()) {
  251. nsCOMPtr<nsIContent> content = do_QueryInterface(newSelectionParent);
  252. while (content) {
  253. nsCOMPtr<nsIFormControl> formControl(do_QueryInterface(content));
  254. if (formControl && !formControl->AllowDrop()) {
  255. // Don't allow dropping into a form control that doesn't allow being
  256. // dropped into.
  257. return NS_OK;
  258. }
  259. content = content->GetParent();
  260. }
  261. }
  262. for (uint32_t i = 0; i < numItems; ++i) {
  263. InsertFromDataTransfer(dataTransfer, i, srcdomdoc, newSelectionParent,
  264. newSelectionOffset, deleteSelection);
  265. }
  266. if (NS_SUCCEEDED(rv)) {
  267. ScrollSelectionIntoView(false);
  268. }
  269. return rv;
  270. }
  271. NS_IMETHODIMP
  272. TextEditor::Paste(int32_t aSelectionType)
  273. {
  274. if (!FireClipboardEvent(ePaste, aSelectionType)) {
  275. return NS_OK;
  276. }
  277. // Get Clipboard Service
  278. nsresult rv;
  279. nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
  280. if (NS_FAILED(rv)) {
  281. return rv;
  282. }
  283. // Get the nsITransferable interface for getting the data from the clipboard
  284. nsCOMPtr<nsITransferable> trans;
  285. rv = PrepareTransferable(getter_AddRefs(trans));
  286. if (NS_SUCCEEDED(rv) && trans) {
  287. // Get the Data from the clipboard
  288. if (NS_SUCCEEDED(clipboard->GetData(trans, aSelectionType)) &&
  289. IsModifiable()) {
  290. // handle transferable hooks
  291. nsCOMPtr<nsIDOMDocument> domdoc = GetDOMDocument();
  292. if (!EditorHookUtils::DoInsertionHook(domdoc, nullptr, trans)) {
  293. return NS_OK;
  294. }
  295. rv = InsertTextFromTransferable(trans, nullptr, 0, true);
  296. }
  297. }
  298. return rv;
  299. }
  300. NS_IMETHODIMP
  301. TextEditor::PasteTransferable(nsITransferable* aTransferable)
  302. {
  303. // Use an invalid value for the clipboard type as data comes from aTransferable
  304. // and we don't currently implement a way to put that in the data transfer yet.
  305. if (!FireClipboardEvent(ePaste, -1)) {
  306. return NS_OK;
  307. }
  308. if (!IsModifiable()) {
  309. return NS_OK;
  310. }
  311. // handle transferable hooks
  312. nsCOMPtr<nsIDOMDocument> domdoc = GetDOMDocument();
  313. if (!EditorHookUtils::DoInsertionHook(domdoc, nullptr, aTransferable)) {
  314. return NS_OK;
  315. }
  316. return InsertTextFromTransferable(aTransferable, nullptr, 0, true);
  317. }
  318. NS_IMETHODIMP
  319. TextEditor::CanPaste(int32_t aSelectionType,
  320. bool* aCanPaste)
  321. {
  322. NS_ENSURE_ARG_POINTER(aCanPaste);
  323. *aCanPaste = false;
  324. // Always enable the paste command when inside of a HTML or XHTML document.
  325. nsCOMPtr<nsIDocument> doc = GetDocument();
  326. if (doc && doc->IsHTMLOrXHTML()) {
  327. *aCanPaste = true;
  328. return NS_OK;
  329. }
  330. // can't paste if readonly
  331. if (!IsModifiable()) {
  332. return NS_OK;
  333. }
  334. nsresult rv;
  335. nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
  336. NS_ENSURE_SUCCESS(rv, rv);
  337. // the flavors that we can deal with
  338. const char* textEditorFlavors[] = { kUnicodeMime };
  339. bool haveFlavors;
  340. rv = clipboard->HasDataMatchingFlavors(textEditorFlavors,
  341. ArrayLength(textEditorFlavors),
  342. aSelectionType, &haveFlavors);
  343. NS_ENSURE_SUCCESS(rv, rv);
  344. *aCanPaste = haveFlavors;
  345. return NS_OK;
  346. }
  347. NS_IMETHODIMP
  348. TextEditor::CanPasteTransferable(nsITransferable* aTransferable,
  349. bool* aCanPaste)
  350. {
  351. NS_ENSURE_ARG_POINTER(aCanPaste);
  352. // can't paste if readonly
  353. if (!IsModifiable()) {
  354. *aCanPaste = false;
  355. return NS_OK;
  356. }
  357. // If |aTransferable| is null, assume that a paste will succeed.
  358. if (!aTransferable) {
  359. *aCanPaste = true;
  360. return NS_OK;
  361. }
  362. nsCOMPtr<nsISupports> data;
  363. uint32_t dataLen;
  364. nsresult rv = aTransferable->GetTransferData(kUnicodeMime,
  365. getter_AddRefs(data),
  366. &dataLen);
  367. if (NS_SUCCEEDED(rv) && data) {
  368. *aCanPaste = true;
  369. } else {
  370. *aCanPaste = false;
  371. }
  372. return NS_OK;
  373. }
  374. bool
  375. TextEditor::IsSafeToInsertData(nsIDOMDocument* aSourceDoc)
  376. {
  377. // Try to determine whether we should use a sanitizing fragment sink
  378. bool isSafe = false;
  379. nsCOMPtr<nsIDocument> destdoc = GetDocument();
  380. NS_ASSERTION(destdoc, "Where is our destination doc?");
  381. nsCOMPtr<nsIDocShellTreeItem> dsti = destdoc->GetDocShell();
  382. nsCOMPtr<nsIDocShellTreeItem> root;
  383. if (dsti) {
  384. dsti->GetRootTreeItem(getter_AddRefs(root));
  385. }
  386. nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(root);
  387. uint32_t appType;
  388. if (docShell && NS_SUCCEEDED(docShell->GetAppType(&appType))) {
  389. isSafe = appType == nsIDocShell::APP_TYPE_EDITOR;
  390. }
  391. if (!isSafe && aSourceDoc) {
  392. nsCOMPtr<nsIDocument> srcdoc = do_QueryInterface(aSourceDoc);
  393. NS_ASSERTION(srcdoc, "Where is our source doc?");
  394. nsIPrincipal* srcPrincipal = srcdoc->NodePrincipal();
  395. nsIPrincipal* destPrincipal = destdoc->NodePrincipal();
  396. NS_ASSERTION(srcPrincipal && destPrincipal, "How come we don't have a principal?");
  397. srcPrincipal->Subsumes(destPrincipal, &isSafe);
  398. }
  399. return isSafe;
  400. }
  401. } // namespace mozilla