void
nsDOMDataTransfer::FillInExternalDragData(TransferItem& aItem, PRUint32 aIndex)
{
  NS_PRECONDITION(mIsExternal, "Not an external drag");

  if (!aItem.mData) {
    nsCOMPtr<nsITransferable> trans =
      do_CreateInstance("@mozilla.org/widget/transferable;1");
    if (!trans)
      return;

    NS_ConvertUTF16toUTF8 utf8format(aItem.mFormat);
    const char* format = utf8format.get();
    if (strcmp(format, "text/plain") == 0)
      format = kUnicodeMime;
    else if (strcmp(format, "text/uri-list") == 0)
      format = kURLDataMime;

    nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
    if (!dragSession)
      return;

    trans->AddDataFlavor(format);
    dragSession->GetData(trans, aIndex);

    PRUint32 length = 0;
    nsCOMPtr<nsISupports> data;
    trans->GetTransferData(format, getter_AddRefs(data), &length);
    if (!data)
      return;

    nsCOMPtr<nsIWritableVariant> variant = do_CreateInstance(NS_VARIANT_CONTRACTID);
    if (!variant)
      return;

    nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data);
    if (supportsstr) {
      nsAutoString str;
      supportsstr->GetData(str);
      variant->SetAsAString(str);
    }
    else {
      variant->SetAsISupports(data);
    }
    aItem.mData = variant;
  }
}
Exemple #2
0
already_AddRefed<nsITransferable>
DataTransfer::GetTransferable(uint32_t aIndex, nsILoadContext* aLoadContext)
{
  if (aIndex >= mItems.Length()) {
    return nullptr;
  }

  nsTArray<TransferItem>& item = mItems[aIndex];
  uint32_t count = item.Length();
  if (!count) {
    return nullptr;
  }

  nsCOMPtr<nsITransferable> transferable =
    do_CreateInstance("@mozilla.org/widget/transferable;1");
  if (!transferable) {
    return nullptr;
  }
  transferable->Init(aLoadContext);

  bool added = false;
  for (uint32_t f = 0; f < count; f++) {
    const TransferItem& formatitem = item[f];
    if (!formatitem.mData) { // skip empty items
      continue;
    }

    uint32_t length;
    nsCOMPtr<nsISupports> convertedData;
    if (!ConvertFromVariant(formatitem.mData, getter_AddRefs(convertedData), &length)) {
      continue;
    }

    // the underlying drag code uses text/unicode, so use that instead of text/plain
    const char* format;
    NS_ConvertUTF16toUTF8 utf8format(formatitem.mFormat);
    if (utf8format.EqualsLiteral("text/plain")) {
      format = kUnicodeMime;
    } else {
      format = utf8format.get();
    }

    // if a converter is set for a format, set the converter for the
    // transferable and don't add the item
    nsCOMPtr<nsIFormatConverter> converter = do_QueryInterface(convertedData);
    if (converter) {
      transferable->AddDataFlavor(format);
      transferable->SetConverter(converter);
      continue;
    }

    nsresult rv = transferable->SetTransferData(format, convertedData, length);
    if (NS_FAILED(rv)) {
      return nullptr;
    }

    added = true;
  }

  // only return the transferable if data was successfully added to it
  if (added) {
    return transferable.forget();
  }

  return nullptr;
}
Exemple #3
0
void
DataTransfer::FillInExternalData(TransferItem& aItem, uint32_t aIndex)
{
  NS_PRECONDITION(mIsExternal, "Not an external data transfer");

  if (aItem.mData) {
    return;
  }

  // only drag and paste events should be calling FillInExternalData
  NS_ASSERTION(mEventType != NS_CUT && mEventType != NS_COPY,
               "clipboard event with empty data");

    NS_ConvertUTF16toUTF8 utf8format(aItem.mFormat);
    const char* format = utf8format.get();
    if (strcmp(format, "text/plain") == 0)
      format = kUnicodeMime;
    else if (strcmp(format, "text/uri-list") == 0)
      format = kURLDataMime;

    nsCOMPtr<nsITransferable> trans =
      do_CreateInstance("@mozilla.org/widget/transferable;1");
    if (!trans)
      return;

  trans->Init(nullptr);
  trans->AddDataFlavor(format);

  if (mEventType == NS_PASTE) {
    MOZ_ASSERT(aIndex == 0, "index in clipboard must be 0");

    nsCOMPtr<nsIClipboard> clipboard = do_GetService("@mozilla.org/widget/clipboard;1");
    if (!clipboard || mClipboardType < 0) {
      return;
    }

    clipboard->GetData(trans, mClipboardType);
  } else {
    nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
    if (!dragSession) {
      return;
    }

#ifdef DEBUG
    // Since this is an external drag, the source document will always be null.
    nsCOMPtr<nsIDOMDocument> domDoc;
    dragSession->GetSourceDocument(getter_AddRefs(domDoc));
    MOZ_ASSERT(!domDoc);
#endif

    dragSession->GetData(trans, aIndex);
  }

    uint32_t length = 0;
    nsCOMPtr<nsISupports> data;
    trans->GetTransferData(format, getter_AddRefs(data), &length);
    if (!data)
      return;

    nsCOMPtr<nsIWritableVariant> variant = do_CreateInstance(NS_VARIANT_CONTRACTID);
    if (!variant)
      return;

    nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data);
    if (supportsstr) {
      nsAutoString str;
      supportsstr->GetData(str);
      variant->SetAsAString(str);
    }
    else {
      variant->SetAsISupports(data);
    }

    aItem.mData = variant;
  }
