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 supports
= this.supports
;
275 if (supports
instanceof Components
.interfaces
.nsISupportsString
)
276 return supports
.data
.substring(0, this.contentLength
/2);
283 * Create a TransferData object with a single FlavourData entry. Used when
284 * unwrapping data of a specific flavour from the drag service.
286 function FlavourToXfer(aData
, aLength
, aFlavour
)
288 return new TransferData([new FlavourData(aData
, aLength
, aFlavour
)]);
291 var transferUtils
= {
293 retrieveURLFromData: function (aData
, flavour
)
298 return aData
.replace(/^\s+|\s+$/g, "");
299 case "text/x-moz-url":
300 return ((aData
instanceof Components
.interfaces
.nsISupportsString
) ? aData
.toString() : aData
).split("\n")[0];
301 case "application/x-moz-file":
302 var ioService
= Components
.classes
["@mozilla.org/network/io-service;1"]
303 .getService(Components
.interfaces
.nsIIOService
);
304 var fileHandler
= ioService
.getProtocolHandler("file")
305 .QueryInterface(Components
.interfaces
.nsIFileProtocolHandler
);
306 return fileHandler
.getURLSpecFromFile(aData
);
314 * nsDragAndDrop - a convenience wrapper for nsTransferable, nsITransferable
315 * and nsIDragService/nsIDragSession.
317 * Use: map the handler functions to the 'ondraggesture', 'ondragover' and
318 * 'ondragdrop' event handlers on your XML element, e.g.
319 * <xmlelement ondraggesture="nsDragAndDrop.startDrag(event, observer);"
320 * ondragover="nsDragAndDrop.dragOver(event, observer);"
321 * ondragdrop="nsDragAndDrop.drop(event, observer);"/>
323 * You need to create an observer js object with the following member
325 * Object onDragStart (event) // called when drag initiated,
326 * // returns flavour list with data
327 * // to stuff into transferable
328 * void onDragOver (Object flavour) // called when element is dragged
329 * // over, so that it can perform
330 * // any drag-over feedback for provided
332 * void onDrop (Object data) // formatted data object dropped.
333 * Object getSupportedFlavours () // returns a flavour list so that
334 * // nsTransferable can determine
335 * // whether or not to accept drop.
338 var nsDragAndDrop
= {
345 const kDSContractID
= "@mozilla.org/widget/dragservice;1";
346 const kDSIID
= Components
.interfaces
.nsIDragService
;
347 this._mDS
= Components
.classes
[kDSContractID
].getService(kDSIID
);
353 * void startDrag (DOMEvent aEvent, Object aDragDropObserver) ;
355 * called when a drag on an element is started.
357 * @param DOMEvent aEvent
358 * the DOM event fired by the drag init
359 * @param Object aDragDropObserver
360 * javascript object of format described above that specifies
361 * the way in which the element responds to drag events.
363 startDrag: function (aEvent
, aDragDropObserver
)
365 if (!("onDragStart" in aDragDropObserver
))
368 const kDSIID
= Components
.interfaces
.nsIDragService
;
369 var dragAction
= { action
: kDSIID
.DRAGDROP_ACTION_COPY
+ kDSIID
.DRAGDROP_ACTION_MOVE
+ kDSIID
.DRAGDROP_ACTION_LINK
};
371 var transferData
= { data
: null };
374 aDragDropObserver
.onDragStart(aEvent
, transferData
, dragAction
);
378 return; // not a draggable item, bail!
381 if (!transferData
.data
) return;
382 transferData
= transferData
.data
;
384 var dt
= aEvent
.dataTransfer
;
387 var tds
= transferData
._XferID
== "TransferData"
389 : transferData
.dataList
[count
]
390 for (var i
= 0; i
< tds
.dataList
.length
; ++i
)
392 var currData
= tds
.dataList
[i
];
393 var currFlavour
= currData
.flavour
.contentType
;
394 var value
= currData
.supports
;
395 if (value
instanceof Components
.interfaces
.nsISupportsString
)
396 value
= value
.toString();
397 dt
.mozSetDataAt(currFlavour
, value
, count
);
402 while (transferData
._XferID
== "TransferDataSet" &&
403 count
< transferData
.dataList
.length
);
405 dt
.effectAllowed
= "all";
406 // a drag targeted at a tree should instead use the treechildren so that
407 // the current selection is used as the drag feedback
408 dt
.addElement(aEvent
.originalTarget
.localName
== "treechildren" ?
409 aEvent
.originalTarget
: aEvent
.target
);
410 aEvent
.stopPropagation();
414 * void dragOver (DOMEvent aEvent, Object aDragDropObserver) ;
416 * called when a drag passes over this element
418 * @param DOMEvent aEvent
419 * the DOM event fired by passing over the element
420 * @param Object aDragDropObserver
421 * javascript object of format described above that specifies
422 * the way in which the element responds to drag events.
424 dragOver: function (aEvent
, aDragDropObserver
)
426 if (!("onDragOver" in aDragDropObserver
))
428 if (!this.checkCanDrop(aEvent
, aDragDropObserver
))
430 var flavourSet
= aDragDropObserver
.getSupportedFlavours();
431 for (var flavour
in flavourSet
.flavourTable
)
433 if (this.mDragSession
.isDataFlavorSupported(flavour
))
435 aDragDropObserver
.onDragOver(aEvent
,
436 flavourSet
.flavourTable
[flavour
],
438 aEvent
.stopPropagation();
439 aEvent
.preventDefault();
448 * void drop (DOMEvent aEvent, Object aDragDropObserver) ;
450 * called when the user drops on the element
452 * @param DOMEvent aEvent
453 * the DOM event fired by the drop
454 * @param Object aDragDropObserver
455 * javascript object of format described above that specifies
456 * the way in which the element responds to drag events.
458 drop: function (aEvent
, aDragDropObserver
)
460 if (!("onDrop" in aDragDropObserver
))
462 if (!this.checkCanDrop(aEvent
, aDragDropObserver
))
465 var flavourSet
= aDragDropObserver
.getSupportedFlavours();
467 var dt
= aEvent
.dataTransfer
;
469 var count
= dt
.mozItemCount
;
470 for (var i
= 0; i
< count
; ++i
) {
471 var types
= dt
.mozTypesAt(i
);
472 for (var j
= 0; j
< flavourSet
.flavours
.length
; j
++) {
473 var type
= flavourSet
.flavours
[j
].contentType
;
474 // dataTransfer uses text/plain but older code used text/unicode, so
475 // switch this for compatibility
476 var modtype
= (type
== "text/unicode") ? "text/plain" : type
;
477 if (Array
.indexOf(types
, modtype
) >= 0) {
478 var data
= dt
.mozGetDataAt(modtype
, i
);
480 // Non-strings need some non-zero value used for their data length.
481 const kNonStringDataLength
= 4;
483 var length
= (typeof data
== "string") ? data
.length
: kNonStringDataLength
;
484 dataArray
[i
] = FlavourToXfer(data
, length
, flavourSet
.flavourTable
[type
]);
491 var transferData
= new TransferDataSet(dataArray
)
493 // hand over to the client to respond to dropped data
494 var multiple
= "canHandleMultipleItems" in aDragDropObserver
&& aDragDropObserver
.canHandleMultipleItems
;
495 var dropData
= multiple
? transferData
: transferData
.first
.first
;
496 aDragDropObserver
.onDrop(aEvent
, dropData
, this.mDragSession
);
497 aEvent
.stopPropagation();
501 * void dragExit (DOMEvent aEvent, Object aDragDropObserver) ;
503 * called when a drag leaves this element
505 * @param DOMEvent aEvent
506 * the DOM event fired by leaving 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 dragExit: function (aEvent
, aDragDropObserver
)
513 if (!this.checkCanDrop(aEvent
, aDragDropObserver
))
515 if ("onDragExit" in aDragDropObserver
)
516 aDragDropObserver
.onDragExit(aEvent
, this.mDragSession
);
520 * void dragEnter (DOMEvent aEvent, Object aDragDropObserver) ;
522 * called when a drag enters in this element
524 * @param DOMEvent aEvent
525 * the DOM event fired by entering in the element
526 * @param Object aDragDropObserver
527 * javascript object of format described above that specifies
528 * the way in which the element responds to drag events.
530 dragEnter: function (aEvent
, aDragDropObserver
)
532 if (!this.checkCanDrop(aEvent
, aDragDropObserver
))
534 if ("onDragEnter" in aDragDropObserver
)
535 aDragDropObserver
.onDragEnter(aEvent
, this.mDragSession
);
539 * Boolean checkCanDrop (DOMEvent aEvent, Object aDragDropObserver) ;
541 * Sets the canDrop attribute for the drag session.
542 * returns false if there is no current drag session.
544 * @param DOMEvent aEvent
545 * the DOM event fired by the drop
546 * @param Object aDragDropObserver
547 * javascript object of format described above that specifies
548 * the way in which the element responds to drag events.
550 checkCanDrop: function (aEvent
, aDragDropObserver
)
552 if (!this.mDragSession
)
553 this.mDragSession
= this.mDragService
.getCurrentSession();
554 if (!this.mDragSession
)
556 this.mDragSession
.canDrop
= this.mDragSession
.sourceNode
!= aEvent
.target
;
557 if ("canDrop" in aDragDropObserver
)
558 this.mDragSession
.canDrop
&= aDragDropObserver
.canDrop(aEvent
, this.mDragSession
);
563 * Do a security check for drag n' drop. Make sure the source document
564 * can load the dragged link.
566 * @param DOMEvent aEvent
567 * the DOM event fired by leaving the element
568 * @param Object aDragDropObserver
569 * javascript object of format described above that specifies
570 * the way in which the element responds to drag events.
571 * @param String aDraggedText
572 * the text being dragged
574 dragDropSecurityCheck: function (aEvent
, aDragSession
, aDraggedText
)
576 var sourceDoc
= aDragSession
.sourceDocument
;
580 // Strip leading and trailing whitespace, then try to create a
581 // URI from the dropped string. If that succeeds, we're
582 // dropping a URI and we need to do a security check to make
583 // sure the source document can load the dropped URI. We don't
584 // so much care about creating the real URI here
585 // (i.e. encoding differences etc don't matter), we just want
586 // to know if aDraggedText really is a URI.
588 aDraggedText
= aDraggedText
.replace(/^\s*|\s*$/g, '');
593 uri
= Components
.classes
["@mozilla.org/network/io-service;1"]
594 .getService(Components
.interfaces
.nsIIOService
)
595 .newURI(aDraggedText
, null, null);
602 // aDraggedText is a URI, do the security check.
603 const nsIScriptSecurityManager
= Components
.interfaces
604 .nsIScriptSecurityManager
;
605 var secMan
= Components
.classes
["@mozilla.org/scriptsecuritymanager;1"]
606 .getService(nsIScriptSecurityManager
);
609 secMan
.checkLoadURIStr(sourceDoc
.documentURI
, aDraggedText
,
610 nsIScriptSecurityManager
.STANDARD
);
612 // Stop event propagation right here.
613 aEvent
.stopPropagation();
615 throw "Drop of " + aDraggedText
+ " denied.";