You are currently viewing a snapshot of www.mozilla.org taken on April 21, 2008. Most of this content is highly out of date (some pages haven't been updated since the project began in 1998) and exists for historical purposes only. If there are any pages on this archive site that you think should be added back to www.mozilla.org, please file a bug.



Introduction To The Clipboard and Drag & Drop

Feature Owner
Mike Pinkerton

Overview

As far as Mozilla is concerned, transfering data between applications via the clipboard and drag and drop are virtually identical, even though the OS-specific mechanisms under the hood are very different. The goal is to provide a uniform API for inter- and intra-application communication regardless of the transport mechanism used. This document covers that API, and subsequent documents describe the various services in detail.

The meat of this API is nsITransferable, an interface to an object that contains various representations, or "flavors," of a piece of data. Each transferable holds only one item and its various flavors.

  • Data Flavors
  • Data Objects
  • Converters
  • Using The Transferable

Data Flavors

Each piece of data, such as a hunk of text or a single bookmark item in a tree, can (and should) have multiple representations of the data of varying (usually decreasing) fidelity. This allows other applications to still be able to process the data even if they don't understand the richest representation used by mozilla. For example, the hunk of text might have html styled text, the stripped-down plain text representation, and possibly a gif illustrating the text. Mozilla knows what to do with the styled html, but SimpleText, for example, does not, yet we still want to be able to communicate with SimpleText.

Each representation of the data is referred to as a "flavor." When referring to data in the transferable, you need to know the flavor. When you can accept a variety of flavors, there are mechanisms for asking for the best one.

Even though there is a flavor "text/plain" to represent plain ASCII text, clients of the Clipboard and the Drag and Drop Services should not use it -- use "text/unicode" instead. Conversion to and from "text/plain" is performed automatically within these services. Assume all your data is double-byte and life will be pure and good. Please fix any Clipboard/D&D code that uses "text/plain" directly ASAP, as it is not guaranteed to work in the future.

Data Objects

In order to work with XPConnect and allow writing clipboard/drag&drop code in JavaScript, the actual must be wrapped in typed objects. The two most common of these objects are nsISupportsString and nsISupportsWString for one byte and two byte strings, respectively.

These "wrapper" objects must be created using the component manager and the actual data to transfer must be placed within them before passing the data to the Transferable object. Here's a code snipped in JavaScript to do this.

// create the wrapper and QI it to the right interface
var wrapper =
  Components.classes["component://netscape/supports-string"].createInstance(Components.interfaces.nsISupportsString);
if ( wrapper ) {
  // ... get the data, place it in |id|
  wrapper.data = id;
}

Converters

Talk about data converters

Using The Transferable

Placing Data Into The Transferable

The basic usage pattern for adding data to the transferable is as follows:

  1. Create the Transferable object
  2. Register the approprate data flavors
  3. Create the data objects for each flavor (as above)
  4. Add the data objects to the transferable
  5. Pass the transferable to a service that will use the data, such as the clipboard or drag&drop services

Note that the flavor must be registered with the transferable before data of that type can be added. Why? That seems quite stupid. Shouldn't it just add it if it can't find it? Also note that the length parameter of setTransferData() is in bytes, not characters, so for double-byte strings, you need to make sure you do the math correctly.

// 1. create the transferable
var trans =
  Components.classes["component://netscape/widget/transferable"].createInstance(Components.interfaces.nsITransferable);
if ( trans ) {

  // 2. register the data flavors
  trans.addDataFlavor("text/html");
  trans.addDataFlavor("text/unicode");

  // 3. create the data objects
  var textWrapper =
    Components.classes["component://netscape/supports-wstring"].createInstance(Components.interfaces.nsISupportsWString);
  var htmlWrapper =
    Components.classes["component://netscape/supports-wstring"].createInstance(Components.interfaces.nsISupportsWString);

  if ( textWrapper && htmlWrapper ) {
    // get the data
    textWrapper.data = plainTextRepresentation;
    htmlWrapper.data = htmlRepresentation;

    // 4. add data objects to transferable
    trans.setTransferData ( "text/html", htmlWrapper, id.length*2 );  // double byte data (len*2)
    trans.setTransferData ( "text/unicode", textWrapper, id.length );  // double byte data (len*2)

  }
}

Retrieving Data From The Transferable

There are two basic cases from retrieving data from the transferable: when you know exactly what you want and when you have a list of several flavors you support and you want the best one available (the more common case, probably). In either case, the basic steps are similiar:

  1. Create the Transferable object
  2. Register the approprate data flavors (see below)
  3. Pass the transferable a service that will fill in the data, such as the clipboard or drag and drop service.
  4. Ask the transferable for the data (see below)

The only tricky part (which only applies to JavaScript) is that since the objects returned by get[Any]TransferData() are out parameters, you must create new JS objects to hold the result and the length. After these objects are filled in, you can get at the actual out parameter values by accessing the value member of these objects.

When you know exactly what you're looking for

This is the simple case or the case where you only care about one flavor. For this, use getTransferData().

// 1. create the transferable
var trans =
  Components.classes["component://netscape/widget/transferable"].createInstance(Components.interfaces.nsITransferable);
if ( trans ) {

  // 2. register the data flavor you want
  trans.addDataFlavor("text/unicode");

  // 3. ...pass transferable to clipboard, etc...

  // 4. ask transferable for the data. Need to create new JS
  //    objects for the out params.
  var dataObj = new Object();
  var len = new Object();
  trans.getTransferData ( "text/unicode", dataObj, len );
  if ( dataObj )
    dataObj = dataObj.value.QueryInterface(Components.interfaces.nsISupportsWString);
  if ( dataObj ) {

    // do something with the data
    var id = dataObj.data;

  }
}

When you want the best available

The most common case is where a client supports a variety of flavors (mozilla flavors plus some from other applications), but certainly has a preference about which flavors it wants have if they are present. For this, use getAnyTransferData().

In order for getAnyTransferData() to work correctly, the order in which you register them is very important. You must register the flavors you are interested in from most interested to least interested (usually highest fidelity to lowest, but not necessarily). Haphazardly registering flavors in random order will cause you to not get the flavor you are expecting.

// 1. create the transferable
var trans =
  Components.classes["component://netscape/widget/transferable"].createInstance(Components.interfaces.nsITransferable);
if ( trans ) {

  // 2. register the data flavors you want, highest fidelity first!
  trans.addDataFlavor("text/html");
  trans.addDataFlavor("text/unicode");

  // 3. ...pass transferable to clipboard, etc...

  // 4. ask transferable for the best flavor. Need to create new JS
  //    objects for the out params.
  var dataObj = new Object();
  var bestFlavor = new Object();
  var len = new Object();
  trans.getAnyTransferData ( bestFlavor, dataObj, len );
  if ( bestFlavor.value == "text/html" ||
         bestFlavor.value == "text/unicode" ) {
    if ( dataObj )
      dataObj = dataObj.value.QueryInterface(Components.interfaces.nsISupportsWString);
    if ( dataObj ) {
      // ...do something with the data. remember len is in bytes, not chars
      var id = dataObj.data.substring(0, len.value / 2);
    }
  }
}