toolkit/content/nsDragAndDrop.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     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 ////////////////////////////////////////////////////////////////////////
     8 //
     9 // USE OF THIS API FOR DRAG AND DROP IS DEPRECATED!
    10 // Do not use this file for new code.
    11 //
    12 // For documentation about what to use instead, see:
    13 //   http://developer.mozilla.org/En/DragDrop/Drag_and_Drop
    14 //
    15 ////////////////////////////////////////////////////////////////////////
    18 /** 
    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
    23  **/ 
    25 var nsTransferable = {
    26   /**
    27    * nsITransferable set (TransferData aTransferData) ;
    28    *
    29    * Creates a transferable with data for a list of supported types ("flavours")
    30    * 
    31    * @param TransferData aTransferData
    32    *        a javascript object in the format described above 
    33    **/ 
    34   set: function (aTransferDataSet)
    35     {
    36       var trans = this.createTransferable();
    37       for (var i = 0; i < aTransferDataSet.dataList.length; ++i) 
    38         {
    39           var currData = aTransferDataSet.dataList[i];
    40           var currFlavour = currData.flavour.contentType;
    41           trans.addDataFlavor(currFlavour);
    42           var supports = null; // nsISupports data
    43           var length = 0;
    44           if (currData.flavour.dataIIDKey == "nsISupportsString")
    45             {
    46               supports = Components.classes["@mozilla.org/supports-string;1"]
    47                                    .createInstance(Components.interfaces.nsISupportsString);
    49               supports.data = currData.supports;
    50               length = supports.data.length;
    51             }
    52           else 
    53             {
    54               // non-string data.
    55               supports = currData.supports;
    56               length = 0; // kFlavorHasDataProvider
    57             }
    58           trans.setTransferData(currFlavour, supports, length * 2);
    59         }
    60       return trans;
    61     },
    63   /**
    64    * TransferData/TransferDataSet get (FlavourSet aFlavourSet, 
    65    *                                   Function aRetrievalFunc, Boolean aAnyFlag) ;
    66    *
    67    * Retrieves data from the transferable provided in aRetrievalFunc, formatted
    68    * for more convenient access.
    69    *
    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.
    79    **/
    80   get: function (aFlavourSet, aRetrievalFunc, aAnyFlag)
    81     {
    82       if (!aRetrievalFunc) 
    83         throw "No data retrieval handler provided!";
    85       var supportsArray = aRetrievalFunc(aFlavourSet);
    86       var dataArray = [];
    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++)
    93         {
    94           var trans = supportsArray.GetElementAt(i);
    95           if (!trans) continue;
    96           trans = trans.QueryInterface(Components.interfaces.nsITransferable);
    98           var data = { };
    99           var length = { };
   101           var currData = null;
   102           if (aAnyFlag)
   103             { 
   104               var flavour = { };
   105               trans.getAnyTransferData(flavour, data, length);
   106               if (data && flavour)
   107                 {
   108                   var selectedFlavour = aFlavourSet.flavourTable[flavour.value];
   109                   if (selectedFlavour) 
   110                     dataArray[i] = FlavourToXfer(data.value, length.value, selectedFlavour);
   111                 }
   112             }
   113           else
   114             {
   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);
   119             }
   120         }
   121       return new TransferDataSet(dataArray);
   122     },
   124   /** 
   125    * nsITransferable createTransferable (void) ;
   126    *
   127    * Creates and returns a transferable object.
   128    **/    
   129   createTransferable: function ()
   130     {
   131       const kXferableContractID = "@mozilla.org/widget/transferable;1";
   132       const kXferableIID = Components.interfaces.nsITransferable;
   133       var trans = Components.classes[kXferableContractID].createInstance(kXferableIID);
   134       trans.init(null);
   135       return trans;
   136     }
   137 };  
   139 /** 
   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:
   143  * 
   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)
   147  *        of data as a key. 
   148  */
   149 function FlavourSet(aFlavourList)
   150 {
   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];
   158 }
   160 FlavourSet.prototype = {
   161   appendFlavour: function (aFlavour, aFlavourIIDKey)
   162   {
   163     var flavour = new Flavour (aFlavour, aFlavourIIDKey);
   164     this.flavours.push(flavour);
   165     this.flavourTable[flavour.contentType] = flavour;
   166   }
   167 };
   169 /** 
   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.
   175  */ 
   176 function Flavour(aContentType, aDataIIDKey)
   177 {
   178   this.contentType = aContentType;
   179   this.dataIIDKey = aDataIIDKey || "nsISupportsString";
   181   this._XferID = "Flavour";
   182 }
   184 function TransferDataBase() {}
   185 TransferDataBase.prototype = {
   186   push: function (aItems)
   187   {
   188     this.dataList.push(aItems);
   189   },
   191   get first ()
   192   {
   193     return "dataList" in this && this.dataList.length ? this.dataList[0] : null;
   194   }
   195 };
   197 /** 
   198  * TransferDataSet is a list (array) of TransferData objects, which represents
   199  * data dragged from one or more elements. 
   200  */
   201 function TransferDataSet(aTransferDataList)
   202 {
   203   this.dataList = aTransferDataList || [];
   205   this._XferID = "TransferDataSet";
   206 }
   207 TransferDataSet.prototype = TransferDataBase.prototype;
   209 /** 
   210  * TransferData is a list (array) of FlavourData for all the applicable content
   211  * types associated with a drag from a single item. 
   212  */
   213 function TransferData(aFlavourDataList)
   214 {
   215   this.dataList = aFlavourDataList || [];
   217   this._XferID = "TransferData";
   218 }
   219 TransferData.prototype = {
   220   __proto__: TransferDataBase.prototype,
   222   addDataForFlavour: function (aFlavourString, aData, aLength, aDataIIDKey)
   223   {
   224     this.dataList.push(new FlavourData(aData, aLength, 
   225                        new Flavour(aFlavourString, aDataIIDKey)));
   226   }
   227 };
   229 /** 
   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. 
   237  */
   238 function FlavourData(aData, aLength, aFlavour) 
   239 {
   240   this.supports = aData;
   241   this.contentLength = aLength;
   242   this.flavour = aFlavour || null;
   244   this._XferID = "FlavourData";
   245 }
   247 FlavourData.prototype = {
   248   get data ()
   249   {
   250     if (this.flavour &&
   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);
   258     return supports;
   259   }
   260 }
   262 /** 
   263  * Create a TransferData object with a single FlavourData entry. Used when 
   264  * unwrapping data of a specific flavour from the drag service. 
   265  */
   266 function FlavourToXfer(aData, aLength, aFlavour) 
   267 {
   268   return new TransferData([new FlavourData(aData, aLength, aFlavour)]);
   269 }
   271 var transferUtils = {
   273   retrieveURLFromData: function (aData, flavour)
   274   {
   275     switch (flavour) {
   276       case "text/unicode":
   277       case "text/plain":
   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);
   288     }
   289     return null;                                                   
   290   }
   292 }
   294 /**
   295  * nsDragAndDrop - a convenience wrapper for nsTransferable, nsITransferable
   296  *                 and nsIDragService/nsIDragSession. 
   297  *
   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);"/>         
   303  *                                                                           
   304  *   You need to create an observer js object with the following member      
   305  *   functions:                                                              
   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
   312  *                                       // flavour                          
   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. 
   317  **/   
   319 var nsDragAndDrop = {
   321   _mDS: null,
   322   get mDragService()
   323     {
   324       if (!this._mDS) 
   325         {
   326           const kDSContractID = "@mozilla.org/widget/dragservice;1";
   327           const kDSIID = Components.interfaces.nsIDragService;
   328           this._mDS = Components.classes[kDSContractID].getService(kDSIID);
   329         }
   330       return this._mDS;
   331     },
   333   /**
   334    * void startDrag (DOMEvent aEvent, Object aDragDropObserver) ;
   335    *
   336    * called when a drag on an element is started.
   337    *
   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.
   343    **/  
   344   startDrag: function (aEvent, aDragDropObserver)
   345     {
   346       if (!("onDragStart" in aDragDropObserver))
   347         return;
   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 };
   353       try 
   354         {
   355           aDragDropObserver.onDragStart(aEvent, transferData, dragAction);
   356         }
   357       catch (e) 
   358         {
   359           return;  // not a draggable item, bail!
   360         }
   362       if (!transferData.data) return;
   363       transferData = transferData.data;
   365       var dt = aEvent.dataTransfer;
   366       var count = 0;
   367       do {
   368         var tds = transferData._XferID == "TransferData" 
   369                                          ? transferData 
   370                                          : transferData.dataList[count]
   371         for (var i = 0; i < tds.dataList.length; ++i) 
   372         {
   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);
   379         }
   381         count++;
   382       }
   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();
   392     },
   394   /** 
   395    * void dragOver (DOMEvent aEvent, Object aDragDropObserver) ;
   396    *
   397    * called when a drag passes over this element
   398    *
   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.
   404    **/
   405   dragOver: function (aEvent, aDragDropObserver)
   406     { 
   407       if (!("onDragOver" in aDragDropObserver)) 
   408         return;
   409       if (!this.checkCanDrop(aEvent, aDragDropObserver))
   410         return;
   411       var flavourSet = aDragDropObserver.getSupportedFlavours();
   412       for (var flavour in flavourSet.flavourTable)
   413         {
   414           if (this.mDragSession.isDataFlavorSupported(flavour))
   415             {
   416               aDragDropObserver.onDragOver(aEvent, 
   417                                            flavourSet.flavourTable[flavour], 
   418                                            this.mDragSession);
   419               aEvent.stopPropagation();
   420               aEvent.preventDefault();
   421               break;
   422             }
   423         }
   424     },
   426   mDragSession: null,
   428   /** 
   429    * void drop (DOMEvent aEvent, Object aDragDropObserver) ;
   430    *
   431    * called when the user drops on the element
   432    *
   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.
   438    **/
   439   drop: function (aEvent, aDragDropObserver)
   440     {
   441       if (!("onDrop" in aDragDropObserver))
   442         return;
   443       if (!this.checkCanDrop(aEvent, aDragDropObserver))
   444         return;  
   446       var flavourSet = aDragDropObserver.getSupportedFlavours();
   448       var dt = aEvent.dataTransfer;
   449       var dataArray = [];
   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);
   460             if (data) {
   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]);
   466               break;
   467             }
   468           }
   469         }
   470       }
   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();
   479     },
   481   /** 
   482    * void dragExit (DOMEvent aEvent, Object aDragDropObserver) ;
   483    *
   484    * called when a drag leaves this element
   485    *
   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.
   491    **/
   492   dragExit: function (aEvent, aDragDropObserver)
   493     {
   494       if (!this.checkCanDrop(aEvent, aDragDropObserver))
   495         return;
   496       if ("onDragExit" in aDragDropObserver)
   497         aDragDropObserver.onDragExit(aEvent, this.mDragSession);
   498     },  
   500   /** 
   501    * void dragEnter (DOMEvent aEvent, Object aDragDropObserver) ;
   502    *
   503    * called when a drag enters in this element
   504    *
   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.
   510    **/
   511   dragEnter: function (aEvent, aDragDropObserver)
   512     {
   513       if (!this.checkCanDrop(aEvent, aDragDropObserver))
   514         return;
   515       if ("onDragEnter" in aDragDropObserver)
   516         aDragDropObserver.onDragEnter(aEvent, this.mDragSession);
   517     },  
   519   /** 
   520    * Boolean checkCanDrop (DOMEvent aEvent, Object aDragDropObserver) ;
   521    *
   522    * Sets the canDrop attribute for the drag session.
   523    * returns false if there is no current drag session.
   524    *
   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.
   530    **/
   531   checkCanDrop: function (aEvent, aDragDropObserver)
   532     {
   533       if (!this.mDragSession) 
   534         this.mDragSession = this.mDragService.getCurrentSession();
   535       if (!this.mDragSession) 
   536         return false;
   537       this.mDragSession.canDrop = this.mDragSession.sourceNode != aEvent.target;
   538       if ("canDrop" in aDragDropObserver)
   539         this.mDragSession.canDrop &= aDragDropObserver.canDrop(aEvent, this.mDragSession);
   540       return true;
   541     },
   543   /**
   544    * Do a security check for drag n' drop. Make sure the source document
   545    * can load the dragged link.
   546    *
   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
   554    **/
   555   dragDropSecurityCheck: function (aEvent, aDragSession, aDraggedText)
   556     {
   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, '');
   567       var uri;
   568       var ioService = Components.classes["@mozilla.org/network/io-service;1"]
   569                                 .getService(Components.interfaces.nsIIOService);
   570       try {
   571         uri = ioService.newURI(aDraggedText, null, null);
   572       } catch (e) {
   573       }
   575       if (!uri)
   576         return;
   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);
   584       if (!aDragSession)
   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));
   593       try {
   594         secMan.checkLoadURIStrWithPrincipal(principal, aDraggedText,
   595                                             nsIScriptSecurityManager.STANDARD);
   596       } catch (e) {
   597         // Stop event propagation right here.
   598         aEvent.stopPropagation();
   600         throw "Drop of " + aDraggedText + " denied.";
   601       }
   602     }
   603 };

mercurial