already_AddRefed<nsITransferable>
DataTransfer::GetTransferable(uint32_t aIndex, nsILoadContext* aLoadContext)
{
  if (aIndex >= mItems.Length()) {
    return nullptr;
  }

  nsTArray<TransferItem>& item = mItems[aIndex];
  uint32_t count = item.Length();
  if (!count) {
    return nullptr;
  }

  nsCOMPtr<nsITransferable> transferable =
    do_CreateInstance("@mozilla.org/widget/transferable;1");
  if (!transferable) {
    return nullptr;
  }
  transferable->Init(aLoadContext);

  nsCOMPtr<nsIStorageStream> storageStream;
  nsCOMPtr<nsIBinaryOutputStream> stream;

  bool added = false;
  bool handlingCustomFormats = true;
  uint32_t totalCustomLength = 0;

  const char* knownFormats[] = { kTextMime, kHTMLMime, kNativeHTMLMime, kRTFMime,
                                 kURLMime, kURLDataMime, kURLDescriptionMime, kURLPrivateMime,
                                 kPNGImageMime, kJPEGImageMime, kGIFImageMime, kNativeImageMime,
                                 kFileMime, kFilePromiseMime, kFilePromiseDirectoryMime,
                                 kMozTextInternal, kHTMLContext, kHTMLInfo };

  /*
   * Two passes are made here to iterate over all of the types. First, look for
   * any types that are not in the list of known types. For this pass, handlingCustomFormats
   * will be true. Data that corresponds to unknown types will be pulled out and
   * inserted into a single type (kCustomTypesMime) by writing the data into a stream.
   *
   * The second pass will iterate over the formats looking for known types. These are
   * added as is. The unknown types are all then inserted as a single type (kCustomTypesMime)
   * in the same position of the first custom type. This model is used to maintain the
   * format order as best as possible.
   *
   * The format of the kCustomTypesMime type is one or more of the following stored sequentially:
   *   <32-bit> type (only none or string is supported)
   *   <32-bit> length of format
   *   <wide string> format
   *   <32-bit> length of data
   *   <wide string> data
   * A type of eCustomClipboardTypeId_None ends the list, without any following data.
   */
  do {
    for (uint32_t f = 0; f < count; f++) {
      const TransferItem& formatitem = item[f];
      if (!formatitem.mData) { // skip empty items
        continue;
      }

      // If the data is of one of the well-known formats, use it directly.
      bool isCustomFormat = true;
      for (uint32_t f = 0; f < ArrayLength(knownFormats); f++) {
        if (formatitem.mFormat.EqualsASCII(knownFormats[f])) {
          isCustomFormat = false;
          break;
        }
      }

      uint32_t lengthInBytes;
      nsCOMPtr<nsISupports> convertedData;

      if (handlingCustomFormats) {
        if (!ConvertFromVariant(formatitem.mData, getter_AddRefs(convertedData), &lengthInBytes)) {
          continue;
        }

        // When handling custom types, add the data to the stream if this is a
        // custom type.
        if (isCustomFormat) {
          // If it isn't a string, just ignore it. The dataTransfer is cached in the
          // drag sesion during drag-and-drop, so non-strings will be available when
          // dragging locally.
          nsCOMPtr<nsISupportsString> str(do_QueryInterface(convertedData));
          if (str) {
            nsAutoString data;
            str->GetData(data);

            if (!stream) {
              // Create a storage stream to write to.
              NS_NewStorageStream(1024, UINT32_MAX, getter_AddRefs(storageStream));

              nsCOMPtr<nsIOutputStream> outputStream;
              storageStream->GetOutputStream(0, getter_AddRefs(outputStream));

              stream = do_CreateInstance("@mozilla.org/binaryoutputstream;1");
              stream->SetOutputStream(outputStream);
            }

            int32_t formatLength = formatitem.mFormat.Length() * sizeof(nsString::char_type);

            stream->Write32(eCustomClipboardTypeId_String);
            stream->Write32(formatLength);
            stream->WriteBytes((const char *)formatitem.mFormat.get(), formatLength);
            stream->Write32(lengthInBytes);
            stream->WriteBytes((const char *)data.get(), lengthInBytes);

            // The total size of the stream is the format length, the data length,
            // two integers to hold the lengths and one integer for the string flag.
            totalCustomLength += formatLength + lengthInBytes + (sizeof(uint32_t) * 3);
          }
        }
      } else if (isCustomFormat && stream) {
        // This is the second pass of the loop (handlingCustomFormats is false).
        // When encountering the first custom format, append all of the stream
        // at this position.

        // Write out a terminator.
        totalCustomLength += sizeof(uint32_t);
        stream->Write32(eCustomClipboardTypeId_None);

        nsCOMPtr<nsIInputStream> inputStream;
        storageStream->NewInputStream(0, getter_AddRefs(inputStream));

        RefPtr<nsStringBuffer> stringBuffer = nsStringBuffer::Alloc(totalCustomLength + 1);

        // Read the data from the string and add a null-terminator as ToString needs it.
        uint32_t amountRead;
        inputStream->Read(static_cast<char*>(stringBuffer->Data()), totalCustomLength, &amountRead);
        static_cast<char*>(stringBuffer->Data())[amountRead] = 0;

        nsCString str;
        stringBuffer->ToString(totalCustomLength, str);
        nsCOMPtr<nsISupportsCString> strSupports(do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
        strSupports->SetData(str);

        nsresult rv = transferable->SetTransferData(kCustomTypesMime, strSupports, totalCustomLength);
        if (NS_FAILED(rv)) {
          return nullptr;
        }

        added = true;

        // Clear the stream so it doesn't get used again.
        stream = nullptr;
      } else {
        // This is the second pass of the loop and a known type is encountered.
        // Add it as is.
        if (!ConvertFromVariant(formatitem.mData, getter_AddRefs(convertedData), &lengthInBytes)) {
          continue;
        }

        // The underlying drag code uses text/unicode, so use that instead of text/plain
        const char* format;
        NS_ConvertUTF16toUTF8 utf8format(formatitem.mFormat);
        if (utf8format.EqualsLiteral(kTextMime)) {
          format = kUnicodeMime;
        } else {
          format = utf8format.get();
        }

        // If a converter is set for a format, set the converter for the
        // transferable and don't add the item
        nsCOMPtr<nsIFormatConverter> converter = do_QueryInterface(convertedData);
        if (converter) {
          transferable->AddDataFlavor(format);
          transferable->SetConverter(converter);
          continue;
        }

        nsresult rv = transferable->SetTransferData(format, convertedData, lengthInBytes);
        if (NS_FAILED(rv)) {
          return nullptr;
        }

        added = true;
      }
    }

    handlingCustomFormats = !handlingCustomFormats;
  } while (!handlingCustomFormats);

  // only return the transferable if data was successfully added to it
  if (added) {
    return transferable.forget();
  }

  return nullptr;
}
void
DataTransferItem::FillInExternalData()
{
  if (mData) {
    return;
  }

  NS_ConvertUTF16toUTF8 utf8format(mType);
  const char* format = utf8format.get();
  if (strcmp(format, "text/plain") == 0) {
    format = kUnicodeMime;
  } else if (strcmp(format, "text/uri-list") == 0) {
    format = kURLDataMime;
  }

  nsCOMPtr<nsITransferable> trans =
    do_CreateInstance("@mozilla.org/widget/transferable;1");
  if (NS_WARN_IF(!trans)) {
    return;
  }

  trans->Init(nullptr);
  trans->AddDataFlavor(format);

  if (mDataTransfer->GetEventMessage() == ePaste) {
    MOZ_ASSERT(mIndex == 0, "index in clipboard must be 0");

    nsCOMPtr<nsIClipboard> clipboard =
      do_GetService("@mozilla.org/widget/clipboard;1");
    if (!clipboard || mDataTransfer->ClipboardType() < 0) {
      return;
    }

    nsresult rv = clipboard->GetData(trans, mDataTransfer->ClipboardType());
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return;
    }
  } else {
    nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
    if (!dragSession) {
      return;
    }

    nsresult rv = dragSession->GetData(trans, mIndex);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return;
    }
  }

  uint32_t length = 0;
  nsCOMPtr<nsISupports> data;
  nsresult rv = trans->GetTransferData(format, getter_AddRefs(data), &length);
  if (NS_WARN_IF(NS_FAILED(rv) || !data)) {
    return;
  }

  // Fill the variant
  RefPtr<nsVariantCC> variant = new nsVariantCC();

  eKind oldKind = Kind();
  if (oldKind == KIND_FILE) {
    // Because this is an external piece of data, mType is one of kFileMime,
    // kPNGImageMime, kJPEGImageMime, or kGIFImageMime. Some of these types
    // are passed in as a nsIInputStream which must be converted to a
    // dom::File before storing.
    if (nsCOMPtr<nsIInputStream> istream = do_QueryInterface(data)) {
      RefPtr<File> file = CreateFileFromInputStream(istream);
      if (NS_WARN_IF(!file)) {
        return;
      }
      data = do_QueryObject(file);
    }

    variant->SetAsISupports(data);
  } else {
    // We have an external piece of string data. Extract it and store it in the variant
    MOZ_ASSERT(oldKind == KIND_STRING);

    nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data);
    if (supportsstr) {
      nsAutoString str;
      supportsstr->GetData(str);
      variant->SetAsAString(str);
    } else {
      nsCOMPtr<nsISupportsCString> supportscstr = do_QueryInterface(data);
      if (supportscstr) {
        nsAutoCString str;
        supportscstr->GetData(str);
        variant->SetAsACString(str);
      }
    }
  }

  SetData(variant);

