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
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/
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
15 # The Original Code is mozilla
.org code
.
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
.
23 # Ben Goodger
<ben
@netscape
.com
> (Original Author
)
24 # Pierre Chanial
<pierrechanial
@netscape
.net
>
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
.
38 # ***** END LICENSE BLOCK
*****
41 * nsTransferable - a wrapper for nsITransferable that simplifies
42 * javascript clipboard and drag&drop. for use in
43 * these situations you should use the nsClipboard
44 * and nsDragAndDrop wrappers for more convenience
47 var nsTransferable
= {
49 * nsITransferable set (TransferData aTransferData) ;
51 * Creates a transferable with data for a list of supported types ("flavours")
53 * @param TransferData aTransferData
54 * a javascript object in the format described above
56 set: function (aTransferDataSet
)
58 var trans
= this.createTransferable();
59 for (var i
= 0; i
< aTransferDataSet
.dataList
.length
; ++i
)
61 var currData
= aTransferDataSet
.dataList
[i
];
62 var currFlavour
= currData
.flavour
.contentType
;
63 trans
.addDataFlavor(currFlavour
);
64 var supports
= null; // nsISupports data
66 if (currData
.flavour
.dataIIDKey
== "nsISupportsString")
68 supports
= Components
.classes
["@mozilla.org/supports-string;1"]
69 .createInstance(Components
.interfaces
.nsISupportsString
);
71 supports
.data
= currData
.supports
;
72 length
= supports
.data
.length
;
77 supports
= currData
.supports
;
78 length
= 0; // kFlavorHasDataProvider
80 trans
.setTransferData(currFlavour
, supports
, length
* 2);
86 * TransferData/TransferDataSet get (FlavourSet aFlavourSet,
87 * Function aRetrievalFunc, Boolean aAnyFlag) ;
89 * Retrieves data from the transferable provided in aRetrievalFunc, formatted
90 * for more convenient access.
92 * @param FlavourSet aFlavourSet
93 * a FlavourSet object that contains a list of supported flavours.
94 * @param Function aRetrievalFunc
95 * a reference to a function that returns a nsISupportsArray of nsITransferables
96 * for each item from the specified source (clipboard/drag&drop etc)
97 * @param Boolean aAnyFlag
98 * a flag specifying whether or not a specific flavour is requested. If false,
99 * data of the type of the first flavour in the flavourlist parameter is returned,
100 * otherwise the best flavour supported will be returned.
102 get: function (aFlavourSet
, aRetrievalFunc
, aAnyFlag
)
105 throw "No data retrieval handler provided!";
107 var supportsArray
= aRetrievalFunc(aFlavourSet
);
109 var count
= supportsArray
.Count();
111 // Iterate over the number of items returned from aRetrievalFunc. For
112 // clipboard operations, this is 1, for drag and drop (where multiple
113 // items may have been dragged) this could be >1.
114 for (var i
= 0; i
< count
; i
++)
116 var trans
= supportsArray
.GetElementAt(i
);
117 if (!trans
) continue;
118 trans
= trans
.QueryInterface(Components
.interfaces
.nsITransferable
);
127 trans
.getAnyTransferData(flavour
, data
, length
);
130 var selectedFlavour
= aFlavourSet
.flavourTable
[flavour
.value
];
132 dataArray
[i
] = FlavourToXfer(data
.value
, length
.value
, selectedFlavour
);
137 var firstFlavour
= aFlavourSet
.flavours
[0];
138 trans
.getTransferData(firstFlavour
, data
, length
);
139 if (data
&& firstFlavour
)
140 dataArray
[i
] = FlavourToXfer(data
.value
, length
.value
, firstFlavour
);
143 return new TransferDataSet(dataArray
);
147 * nsITransferable createTransferable (void) ;
149 * Creates and returns a transferable object.
151 createTransferable: function ()
153 const kXferableContractID
= "@mozilla.org/widget/transferable;1";
154 const kXferableIID
= Components
.interfaces
.nsITransferable
;
155 return Components
.classes
[kXferableContractID
].createInstance(kXferableIID
);
160 * A FlavourSet is a simple type that represents a collection of Flavour objects.
161 * FlavourSet is constructed from an array of Flavours, and stores this list as
162 * an array and a hashtable. The rationale for the dual storage is as follows:
164 * Array: Ordering is important when adding data flavours to a transferable.
165 * Flavours added first are deemed to be 'preferred' by the client.
166 * Hash: Convenient lookup of flavour data using the content type (MIME type)
169 function FlavourSet(aFlavourList
)
171 this.flavours
= aFlavourList
|| [];
172 this.flavourTable
= { };
174 this._XferID
= "FlavourSet";
176 for (var i
= 0; i
< this.flavours
.length
; ++i
)
177 this.flavourTable
[this.flavours
[i
].contentType
] = this.flavours
[i
];
180 FlavourSet
.prototype = {
181 appendFlavour: function (aFlavour
, aFlavourIIDKey
)
183 var flavour
= new Flavour (aFlavour
, aFlavourIIDKey
);
184 this.flavours
.push(flavour
);
185 this.flavourTable
[flavour
.contentType
] = flavour
;
190 * A Flavour is a simple type that represents a data type that can be handled.
191 * It takes a content type (MIME type) which is used when storing data on the
192 * system clipboard/drag and drop, and an IIDKey (string interface name
193 * which is used to QI data to an appropriate form. The default interface is
194 * assumed to be wide-string.
196 function Flavour(aContentType
, aDataIIDKey
)
198 this.contentType
= aContentType
;
199 this.dataIIDKey
= aDataIIDKey
|| "nsISupportsString";
201 this._XferID
= "Flavour";
204 function TransferDataBase() {}
205 TransferDataBase
.prototype = {
206 push: function (aItems
)
208 this.dataList
.push(aItems
);
213 return "dataList" in this && this.dataList
.length
? this.dataList
[0] : null;
218 * TransferDataSet is a list (array) of TransferData objects, which represents
219 * data dragged from one or more elements.
221 function TransferDataSet(aTransferDataList
)
223 this.dataList
= aTransferDataList
|| [];
225 this._XferID
= "TransferDataSet";
227 TransferDataSet
.prototype = TransferDataBase
.prototype;
230 * TransferData is a list (array) of FlavourData for all the applicable content
231 * types associated with a drag from a single item.
233 function TransferData(aFlavourDataList
)
235 this.dataList
= aFlavourDataList
|| [];
237 this._XferID
= "TransferData";
239 TransferData
.prototype = {
240 __proto__
: TransferDataBase
.prototype,
242 addDataForFlavour: function (aFlavourString
, aData
, aLength
, aDataIIDKey
)
244 this.dataList
.push(new FlavourData(aData
, aLength
,
245 new Flavour(aFlavourString
, aDataIIDKey
)));
250 * FlavourData is a type that represents data retrieved from the system
251 * clipboard or drag and drop. It is constructed internally by the Transferable
252 * using the raw (nsISupports) data from the clipboard, the length of the data,
253 * and an object of type Flavour representing the type. Clients implementing
254 * IDragDropObserver receive an object of this type in their implementation of
255 * onDrop. They access the 'data' property to retrieve data, which is either data
256 * QI'ed to a usable form, or unicode string.
258 function FlavourData(aData
, aLength
, aFlavour
)
260 this.supports
= aData
;
261 this.contentLength
= aLength
;
262 this.flavour
= aFlavour
|| null;
264 this._XferID
= "FlavourData";
267 FlavourData
.prototype = {
271 this.flavour
.dataIIDKey
!= "nsISupportsString" )
272 return this.supports
.QueryInterface(Components
.interfaces
[this.flavour
.dataIIDKey
]);
274 var unicode
= this.supports
.QueryInterface(Components
.interfaces
.nsISupportsString
);
276 return unicode
.data
.substring(0, this.contentLength
/2);
278 return this.supports
;
285 * Create a TransferData object with a single FlavourData entry. Used when
286 * unwrapping data of a specific flavour from the drag service.
288 function FlavourToXfer(aData
, aLength
, aFlavour
)
290 return new TransferData([new FlavourData(aData
, aLength
, aFlavour
)]);
293 var transferUtils
= {
295 retrieveURLFromData: function (aData
, flavour
)
299 return aData
.replace(/^\s+|\s+$/g, "");
300 case "text/x-moz-url":
301 return aData
.toString().split("\n")[0];
302 case "application/x-moz-file":
303 var ioService
= Components
.classes
["@mozilla.org/network/io-service;1"]
304 .getService(Components
.interfaces
.nsIIOService
);
305 var fileHandler
= ioService
.getProtocolHandler("file")
306 .QueryInterface(Components
.interfaces
.nsIFileProtocolHandler
);
307 return fileHandler
.getURLSpecFromFile(aData
);
315 * nsDragAndDrop - a convenience wrapper for nsTransferable, nsITransferable
316 * and nsIDragService/nsIDragSession.
318 * Use: map the handler functions to the 'ondraggesture', 'ondragover' and
319 * 'ondragdrop' event handlers on your XML element, e.g.
320 * <xmlelement ondraggesture="nsDragAndDrop.startDrag(event, observer);"
321 * ondragover="nsDragAndDrop.dragOver(event, observer);"
322 * ondragdrop="nsDragAndDrop.drop(event, observer);"/>
324 * You need to create an observer js object with the following member
326 * Object onDragStart (event) // called when drag initiated,
327 * // returns flavour list with data
328 * // to stuff into transferable
329 * void onDragOver (Object flavour) // called when element is dragged
330 * // over, so that it can perform
331 * // any drag-over feedback for provided
333 * void onDrop (Object data) // formatted data object dropped.
334 * Object getSupportedFlavours () // returns a flavour list so that
335 * // nsTransferable can determine
336 * // whether or not to accept drop.
339 var nsDragAndDrop
= {
346 const kDSContractID
= "@mozilla.org/widget/dragservice;1";
347 const kDSIID
= Components
.interfaces
.nsIDragService
;
348 this._mDS
= Components
.classes
[kDSContractID
].getService(kDSIID
);
354 * void startDrag (DOMEvent aEvent, Object aDragDropObserver) ;
356 * called when a drag on an element is started.
358 * @param DOMEvent aEvent
359 * the DOM event fired by the drag init
360 * @param Object aDragDropObserver
361 * javascript object of format described above that specifies
362 * the way in which the element responds to drag events.
364 startDrag: function (aEvent
, aDragDropObserver
)
366 if (!("onDragStart" in aDragDropObserver
))
369 const kDSIID
= Components
.interfaces
.nsIDragService
;
370 var dragAction
= { action
: kDSIID
.DRAGDROP_ACTION_COPY
+ kDSIID
.DRAGDROP_ACTION_MOVE
+ kDSIID
.DRAGDROP_ACTION_LINK
};
372 var transferData
= { data
: null };
375 aDragDropObserver
.onDragStart(aEvent
, transferData
, dragAction
);
379 return; // not a draggable item, bail!
382 if (!transferData
.data
) return;
383 transferData
= transferData
.data
;
385 var transArray
= Components
.classes
["@mozilla.org/supports-array;1"]
386 .createInstance(Components
.interfaces
.nsISupportsArray
);
389 if (aEvent
.originalTarget
.localName
== "treechildren") {
390 // let's build the drag region
391 var tree
= aEvent
.originalTarget
.parentNode
;
393 region
= Components
.classes
["@mozilla.org/gfx/region;1"].createInstance(Components
.interfaces
.nsIScriptableRegion
);
395 var obo
= tree
.treeBoxObject
;
396 var bo
= obo
.treeBody
.boxObject
;
397 var sel
= obo
.view
.selection
;
401 var rowHeight
= obo
.rowHeight
;
402 var rowWidth
= bo
.width
;
404 //add a rectangle for each visible selected row
405 for (var i
= obo
.getFirstVisibleRow(); i
<= obo
.getLastVisibleRow(); i
++)
407 if (sel
.isSelected(i
))
408 region
.unionRect(rowX
, rowY
, rowWidth
, rowHeight
);
409 rowY
= rowY
+ rowHeight
;
412 //and finally, clip the result to be sure we don't spill over...
413 region
.intersectRect(bo
.x
, bo
.y
, bo
.width
, bo
.height
);
415 dump("Error while building selection region: " + ex
+ "\n");
423 var trans
= nsTransferable
.set(transferData
._XferID
== "TransferData"
425 : transferData
.dataList
[count
++]);
426 transArray
.AppendElement(trans
.QueryInterface(Components
.interfaces
.nsISupports
));
428 while (transferData
._XferID
== "TransferDataSet" &&
429 count
< transferData
.dataList
.length
);
432 this.mDragService
.invokeDragSessionWithImage(aEvent
.target
, transArray
,
433 region
, dragAction
.action
,
437 // this could be because the user pressed escape to
438 // cancel the drag. even if it's not, there's not much
439 // we can do, so be silent.
441 aEvent
.stopPropagation();
445 * void dragOver (DOMEvent aEvent, Object aDragDropObserver) ;
447 * called when a drag passes over this element
449 * @param DOMEvent aEvent
450 * the DOM event fired by passing over the element
451 * @param Object aDragDropObserver
452 * javascript object of format described above that specifies
453 * the way in which the element responds to drag events.
455 dragOver: function (aEvent
, aDragDropObserver
)
457 if (!("onDragOver" in aDragDropObserver
))
459 if (!this.checkCanDrop(aEvent
, aDragDropObserver
))
461 var flavourSet
= aDragDropObserver
.getSupportedFlavours();
462 for (var flavour
in flavourSet
.flavourTable
)
464 if (this.mDragSession
.isDataFlavorSupported(flavour
))
466 aDragDropObserver
.onDragOver(aEvent
,
467 flavourSet
.flavourTable
[flavour
],
469 aEvent
.stopPropagation();
478 * void drop (DOMEvent aEvent, Object aDragDropObserver) ;
480 * called when the user drops on the element
482 * @param DOMEvent aEvent
483 * the DOM event fired by the drop
484 * @param Object aDragDropObserver
485 * javascript object of format described above that specifies
486 * the way in which the element responds to drag events.
488 drop: function (aEvent
, aDragDropObserver
)
490 if (!("onDrop" in aDragDropObserver
))
492 if (!this.checkCanDrop(aEvent
, aDragDropObserver
))
494 if (this.mDragSession
.canDrop
) {
495 var flavourSet
= aDragDropObserver
.getSupportedFlavours();
496 var transferData
= nsTransferable
.get(flavourSet
, this.getDragData
, true);
497 // hand over to the client to respond to dropped data
498 var multiple
= "canHandleMultipleItems" in aDragDropObserver
&& aDragDropObserver
.canHandleMultipleItems
;
499 var dropData
= multiple
? transferData
: transferData
.first
.first
;
500 aDragDropObserver
.onDrop(aEvent
, dropData
, this.mDragSession
);
502 aEvent
.stopPropagation();
506 * void dragExit (DOMEvent aEvent, Object aDragDropObserver) ;
508 * called when a drag leaves this element
510 * @param DOMEvent aEvent
511 * the DOM event fired by leaving the element
512 * @param Object aDragDropObserver
513 * javascript object of format described above that specifies
514 * the way in which the element responds to drag events.
516 dragExit: function (aEvent
, aDragDropObserver
)
518 if (!this.checkCanDrop(aEvent
, aDragDropObserver
))
520 if ("onDragExit" in aDragDropObserver
)
521 aDragDropObserver
.onDragExit(aEvent
, this.mDragSession
);
525 * void dragEnter (DOMEvent aEvent, Object aDragDropObserver) ;
527 * called when a drag enters in this element
529 * @param DOMEvent aEvent
530 * the DOM event fired by entering in the element
531 * @param Object aDragDropObserver
532 * javascript object of format described above that specifies
533 * the way in which the element responds to drag events.
535 dragEnter: function (aEvent
, aDragDropObserver
)
537 if (!this.checkCanDrop(aEvent
, aDragDropObserver
))
539 if ("onDragEnter" in aDragDropObserver
)
540 aDragDropObserver
.onDragEnter(aEvent
, this.mDragSession
);
544 * nsISupportsArray getDragData (Object aFlavourList)
546 * Creates a nsISupportsArray of all droppable items for the given
547 * set of supported flavours.
549 * @param FlavourSet aFlavourSet
550 * formatted flavour list.
552 getDragData: function (aFlavourSet
)
554 var supportsArray
= Components
.classes
["@mozilla.org/supports-array;1"]
555 .createInstance(Components
.interfaces
.nsISupportsArray
);
557 for (var i
= 0; i
< nsDragAndDrop
.mDragSession
.numDropItems
; ++i
)
559 var trans
= nsTransferable
.createTransferable();
560 for (var j
= 0; j
< aFlavourSet
.flavours
.length
; ++j
)
561 trans
.addDataFlavor(aFlavourSet
.flavours
[j
].contentType
);
562 nsDragAndDrop
.mDragSession
.getData(trans
, i
);
563 supportsArray
.AppendElement(trans
);
565 return supportsArray
;
569 * Boolean checkCanDrop (DOMEvent aEvent, Object aDragDropObserver) ;
571 * Sets the canDrop attribute for the drag session.
572 * returns false if there is no current drag session.
574 * @param DOMEvent aEvent
575 * the DOM event fired by the drop
576 * @param Object aDragDropObserver
577 * javascript object of format described above that specifies
578 * the way in which the element responds to drag events.
580 checkCanDrop: function (aEvent
, aDragDropObserver
)
582 if (!this.mDragSession
)
583 this.mDragSession
= this.mDragService
.getCurrentSession();
584 if (!this.mDragSession
)
586 this.mDragSession
.canDrop
= this.mDragSession
.sourceNode
!= aEvent
.target
;
587 if ("canDrop" in aDragDropObserver
)
588 this.mDragSession
.canDrop
&= aDragDropObserver
.canDrop(aEvent
, this.mDragSession
);
593 * Do a security check for drag n' drop. Make sure the source document
594 * can load the dragged link.
596 * @param DOMEvent aEvent
597 * the DOM event fired by leaving the element
598 * @param Object aDragDropObserver
599 * javascript object of format described above that specifies
600 * the way in which the element responds to drag events.
601 * @param String aDraggedText
602 * the text being dragged
604 dragDropSecurityCheck: function (aEvent
, aDragSession
, aDraggedText
)
606 var sourceDoc
= aDragSession
.sourceDocument
;
610 // Strip leading and trailing whitespace, then try to create a
611 // URI from the dropped string. If that succeeds, we're
612 // dropping a URI and we need to do a security check to make
613 // sure the source document can load the dropped URI. We don't
614 // so much care about creating the real URI here
615 // (i.e. encoding differences etc don't matter), we just want
616 // to know if aDraggedText really is a URI.
618 aDraggedText
= aDraggedText
.replace(/^\s*|\s*$/g, '');
623 uri
= Components
.classes
["@mozilla.org/network/io-service;1"]
624 .getService(Components
.interfaces
.nsIIOService
)
625 .newURI(aDraggedText
, null, null);
632 // aDraggedText is a URI, do the security check.
633 const nsIScriptSecurityManager
= Components
.interfaces
634 .nsIScriptSecurityManager
;
635 var secMan
= Components
.classes
["@mozilla.org/scriptsecuritymanager;1"]
636 .getService(nsIScriptSecurityManager
);
639 secMan
.checkLoadURIStr(sourceDoc
.documentURI
, aDraggedText
,
640 nsIScriptSecurityManager
.STANDARD
);
642 // Stop event propagation right here.
643 aEvent
.stopPropagation();
645 throw "Drop of " + aDraggedText
+ " denied.";