/toolkit/content/nsDragAndDrop.js

http://github.com/zpao/v8monkey · JavaScript · 635 lines · 363 code · 65 blank · 207 comment · 58 complexity · 1f8334d82e61994cb741c5e2e86ebe89 MD5 · raw file

  1. # -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  2. # ***** BEGIN LICENSE BLOCK *****
  3. # Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4. #
  5. # The contents of this file are subject to the Mozilla Public License Version
  6. # 1.1 (the "License"); you may not use this file except in compliance with
  7. # the License. You may obtain a copy of the License at
  8. # http://www.mozilla.org/MPL/
  9. #
  10. # Software distributed under the License is distributed on an "AS IS" basis,
  11. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12. # for the specific language governing rights and limitations under the
  13. # License.
  14. #
  15. # The Original Code is mozilla.org code.
  16. #
  17. # The Initial Developer of the Original Code is
  18. # Netscape Communications Corporation.
  19. # Portions created by the Initial Developer are Copyright (C) 1998
  20. # the Initial Developer. All Rights Reserved.
  21. #
  22. # Contributor(s):
  23. # Ben Goodger <ben@netscape.com> (Original Author)
  24. # Pierre Chanial <pierrechanial@netscape.net>
  25. #
  26. # Alternatively, the contents of this file may be used under the terms of
  27. # either the GNU General Public License Version 2 or later (the "GPL"), or
  28. # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  29. # in which case the provisions of the GPL or the LGPL are applicable instead
  30. # of those above. If you wish to allow use of your version of this file only
  31. # under the terms of either the GPL or the LGPL, and not to allow others to
  32. # use your version of this file under the terms of the MPL, indicate your
  33. # decision by deleting the provisions above and replace them with the notice
  34. # and other provisions required by the GPL or the LGPL. If you do not delete
  35. # the provisions above, a recipient may use your version of this file under
  36. # the terms of any one of the MPL, the GPL or the LGPL.
  37. #
  38. # ***** END LICENSE BLOCK *****
  39. ////////////////////////////////////////////////////////////////////////
  40. //
  41. // USE OF THIS API FOR DRAG AND DROP IS DEPRECATED!
  42. // Do not use this file for new code.
  43. //
  44. // For documentation about what to use instead, see:
  45. // http://developer.mozilla.org/En/DragDrop/Drag_and_Drop
  46. //
  47. ////////////////////////////////////////////////////////////////////////
  48. /**
  49. * nsTransferable - a wrapper for nsITransferable that simplifies
  50. * javascript clipboard and drag&drop. for use in
  51. * these situations you should use the nsClipboard
  52. * and nsDragAndDrop wrappers for more convenience
  53. **/
  54. var nsTransferable = {
  55. /**
  56. * nsITransferable set (TransferData aTransferData) ;
  57. *
  58. * Creates a transferable with data for a list of supported types ("flavours")
  59. *
  60. * @param TransferData aTransferData
  61. * a javascript object in the format described above
  62. **/
  63. set: function (aTransferDataSet)
  64. {
  65. var trans = this.createTransferable();
  66. for (var i = 0; i < aTransferDataSet.dataList.length; ++i)
  67. {
  68. var currData = aTransferDataSet.dataList[i];
  69. var currFlavour = currData.flavour.contentType;
  70. trans.addDataFlavor(currFlavour);
  71. var supports = null; // nsISupports data
  72. var length = 0;
  73. if (currData.flavour.dataIIDKey == "nsISupportsString")
  74. {
  75. supports = Components.classes["@mozilla.org/supports-string;1"]
  76. .createInstance(Components.interfaces.nsISupportsString);
  77. supports.data = currData.supports;
  78. length = supports.data.length;
  79. }
  80. else
  81. {
  82. // non-string data.
  83. supports = currData.supports;
  84. length = 0; // kFlavorHasDataProvider
  85. }
  86. trans.setTransferData(currFlavour, supports, length * 2);
  87. }
  88. return trans;
  89. },
  90. /**
  91. * TransferData/TransferDataSet get (FlavourSet aFlavourSet,
  92. * Function aRetrievalFunc, Boolean aAnyFlag) ;
  93. *
  94. * Retrieves data from the transferable provided in aRetrievalFunc, formatted
  95. * for more convenient access.
  96. *
  97. * @param FlavourSet aFlavourSet
  98. * a FlavourSet object that contains a list of supported flavours.
  99. * @param Function aRetrievalFunc
  100. * a reference to a function that returns a nsISupportsArray of nsITransferables
  101. * for each item from the specified source (clipboard/drag&drop etc)
  102. * @param Boolean aAnyFlag
  103. * a flag specifying whether or not a specific flavour is requested. If false,
  104. * data of the type of the first flavour in the flavourlist parameter is returned,
  105. * otherwise the best flavour supported will be returned.
  106. **/
  107. get: function (aFlavourSet, aRetrievalFunc, aAnyFlag)
  108. {
  109. if (!aRetrievalFunc)
  110. throw "No data retrieval handler provided!";
  111. var supportsArray = aRetrievalFunc(aFlavourSet);
  112. var dataArray = [];
  113. var count = supportsArray.Count();
  114. // Iterate over the number of items returned from aRetrievalFunc. For
  115. // clipboard operations, this is 1, for drag and drop (where multiple
  116. // items may have been dragged) this could be >1.
  117. for (var i = 0; i < count; i++)
  118. {
  119. var trans = supportsArray.GetElementAt(i);
  120. if (!trans) continue;
  121. trans = trans.QueryInterface(Components.interfaces.nsITransferable);
  122. var data = { };
  123. var length = { };
  124. var currData = null;
  125. if (aAnyFlag)
  126. {
  127. var flavour = { };
  128. trans.getAnyTransferData(flavour, data, length);
  129. if (data && flavour)
  130. {
  131. var selectedFlavour = aFlavourSet.flavourTable[flavour.value];
  132. if (selectedFlavour)
  133. dataArray[i] = FlavourToXfer(data.value, length.value, selectedFlavour);
  134. }
  135. }
  136. else
  137. {
  138. var firstFlavour = aFlavourSet.flavours[0];
  139. trans.getTransferData(firstFlavour, data, length);
  140. if (data && firstFlavour)
  141. dataArray[i] = FlavourToXfer(data.value, length.value, firstFlavour);
  142. }
  143. }
  144. return new TransferDataSet(dataArray);
  145. },
  146. /**
  147. * nsITransferable createTransferable (void) ;
  148. *
  149. * Creates and returns a transferable object.
  150. **/
  151. createTransferable: function ()
  152. {
  153. const kXferableContractID = "@mozilla.org/widget/transferable;1";
  154. const kXferableIID = Components.interfaces.nsITransferable;
  155. return Components.classes[kXferableContractID].createInstance(kXferableIID);
  156. }
  157. };
  158. /**
  159. * A FlavourSet is a simple type that represents a collection of Flavour objects.
  160. * FlavourSet is constructed from an array of Flavours, and stores this list as
  161. * an array and a hashtable. The rationale for the dual storage is as follows:
  162. *
  163. * Array: Ordering is important when adding data flavours to a transferable.
  164. * Flavours added first are deemed to be 'preferred' by the client.
  165. * Hash: Convenient lookup of flavour data using the content type (MIME type)
  166. * of data as a key.
  167. */
  168. function FlavourSet(aFlavourList)
  169. {
  170. this.flavours = aFlavourList || [];
  171. this.flavourTable = { };
  172. this._XferID = "FlavourSet";
  173. for (var i = 0; i < this.flavours.length; ++i)
  174. this.flavourTable[this.flavours[i].contentType] = this.flavours[i];
  175. }
  176. FlavourSet.prototype = {
  177. appendFlavour: function (aFlavour, aFlavourIIDKey)
  178. {
  179. var flavour = new Flavour (aFlavour, aFlavourIIDKey);
  180. this.flavours.push(flavour);
  181. this.flavourTable[flavour.contentType] = flavour;
  182. }
  183. };
  184. /**
  185. * A Flavour is a simple type that represents a data type that can be handled.
  186. * It takes a content type (MIME type) which is used when storing data on the
  187. * system clipboard/drag and drop, and an IIDKey (string interface name
  188. * which is used to QI data to an appropriate form. The default interface is
  189. * assumed to be wide-string.
  190. */
  191. function Flavour(aContentType, aDataIIDKey)
  192. {
  193. this.contentType = aContentType;
  194. this.dataIIDKey = aDataIIDKey || "nsISupportsString";
  195. this._XferID = "Flavour";
  196. }
  197. function TransferDataBase() {}
  198. TransferDataBase.prototype = {
  199. push: function (aItems)
  200. {
  201. this.dataList.push(aItems);
  202. },
  203. get first ()
  204. {
  205. return "dataList" in this && this.dataList.length ? this.dataList[0] : null;
  206. }
  207. };
  208. /**
  209. * TransferDataSet is a list (array) of TransferData objects, which represents
  210. * data dragged from one or more elements.
  211. */
  212. function TransferDataSet(aTransferDataList)
  213. {
  214. this.dataList = aTransferDataList || [];
  215. this._XferID = "TransferDataSet";
  216. }
  217. TransferDataSet.prototype = TransferDataBase.prototype;
  218. /**
  219. * TransferData is a list (array) of FlavourData for all the applicable content
  220. * types associated with a drag from a single item.
  221. */
  222. function TransferData(aFlavourDataList)
  223. {
  224. this.dataList = aFlavourDataList || [];
  225. this._XferID = "TransferData";
  226. }
  227. TransferData.prototype = {
  228. __proto__: TransferDataBase.prototype,
  229. addDataForFlavour: function (aFlavourString, aData, aLength, aDataIIDKey)
  230. {
  231. this.dataList.push(new FlavourData(aData, aLength,
  232. new Flavour(aFlavourString, aDataIIDKey)));
  233. }
  234. };
  235. /**
  236. * FlavourData is a type that represents data retrieved from the system
  237. * clipboard or drag and drop. It is constructed internally by the Transferable
  238. * using the raw (nsISupports) data from the clipboard, the length of the data,
  239. * and an object of type Flavour representing the type. Clients implementing
  240. * IDragDropObserver receive an object of this type in their implementation of
  241. * onDrop. They access the 'data' property to retrieve data, which is either data
  242. * QI'ed to a usable form, or unicode string.
  243. */
  244. function FlavourData(aData, aLength, aFlavour)
  245. {
  246. this.supports = aData;
  247. this.contentLength = aLength;
  248. this.flavour = aFlavour || null;
  249. this._XferID = "FlavourData";
  250. }
  251. FlavourData.prototype = {
  252. get data ()
  253. {
  254. if (this.flavour &&
  255. this.flavour.dataIIDKey != "nsISupportsString")
  256. return this.supports.QueryInterface(Components.interfaces[this.flavour.dataIIDKey]);
  257. var supports = this.supports;
  258. if (supports instanceof Components.interfaces.nsISupportsString)
  259. return supports.data.substring(0, this.contentLength/2);
  260. return supports;
  261. }
  262. }
  263. /**
  264. * Create a TransferData object with a single FlavourData entry. Used when
  265. * unwrapping data of a specific flavour from the drag service.
  266. */
  267. function FlavourToXfer(aData, aLength, aFlavour)
  268. {
  269. return new TransferData([new FlavourData(aData, aLength, aFlavour)]);
  270. }
  271. var transferUtils = {
  272. retrieveURLFromData: function (aData, flavour)
  273. {
  274. switch (flavour) {
  275. case "text/unicode":
  276. case "text/plain":
  277. case "text/x-moz-text-internal":
  278. return aData.replace(/^\s+|\s+$/g, "");
  279. case "text/x-moz-url":
  280. return ((aData instanceof Components.interfaces.nsISupportsString) ? aData.toString() : aData).split("\n")[0];
  281. case "application/x-moz-file":
  282. var ioService = Components.classes["@mozilla.org/network/io-service;1"]
  283. .getService(Components.interfaces.nsIIOService);
  284. var fileHandler = ioService.getProtocolHandler("file")
  285. .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
  286. return fileHandler.getURLSpecFromFile(aData);
  287. }
  288. return null;
  289. }
  290. }
  291. /**
  292. * nsDragAndDrop - a convenience wrapper for nsTransferable, nsITransferable
  293. * and nsIDragService/nsIDragSession.
  294. *
  295. * Use: map the handler functions to the 'ondraggesture', 'ondragover' and
  296. * 'ondragdrop' event handlers on your XML element, e.g.
  297. * <xmlelement ondraggesture="nsDragAndDrop.startDrag(event, observer);"
  298. * ondragover="nsDragAndDrop.dragOver(event, observer);"
  299. * ondragdrop="nsDragAndDrop.drop(event, observer);"/>
  300. *
  301. * You need to create an observer js object with the following member
  302. * functions:
  303. * Object onDragStart (event) // called when drag initiated,
  304. * // returns flavour list with data
  305. * // to stuff into transferable
  306. * void onDragOver (Object flavour) // called when element is dragged
  307. * // over, so that it can perform
  308. * // any drag-over feedback for provided
  309. * // flavour
  310. * void onDrop (Object data) // formatted data object dropped.
  311. * Object getSupportedFlavours () // returns a flavour list so that
  312. * // nsTransferable can determine
  313. * // whether or not to accept drop.
  314. **/
  315. var nsDragAndDrop = {
  316. _mDS: null,
  317. get mDragService()
  318. {
  319. if (!this._mDS)
  320. {
  321. const kDSContractID = "@mozilla.org/widget/dragservice;1";
  322. const kDSIID = Components.interfaces.nsIDragService;
  323. this._mDS = Components.classes[kDSContractID].getService(kDSIID);
  324. }
  325. return this._mDS;
  326. },
  327. /**
  328. * void startDrag (DOMEvent aEvent, Object aDragDropObserver) ;
  329. *
  330. * called when a drag on an element is started.
  331. *
  332. * @param DOMEvent aEvent
  333. * the DOM event fired by the drag init
  334. * @param Object aDragDropObserver
  335. * javascript object of format described above that specifies
  336. * the way in which the element responds to drag events.
  337. **/
  338. startDrag: function (aEvent, aDragDropObserver)
  339. {
  340. if (!("onDragStart" in aDragDropObserver))
  341. return;
  342. const kDSIID = Components.interfaces.nsIDragService;
  343. var dragAction = { action: kDSIID.DRAGDROP_ACTION_COPY + kDSIID.DRAGDROP_ACTION_MOVE + kDSIID.DRAGDROP_ACTION_LINK };
  344. var transferData = { data: null };
  345. try
  346. {
  347. aDragDropObserver.onDragStart(aEvent, transferData, dragAction);
  348. }
  349. catch (e)
  350. {
  351. return; // not a draggable item, bail!
  352. }
  353. if (!transferData.data) return;
  354. transferData = transferData.data;
  355. var dt = aEvent.dataTransfer;
  356. var count = 0;
  357. do {
  358. var tds = transferData._XferID == "TransferData"
  359. ? transferData
  360. : transferData.dataList[count]
  361. for (var i = 0; i < tds.dataList.length; ++i)
  362. {
  363. var currData = tds.dataList[i];
  364. var currFlavour = currData.flavour.contentType;
  365. var value = currData.supports;
  366. if (value instanceof Components.interfaces.nsISupportsString)
  367. value = value.toString();
  368. dt.mozSetDataAt(currFlavour, value, count);
  369. }
  370. count++;
  371. }
  372. while (transferData._XferID == "TransferDataSet" &&
  373. count < transferData.dataList.length);
  374. dt.effectAllowed = "all";
  375. // a drag targeted at a tree should instead use the treechildren so that
  376. // the current selection is used as the drag feedback
  377. dt.addElement(aEvent.originalTarget.localName == "treechildren" ?
  378. aEvent.originalTarget : aEvent.target);
  379. aEvent.stopPropagation();
  380. },
  381. /**
  382. * void dragOver (DOMEvent aEvent, Object aDragDropObserver) ;
  383. *
  384. * called when a drag passes over this element
  385. *
  386. * @param DOMEvent aEvent
  387. * the DOM event fired by passing over the element
  388. * @param Object aDragDropObserver
  389. * javascript object of format described above that specifies
  390. * the way in which the element responds to drag events.
  391. **/
  392. dragOver: function (aEvent, aDragDropObserver)
  393. {
  394. if (!("onDragOver" in aDragDropObserver))
  395. return;
  396. if (!this.checkCanDrop(aEvent, aDragDropObserver))
  397. return;
  398. var flavourSet = aDragDropObserver.getSupportedFlavours();
  399. for (var flavour in flavourSet.flavourTable)
  400. {
  401. if (this.mDragSession.isDataFlavorSupported(flavour))
  402. {
  403. aDragDropObserver.onDragOver(aEvent,
  404. flavourSet.flavourTable[flavour],
  405. this.mDragSession);
  406. aEvent.stopPropagation();
  407. aEvent.preventDefault();
  408. break;
  409. }
  410. }
  411. },
  412. mDragSession: null,
  413. /**
  414. * void drop (DOMEvent aEvent, Object aDragDropObserver) ;
  415. *
  416. * called when the user drops on the element
  417. *
  418. * @param DOMEvent aEvent
  419. * the DOM event fired by the drop
  420. * @param Object aDragDropObserver
  421. * javascript object of format described above that specifies
  422. * the way in which the element responds to drag events.
  423. **/
  424. drop: function (aEvent, aDragDropObserver)
  425. {
  426. if (!("onDrop" in aDragDropObserver))
  427. return;
  428. if (!this.checkCanDrop(aEvent, aDragDropObserver))
  429. return;
  430. var flavourSet = aDragDropObserver.getSupportedFlavours();
  431. var dt = aEvent.dataTransfer;
  432. var dataArray = [];
  433. var count = dt.mozItemCount;
  434. for (var i = 0; i < count; ++i) {
  435. var types = dt.mozTypesAt(i);
  436. for (var j = 0; j < flavourSet.flavours.length; j++) {
  437. var type = flavourSet.flavours[j].contentType;
  438. // dataTransfer uses text/plain but older code used text/unicode, so
  439. // switch this for compatibility
  440. var modtype = (type == "text/unicode") ? "text/plain" : type;
  441. if (Array.indexOf(types, modtype) >= 0) {
  442. var data = dt.mozGetDataAt(modtype, i);
  443. if (data) {
  444. // Non-strings need some non-zero value used for their data length.
  445. const kNonStringDataLength = 4;
  446. var length = (typeof data == "string") ? data.length : kNonStringDataLength;
  447. dataArray[i] = FlavourToXfer(data, length, flavourSet.flavourTable[type]);
  448. break;
  449. }
  450. }
  451. }
  452. }
  453. var transferData = new TransferDataSet(dataArray)
  454. // hand over to the client to respond to dropped data
  455. var multiple = "canHandleMultipleItems" in aDragDropObserver && aDragDropObserver.canHandleMultipleItems;
  456. var dropData = multiple ? transferData : transferData.first.first;
  457. aDragDropObserver.onDrop(aEvent, dropData, this.mDragSession);
  458. aEvent.stopPropagation();
  459. },
  460. /**
  461. * void dragExit (DOMEvent aEvent, Object aDragDropObserver) ;
  462. *
  463. * called when a drag leaves this element
  464. *
  465. * @param DOMEvent aEvent
  466. * the DOM event fired by leaving the element
  467. * @param Object aDragDropObserver
  468. * javascript object of format described above that specifies
  469. * the way in which the element responds to drag events.
  470. **/
  471. dragExit: function (aEvent, aDragDropObserver)
  472. {
  473. if (!this.checkCanDrop(aEvent, aDragDropObserver))
  474. return;
  475. if ("onDragExit" in aDragDropObserver)
  476. aDragDropObserver.onDragExit(aEvent, this.mDragSession);
  477. },
  478. /**
  479. * void dragEnter (DOMEvent aEvent, Object aDragDropObserver) ;
  480. *
  481. * called when a drag enters in this element
  482. *
  483. * @param DOMEvent aEvent
  484. * the DOM event fired by entering in the element
  485. * @param Object aDragDropObserver
  486. * javascript object of format described above that specifies
  487. * the way in which the element responds to drag events.
  488. **/
  489. dragEnter: function (aEvent, aDragDropObserver)
  490. {
  491. if (!this.checkCanDrop(aEvent, aDragDropObserver))
  492. return;
  493. if ("onDragEnter" in aDragDropObserver)
  494. aDragDropObserver.onDragEnter(aEvent, this.mDragSession);
  495. },
  496. /**
  497. * Boolean checkCanDrop (DOMEvent aEvent, Object aDragDropObserver) ;
  498. *
  499. * Sets the canDrop attribute for the drag session.
  500. * returns false if there is no current drag session.
  501. *
  502. * @param DOMEvent aEvent
  503. * the DOM event fired by the drop
  504. * @param Object aDragDropObserver
  505. * javascript object of format described above that specifies
  506. * the way in which the element responds to drag events.
  507. **/
  508. checkCanDrop: function (aEvent, aDragDropObserver)
  509. {
  510. if (!this.mDragSession)
  511. this.mDragSession = this.mDragService.getCurrentSession();
  512. if (!this.mDragSession)
  513. return false;
  514. this.mDragSession.canDrop = this.mDragSession.sourceNode != aEvent.target;
  515. if ("canDrop" in aDragDropObserver)
  516. this.mDragSession.canDrop &= aDragDropObserver.canDrop(aEvent, this.mDragSession);
  517. return true;
  518. },
  519. /**
  520. * Do a security check for drag n' drop. Make sure the source document
  521. * can load the dragged link.
  522. *
  523. * @param DOMEvent aEvent
  524. * the DOM event fired by leaving the element
  525. * @param Object aDragDropObserver
  526. * javascript object of format described above that specifies
  527. * the way in which the element responds to drag events.
  528. * @param String aDraggedText
  529. * the text being dragged
  530. **/
  531. dragDropSecurityCheck: function (aEvent, aDragSession, aDraggedText)
  532. {
  533. // Strip leading and trailing whitespace, then try to create a
  534. // URI from the dropped string. If that succeeds, we're
  535. // dropping a URI and we need to do a security check to make
  536. // sure the source document can load the dropped URI. We don't
  537. // so much care about creating the real URI here
  538. // (i.e. encoding differences etc don't matter), we just want
  539. // to know if aDraggedText really is a URI.
  540. aDraggedText = aDraggedText.replace(/^\s*|\s*$/g, '');
  541. var uri;
  542. try {
  543. uri = Components.classes["@mozilla.org/network/io-service;1"]
  544. .getService(Components.interfaces.nsIIOService)
  545. .newURI(aDraggedText, null, null);
  546. } catch (e) {
  547. }
  548. if (!uri)
  549. return;
  550. // aDraggedText is a URI, do the security check.
  551. const nsIScriptSecurityManager = Components.interfaces
  552. .nsIScriptSecurityManager;
  553. var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
  554. .getService(nsIScriptSecurityManager);
  555. if (!aDragSession)
  556. aDragSession = this.mDragService.getCurrentSession();
  557. var sourceDoc = aDragSession.sourceDocument;
  558. // Use "file:///" as the default sourceURI so that drops of file:// URIs
  559. // are always allowed.
  560. var sourceURI = sourceDoc ? sourceDoc.documentURI : "file:///";
  561. try {
  562. secMan.checkLoadURIStr(sourceURI, aDraggedText,
  563. nsIScriptSecurityManager.STANDARD);
  564. } catch (e) {
  565. // Stop event propagation right here.
  566. aEvent.stopPropagation();
  567. throw "Drop of " + aDraggedText + " denied.";
  568. }
  569. }
  570. };