#ifdef DEBUG
  if (oldKind != Kind()) {
    NS_WARNING("Clipboard data provided by the OS does not match predicted kind");
  }
#endif
}
already_AddRefed<nsITransferable>
DataTransfer::GetTransferable(uint32_t aIndex, nsILoadContext* aLoadContext)
{
  if (aIndex >= MozItemCount()) {
    return nullptr;
  }

  const nsTArray<RefPtr<DataTransferItem>>& item = *mItems->MozItemsAt(aIndex);
  uint32_t count = item.Length();
  if (!count) {
    return nullptr;
  }

  nsCOMPtr<nsITransferable> transferable =
    do_CreateInstance("@mozilla.org/widget/transferable;1");
  if (!transferable) {
    return nullptr;
  }
  transferable->Init(aLoadContext);

  nsCOMPtr<nsIStorageStream> storageStream;
  nsCOMPtr<nsIBinaryOutputStream> stream;

  bool added = false;
  bool handlingCustomFormats = true;

  // When writing the custom data, we need to ensure that there is sufficient
  // space for a (uint32_t) data ending type, and the null byte character at
  // the end of the nsCString. We claim that space upfront and store it in
  // baseLength. This value will be set to zero if a write error occurs
  // indicating that the data and length are no longer valid.
  const uint32_t baseLength = sizeof(uint32_t) + 1;
  uint32_t totalCustomLength = baseLength;

  const char* knownFormats[] = {
    kTextMime, kHTMLMime, kNativeHTMLMime, kRTFMime,
    kURLMime, kURLDataMime, kURLDescriptionMime, kURLPrivateMime,
    kPNGImageMime, kJPEGImageMime, kGIFImageMime, kNativeImageMime,
    kFileMime, kFilePromiseMime, kFilePromiseURLMime,
    kFilePromiseDestFilename, kFilePromiseDirectoryMime,
    kMozTextInternal, kHTMLContext, kHTMLInfo };

  /*
   * Two passes are made here to iterate over all of the types. First, look for
   * any types that are not in the list of known types. For this pass,
   * handlingCustomFormats will be true. Data that corresponds to unknown types
   * will be pulled out and inserted into a single type (kCustomTypesMime) by
   * writing the data into a stream.
   *
   * The second pass will iterate over the formats looking for known types.
   * These are added as is. The unknown types are all then inserted as a single
   * type (kCustomTypesMime) in the same position of the first custom type. This
   * model is used to maintain the format order as best as possible.
   *
   * The format of the kCustomTypesMime type is one or more of the following
   * stored sequentially:
   *   <32-bit> type (only none or string is supported)
   *   <32-bit> length of format
   *   <wide string> format
   *   <32-bit> length of data
   *   <wide string> data
   * A type of eCustomClipboardTypeId_None ends the list, without any following
   * data.
   */
  do {
    for (uint32_t f = 0; f < count; f++) {
      RefPtr<DataTransferItem> formatitem = item[f];
      nsCOMPtr<nsIVariant> variant = formatitem->DataNoSecurityCheck();
      if (!variant) { // skip empty items
        continue;
      }

      nsAutoString type;
      formatitem->GetInternalType(type);

      // If the data is of one of the well-known formats, use it directly.
      bool isCustomFormat = true;
      for (uint32_t f = 0; f < ArrayLength(knownFormats); f++) {
        if (type.EqualsASCII(knownFormats[f])) {
          isCustomFormat = false;
          break;
        }
      }

      uint32_t lengthInBytes;
      nsCOMPtr<nsISupports> convertedData;

      if (handlingCustomFormats) {
        if (!ConvertFromVariant(variant, getter_AddRefs(convertedData),
                                &lengthInBytes)) {
          continue;
        }

        // When handling custom types, add the data to the stream if this is a
        // custom type. If totalCustomLength is 0, then a write error occurred
        // on a previous item, so ignore any others.
        if (isCustomFormat && totalCustomLength > 0) {
          // If it isn't a string, just ignore it. The dataTransfer is cached in
          // the drag sesion during drag-and-drop, so non-strings will be
          // available when dragging locally.
          nsCOMPtr<nsISupportsString> str(do_QueryInterface(convertedData));
          if (str) {
            nsAutoString data;
            str->GetData(data);

            if (!stream) {
              // Create a storage stream to write to.
              NS_NewStorageStream(1024, UINT32_MAX, getter_AddRefs(storageStream));

              nsCOMPtr<nsIOutputStream> outputStream;
              storageStream->GetOutputStream(0, getter_AddRefs(outputStream));

              stream = do_CreateInstance("@mozilla.org/binaryoutputstream;1");
              stream->SetOutputStream(outputStream);
            }

            CheckedInt<uint32_t> formatLength =
              CheckedInt<uint32_t>(type.Length()) * sizeof(nsString::char_type);

            // The total size of the stream is the format length, the data
            // length, two integers to hold the lengths and one integer for
            // the string flag. Guard against large data by ignoring any that
            // don't fit.
            CheckedInt<uint32_t> newSize = formatLength + totalCustomLength +
                                           lengthInBytes + (sizeof(uint32_t) * 3);
            if (newSize.isValid()) {
              // If a write error occurs, set totalCustomLength to 0 so that
              // further processing gets ignored.
              nsresult rv = stream->Write32(eCustomClipboardTypeId_String);
              if (NS_WARN_IF(NS_FAILED(rv))) {
                totalCustomLength = 0;
                continue;
              }
              rv = stream->Write32(formatLength.value());
              if (NS_WARN_IF(NS_FAILED(rv))) {
                totalCustomLength = 0;
                continue;
              }
              rv = stream->WriteBytes((const char *)type.get(), formatLength.value());
              if (NS_WARN_IF(NS_FAILED(rv))) {
                totalCustomLength = 0;
                continue;
              }
              rv = stream->Write32(lengthInBytes);
              if (NS_WARN_IF(NS_FAILED(rv))) {
                totalCustomLength = 0;
                continue;
              }
              rv = stream->WriteBytes((const char *)data.get(), lengthInBytes);
              if (NS_WARN_IF(NS_FAILED(rv))) {
                totalCustomLength = 0;
                continue;
              }

              totalCustomLength = newSize.value();
            }
          }
        }
      } else if (isCustomFormat && stream) {
        // This is the second pass of the loop (handlingCustomFormats is false).
        // When encountering the first custom format, append all of the stream
        // at this position. If totalCustomLength is 0 indicating a write error
        // occurred, or no data has been added to it, don't output anything,
        if (totalCustomLength > baseLength) {
          // Write out an end of data terminator.
          nsresult rv = stream->Write32(eCustomClipboardTypeId_None);
          if (NS_SUCCEEDED(rv)) {
            nsCOMPtr<nsIInputStream> inputStream;
            storageStream->NewInputStream(0, getter_AddRefs(inputStream));

            RefPtr<nsStringBuffer> stringBuffer =
              nsStringBuffer::Alloc(totalCustomLength);

            // Subtract off the null terminator when reading.
            totalCustomLength--;

            // Read the data from the stream and add a null-terminator as
            // ToString needs it.
            uint32_t amountRead;
            rv = inputStream->Read(static_cast<char*>(stringBuffer->Data()),
                              totalCustomLength, &amountRead);
            if (NS_SUCCEEDED(rv)) {
              static_cast<char*>(stringBuffer->Data())[amountRead] = 0;

              nsCString str;
              stringBuffer->ToString(totalCustomLength, str);
              nsCOMPtr<nsISupportsCString>
                strSupports(do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
              strSupports->SetData(str);

              nsresult rv = transferable->SetTransferData(kCustomTypesMime,
                                                          strSupports,
                                                          totalCustomLength);
              if (NS_FAILED(rv)) {
                return nullptr;
              }

              added = true;
            }
          }
        }

        // Clear the stream so it doesn't get used again.
        stream = nullptr;
      } else {
        // This is the second pass of the loop and a known type is encountered.
        // Add it as is.
        if (!ConvertFromVariant(variant, getter_AddRefs(convertedData),
                                &lengthInBytes)) {
          continue;
        }

        // The underlying drag code uses text/unicode, so use that instead of
        // text/plain
        const char* format;
        NS_ConvertUTF16toUTF8 utf8format(type);
        if (utf8format.EqualsLiteral(kTextMime)) {
          format = kUnicodeMime;
        } else {
          format = utf8format.get();
        }

        // If a converter is set for a format, set the converter for the
        // transferable and don't add the item
        nsCOMPtr<nsIFormatConverter> converter =
          do_QueryInterface(convertedData);
        if (converter) {
          transferable->AddDataFlavor(format);
          transferable->SetConverter(converter);
          continue;
        }

        nsresult rv = transferable->SetTransferData(format, convertedData,
                                                    lengthInBytes);
        if (NS_FAILED(rv)) {
          return nullptr;
        }

        added = true;
      }
    }

    handlingCustomFormats = !handlingCustomFormats;
  } while (!handlingCustomFormats);

  // only return the transferable if data was successfully added to it
  if (added) {
    return transferable.forget();
  }

  return nullptr;
}
void
nsDOMDataTransfer::GetTransferables(nsISupportsArray** aArray)
{
  *aArray = nsnull;

  nsCOMPtr<nsISupportsArray> transArray =
    do_CreateInstance("@mozilla.org/supports-array;1");
  if (!transArray)
    return;

  PRBool added = PR_FALSE;
  PRUint32 count = mItems.Length();
  for (PRUint32 i = 0; i < count; i++) {

    nsTArray<TransferItem>& item = mItems[i];
    PRUint32 count = item.Length();
    if (!count)
      continue;

    nsCOMPtr<nsITransferable> transferable =
      do_CreateInstance("@mozilla.org/widget/transferable;1");
    if (!transferable)
      return;

    for (PRUint32 f = 0; f < count; f++) {
      TransferItem& formatitem = item[f];
      if (!formatitem.mData) // skip empty items
        continue;

      PRUint32 length;
      nsCOMPtr<nsISupports> convertedData;
      if (!ConvertFromVariant(formatitem.mData, getter_AddRefs(convertedData), &length))
        continue;

      // the underlying drag code uses text/unicode, so use that instead of text/plain
      const char* format;
      NS_ConvertUTF16toUTF8 utf8format(formatitem.mFormat);
      if (utf8format.EqualsLiteral("text/plain"))
        format = kUnicodeMime;
      else
        format = utf8format.get();

      // if a converter is set for a format, set the converter for the
      // transferable and don't add the item
      nsCOMPtr<nsIFormatConverter> converter = do_QueryInterface(convertedData);
      if (converter) {
        transferable->AddDataFlavor(format);
        transferable->SetConverter(converter);
        continue;
      }

      nsresult rv = transferable->SetTransferData(format, convertedData, length);
      if (NS_FAILED(rv))
        return;

      added = PR_TRUE;
    }

    // only append the transferable if data was successfully added to it
    if (added)
      transArray->AppendElement(transferable);
  }

  NS_ADDREF(*aArray = transArray);
}