1 // -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
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 ////////////////////////////////////////////////////////////////////////
9 // USE OF THIS API FOR DRAG AND DROP IS DEPRECATED!
10 // Do not use this file for new code.
12 // For documentation about what to use instead, see:
13 // http://developer.mozilla.org/En/DragDrop/Drag_and_Drop
15 ////////////////////////////////////////////////////////////////////////
19 * nsTransferable - a wrapper for nsITransferable that simplifies
20 * javascript clipboard and drag&drop. for use in
21 * these situations you should use the nsClipboard
22 * and nsDragAndDrop wrappers for more convenience
25 var nsTransferable = {
27 * nsITransferable set (TransferData aTransferData) ;
29 * Creates a transferable with data for a list of supported types ("flavours")
31 * @param TransferData aTransferData
32 * a javascript object in the format described above
34 set: function (aTransferDataSet)
36 var trans = this.createTransferable();
37 for (var i = 0; i < aTransferDataSet.dataList.length; ++i)
39 var currData = aTransferDataSet.dataList[i];
40 var currFlavour = currData.flavour.contentType;
41 trans.addDataFlavor(currFlavour);
42 var supports = null; // nsISupports data
44 if (currData.flavour.dataIIDKey == "nsISupportsString")
46 supports = Components.classes["@mozilla.org/supports-string;1"]
47 .createInstance(Components.interfaces.nsISupportsString);
49 supports.data = currData.supports;
50 length = supports.data.length;
55 supports = currData.supports;
56 length = 0; // kFlavorHasDataProvider
58 trans.setTransferData(currFlavour, supports, length * 2);
64 * TransferData/TransferDataSet get (FlavourSet aFlavourSet,
65 * Function aRetrievalFunc, Boolean aAnyFlag) ;
67 * Retrieves data from the transferable provided in aRetrievalFunc, formatted
68 * for more convenient access.
70 * @param FlavourSet aFlavourSet
71 * a FlavourSet object that contains a list of supported flavours.
72 * @param Function aRetrievalFunc
73 * a reference to a function that returns a nsISupportsArray of nsITransferables
74 * for each item from the specified source (clipboard/drag&drop etc)
75 * @param Boolean aAnyFlag
76 * a flag specifying whether or not a specific flavour is requested. If false,
77 * data of the type of the first flavour in the flavourlist parameter is returned,
78 * otherwise the best flavour supported will be returned.
80 get: function (aFlavourSet, aRetrievalFunc, aAnyFlag)
83 throw "No data retrieval handler provided!";
85 var supportsArray = aRetrievalFunc(aFlavourSet);
87 var count = supportsArray.Count();
89 // Iterate over the number of items returned from aRetrievalFunc. For
90 // clipboard operations, this is 1, for drag and drop (where multiple
91 // items may have been dragged) this could be >1.
92 for (var i = 0; i < count; i++)
94 var trans = supportsArray.GetElementAt(i);
96 trans = trans.QueryInterface(Components.interfaces.nsITransferable);
105 trans.getAnyTransferData(flavour, data, length);
108 var selectedFlavour = aFlavourSet.flavourTable[flavour.value];
110 dataArray[i] = FlavourToXfer(data.value, length.value, selectedFlavour);
115 var firstFlavour = aFlavourSet.flavours[0];
116 trans.getTransferData(firstFlavour, data, length);
117 if (data && firstFlavour)
118 dataArray[i] = FlavourToXfer(data.value, length.value, firstFlavour);
121 return new TransferDataSet(dataArray);
125 * nsITransferable createTransferable (void) ;
127 * Creates and returns a transferable object.
129 createTransferable: function ()
131 const kXferableContractID = "@mozilla.org/widget/transferable;1";
132 const kXferableIID = Components.interfaces.nsITransferable;
133 var trans = Components.classes[kXferableContractID].createInstance(kXferableIID);
140 * A FlavourSet is a simple type that represents a collection of Flavour objects.
141 * FlavourSet is constructed from an array of Flavours, and stores this list as
142 * an array and a hashtable. The rationale for the dual storage is as follows:
144 * Array: Ordering is important when adding data flavours to a transferable.
145 * Flavours added first are deemed to be 'preferred' by the client.
146 * Hash: Convenient lookup of flavour data using the content type (MIME type)
149 function FlavourSet(aFlavourList)
151 this.flavours = aFlavourList || [];
152 this.flavourTable = { };
154 this._XferID = "FlavourSet";
156 for (var i = 0; i < this.flavours.length; ++i)
157 this.flavourTable[this.flavours[i].contentType] = this.flavours[i];
160 FlavourSet.prototype = {
161 appendFlavour: function (aFlavour, aFlavourIIDKey)
163 var flavour = new Flavour (aFlavour, aFlavourIIDKey);
164 this.flavours.push(flavour);
165 this.flavourTable[flavour.contentType] = flavour;
170 * A Flavour is a simple type that represents a data type that can be handled.
171 * It takes a content type (MIME type) which is used when storing data on the
172 * system clipboard/drag and drop, and an IIDKey (string interface name
173 * which is used to QI data to an appropriate form. The default interface is
174 * assumed to be wide-string.
176 function Flavour(aContentType, aDataIIDKey)
178 this.contentType = aContentType;
179 this.dataIIDKey = aDataIIDKey || "nsISupportsString";
181 this._XferID = "Flavour";
184 function TransferDataBase() {}
185 TransferDataBase.prototype = {
186 push: function (aItems)
188 this.dataList.push(aItems);
193 return "dataList" in this && this.dataList.length ? this.dataList[0] : null;
198 * TransferDataSet is a list (array) of TransferData objects, which represents
199 * data dragged from one or more elements.
201 function TransferDataSet(aTransferDataList)
203 this.dataList = aTransferDataList || [];
205 this._XferID = "TransferDataSet";
207 TransferDataSet.prototype = TransferDataBase.prototype;
210 * TransferData is a list (array) of FlavourData for all the applicable content
211 * types associated with a drag from a single item.
213 function TransferData(aFlavourDataList)
215 this.dataList = aFlavourDataList || [];
217 this._XferID = "TransferData";
219 TransferData.prototype = {
220 __proto__: TransferDataBase.prototype,
222 addDataForFlavour: function (aFlavourString, aData, aLength, aDataIIDKey)
224 this.dataList.push(new FlavourData(aData, aLength,
225 new Flavour(aFlavourString, aDataIIDKey)));
230 * FlavourData is a type that represents data retrieved from the system
231 * clipboard or drag and drop. It is constructed internally by the Transferable
232 * using the raw (nsISupports) data from the clipboard, the length of the data,
233 * and an object of type Flavour representing the type. Clients implementing
234 * IDragDropObserver receive an object of this type in their implementation of
235 * onDrop. They access the 'data' property to retrieve data, which is either data
236 * QI'ed to a usable form, or unicode string.
238 function FlavourData(aData, aLength, aFlavour)
240 this.supports = aData;
241 this.contentLength = aLength;
242 this.flavour = aFlavour || null;
244 this._XferID = "FlavourData";
247 FlavourData.prototype = {
251 this.flavour.dataIIDKey != "nsISupportsString")
252 return this.supports.QueryInterface(Components.interfaces[this.flavour.dataIIDKey]);
254 var supports = this.supports;
255 if (supports instanceof Components.interfaces.nsISupportsString)
256 return supports.data.substring(0, this.contentLength/2);
263 * Create a TransferData object with a single FlavourData entry. Used when
264 * unwrapping data of a specific flavour from the drag service.
266 function FlavourToXfer(aData, aLength, aFlavour)
268 return new TransferData([new FlavourData(aData, aLength, aFlavour)]);
271 var transferUtils = {
273 retrieveURLFromData: function (aData, flavour)
278 case "text/x-moz-text-internal":
279 return aData.replace(/^\s+|\s+$/g, "");
280 case "text/x-moz-url":
281 return ((aData instanceof Components.interfaces.nsISupportsString) ? aData.toString() : aData).split("\n")[0];
282 case "application/x-moz-file":
283 var ioService = Components.classes["@mozilla.org/network/io-service;1"]
284 .getService(Components.interfaces.nsIIOService);
285 var fileHandler = ioService.getProtocolHandler("file")
286 .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
287 return fileHandler.getURLSpecFromFile(aData);
295 * nsDragAndDrop - a convenience wrapper for nsTransferable, nsITransferable
296 * and nsIDragService/nsIDragSession.
298 * Use: map the handler functions to the 'ondraggesture', 'ondragover' and
299 * 'ondragdrop' event handlers on your XML element, e.g.
300 * <xmlelement ondraggesture="nsDragAndDrop.startDrag(event, observer);"
301 * ondragover="nsDragAndDrop.dragOver(event, observer);"
302 * ondragdrop="nsDragAndDrop.drop(event, observer);"/>
304 * You need to create an observer js object with the following member
306 * Object onDragStart (event) // called when drag initiated,
307 * // returns flavour list with data
308 * // to stuff into transferable
309 * void onDragOver (Object flavour) // called when element is dragged
310 * // over, so that it can perform
311 * // any drag-over feedback for provided
313 * void onDrop (Object data) // formatted data object dropped.
314 * Object getSupportedFlavours () // returns a flavour list so that
315 * // nsTransferable can determine
316 * // whether or not to accept drop.
319 var nsDragAndDrop = {
326 const kDSContractID = "@mozilla.org/widget/dragservice;1";
327 const kDSIID = Components.interfaces.nsIDragService;
328 this._mDS = Components.classes[kDSContractID].getService(kDSIID);
334 * void startDrag (DOMEvent aEvent, Object aDragDropObserver) ;
336 * called when a drag on an element is started.
338 * @param DOMEvent aEvent
339 * the DOM event fired by the drag init
340 * @param Object aDragDropObserver
341 * javascript object of format described above that specifies
342 * the way in which the element responds to drag events.
344 startDrag: function (aEvent, aDragDropObserver)
346 if (!("onDragStart" in aDragDropObserver))
349 const kDSIID = Components.interfaces.nsIDragService;
350 var dragAction = { action: kDSIID.DRAGDROP_ACTION_COPY + kDSIID.DRAGDROP_ACTION_MOVE + kDSIID.DRAGDROP_ACTION_LINK };
352 var transferData = { data: null };
355 aDragDropObserver.onDragStart(aEvent, transferData, dragAction);
359 return; // not a draggable item, bail!
362 if (!transferData.data) return;
363 transferData = transferData.data;
365 var dt = aEvent.dataTransfer;
368 var tds = transferData._XferID == "TransferData"
370 : transferData.dataList[count]
371 for (var i = 0; i < tds.dataList.length; ++i)
373 var currData = tds.dataList[i];
374 var currFlavour = currData.flavour.contentType;
375 var value = currData.supports;
376 if (value instanceof Components.interfaces.nsISupportsString)
377 value = value.toString();
378 dt.mozSetDataAt(currFlavour, value, count);
383 while (transferData._XferID == "TransferDataSet" &&
384 count < transferData.dataList.length);
386 dt.effectAllowed = "all";
387 // a drag targeted at a tree should instead use the treechildren so that
388 // the current selection is used as the drag feedback
389 dt.addElement(aEvent.originalTarget.localName == "treechildren" ?
390 aEvent.originalTarget : aEvent.target);
391 aEvent.stopPropagation();
395 * void dragOver (DOMEvent aEvent, Object aDragDropObserver) ;
397 * called when a drag passes over this element
399 * @param DOMEvent aEvent
400 * the DOM event fired by passing over the element
401 * @param Object aDragDropObserver
402 * javascript object of format described above that specifies
403 * the way in which the element responds to drag events.
405 dragOver: function (aEvent, aDragDropObserver)
407 if (!("onDragOver" in aDragDropObserver))
409 if (!this.checkCanDrop(aEvent, aDragDropObserver))
411 var flavourSet = aDragDropObserver.getSupportedFlavours();
412 for (var flavour in flavourSet.flavourTable)
414 if (this.mDragSession.isDataFlavorSupported(flavour))
416 aDragDropObserver.onDragOver(aEvent,
417 flavourSet.flavourTable[flavour],
419 aEvent.stopPropagation();
420 aEvent.preventDefault();
429 * void drop (DOMEvent aEvent, Object aDragDropObserver) ;
431 * called when the user drops on the element
433 * @param DOMEvent aEvent
434 * the DOM event fired by the drop
435 * @param Object aDragDropObserver
436 * javascript object of format described above that specifies
437 * the way in which the element responds to drag events.
439 drop: function (aEvent, aDragDropObserver)
441 if (!("onDrop" in aDragDropObserver))
443 if (!this.checkCanDrop(aEvent, aDragDropObserver))
446 var flavourSet = aDragDropObserver.getSupportedFlavours();
448 var dt = aEvent.dataTransfer;
450 var count = dt.mozItemCount;
451 for (var i = 0; i < count; ++i) {
452 var types = dt.mozTypesAt(i);
453 for (var j = 0; j < flavourSet.flavours.length; j++) {
454 var type = flavourSet.flavours[j].contentType;
455 // dataTransfer uses text/plain but older code used text/unicode, so
456 // switch this for compatibility
457 var modtype = (type == "text/unicode") ? "text/plain" : type;
458 if (Array.indexOf(types, modtype) >= 0) {
459 var data = dt.mozGetDataAt(modtype, i);
461 // Non-strings need some non-zero value used for their data length.
462 const kNonStringDataLength = 4;
464 var length = (typeof data == "string") ? data.length : kNonStringDataLength;
465 dataArray[i] = FlavourToXfer(data, length, flavourSet.flavourTable[type]);
472 var transferData = new TransferDataSet(dataArray)
474 // hand over to the client to respond to dropped data
475 var multiple = "canHandleMultipleItems" in aDragDropObserver && aDragDropObserver.canHandleMultipleItems;
476 var dropData = multiple ? transferData : transferData.first.first;
477 aDragDropObserver.onDrop(aEvent, dropData, this.mDragSession);
478 aEvent.stopPropagation();
482 * void dragExit (DOMEvent aEvent, Object aDragDropObserver) ;
484 * called when a drag leaves this element
486 * @param DOMEvent aEvent
487 * the DOM event fired by leaving the element
488 * @param Object aDragDropObserver
489 * javascript object of format described above that specifies
490 * the way in which the element responds to drag events.
492 dragExit: function (aEvent, aDragDropObserver)
494 if (!this.checkCanDrop(aEvent, aDragDropObserver))
496 if ("onDragExit" in aDragDropObserver)
497 aDragDropObserver.onDragExit(aEvent, this.mDragSession);
501 * void dragEnter (DOMEvent aEvent, Object aDragDropObserver) ;
503 * called when a drag enters in this element
505 * @param DOMEvent aEvent
506 * the DOM event fired by entering in the element
507 * @param Object aDragDropObserver
508 * javascript object of format described above that specifies
509 * the way in which the element responds to drag events.
511 dragEnter: function (aEvent, aDragDropObserver)
513 if (!this.checkCanDrop(aEvent, aDragDropObserver))
515 if ("onDragEnter" in aDragDropObserver)
516 aDragDropObserver.onDragEnter(aEvent, this.mDragSession);
520 * Boolean checkCanDrop (DOMEvent aEvent, Object aDragDropObserver) ;
522 * Sets the canDrop attribute for the drag session.
523 * returns false if there is no current drag session.
525 * @param DOMEvent aEvent
526 * the DOM event fired by the drop
527 * @param Object aDragDropObserver
528 * javascript object of format described above that specifies
529 * the way in which the element responds to drag events.
531 checkCanDrop: function (aEvent, aDragDropObserver)
533 if (!this.mDragSession)
534 this.mDragSession = this.mDragService.getCurrentSession();
535 if (!this.mDragSession)
537 this.mDragSession.canDrop = this.mDragSession.sourceNode != aEvent.target;
538 if ("canDrop" in aDragDropObserver)
539 this.mDragSession.canDrop &= aDragDropObserver.canDrop(aEvent, this.mDragSession);
544 * Do a security check for drag n' drop. Make sure the source document
545 * can load the dragged link.
547 * @param DOMEvent aEvent
548 * the DOM event fired by leaving the element
549 * @param Object aDragDropObserver
550 * javascript object of format described above that specifies
551 * the way in which the element responds to drag events.
552 * @param String aDraggedText
553 * the text being dragged
555 dragDropSecurityCheck: function (aEvent, aDragSession, aDraggedText)
557 // Strip leading and trailing whitespace, then try to create a
558 // URI from the dropped string. If that succeeds, we're
559 // dropping a URI and we need to do a security check to make
560 // sure the source document can load the dropped URI. We don't
561 // so much care about creating the real URI here
562 // (i.e. encoding differences etc don't matter), we just want
563 // to know if aDraggedText really is a URI.
565 aDraggedText = aDraggedText.replace(/^\s*|\s*$/g, '');
568 var ioService = Components.classes["@mozilla.org/network/io-service;1"]
569 .getService(Components.interfaces.nsIIOService);
571 uri = ioService.newURI(aDraggedText, null, null);
578 // aDraggedText is a URI, do the security check.
579 const nsIScriptSecurityManager = Components.interfaces
580 .nsIScriptSecurityManager;
581 var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
582 .getService(nsIScriptSecurityManager);
585 aDragSession = this.mDragService.getCurrentSession();
587 var sourceDoc = aDragSession.sourceDocument;
588 // Use "file:///" as the default sourceURI so that drops of file:// URIs
589 // are always allowed.
590 var principal = sourceDoc ? sourceDoc.nodePrincipal
591 : secMan.getSimpleCodebasePrincipal(ioService.newURI("file:///", null, null));
594 secMan.checkLoadURIStrWithPrincipal(principal, aDraggedText,
595 nsIScriptSecurityManager.STANDARD);
597 // Stop event propagation right here.
598 aEvent.stopPropagation();
600 throw "Drop of " + aDraggedText + " denied.";