Bug 906420 - Part 1: Implement DataTransferItem and DataTransferItemList, r=baku
authorMichael Layzell <michael@thelayzells.com>
Fri, 28 Aug 2015 15:21:08 -0400
changeset 383903 170fc45fb69209cd07ffd439e0cddc6411442dcf
parent 383902 b2409b36cd07bba045bf38dd131166c36ea3da4a
child 383904 3e50fc285df9258dce7e1b0e5199a6f4a645e29f
push id22127
push usercykesiopka.bmo@gmail.com
push dateTue, 05 Jul 2016 10:04:38 +0000
reviewersbaku
bugs906420
milestone50.0a1
Bug 906420 - Part 1: Implement DataTransferItem and DataTransferItemList, r=baku
dom/base/nsCopySupport.cpp
dom/events/DataTransfer.cpp
dom/events/DataTransfer.h
dom/events/DataTransferItem.cpp
dom/events/DataTransferItem.h
dom/events/DataTransferItemList.cpp
dom/events/DataTransferItemList.h
dom/events/EventStateManager.cpp
dom/events/moz.build
dom/events/test/chrome.ini
dom/events/test/test_DataTransferItemList.html
dom/events/test/test_dragstart.html
dom/locales/en-US/chrome/dom/dom.properties
dom/tests/mochitest/general/test_clipboard_events.html
dom/tests/mochitest/general/test_interfaces.html
dom/webidl/DataTransfer.webidl
dom/webidl/DataTransferItem.webidl
dom/webidl/DataTransferItemList.webidl
dom/webidl/moz.build
editor/libeditor/nsEditorEventListener.cpp
layout/forms/nsFileControlFrame.cpp
testing/web-platform/meta/html/dom/interfaces.html.ini
widget/gonk/nsClipboard.cpp
--- a/dom/base/nsCopySupport.cpp
+++ b/dom/base/nsCopySupport.cpp
@@ -677,18 +677,18 @@ nsCopySupport::FireClipboardEvent(EventM
   const bool chromeShell =
     docShell && docShell->ItemType() == nsIDocShellTreeItem::typeChrome;
 
   // next, fire the cut, copy or paste event
   bool doDefault = true;
   RefPtr<DataTransfer> clipboardData;
   if (chromeShell || Preferences::GetBool("dom.event.clipboardevents.enabled", true)) {
     clipboardData =
-      new DataTransfer(piWindow, aEventMessage, aEventMessage == ePaste,
-                       aClipboardType);
+      new DataTransfer(doc->GetScopeObject(), aEventMessage,
+                       aEventMessage == ePaste, aClipboardType);
 
     nsEventStatus status = nsEventStatus_eIgnore;
     InternalClipboardEvent evt(true, aEventMessage);
     evt.mClipboardData = clipboardData;
     EventDispatcher::Dispatch(content, presShell->GetPresContext(), &evt,
                               nullptr, &status);
     // If the event was cancelled, don't do the clipboard operation
     doDefault = (status != nsEventStatus_eConsumeNoDefault);
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -25,48 +25,39 @@
 #include "nsCRT.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIScriptContext.h"
 #include "nsIDocument.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsVariant.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/DataTransferBinding.h"
+#include "mozilla/dom/DataTransferItemList.h"
 #include "mozilla/dom/Directory.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/FileList.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/OSFileSystem.h"
 #include "mozilla/dom/Promise.h"
+#include "nsNetUtil.h"
 
 namespace mozilla {
 namespace dom {
 
-inline void
-ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
-                            TransferItem& aField,
-                            const char* aName,
-                            uint32_t aFlags = 0)
-{
-  ImplCycleCollectionTraverse(aCallback, aField.mData, aName, aFlags);
-}
-
 NS_IMPL_CYCLE_COLLECTION_CLASS(DataTransfer)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DataTransfer)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mFileList)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mItems)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDragTarget)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDragImage)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DataTransfer)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFileList)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mItems)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDragTarget)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDragImage)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(DataTransfer)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(DataTransfer)
@@ -100,16 +91,17 @@ DataTransfer::DataTransfer(nsISupports* 
   , mReadOnly(true)
   , mIsExternal(aIsExternal)
   , mUserCancelled(false)
   , mIsCrossDomainSubFrameDrop(false)
   , mClipboardType(aClipboardType)
   , mDragImageX(0)
   , mDragImageY(0)
 {
+  mItems = new DataTransferItemList(this, aIsExternal, false /* aIsCrossDomainSubFrameDrop */);
   // For these events, we want to be able to add data to the data transfer, so
   // clear the readonly state. Otherwise, the data is already present. For
   // external usage, cache the data from the native clipboard or drag.
   if (aEventMessage == eCut ||
       aEventMessage == eCopy ||
       aEventMessage == eDragStart ||
       aEventMessage == eLegacyDragGesture) {
     mReadOnly = false;
@@ -126,36 +118,40 @@ DataTransfer::DataTransfer(nsISupports* 
 DataTransfer::DataTransfer(nsISupports* aParent,
                            EventMessage aEventMessage,
                            const uint32_t aEffectAllowed,
                            bool aCursorState,
                            bool aIsExternal,
                            bool aUserCancelled,
                            bool aIsCrossDomainSubFrameDrop,
                            int32_t aClipboardType,
-                           nsTArray<nsTArray<TransferItem> >& aItems,
+                           DataTransferItemList* aItems,
                            Element* aDragImage,
                            uint32_t aDragImageX,
                            uint32_t aDragImageY)
   : mParent(aParent)
   , mDropEffect(nsIDragService::DRAGDROP_ACTION_NONE)
   , mEffectAllowed(aEffectAllowed)
   , mEventMessage(aEventMessage)
   , mCursorState(aCursorState)
   , mReadOnly(true)
   , mIsExternal(aIsExternal)
   , mUserCancelled(aUserCancelled)
   , mIsCrossDomainSubFrameDrop(aIsCrossDomainSubFrameDrop)
   , mClipboardType(aClipboardType)
-  , mItems(aItems)
   , mDragImage(aDragImage)
   , mDragImageX(aDragImageX)
   , mDragImageY(aDragImageY)
 {
   MOZ_ASSERT(mParent);
+  MOZ_ASSERT(aItems);
+
+  // We clone the items array after everything else, so that it has a valid
+  // mParent value
+  mItems = aItems->Clone(this);
   // The items are copied from aItems into mItems. There is no need to copy
   // the actual data in the items as the data transfer will be read only. The
   // draggesture and dragstart events are the only times when items are
   // modifiable, but those events should have been using the first constructor
   // above.
   NS_ASSERTION(aEventMessage != eLegacyDragGesture &&
                aEventMessage != eDragStart,
                "invalid event type for DataTransfer constructor");
@@ -287,112 +283,86 @@ DataTransfer::GetMozUserCancelled(bool* 
 {
   *aUserCancelled = MozUserCancelled();
   return NS_OK;
 }
 
 FileList*
 DataTransfer::GetFiles(ErrorResult& aRv)
 {
-  return GetFileListInternal(aRv, nsContentUtils::SubjectPrincipal());
-}
-
-FileList*
-DataTransfer::GetFileListInternal(ErrorResult& aRv,
-                                  nsIPrincipal* aSubjectPrincipal)
-{
-  if (mEventMessage != eDrop &&
-      mEventMessage != eLegacyDragDrop &&
-      mEventMessage != ePaste) {
-    return nullptr;
-  }
-
-  if (!mFileList) {
-    mFileList = new FileList(static_cast<nsIDOMDataTransfer*>(this));
-
-    uint32_t count = mItems.Length();
-
-    for (uint32_t i = 0; i < count; i++) {
-      nsCOMPtr<nsIVariant> variant;
-      aRv = GetDataAtInternal(NS_ConvertUTF8toUTF16(kFileMime), i,
-                              aSubjectPrincipal, getter_AddRefs(variant));
-      if (NS_WARN_IF(aRv.Failed())) {
-        return nullptr;
-      }
-
-      if (!variant) {
-        continue;
-      }
-
-      nsCOMPtr<nsISupports> supports;
-      nsresult rv = variant->GetAsISupports(getter_AddRefs(supports));
-
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        continue;
-      }
-
-      nsCOMPtr<nsIFile> file = do_QueryInterface(supports);
-
-      RefPtr<File> domFile;
-      if (file) {
-        MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default,
-                   "nsIFile objects are not expected on the content process");
-
-        bool isDir;
-        aRv = file->IsDirectory(&isDir);
-        if (NS_WARN_IF(aRv.Failed())) {
-          return nullptr;
-        }
-
-        if (isDir) {
-          continue;
-        }
-
-        domFile = File::CreateFromFile(GetParentObject(), file);
-      } else {
-        nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(supports);
-        if (!blobImpl) {
-          continue;
-        }
-
-        MOZ_ASSERT(blobImpl->IsFile());
-
-        domFile = File::Create(GetParentObject(), blobImpl);
-        MOZ_ASSERT(domFile);
-      }
-
-      mFileList->Append(domFile);
-    }
-  }
-
-  return mFileList;
+  return mItems->Files();
 }
 
 NS_IMETHODIMP
 DataTransfer::GetFiles(nsIDOMFileList** aFileList)
 {
+  if (!aFileList) {
+    return NS_ERROR_FAILURE;
+  }
+
   ErrorResult rv;
-  NS_IF_ADDREF(*aFileList =
-    GetFileListInternal(rv, nsContentUtils::GetSystemPrincipal()));
-  return rv.StealNSResult();
+  RefPtr<FileList> files = GetFiles(rv);
+  if (NS_WARN_IF(rv.Failed())) {
+    return rv.StealNSResult();
+  }
+
+  files.forget(aFileList);
+  return NS_OK;
 }
 
 already_AddRefed<DOMStringList>
-DataTransfer::Types() const
+DataTransfer::GetTypes(ErrorResult& aRv) const
 {
-  ErrorResult rv;
-  return MozTypesAt(0, rv);
+  RefPtr<DOMStringList> types = new DOMStringList();
+
+  const nsTArray<RefPtr<DataTransferItem>>* items = mItems->MozItemsAt(0);
+  if (!items || items->IsEmpty()) {
+    return types.forget();
+  }
+
+  bool addFile = false;
+  for (uint32_t i = 0; i < items->Length(); i++) {
+    DataTransferItem* item = items->ElementAt(i);
+    MOZ_ASSERT(item);
+
+    nsAutoString type;
+    item->GetType(type);
+    if (NS_WARN_IF(!types->Add(type))) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return nullptr;
+    }
+
+    if (!addFile) {
+      addFile = item->Kind() == DataTransferItem::KIND_FILE;
+    }
+  }
+
+  // If we have any files, we need to also add the "Files" type!
+  if (addFile && NS_WARN_IF(!types->Add(NS_LITERAL_STRING("Files")))) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  return types.forget();
 }
 
 NS_IMETHODIMP
 DataTransfer::GetTypes(nsISupports** aTypes)
 {
-  RefPtr<DOMStringList> types = Types();
+  if (NS_WARN_IF(!aTypes)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  ErrorResult rv;
+  RefPtr<DOMStringList> types = GetTypes(rv);
+  if (NS_WARN_IF(rv.Failed())) {
+    return rv.StealNSResult();
+  }
+
   types.forget(aTypes);
-
   return NS_OK;
 }
 
 void
 DataTransfer::GetData(const nsAString& aFormat, nsAString& aData,
                       ErrorResult& aRv)
 {
   // return an empty string if data for the format was not found
@@ -470,17 +440,17 @@ DataTransfer::SetData(const nsAString& a
 void
 DataTransfer::ClearData(const Optional<nsAString>& aFormat, ErrorResult& aRv)
 {
   if (mReadOnly) {
     aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
     return;
   }
 
-  if (mItems.Length() == 0) {
+  if (MozItemCount() == 0) {
     return;
   }
 
   if (aFormat.WasPassed()) {
     MozClearDataAtHelper(aFormat.Value(), 0, aRv);
   } else {
     MozClearDataAtHelper(EmptyString(), 0, aRv);
   }
@@ -559,39 +529,28 @@ DataTransfer::MozTypesAt(uint32_t aIndex
   if (aIndex > 0 &&
       (mEventMessage == eCut || mEventMessage == eCopy ||
        mEventMessage == ePaste)) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return nullptr;
   }
 
   RefPtr<DOMStringList> types = new DOMStringList();
-  if (aIndex < mItems.Length()) {
-    bool addFile = false;
+  if (aIndex < MozItemCount()) {
     // note that you can retrieve the types regardless of their principal
-    const nsTArray<TransferItem>& item = mItems[aIndex];
-    for (uint32_t i = 0; i < item.Length(); i++) {
-      const nsString& format = item[i].mFormat;
-      types->Add(format);
-      if (!addFile) {
-        addFile = format.EqualsASCII(kFileMime);
+    const nsTArray<RefPtr<DataTransferItem>>& items = *mItems->MozItemsAt(aIndex);
+
+    for (uint32_t i = 0; i < items.Length(); i++) {
+      nsAutoString type;
+      items[i]->GetType(type);
+      if (NS_WARN_IF(!types->Add(type))) {
+        aRv.Throw(NS_ERROR_FAILURE);
+        return nullptr;
       }
     }
-
-    if (addFile) {
-      // If this is a content caller, and a file is in the data transfer, remove
-      // the non-file types. This prevents alternate text forms of the file
-      // from being returned.
-      if (!nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
-        types->Clear();
-        types->Add(NS_LITERAL_STRING(kFileMime));
-      }
-
-      types->Add(NS_LITERAL_STRING("Files"));
-    }
   }
 
   return types.forget();
 }
 
 NS_IMETHODIMP
 DataTransfer::MozTypesAt(uint32_t aIndex, nsISupports** aTypes)
 {
@@ -616,90 +575,87 @@ DataTransfer::GetDataAtInternal(const ns
                                 nsIVariant** aData)
 {
   *aData = nullptr;
 
   if (aFormat.IsEmpty()) {
     return NS_OK;
   }
 
-  if (aIndex >= mItems.Length()) {
+  if (aIndex >= MozItemCount()) {
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
 
   // Only the first item is valid for clipboard events
   if (aIndex > 0 &&
       (mEventMessage == eCut || mEventMessage == eCopy ||
        mEventMessage == ePaste)) {
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
 
   nsAutoString format;
   GetRealFormat(aFormat, format);
 
-  nsTArray<TransferItem>& item = mItems[aIndex];
-
-  // If this is a content caller, and a file is in the data transfer, only
-  // return the file type.
-  if (!format.EqualsLiteral(kFileMime) &&
+  const nsTArray<RefPtr<DataTransferItem>>& items = *mItems->MozItemsAt(aIndex);
+  if (!aFormat.EqualsLiteral(kFileMime) &&
       !nsContentUtils::IsSystemPrincipal(aSubjectPrincipal)) {
-    uint32_t count = item.Length();
-    for (uint32_t i = 0; i < count; i++) {
-      if (item[i].mFormat.EqualsLiteral(kFileMime)) {
+    for (uint32_t i = 0; i < items.Length(); ++i) {
+      if (items[i]->IsFile()) {
         return NS_OK;
       }
     }
   }
 
   // Check if the caller is allowed to access the drag data. Callers with
   // chrome privileges can always read the data. During the
   // drop event, allow retrieving the data except in the case where the
   // source of the drag is in a child frame of the caller. In that case,
   // we only allow access to data of the same principal. During other events,
   // only allow access to the data with the same principal.
   bool checkFormatItemPrincipal = mIsCrossDomainSubFrameDrop ||
       (mEventMessage != eDrop && mEventMessage != eLegacyDragDrop &&
        mEventMessage != ePaste);
+  MOZ_ASSERT(aSubjectPrincipal);
 
-  uint32_t count = item.Length();
-  for (uint32_t i = 0; i < count; i++) {
-    TransferItem& formatitem = item[i];
-    if (formatitem.mFormat.Equals(format)) {
-      if (formatitem.mPrincipal && checkFormatItemPrincipal &&
-          !aSubjectPrincipal->Subsumes(formatitem.mPrincipal)) {
-        return NS_ERROR_DOM_SECURITY_ERR;
-      }
+  RefPtr<DataTransferItem> item = mItems->MozItemByTypeAt(format, aIndex);
+  if (!item) {
+    // The index exists but there's no data for the specified format, in this
+    // case we just return undefined
+    return NS_OK;
+  }
+
+  if (item->Principal() && checkFormatItemPrincipal &&
+      !aSubjectPrincipal->Subsumes(item->Principal())) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  nsCOMPtr<nsIVariant> data = item->Data();
+  MOZ_ASSERT(data);
 
-      if (!formatitem.mData) {
-        FillInExternalData(formatitem, aIndex);
-      } else {
-        nsCOMPtr<nsISupports> data;
-        formatitem.mData->GetAsISupports(getter_AddRefs(data));
-        // Make sure the code that is calling us is same-origin with the data.
-        nsCOMPtr<EventTarget> pt = do_QueryInterface(data);
-        if (pt) {
-          nsresult rv = NS_OK;
-          nsIScriptContext* c = pt->GetContextForEventHandlers(&rv);
-          NS_ENSURE_TRUE(c && NS_SUCCEEDED(rv), NS_ERROR_DOM_SECURITY_ERR);
-          nsIGlobalObject* go = c->GetGlobalObject();
-          NS_ENSURE_TRUE(go, NS_ERROR_DOM_SECURITY_ERR);
-          nsCOMPtr<nsIScriptObjectPrincipal> sp = do_QueryInterface(go);
-          MOZ_ASSERT(sp, "This cannot fail on the main thread.");
-          nsIPrincipal* dataPrincipal = sp->GetPrincipal();
-          NS_ENSURE_TRUE(dataPrincipal, NS_ERROR_DOM_SECURITY_ERR);
-          NS_ENSURE_TRUE(aSubjectPrincipal->Subsumes(dataPrincipal),
-                                                     NS_ERROR_DOM_SECURITY_ERR);
-        }
-      }
-      *aData = formatitem.mData;
-      NS_IF_ADDREF(*aData);
-      return NS_OK;
+  nsCOMPtr<nsISupports> isupportsData;
+  nsresult rv = data->GetAsISupports(getter_AddRefs(isupportsData));
+
+  if (NS_SUCCEEDED(rv) && isupportsData) {
+    // Make sure the code that is calling us is same-origin with the data.
+    nsCOMPtr<EventTarget> pt = do_QueryInterface(isupportsData);
+    if (pt) {
+      nsresult rv = NS_OK;
+      nsIScriptContext* c = pt->GetContextForEventHandlers(&rv);
+      NS_ENSURE_TRUE(c && NS_SUCCEEDED(rv), NS_ERROR_DOM_SECURITY_ERR);
+      nsIGlobalObject* go = c->GetGlobalObject();
+      NS_ENSURE_TRUE(go, NS_ERROR_DOM_SECURITY_ERR);
+      nsCOMPtr<nsIScriptObjectPrincipal> sp = do_QueryInterface(go);
+      MOZ_ASSERT(sp, "This cannot fail on the main thread.");
+      nsIPrincipal* dataPrincipal = sp->GetPrincipal();
+      NS_ENSURE_TRUE(dataPrincipal, NS_ERROR_DOM_SECURITY_ERR);
+      NS_ENSURE_TRUE(aSubjectPrincipal->Subsumes(dataPrincipal), NS_ERROR_DOM_SECURITY_ERR);
     }
   }
 
+  data.forget(aData);
   return NS_OK;
 }
 
 void
 DataTransfer::MozGetDataAt(JSContext* aCx, const nsAString& aFormat,
                            uint32_t aIndex,
                            JS::MutableHandle<JS::Value> aRetval,
                            mozilla::ErrorResult& aRv)
@@ -733,17 +689,17 @@ DataTransfer::SetDataAtInternal(const ns
   }
 
   if (mReadOnly) {
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   }
 
   // Specifying an index less than the current length will replace an existing
   // item. Specifying an index equal to the current length will add a new item.
-  if (aIndex > mItems.Length()) {
+  if (aIndex > MozItemCount()) {
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
 
   // Only the first item is valid for clipboard events
   if (aIndex > 0 &&
       (mEventMessage == eCut || mEventMessage == eCopy ||
        mEventMessage == ePaste)) {
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
@@ -791,79 +747,57 @@ void
 DataTransfer::MozClearDataAt(const nsAString& aFormat, uint32_t aIndex,
                              ErrorResult& aRv)
 {
   if (mReadOnly) {
     aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
     return;
   }
 
-  if (aIndex >= mItems.Length()) {
+  if (aIndex >= MozItemCount()) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return;
   }
 
   // Only the first item is valid for clipboard events
   if (aIndex > 0 &&
       (mEventMessage == eCut || mEventMessage == eCopy ||
        mEventMessage == ePaste)) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return;
   }
 
   MozClearDataAtHelper(aFormat, aIndex, aRv);
+
+  // If we just cleared the 0-th index, and there are still more than 1 indexes
+  // remaining, MozClearDataAt should cause the 1st index to become the 0th
+  // index. This should _only_ happen when the MozClearDataAt function is
+  // explicitly called by script, as this behavior is inconsistent with spec.
+  // (however, so is the MozClearDataAt API)
+
+  if (aIndex == 0 && mItems->MozItemCount() > 1 &&
+      mItems->MozItemsAt(0)->Length() == 0) {
+    mItems->PopIndexZero();
+  }
 }
 
 void
 DataTransfer::MozClearDataAtHelper(const nsAString& aFormat, uint32_t aIndex,
                                    ErrorResult& aRv)
 {
   MOZ_ASSERT(!mReadOnly);
-  MOZ_ASSERT(aIndex < mItems.Length());
+  MOZ_ASSERT(aIndex < MozItemCount());
   MOZ_ASSERT(aIndex == 0 ||
              (mEventMessage != eCut && mEventMessage != eCopy &&
               mEventMessage != ePaste));
 
   nsAutoString format;
   GetRealFormat(aFormat, format);
 
-  nsIPrincipal* principal = nsContentUtils::SubjectPrincipal();
-
-  // if the format is empty, clear all formats
-  bool clearall = format.IsEmpty();
-
-  nsTArray<TransferItem>& item = mItems[aIndex];
-  // count backwards so that the count and index don't have to be adjusted
-  // after removing an element
-  for (int32_t i = item.Length() - 1; i >= 0; i--) {
-    TransferItem& formatitem = item[i];
-    if (clearall || formatitem.mFormat.Equals(format)) {
-      // don't allow removing data that has a stronger principal
-      bool subsumes;
-      if (formatitem.mPrincipal && principal &&
-          (NS_FAILED(principal->Subsumes(formatitem.mPrincipal, &subsumes)) ||
-           !subsumes)) {
-        aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
-        return;
-      }
-
-      item.RemoveElementAt(i);
-
-      // if a format was specified, break out. Otherwise, loop around until
-      // all formats have been removed
-      if (!clearall) {
-        break;
-      }
-    }
-  }
-
-  // if the last format for an item is removed, remove the entire item
-  if (!item.Length()) {
-     mItems.RemoveElementAt(aIndex);
-  }
+  mItems->MozRemoveByTypeAt(format, aIndex, aRv);
 }
 
 NS_IMETHODIMP
 DataTransfer::MozClearDataAt(const nsAString& aFormat, uint32_t aIndex)
 {
   ErrorResult rv;
   MozClearDataAt(aFormat, aIndex, rv);
   return rv.StealNSResult();
@@ -906,29 +840,27 @@ DataTransfer::GetFilesAndDirectories(Err
   nsCOMPtr<nsIGlobalObject> global = parentNode->OwnerDoc()->GetScopeObject();
   MOZ_ASSERT(global);
   if (!global) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   RefPtr<Promise> p = Promise::Create(global, aRv);
-  if (aRv.Failed()) {
+  if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
-  if (!mFileList) {
-    GetFiles(aRv);
-    if (NS_WARN_IF(aRv.Failed())) {
-      return nullptr;
-    }
+  RefPtr<FileList> files = mItems->Files();
+  if (NS_WARN_IF(!files)) {
+    return nullptr;
   }
 
   Sequence<RefPtr<File>> filesSeq;
-  mFileList->ToSequence(filesSeq, aRv);
+  files->ToSequence(filesSeq, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   p->MaybeResolve(filesSeq);
 
   return p.forget();
 }
@@ -957,24 +889,23 @@ DataTransfer::AddElement(nsIDOMElement* 
   return rv.StealNSResult();
 }
 
 nsresult
 DataTransfer::Clone(nsISupports* aParent, EventMessage aEventMessage,
                     bool aUserCancelled, bool aIsCrossDomainSubFrameDrop,
                     DataTransfer** aNewDataTransfer)
 {
-  DataTransfer* newDataTransfer =
+  RefPtr<DataTransfer> newDataTransfer =
     new DataTransfer(aParent, aEventMessage, mEffectAllowed, mCursorState,
                      mIsExternal, aUserCancelled, aIsCrossDomainSubFrameDrop,
                      mClipboardType, mItems, mDragImage, mDragImageX,
                      mDragImageY);
 
-  *aNewDataTransfer = newDataTransfer;
-  NS_ADDREF(*aNewDataTransfer);
+  newDataTransfer.forget(aNewDataTransfer);
   return NS_OK;
 }
 
 already_AddRefed<nsISupportsArray>
 DataTransfer::GetTransferables(nsIDOMNode* aDragTarget)
 {
   MOZ_ASSERT(aDragTarget);
 
@@ -996,35 +927,35 @@ DataTransfer::GetTransferables(nsILoadCo
 {
 
   nsCOMPtr<nsISupportsArray> transArray =
     do_CreateInstance("@mozilla.org/supports-array;1");
   if (!transArray) {
     return nullptr;
   }
 
-  uint32_t count = mItems.Length();
+  uint32_t count = MozItemCount();
   for (uint32_t i = 0; i < count; i++) {
     nsCOMPtr<nsITransferable> transferable = GetTransferable(i, aLoadContext);
     if (transferable) {
       transArray->AppendElement(transferable);
     }
   }
 
   return transArray.forget();
 }
 
 already_AddRefed<nsITransferable>
 DataTransfer::GetTransferable(uint32_t aIndex, nsILoadContext* aLoadContext)
 {
-  if (aIndex >= mItems.Length()) {
+  if (aIndex >= MozItemCount()) {
     return nullptr;
   }
 
-  nsTArray<TransferItem>& item = mItems[aIndex];
+  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) {
@@ -1066,35 +997,38 @@ DataTransfer::GetTransferable(uint32_t a
    *   <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
+      RefPtr<DataTransferItem> formatitem = item[f];
+      if (!formatitem->Data()) { // skip empty items
         continue;
       }
 
+      nsAutoString type;
+      formatitem->GetType(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 (formatitem.mFormat.EqualsASCII(knownFormats[f])) {
+        if (type.EqualsASCII(knownFormats[f])) {
           isCustomFormat = false;
           break;
         }
       }
 
       uint32_t lengthInBytes;
       nsCOMPtr<nsISupports> convertedData;
 
       if (handlingCustomFormats) {
-        if (!ConvertFromVariant(formatitem.mData, getter_AddRefs(convertedData),
+        if (!ConvertFromVariant(formatitem->Data(), 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
@@ -1111,22 +1045,21 @@ DataTransfer::GetTransferable(uint32_t a
 
               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);
+            int32_t formatLength = type.Length() * sizeof(nsString::char_type);
 
             stream->Write32(eCustomClipboardTypeId_String);
             stream->Write32(formatLength);
-            stream->WriteBytes((const char *)formatitem.mFormat.get(),
+            stream->WriteBytes((const char *)type.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 +=
@@ -1170,25 +1103,25 @@ DataTransfer::GetTransferable(uint32_t a
 
         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),
+        if (!ConvertFromVariant(formatitem->Data(), 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);
+        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
@@ -1230,17 +1163,17 @@ DataTransfer::ConvertFromVariant(nsIVari
   *aLength = 0;
 
   uint16_t type;
   aVariant->GetDataType(&type);
   if (type == nsIDataType::VTYPE_INTERFACE ||
       type == nsIDataType::VTYPE_INTERFACE_IS) {
     nsCOMPtr<nsISupports> data;
     if (NS_FAILED(aVariant->GetAsISupports(getter_AddRefs(data)))) {
-     return false;
+      return false;
     }
 
     nsCOMPtr<nsIFlavorDataProvider> fdp = do_QueryInterface(data);
     if (fdp) {
       // for flavour data providers, use kFlavorHasDataProvider (which has the
       // value 0) as the length.
       fdp.forget(aSupports);
       *aLength = nsITransferable::kFlavorHasDataProvider;
@@ -1286,71 +1219,38 @@ DataTransfer::ConvertFromVariant(nsIVari
   *aLength = str.Length() << 1;
 
   return true;
 }
 
 void
 DataTransfer::ClearAll()
 {
-  mItems.Clear();
+  mItems->ClearAllItems();
+}
+
+uint32_t
+DataTransfer::MozItemCount() const
+{
+  return mItems->MozItemCount();
 }
 
 nsresult
 DataTransfer::SetDataWithPrincipal(const nsAString& aFormat,
                                    nsIVariant* aData,
                                    uint32_t aIndex,
                                    nsIPrincipal* aPrincipal)
 {
   nsAutoString format;
   GetRealFormat(aFormat, format);
 
-  // check if the item for the format already exists. In that case,
-  // just replace it.
-  TransferItem* formatitem;
-  if (aIndex < mItems.Length()) {
-    nsTArray<TransferItem>& item = mItems[aIndex];
-    uint32_t count = item.Length();
-    for (uint32_t i = 0; i < count; i++) {
-      TransferItem& itemformat = item[i];
-      if (itemformat.mFormat.Equals(format)) {
-        // don't allow replacing data that has a stronger principal
-        bool subsumes;
-        if (itemformat.mPrincipal && aPrincipal &&
-            (NS_FAILED(aPrincipal->Subsumes(itemformat.mPrincipal,
-                                            &subsumes)) || !subsumes)) {
-          return NS_ERROR_DOM_SECURITY_ERR;
-        }
-
-        itemformat.mPrincipal = aPrincipal;
-        itemformat.mData = aData;
-        return NS_OK;
-      }
-    }
-
-    // add a new format
-    formatitem = item.AppendElement();
-  }
-  else {
-    NS_ASSERTION(aIndex == mItems.Length(), "Index out of range");
-
-    // add a new index
-    nsTArray<TransferItem>* item = mItems.AppendElement();
-    NS_ENSURE_TRUE(item, NS_ERROR_OUT_OF_MEMORY);
-
-    formatitem = item->AppendElement();
-  }
-
-  NS_ENSURE_TRUE(formatitem, NS_ERROR_OUT_OF_MEMORY);
-
-  formatitem->mFormat = format;
-  formatitem->mPrincipal = aPrincipal;
-  formatitem->mData = aData;
-
-  return NS_OK;
+  ErrorResult rv;
+  RefPtr<DataTransferItem> item =
+    mItems->SetDataWithPrincipal(format, aData, aIndex, aPrincipal, false, rv);
+  return rv.StealNSResult();
 }
 
 void
 DataTransfer::SetDataWithPrincipalFromOtherProcess(const nsAString& aFormat,
                                                    nsIVariant* aData,
                                                    uint32_t aIndex,
                                                    nsIPrincipal* aPrincipal)
 {
@@ -1397,16 +1297,22 @@ DataTransfer::CacheExternalData(const ch
                          aPrincipal);
     return;
   }
 
   SetDataWithPrincipal(NS_ConvertUTF8toUTF16(aFormat), nullptr, aIndex,
                        aPrincipal);
 }
 
+// there isn't a way to get a list of the formats that might be available on
+// all platforms, so just check for the types that can actually be imported
+// XXXndeakin there are some other formats but those are platform specific.
+const char* kFormats[] = { kFileMime, kHTMLMime, kURLMime, kURLDataMime,
+                           kUnicodeMime };
+
 void
 DataTransfer::CacheExternalDragFormats()
 {
   // Called during the constructor to cache the formats available from an
   // external drag. The data associated with each format will be set to null.
   // This data will instead only be retrieved in FillInExternalDragData when
   // asked for, as it may be time consuming for the source application to
   // generate it.
@@ -1438,21 +1344,21 @@ DataTransfer::CacheExternalDragFormats()
     }
 
     for (uint32_t f = 0; f < ArrayLength(formats); f++) {
       // IsDataFlavorSupported doesn't take an index as an argument and just
       // checks if any of the items support a particular flavor, even though
       // the GetData method does take an index. Here, we just assume that
       // every item being dragged has the same set of flavors.
       bool supported;
-      dragSession->IsDataFlavorSupported(formats[f], &supported);
+      dragSession->IsDataFlavorSupported(kFormats[f], &supported);
       // if the format is supported, add an item to the array with null as
       // the data. When retrieved, GetRealData will read the data.
       if (supported) {
-        CacheExternalData(formats[f], c, sysPrincipal);
+        CacheExternalData(kFormats[f], c, sysPrincipal);
       }
     }
   }
 }
 
 void
 DataTransfer::CacheExternalClipboardFormats()
 {
@@ -1492,127 +1398,44 @@ DataTransfer::CacheExternalClipboardForm
       } else {
         CacheExternalData(formats[f], 0, sysPrincipal);
       }
     }
   }
 }
 
 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(mEventMessage != eCut && mEventMessage != eCopy,
-               "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 (mEventMessage == ePaste) {
-    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;
-
-  RefPtr<nsVariantCC> variant = new nsVariantCC();
-
-  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);
-    } else {
-      variant->SetAsISupports(data);
-    }
-  }
-
-  aItem.mData = variant;
-}
-
-void
 DataTransfer::FillAllExternalData()
 {
   if (mIsExternal) {
-    for (uint32_t i = 0; i < mItems.Length(); ++i) {
-      nsTArray<TransferItem>& itemArray = mItems[i];
-      for (uint32_t j = 0; j < itemArray.Length(); ++j) {
-        if (!itemArray[j].mData) {
-          FillInExternalData(itemArray[j], i);
-        }
+    for (uint32_t i = 0; i < MozItemCount(); ++i) {
+      const nsTArray<RefPtr<DataTransferItem>>& items = *mItems->MozItemsAt(i);
+      for (uint32_t j = 0; j < items.Length(); ++j) {
+        MOZ_ASSERT(items[j]->Index() == i);
+
+        items[j]->FillInExternalData();
       }
     }
   }
 }
 
 void
 DataTransfer::FillInExternalCustomTypes(uint32_t aIndex,
                                         nsIPrincipal* aPrincipal)
 {
-  TransferItem item;
-  item.mFormat.AssignLiteral(kCustomTypesMime);
+  RefPtr<DataTransferItem> item = new DataTransferItem(mItems,
+                                                       NS_LITERAL_STRING(kCustomTypesMime));
+  item->SetKind(DataTransferItem::KIND_STRING);
+  item->SetIndex(aIndex);
 
-  FillInExternalData(item, aIndex);
-  if (!item.mData) {
+  if (!item->Data()) {
     return;
   }
 
-  FillInExternalCustomTypes(item.mData, aIndex, aPrincipal);
+  FillInExternalCustomTypes(item->Data(), aIndex, aPrincipal);
 }
 
 void
 DataTransfer::FillInExternalCustomTypes(nsIVariant* aData, uint32_t aIndex,
                                         nsIPrincipal* aPrincipal)
 {
   char* chrs;
   uint32_t len = 0;
--- a/dom/events/DataTransfer.h
+++ b/dom/events/DataTransfer.h
@@ -26,38 +26,27 @@ class nsISupportsArray;
 class nsILoadContext;
 
 namespace mozilla {
 
 class EventStateManager;
 
 namespace dom {
 
+class DataTransferItem;
+class DataTransferItemList;
 class DOMStringList;
 class Element;
 class FileList;
 class Promise;
 template<typename T> class Optional;
 
-/**
- * TransferItem is used to hold data for a particular format. Each piece of
- * data has a principal set from the caller which added it. This allows a
- * caller that wishes to retrieve the data to only be able to access the data
- * it is allowed to, yet still allow a chrome caller to retrieve any of the
- * data.
- */
-struct TransferItem {
-  nsString mFormat;
-  nsCOMPtr<nsIPrincipal> mPrincipal;
-  nsCOMPtr<nsIVariant> mData;
-};
-
 #define NS_DATATRANSFER_IID \
-{ 0x43ee0327, 0xde5d, 0x463d, \
-  { 0x9b, 0xd0, 0xf1, 0x79, 0x09, 0x69, 0xf2, 0xfb } }
+{ 0x6c5f90d1, 0xa886, 0x42c8, \
+  { 0x85, 0x06, 0x10, 0xbe, 0x5c, 0x0d, 0xc6, 0x77 } }
 
 class DataTransfer final : public nsIDOMDataTransfer,
                            public nsWrapperCache
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_DATATRANSFER_IID)
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
@@ -82,27 +71,26 @@ protected:
   DataTransfer(nsISupports* aParent,
                EventMessage aEventMessage,
                const uint32_t aEffectAllowed,
                bool aCursorState,
                bool aIsExternal,
                bool aUserCancelled,
                bool aIsCrossDomainSubFrameDrop,
                int32_t aClipboardType,
-               nsTArray<nsTArray<TransferItem> >& aItems,
+               DataTransferItemList* aItems,
                Element* aDragImage,
                uint32_t aDragImageX,
                uint32_t aDragImageY);
 
   ~DataTransfer();
 
   static const char sEffects[8][9];
 
 public:
-
   // Constructor for DataTransfer.
   //
   // aIsExternal must only be true when used to create a dataTransfer for a
   // paste or a drag that was started without using a data transfer. The
   // latter will occur when an external drag occurs, that is, a drag where the
   // source is another application, or a drag is started by calling the drag
   // service directly. For clipboard operations, aClipboardType indicates
   // which clipboard to use, from nsIClipboard, or -1 for non-clipboard
@@ -143,36 +131,33 @@ public:
     } else {
       aEffectAllowed.AssignASCII(sEffects[mEffectAllowed]);
     }
   }
 
   void SetDragImage(Element& aElement, int32_t aX, int32_t aY,
                     ErrorResult& aRv);
 
-  already_AddRefed<DOMStringList> Types() const;
+  already_AddRefed<DOMStringList> GetTypes(ErrorResult& rv) const;
 
   void GetData(const nsAString& aFormat, nsAString& aData, ErrorResult& aRv);
 
   void SetData(const nsAString& aFormat, const nsAString& aData,
                ErrorResult& aRv);
 
   void ClearData(const mozilla::dom::Optional<nsAString>& aFormat,
                  mozilla::ErrorResult& aRv);
 
   FileList* GetFiles(mozilla::ErrorResult& aRv);
 
   already_AddRefed<Promise> GetFilesAndDirectories(ErrorResult& aRv);
 
   void AddElement(Element& aElement, mozilla::ErrorResult& aRv);
 
-  uint32_t MozItemCount() const
-  {
-    return mItems.Length();
-  }
+  uint32_t MozItemCount() const;
 
   void GetMozCursor(nsString& aCursor)
   {
     if (mCursorState) {
       aCursor.AssignLiteral("default");
     } else {
       aCursor.AssignLiteral("auto");
     }
@@ -204,18 +189,24 @@ public:
     return mDragTarget;
   }
 
   nsresult GetDataAtNoSecurityCheck(const nsAString& aFormat, uint32_t aIndex,
                                     nsIVariant** aData);
 
   // a readonly dataTransfer cannot have new data added or existing data
   // removed. Only the dropEffect and effectAllowed may be modified.
+  DataTransferItemList* Items() const { return mItems; }
+
+  bool IsReadOnly() const { return mReadOnly; }
   void SetReadOnly() { mReadOnly = true; }
 
+  int32_t ClipboardType() const { return mClipboardType; }
+  EventMessage GetEventMessage() const { return mEventMessage; }
+
   // converts the data into an array of nsITransferable objects to be used for
   // drag and drop or clipboard operations.
   already_AddRefed<nsISupportsArray> GetTransferables(nsIDOMNode* aDragTarget);
 
   already_AddRefed<nsISupportsArray>
   GetTransferables(nsILoadContext* aLoadContext);
 
   // converts the data for a single item at aIndex into an nsITransferable
@@ -254,42 +245,35 @@ public:
     *aY = mDragImageY;
     return mDragImage;
   }
 
   nsresult Clone(nsISupports* aParent, EventMessage aEventMessage,
                  bool aUserCancelled, bool aIsCrossDomainSubFrameDrop,
                  DataTransfer** aResult);
 
-protected:
-
   // converts some formats used for compatibility in aInFormat into aOutFormat.
   // Text and text/unicode become text/plain, and URL becomes text/uri-list
   void GetRealFormat(const nsAString& aInFormat, nsAString& aOutFormat) const;
 
+protected:
+
   // caches text and uri-list data formats that exist in the drag service or
   // clipboard for retrieval later.
   void CacheExternalData(const char* aFormat, uint32_t aIndex,
                          nsIPrincipal* aPrincipal);
 
   // caches the formats that exist in the drag service that were added by an
   // external drag
   void CacheExternalDragFormats();
 
   // caches the formats that exist in the clipboard
   void CacheExternalClipboardFormats();
 
-  // fills in the data field of aItem with the data from the drag service or
-  // clipboard for a given index.
-  void FillInExternalData(TransferItem& aItem, uint32_t aIndex);
-
-
-  FileList* GetFileListInternal(ErrorResult& aRv,
-                                nsIPrincipal* aSubjectPrincipal);
-
+  FileList* GetFilesInternal(ErrorResult& aRv, nsIPrincipal* aSubjectPrincipal);
   nsresult GetDataAtInternal(const nsAString& aFormat, uint32_t aIndex,
                              nsIPrincipal* aSubjectPrincipal,
                              nsIVariant** aData);
 
   nsresult SetDataAtInternal(const nsAString& aFormat, nsIVariant* aData,
                              uint32_t aIndex, nsIPrincipal* aSubjectPrincipal);
 
   friend class ContentParent;
@@ -331,22 +315,18 @@ protected:
   // true if this is a cross-domain drop from a subframe where access to the
   // data should be prevented
   bool mIsCrossDomainSubFrameDrop;
 
   // Indicates which clipboard type to use for clipboard operations. Ignored for
   // drag and drop.
   int32_t mClipboardType;
 
-  // array of items, each containing an array of format->data pairs
-  nsTArray<nsTArray<TransferItem> > mItems;
-
-  // array of files and directories, containing only the files present in the
-  // dataTransfer
-  RefPtr<FileList> mFileList;
+  // The items contained with the DataTransfer
+  RefPtr<DataTransferItemList> mItems;
 
   // the target of the drag. The drag and dragend events will fire at this.
   nsCOMPtr<mozilla::dom::Element> mDragTarget;
 
   // the custom drag image and coordinates within the image. If mDragImage is
   // null, the default image is created from the drag target.
   nsCOMPtr<mozilla::dom::Element> mDragImage;
   uint32_t mDragImageX;
new file mode 100644
--- /dev/null
+++ b/dom/events/DataTransferItem.cpp
@@ -0,0 +1,365 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DataTransferItem.h"
+#include "DataTransferItemList.h"
+
+#include "mozilla/ContentEvents.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/dom/DataTransferItemBinding.h"
+#include "nsIClipboard.h"
+#include "nsISupportsPrimitives.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "nsContentUtils.h"
+#include "nsVariant.h"
+
+namespace {
+
+struct FileMimeNameData
+{
+  const char* mMimeName;
+  const char* mFileName;
+};
+
+FileMimeNameData kFileMimeNameMap[] = {
+  { kFileMime, "GenericFileName" },
+};
+
+already_AddRefed<mozilla::dom::File>
+FileFromISupports(nsISupports* aSupports)
+{
+  MOZ_ASSERT(aSupports);
+
+  nsCOMPtr<nsIDOMBlob> domBlob = do_QueryInterface(aSupports);
+  if (domBlob) {
+    // Get out the blob - this is OK, because nsIDOMBlob is a builtinclass
+    // and the only implementer is Blob.
+    mozilla::dom::Blob* blob = static_cast<mozilla::dom::Blob*>(domBlob.get());
+    return blob->ToFile();
+  }
+
+  return nullptr;
+}
+
+} // anonymous namespace
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DataTransferItem, mData,
+                                      mPrincipal, mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DataTransferItem)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DataTransferItem)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DataTransferItem)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject*
+DataTransferItem::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return DataTransferItemBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<DataTransferItem>
+DataTransferItem::Clone(DataTransferItemList* aParent) const
+{
+  MOZ_ASSERT(aParent);
+
+  RefPtr<DataTransferItem> it = new DataTransferItem(aParent, mType);
+
+  // Copy over all of the fields
+  it->mKind = mKind;
+  it->mIndex = mIndex;
+  it->mData = mData;
+  it->mPrincipal = mPrincipal;
+
+  return it.forget();
+}
+
+void
+DataTransferItem::SetType(const nsAString& aType)
+{
+  mType = aType;
+}
+
+void
+DataTransferItem::SetData(nsIVariant* aData)
+{
+  if (!aData) {
+    // We are holding a temporary null which will later be filled.
+    // These are provided by the system, and have guaranteed properties about
+    // their kind based on their type.
+    MOZ_ASSERT(!mType.IsEmpty());
+
+    mKind = KIND_STRING;
+    for (uint32_t i = 0; i < ArrayLength(kFileMimeNameMap); ++i) {
+      if (mType.EqualsASCII(kFileMimeNameMap[i].mMimeName)) {
+        mKind = KIND_FILE;
+        break;
+      }
+    }
+
+    mData = nullptr;
+    return;
+  }
+
+  mKind = KIND_OTHER;
+  mData = aData;
+
+  nsCOMPtr<nsISupports> supports;
+  nsresult rv = aData->GetAsISupports(getter_AddRefs(supports));
+  if (NS_SUCCEEDED(rv) && supports) {
+    RefPtr<File> file = FileFromISupports(supports);
+    if (file) {
+      mKind = KIND_FILE;
+      return;
+    }
+  }
+
+  nsAutoString string;
+  // If we can't get the data type as a string, that means that the object
+  // should be considered to be of the "other" type. This is impossible
+  // through the APIs defined by the spec, but we provide extra Moz* APIs,
+  // which allow setting of non-string data. We determine whether we can
+  // consider it a string, by calling GetAsAString, and checking for success.
+  rv = aData->GetAsAString(string);
+  if (NS_SUCCEEDED(rv)) {
+    mKind = KIND_STRING;
+  }
+}
+
+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 (mParent->GetEventMessage() == ePaste) {
+    MOZ_ASSERT(mIndex == 0, "index in clipboard must be 0");
+
+    nsCOMPtr<nsIClipboard> clipboard =
+      do_GetService("@mozilla.org/widget/clipboard;1");
+    if (!clipboard || mParent->ClipboardType() < 0) {
+      return;
+    }
+
+    nsresult rv = clipboard->GetData(trans, mParent->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;
+  }
+
+  if (Kind() == KIND_FILE) {
+    // Because this is an external piece of data, mType is kFileMime. We want to
+    // convert whatever type happens to actually be stored into a dom::File.
+
+    RefPtr<File> file = FileFromISupports(data);
+    if (!file) {
+      if (nsCOMPtr<nsIFile> ifile = do_QueryInterface(data)) {
+        file = File::CreateFromFile(GetParentObject(), ifile);
+      } else if (nsCOMPtr<nsIInputStream> stream = do_QueryInterface(data)) {
+        // This consumes the stream object
+        ErrorResult rv;
+        file = CreateFileFromInputStream(GetParentObject(), stream, rv);
+        if (NS_WARN_IF(rv.Failed())) {
+          rv.SuppressException();
+        }
+      } else if (nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(data)) {
+        MOZ_ASSERT(blobImpl->IsFile());
+        file = File::Create(GetParentObject(), blobImpl);
+      }
+    }
+
+    MOZ_ASSERT(file, "Invalid format for Kind() == KIND_FILE");
+
+    data = do_QueryObject(file);
+  }
+
+  RefPtr<nsVariantCC> variant = new nsVariantCC();
+
+  nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data);
+  if (supportsstr) {
+    MOZ_ASSERT(Kind() == KIND_STRING);
+    nsAutoString str;
+    supportsstr->GetData(str);
+    variant->SetAsAString(str);
+  } else {
+    nsCOMPtr<nsISupportsCString> supportscstr = do_QueryInterface(data);
+    if (supportscstr) {
+      MOZ_ASSERT(Kind() == KIND_STRING);
+      nsAutoCString str;
+      supportscstr->GetData(str);
+      variant->SetAsACString(str);
+    } else {
+      MOZ_ASSERT(Kind() == KIND_FILE);
+      variant->SetAsISupports(data);
+    }
+  }
+
+  SetData(variant);
+}
+
+already_AddRefed<File>
+DataTransferItem::GetAsFile(ErrorResult& aRv)
+{
+  if (mKind != KIND_FILE) {
+    return nullptr;
+  }
+
+  nsIVariant* data = Data();
+  if (NS_WARN_IF(!data)) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsISupports> supports;
+  aRv = data->GetAsISupports(getter_AddRefs(supports));
+  MOZ_ASSERT(!aRv.Failed() && supports,
+             "Files should be stored with type dom::File!");
+  if (aRv.Failed() || !supports) {
+    return nullptr;
+  }
+
+  RefPtr<File> file = FileFromISupports(supports);
+  MOZ_ASSERT(file, "Files should be stored with type dom::File!");
+  if (!file) {
+    return nullptr;
+  }
+
+  // The File object should have been stored as a File in the nsIVariant. If it
+  // was stored as a Blob, with a file BlobImpl, we could still get to this
+  // point, except that file is a new File object, with a different identity
+  // then the original blob ibject. This should never happen so we assert
+  // against it.
+  MOZ_ASSERT(SameCOMIdentity(supports, NS_ISUPPORTS_CAST(nsIDOMBlob*, file)),
+             "Got back a new File object from FileFromISupports in GetAsFile!");
+
+  return file.forget();
+}
+
+already_AddRefed<File>
+DataTransferItem::CreateFileFromInputStream(nsISupports* aParent,
+                                            nsIInputStream* aStream,
+                                            ErrorResult& aRv)
+{
+  const char* key = nullptr;
+  for (uint32_t i = 0; i < ArrayLength(kFileMimeNameMap); ++i) {
+    if (mType.EqualsASCII(kFileMimeNameMap[i].mMimeName)) {
+      key = kFileMimeNameMap[i].mFileName;
+      break;
+    }
+  }
+  if (!key) {
+    MOZ_ASSERT_UNREACHABLE("Unsupported mime type");
+    key = "GenericFileName";
+  }
+
+  nsXPIDLString fileName;
+  aRv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+                                           key, fileName);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  uint64_t available;
+  aRv = aStream->Available(&available);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  void* data = nullptr;
+  aRv = NS_ReadInputStreamToBuffer(aStream, &data, available);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  return File::CreateMemoryFile(aParent, data, available, fileName,
+                                mType, PR_Now());
+}
+
+void
+DataTransferItem::GetAsString(const RefPtr<FunctionStringCallback>& aCallback,
+                              ErrorResult& aRv)
+{
+  if (!aCallback || mKind != KIND_STRING) {
+    return;
+  }
+
+  nsIVariant* data = Data();
+  if (NS_WARN_IF(!data)) {
+    return;
+  }
+
+  nsAutoString stringData;
+  data->GetAsAString(stringData);
+
+  // Dispatch the callback to the main thread
+  class GASRunnable final : public Runnable
+  {
+  public:
+    GASRunnable(const RefPtr<FunctionStringCallback>& aCallback,
+                const nsAString& aStringData)
+      : mCallback(aCallback), mStringData(aStringData)
+    {}
+
+    NS_IMETHOD Run() override
+    {
+      ErrorResult rv;
+      mCallback->Call(mStringData, rv);
+      NS_WARN_IF(rv.Failed());
+      return rv.StealNSResult();
+    }
+  private:
+    RefPtr<FunctionStringCallback> mCallback;
+    nsString mStringData;
+  };
+
+  RefPtr<GASRunnable> runnable = new GASRunnable(aCallback, stringData);
+  nsresult rv = NS_DispatchToMainThread(runnable);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("NS_DispatchToMainThread Failed in "
+               "DataTransferItem::GetAsString!");
+  }
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/events/DataTransferItem.h
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_DataTransferItem_h
+#define mozilla_dom_DataTransferItem_h
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/DataTransfer.h"
+#include "mozilla/dom/DOMString.h"
+#include "mozilla/dom/File.h"
+
+namespace mozilla {
+namespace dom {
+
+class FunctionStringCallback;
+
+class DataTransferItem final : public nsISupports
+                             , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DataTransferItem);
+
+public:
+  // The spec only talks about the "file" and "string" kinds. Due to the Moz*
+  // APIs, it is possible to attach any type to a DataTransferItem, meaning that
+  // we can have other kinds then just FILE and STRING. These others are simply
+  // marked as "other" and can only be produced throug the Moz* APIs.
+  enum eKind {
+    KIND_FILE,
+    KIND_STRING,
+    KIND_OTHER,
+  };
+
+  DataTransferItem(DataTransferItemList* aParent, const nsAString& aType)
+    : mIndex(0), mKind(KIND_OTHER), mType(aType), mParent(aParent)
+  {}
+
+  already_AddRefed<DataTransferItem> Clone(DataTransferItemList* aParent) const;
+
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+  void GetAsString(const RefPtr<FunctionStringCallback>& aCallback,
+                   ErrorResult& aRv);
+  void GetKind(nsAString& aKind) const
+  {
+    switch (mKind) {
+    case KIND_FILE:
+      aKind = NS_LITERAL_STRING("file");
+      return;
+    case KIND_STRING:
+      aKind = NS_LITERAL_STRING("string");
+      return;
+    default:
+      aKind = NS_LITERAL_STRING("other");
+      return;
+    }
+  }
+
+  void GetType(nsAString& aType) const
+  {
+    aType = mType;
+  }
+  void SetType(const nsAString& aType);
+
+  eKind Kind() const
+  {
+    return mKind;
+  }
+  void SetKind(eKind aKind)
+  {
+    mKind = aKind;
+  }
+
+  already_AddRefed<File> GetAsFile(ErrorResult& aRv);
+
+  DataTransferItemList* GetParentObject() const
+  {
+    return mParent;
+  }
+
+  nsIPrincipal* Principal() const
+  {
+    return mPrincipal;
+  }
+  void SetPrincipal(nsIPrincipal* aPrincipal)
+  {
+    mPrincipal = aPrincipal;
+  }
+
+  nsIVariant* Data()
+  {
+    if (!mData) {
+      FillInExternalData();
+    }
+    return mData;
+  }
+  void SetData(nsIVariant* aData);
+
+  uint32_t Index() const
+  {
+    return mIndex;
+  }
+  void SetIndex(uint32_t aIndex)
+  {
+    mIndex = aIndex;
+  }
+  void FillInExternalData();
+
+private:
+  ~DataTransferItem() {}
+  already_AddRefed<File> CreateFileFromInputStream(nsISupports* aParent,
+                                                   nsIInputStream* aStream,
+                                                   ErrorResult& aRv);
+
+  // The index in the 2d mIndexedItems array
+  uint32_t mIndex;
+
+  eKind mKind;
+  nsString mType;
+  nsCOMPtr<nsIVariant> mData;
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+  RefPtr<DataTransferItemList> mParent;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_DataTransferItem_h */
new file mode 100644
--- /dev/null
+++ b/dom/events/DataTransferItemList.cpp
@@ -0,0 +1,585 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DataTransferItemList.h"
+
+#include "nsContentUtils.h"
+#include "nsIGlobalObject.h"
+#include "nsIClipboard.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptContext.h"
+#include "nsISupportsPrimitives.h"
+#include "nsQueryObject.h"
+#include "nsVariant.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/storage/Variant.h"
+#include "mozilla/dom/DataTransferItemListBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DataTransferItemList, mParent, mItems,
+                                      mIndexedItems, mFiles)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DataTransferItemList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DataTransferItemList)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DataTransferItemList)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject*
+DataTransferItemList::WrapObject(JSContext* aCx,
+                                 JS::Handle<JSObject*> aGivenProto)
+{
+  return DataTransferItemListBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<DataTransferItemList>
+DataTransferItemList::Clone(DataTransfer* aParent) const
+{
+  RefPtr<DataTransferItemList> list =
+    new DataTransferItemList(aParent, mIsExternal, mIsCrossDomainSubFrameDrop);
+
+  // We need to clone the mItems and mIndexedItems lists while keeping the same
+  // correspondences between the mIndexedItems and mItems lists (namely, if an
+  // item is in mIndexedItems, and mItems it must have the same new identity)
+
+  // First, we copy over indexedItems, and clone every entry. Then, we go over
+  // mItems. For every entry, we use its mIndex property to locate it in
+  // mIndexedItems on the original DataTransferItemList, and then copy over the
+  // reference from the same index pair on the new DataTransferItemList
+
+  list->mIndexedItems.SetLength(mIndexedItems.Length());
+  list->mItems.SetLength(mItems.Length());
+
+  // Copy over mIndexedItems, cloning every entry
+  for (uint32_t i = 0; i < mIndexedItems.Length(); i++) {
+    const nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[i];
+    nsTArray<RefPtr<DataTransferItem>>& newItems = list->mIndexedItems[i];
+    newItems.SetLength(items.Length());
+    for (uint32_t j = 0; j < items.Length(); j++) {
+      newItems[j] = items[j]->Clone(list);
+    }
+  }
+
+  // Copy over mItems, getting the actual entries from mIndexedItems
+  for (uint32_t i = 0; i < mItems.Length(); i++) {
+    uint32_t index = mItems[i]->Index();
+    MOZ_ASSERT(index < mIndexedItems.Length());
+    uint32_t subIndex = mIndexedItems[index].IndexOf(mItems[i]);
+
+    // Copy over the reference
+    list->mItems[i] = list->mIndexedItems[index][subIndex];
+  }
+
+  return list.forget();
+}
+
+void
+DataTransferItemList::Remove(uint32_t aIndex, ErrorResult& aRv)
+{
+  if (IsReadOnly()) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
+
+  if (aIndex >= Length()) {
+    aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+    return;
+  }
+
+  ClearDataHelper(mItems[aIndex], aIndex, -1, aRv);
+}
+
+DataTransferItem*
+DataTransferItemList::IndexedGetter(uint32_t aIndex, bool& aFound, ErrorResult& aRv) const
+{
+  if (aIndex >= mItems.Length()) {
+    aFound = false;
+    return nullptr;
+  }
+
+  RefPtr<DataTransferItem> item = mItems[aIndex];
+
+  // Check if the caller is allowed to access the drag data. Callers with
+  // chrome privileges can always read the data. During the
+  // drop event, allow retrieving the data except in the case where the
+  // source of the drag is in a child frame of the caller. In that case,
+  // we only allow access to data of the same principal. During other events,
+  // only allow access to the data with the same principal.
+  nsIPrincipal* principal = nullptr;
+  if (mIsCrossDomainSubFrameDrop) {
+    principal = nsContentUtils::SubjectPrincipal();
+  }
+
+  if (item->Principal() && principal &&
+      !principal->Subsumes(item->Principal())) {
+    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    aFound = false;
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIVariant> variantData = item->Data();
+  nsCOMPtr<nsISupports> data;
+  if (variantData &&
+      NS_SUCCEEDED(variantData->GetAsISupports(getter_AddRefs(data))) &&
+      data) {
+    // Make sure the code that is calling us is same-origin with the data.
+    nsCOMPtr<EventTarget> pt = do_QueryInterface(data);
+    if (pt) {
+      nsresult rv = NS_OK;
+      nsIScriptContext* c = pt->GetContextForEventHandlers(&rv);
+      if (NS_WARN_IF(NS_FAILED(rv) || !c)) {
+        aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+        return nullptr;
+      }
+
+      nsIGlobalObject* go = c->GetGlobalObject();
+      if (NS_WARN_IF(!go)) {
+        aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+        return nullptr;
+      }
+
+      nsCOMPtr<nsIScriptObjectPrincipal> sp = do_QueryInterface(go);
+      MOZ_ASSERT(sp, "This cannot fail on the main thread.");
+
+      nsIPrincipal* dataPrincipal = sp->GetPrincipal();
+      if (!principal) {
+        principal = nsContentUtils::SubjectPrincipal();
+      }
+      if (NS_WARN_IF(!dataPrincipal || !principal->Equals(dataPrincipal))) {
+        aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+        return nullptr;
+      }
+    }
+  }
+
+  aFound = true;
+  return item;
+}
+
+uint32_t
+DataTransferItemList::MozItemCount() const
+{
+  uint32_t length = mIndexedItems.Length();
+  // XXX: Compat hack - Index 0 always exists due to changes in internals, but
+  // if it is empty, scripts using the moz* APIs should see it as not existing.
+  if (length == 1 && mIndexedItems[0].IsEmpty()) {
+    return 0;
+  }
+  return length;
+}
+
+void
+DataTransferItemList::Clear(ErrorResult& aRv)
+{
+  if (NS_WARN_IF(IsReadOnly())) {
+    return;
+  }
+
+  uint32_t count = Length();
+  for (uint32_t i = 0; i < count; i++) {
+    // We always remove the last item first, to avoid moving items around in
+    // memory as much
+    Remove(Length() - 1, aRv);
+    ENSURE_SUCCESS_VOID(aRv);
+  }
+
+  MOZ_ASSERT(Length() == 0);
+}
+
+DataTransferItem*
+DataTransferItemList::Add(const nsAString& aData,
+                          const nsAString& aType,
+                          ErrorResult& aRv)
+{
+  if (NS_WARN_IF(IsReadOnly())) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIVariant> data(new storage::TextVariant(aData));
+
+  nsAutoString format;
+  mParent->GetRealFormat(aType, format);
+
+  // We add the textual data to index 0. We set aInsertOnly to true, as we don't
+  // want to update an existing entry if it is already present, as per the spec.
+  RefPtr<DataTransferItem> item =
+    SetDataWithPrincipal(format, data, 0,
+                         nsContentUtils::SubjectPrincipal(),
+                         true, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+  MOZ_ASSERT(item->Kind() != DataTransferItem::KIND_FILE);
+
+  return item;
+}
+
+DataTransferItem*
+DataTransferItemList::Add(File& aData, ErrorResult& aRv)
+{
+  if (IsReadOnly()) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsISupports> supports = do_QueryObject(&aData);
+  nsCOMPtr<nsIWritableVariant> data = new nsVariant();
+  data->SetAsISupports(supports);
+
+  nsAutoString type;
+  aData.GetType(type);
+
+
+  // We need to add this as a new item, as multiple files can't exist in the
+  // same item in the Moz DataTransfer layout. It will be appended at the end of
+  // the internal specced layout.
+  uint32_t index = mIndexedItems.Length();
+  RefPtr<DataTransferItem> item =
+    SetDataWithPrincipal(type, data, index,
+                         nsContentUtils::SubjectPrincipal(),
+                         true, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+  MOZ_ASSERT(item->Kind() == DataTransferItem::KIND_FILE);
+
+  return item;
+}
+
+FileList*
+DataTransferItemList::Files()
+{
+  if (!mFiles) {
+    mFiles = new FileList(static_cast<nsIDOMDataTransfer*>(mParent));
+    RegenerateFiles();
+  }
+
+  return mFiles;
+}
+
+void
+DataTransferItemList::MozRemoveByTypeAt(const nsAString& aType,
+                                        uint32_t aIndex,
+                                        ErrorResult& aRv)
+{
+  if (NS_WARN_IF(IsReadOnly() ||
+                 aIndex >= mIndexedItems.Length())) {
+    return;
+  }
+
+  bool removeAll = aType.IsEmpty();
+
+  nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[aIndex];
+  uint32_t count = items.Length();
+  // We remove the last item of the list repeatedly - that way we don't
+  // have to worry about modifying the loop iterator
+  if (removeAll) {
+    for (uint32_t i = 0; i < count; ++i) {
+      uint32_t index = items.Length() - 1;
+      MOZ_ASSERT(index == count - i - 1);
+
+      ClearDataHelper(items[index], -1, index, aRv);
+      if (NS_WARN_IF(aRv.Failed())) {
+        return;
+      }
+    }
+
+    // items is no longer a valid reference, as removing the last element from
+    // it via ClearDataHelper invalidated it. so we can't MOZ_ASSERT that the
+    // length is now 0.
+    return;
+  }
+
+  for (uint32_t i = 0; i < count; ++i) {
+    nsAutoString type;
+    items[i]->GetType(type);
+    if (type == aType) {
+      ClearDataHelper(items[i], -1, i, aRv);
+      return;
+    }
+  }
+}
+
+DataTransferItem*
+DataTransferItemList::MozItemByTypeAt(const nsAString& aType, uint32_t aIndex)
+{
+  if (NS_WARN_IF(aIndex >= mIndexedItems.Length())) {
+    return nullptr;
+  }
+
+  uint32_t count = mIndexedItems[aIndex].Length();
+  for (uint32_t i = 0; i < count; i++) {
+    RefPtr<DataTransferItem> item = mIndexedItems[aIndex][i];
+    nsString type;
+    item->GetType(type);
+    if (type.Equals(aType)) {
+      return item;
+    }
+  }
+
+  return nullptr;
+}
+
+already_AddRefed<DataTransferItem>
+DataTransferItemList::SetDataWithPrincipal(const nsAString& aType,
+                                           nsIVariant* aData,
+                                           uint32_t aIndex,
+                                           nsIPrincipal* aPrincipal,
+                                           bool aInsertOnly,
+                                           ErrorResult& aRv)
+{
+  if (aIndex < mIndexedItems.Length()) {
+    nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[aIndex];
+    uint32_t count = items.Length();
+    for (uint32_t i = 0; i < count; i++) {
+      RefPtr<DataTransferItem> item = items[i];
+      nsString type;
+      item->GetType(type);
+      if (type.Equals(aType)) {
+        if (NS_WARN_IF(aInsertOnly)) {
+          aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+          return nullptr;
+        }
+
+        // don't allow replacing data that has a stronger principal
+        bool subsumes;
+        if (NS_WARN_IF(item->Principal() && aPrincipal &&
+                       (NS_FAILED(aPrincipal->Subsumes(item->Principal(),
+                                                       &subsumes))
+                        || !subsumes))) {
+          aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+          return nullptr;
+        }
+        item->SetPrincipal(aPrincipal);
+
+        DataTransferItem::eKind oldKind = item->Kind();
+        item->SetData(aData);
+
+        if (aIndex != 0) {
+          // If the item changes from being a file to not a file or vice-versa,
+          // its presence in the mItems array may need to change.
+          if (item->Kind() == DataTransferItem::KIND_FILE &&
+              oldKind != DataTransferItem::KIND_FILE) {
+            // not file => file
+            mItems.AppendElement(item);
+          } else if (item->Kind() != DataTransferItem::KIND_FILE &&
+                     oldKind == DataTransferItem::KIND_FILE) {
+            // file => not file
+            mItems.RemoveElement(item);
+          }
+        }
+
+        // Regenerate the Files array if we have modified a file's status
+        if (item->Kind() == DataTransferItem::KIND_FILE ||
+            oldKind == DataTransferItem::KIND_FILE) {
+          RegenerateFiles();
+        }
+
+        return item.forget();
+      }
+    }
+  } else {
+    // Make sure that we aren't adding past the end of the mIndexedItems array.
+    // XXX Should this be a MOZ_ASSERT instead?
+    aIndex = mIndexedItems.Length();
+  }
+
+  // Add the new item
+  RefPtr<DataTransferItem> item = AppendNewItem(aIndex, aType, aData, aPrincipal);
+
+  if (item->Kind() == DataTransferItem::KIND_FILE) {
+    RegenerateFiles();
+  }
+
+  return item.forget();
+}
+
+DataTransferItem*
+DataTransferItemList::AppendNewItem(uint32_t aIndex,
+                                    const nsAString& aType,
+                                    nsIVariant* aData,
+                                    nsIPrincipal* aPrincipal)
+{
+  if (mIndexedItems.Length() <= aIndex) {
+    MOZ_ASSERT(mIndexedItems.Length() == aIndex);
+    mIndexedItems.AppendElement();
+  }
+  RefPtr<DataTransferItem> item = new DataTransferItem(this, aType);
+  item->SetIndex(aIndex);
+  item->SetPrincipal(aPrincipal);
+  item->SetData(aData);
+
+  mIndexedItems[aIndex].AppendElement(item);
+
+  // We only want to add the item to the main mItems list if the index we are
+  // adding to is 0, or the item we are adding is a file. If we add an item
+  // which is not a file to a non-zero index, invariants could be broken.
+  // (namely the invariant that there are not 2 non-file entries in the items
+  // array with the same type)
+  if (item->Kind() == DataTransferItem::KIND_FILE || aIndex == 0) {
+    mItems.AppendElement(item);
+  }
+
+  return item;
+}
+
+const nsTArray<RefPtr<DataTransferItem>>*
+DataTransferItemList::MozItemsAt(uint32_t aIndex) // -- INDEXED
+{
+  if (aIndex >= mIndexedItems.Length()) {
+    return nullptr;
+  }
+
+  return &mIndexedItems[aIndex];
+}
+
+bool
+DataTransferItemList::IsReadOnly() const
+{
+  MOZ_ASSERT(mParent);
+  return mParent->IsReadOnly();
+}
+
+int32_t
+DataTransferItemList::ClipboardType() const
+{
+  MOZ_ASSERT(mParent);
+  return mParent->ClipboardType();
+}
+
+EventMessage
+DataTransferItemList::GetEventMessage() const
+{
+  MOZ_ASSERT(mParent);
+  return mParent->GetEventMessage();
+}
+
+void
+DataTransferItemList::PopIndexZero()
+{
+  MOZ_ASSERT(mIndexedItems.Length() > 1);
+  MOZ_ASSERT(mIndexedItems[0].IsEmpty());
+
+  mIndexedItems.RemoveElementAt(0);
+
+  // Update the index of every element which has now been shifted
+  for (uint32_t i = 0; i < mIndexedItems.Length(); i++) {
+    nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[i];
+    for (uint32_t j = 0; j < items.Length(); j++) {
+      items[j]->SetIndex(i);
+    }
+  }
+}
+
+void
+DataTransferItemList::ClearAllItems()
+{
+  // We always need to have index 0, so don't delete that one
+  mItems.Clear();
+  mIndexedItems.Clear();
+  mIndexedItems.SetLength(1);
+
+  // Re-generate files (into an empty list)
+  RegenerateFiles();
+}
+
+void
+DataTransferItemList::ClearDataHelper(DataTransferItem* aItem,
+                                      uint32_t aIndexHint,
+                                      uint32_t aMozOffsetHint,
+                                      ErrorResult& aRv)
+{
+  MOZ_ASSERT(aItem);
+  if (NS_WARN_IF(IsReadOnly())) {
+    return;
+  }
+
+  nsIPrincipal* principal = nsContentUtils::SubjectPrincipal();
+  if (aItem->Principal() && principal &&
+      !principal->Subsumes(aItem->Principal())) {
+    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return;
+  }
+
+  // Check if the aIndexHint is actually the index, and then remove the item
+  // from aItems
+  ErrorResult rv;
+  bool found;
+  if (IndexedGetter(aIndexHint, found, rv) == aItem) {
+    mItems.RemoveElementAt(aIndexHint);
+  } else {
+    mItems.RemoveElement(aItem);
+  }
+  rv.SuppressException();
+
+  // Check if the aMozIndexHint and aMozOffsetHint are actually the index and
+  // offset, and then remove them from mIndexedItems
+  MOZ_ASSERT(aItem->Index() < mIndexedItems.Length());
+  nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[aItem->Index()];
+  if (aMozOffsetHint < items.Length() && aItem == items[aMozOffsetHint]) {
+    items.RemoveElementAt(aMozOffsetHint);
+  } else {
+    items.RemoveElement(aItem);
+  }
+
+  // Check if we should remove the index. We never remove index 0.
+  if (items.Length() == 0 && aItem->Index() != 0) {
+    mIndexedItems.RemoveElementAt(aItem->Index());
+
+    // Update the index of every element which has now been shifted
+    for (uint32_t i = aItem->Index(); i < mIndexedItems.Length(); i++) {
+      nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[i];
+      for (uint32_t j = 0; j < items.Length(); j++) {
+        items[j]->SetIndex(i);
+      }
+    }
+  }
+
+  // Give the removed item the invalid index
+  aItem->SetIndex(-1);
+
+  if (aItem->Kind() == DataTransferItem::KIND_FILE) {
+    RegenerateFiles();
+  }
+}
+
+void
+DataTransferItemList::RegenerateFiles()
+{
+  // We don't want to regenerate the files list unless we already have a files
+  // list. That way we can avoid the unnecessary work if the user never touches
+  // the files list.
+  if (mFiles) {
+    // We clear the list rather than performing smaller updates, because it
+    // simplifies the logic greatly on this code path, which should be very
+    // infrequently used.
+    mFiles->Clear();
+
+    uint32_t count = Length();
+    for (uint32_t i = 0; i < count; i++) {
+      ErrorResult rv;
+      bool found;
+      RefPtr<DataTransferItem> item = IndexedGetter(i, found, rv);
+      if (NS_WARN_IF(!found || rv.Failed())) {
+        continue;
+      }
+
+      if (item->Kind() == DataTransferItem::KIND_FILE) {
+        RefPtr<File> file = item->GetAsFile(rv);
+        if (NS_WARN_IF(rv.Failed() || !file)) {
+          continue;
+        }
+        mFiles->Append(file);
+      }
+    }
+  }
+}
+
+} // namespace mozilla
+} // namespace dom
new file mode 100644
--- /dev/null
+++ b/dom/events/DataTransferItemList.h
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_DataTransferItemList_h
+#define mozilla_dom_DataTransferItemList_h
+
+#include "mozilla/dom/DataTransfer.h"
+#include "mozilla/dom/DataTransferItem.h"
+#include "mozilla/dom/FileList.h"
+
+namespace mozilla {
+namespace dom {
+
+class DataTransferItem;
+
+class DataTransferItemList final : public nsISupports
+                                 , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DataTransferItemList);
+
+  DataTransferItemList(DataTransfer* aParent, bool aIsExternal,
+                       bool aIsCrossDomainSubFrameDrop)
+    : mParent(aParent)
+    , mIsCrossDomainSubFrameDrop(aIsCrossDomainSubFrameDrop)
+    , mIsExternal(aIsExternal)
+  {
+    // We always allocate an index 0 in our DataTransferItemList. This is done
+    // in order to maintain the invariants according to the spec. Mainly, within
+    // the spec's list, there is intended to be a single copy of each mime type,
+    // for string typed items. File typed items are allowed to have duplicates.
+    // In the old moz* system, this was modeled by having multiple indexes, each
+    // of which was independent. Files were fetched from all indexes, but
+    // strings were only fetched from the first index. In order to maintain this
+    // correlation and avoid breaking code with the new changes, index 0 is now
+    // always present and used to store strings, and all file items are given
+    // their own index starting at index 1.
+    mIndexedItems.SetLength(1);
+  }
+
+  already_AddRefed<DataTransferItemList> Clone(DataTransfer* aParent) const;
+
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto) override;
+
+  uint32_t Length() const
+  {
+    return mItems.Length();
+  };
+
+  DataTransferItem* Add(const nsAString& aData, const nsAString& aType,
+                        ErrorResult& rv);
+  DataTransferItem* Add(File& aData, ErrorResult& aRv);
+
+  void Remove(uint32_t aIndex, ErrorResult& aRv);
+
+  DataTransferItem* IndexedGetter(uint32_t aIndex, bool& aFound,
+                                  ErrorResult& aRv) const;
+
+  void Clear(ErrorResult& aRv);
+
+  DataTransfer* GetParentObject() const
+  {
+    return mParent;
+  }
+
+  // Accessors for data from ParentObject
+  bool IsReadOnly() const;
+  int32_t ClipboardType() const;
+  EventMessage GetEventMessage() const;
+
+  already_AddRefed<DataTransferItem>
+  SetDataWithPrincipal(const nsAString& aType, nsIVariant* aData,
+                       uint32_t aIndex, nsIPrincipal* aPrincipal,
+                       bool aInsertOnly, ErrorResult& aRv);
+
+  FileList* Files();
+
+  // Moz-style helper methods for interacting with the stored data
+  void MozRemoveByTypeAt(const nsAString& aType, uint32_t aIndex,
+                         ErrorResult& aRv);
+  DataTransferItem* MozItemByTypeAt(const nsAString& aType, uint32_t aIndex);
+  const nsTArray<RefPtr<DataTransferItem>>* MozItemsAt(uint32_t aIndex);
+  uint32_t MozItemCount() const;
+
+  // Causes everything in indexes above 0 to shift down one index.
+  void PopIndexZero();
+
+  // Delete every item in the DataTransferItemList, without checking for
+  // permissions or read-only status (for internal use only).
+  void ClearAllItems();
+
+private:
+  void ClearDataHelper(DataTransferItem* aItem, uint32_t aIndexHint,
+                       uint32_t aMozOffsetHint, ErrorResult& aRv);
+  DataTransferItem* AppendNewItem(uint32_t aIndex, const nsAString& aType,
+                                  nsIVariant* aData, nsIPrincipal* aPrincipal);
+  void RegenerateFiles();
+
+  ~DataTransferItemList() {}
+
+  RefPtr<DataTransfer> mParent;
+  bool mIsCrossDomainSubFrameDrop;
+  bool mIsExternal;
+  RefPtr<FileList> mFiles;
+  nsTArray<RefPtr<DataTransferItem>> mItems;
+  nsTArray<nsTArray<RefPtr<DataTransferItem>>> mIndexedItems;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_DataTransferItemList_h
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -1808,17 +1808,17 @@ EventStateManager::GenerateDragGesture(n
       // Emit observer event to allow addons to modify the DataTransfer object.
       if (observerService) {
         observerService->NotifyObservers(dataTransfer,
                                          "on-datatransfer-available",
                                          nullptr);
       }
 
       // now that the dataTransfer has been updated in the dragstart and
-      // draggesture events, make it read only so that the data doesn't
+      // draggesture events, make it readonly so that the data doesn't
       // change during the drag.
       dataTransfer->SetReadOnly();
 
       if (status != nsEventStatus_eConsumeNoDefault) {
         bool dragStarted = DoDefaultDragStart(aPresContext, event, dataTransfer,
                                               targetContent, selection);
         if (dragStarted) {
           sActiveESM = nullptr;
--- a/dom/events/moz.build
+++ b/dom/events/moz.build
@@ -39,16 +39,18 @@ EXPORTS.mozilla.dom += [
     'BeforeAfterKeyboardEvent.h',
     'BeforeUnloadEvent.h',
     'ClipboardEvent.h',
     'CommandEvent.h',
     'CompositionEvent.h',
     'CustomEvent.h',
     'DataContainerEvent.h',
     'DataTransfer.h',
+    'DataTransferItem.h',
+    'DataTransferItemList.h',
     'DeviceMotionEvent.h',
     'DragEvent.h',
     'Event.h',
     'EventTarget.h',
     'FocusEvent.h',
     'ImageCaptureError.h',
     'InputEvent.h',
     'KeyboardEvent.h',
@@ -117,16 +119,18 @@ UNIFIED_SOURCES += [
     'UIEvent.cpp',
     'WheelEvent.cpp',
     'WheelHandlingHelper.cpp',
     'XULCommandEvent.cpp',
 ]
 
 # nsEventStateManager.cpp should be built separately because of Mac OS X headers.
 SOURCES += [
+    'DataTransferItem.cpp',
+    'DataTransferItemList.cpp',
     'EventStateManager.cpp',
 ]
 
 if CONFIG['MOZ_WEBSPEECH']:
     UNIFIED_SOURCES += ['SpeechRecognitionError.cpp']
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
--- a/dom/events/test/chrome.ini
+++ b/dom/events/test/chrome.ini
@@ -20,8 +20,9 @@ support-files =
 [test_bug602962.xul]
 [test_bug617528.xul]
 [test_bug679494.xul]
 [test_bug930374-chrome.html]
 [test_bug1128787-1.html]
 [test_bug1128787-2.html]
 [test_bug1128787-3.html]
 [test_eventctors.xul]
+[test_DataTransferItemList.html]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/events/test/test_DataTransferItemList.html
@@ -0,0 +1,227 @@
+<html>
+<head>
+  <title>Tests for the DatTransferItemList object</title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body style="height: 300px; overflow: auto;">
+<p id="display"> </p>
+<img id="image" draggable="true" src="data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%18%00%00%00%18%02%03%00%00%00%9D%19%D5k%00%00%00%04gAMA%00%00%B1%8F%0B%FCa%05%00%00%00%0CPLTE%FF%FF%FF%FF%FF%FF%F7%DC%13%00%00%00%03%80%01X%00%00%00%01tRNS%08N%3DPT%00%00%00%01bKGD%00%88%05%1DH%00%00%00%09pHYs%00%00%0B%11%00%00%0B%11%01%7Fd_%91%00%00%00%07tIME%07%D2%05%0C%14%0C%0D%D8%3F%1FQ%00%00%00%5CIDATx%9C%7D%8E%CB%09%C0%20%10D%07r%B7%20%2F%E9wV0%15h%EA%D9%12D4%BB%C1x%CC%5C%1E%0C%CC%07%C0%9C0%9Dd7()%C0A%D3%8D%E0%B8%10%1DiCHM%D0%AC%D2d%C3M%F1%B4%E7%FF%10%0BY%AC%25%93%CD%CBF%B5%B2%C0%3Alh%CD%AE%13%DF%A5%F7%E0%03byW%09A%B4%F3%E2%00%00%00%00IEND%AEB%60%82">
+<div id="over" "style="width: 100px; height: 100px; border: 2px black dashed;">
+  drag over here
+</div>
+
+<script>
+  function spin() {
+    // Defer to the event loop twice to wait for any events to be flushed out.
+    return new Promise(function(a) {
+      SimpleTest.executeSoon(function() {
+        SimpleTest.executeSoon(a)
+      });
+    });
+  }
+
+  add_task(function* () {
+    yield spin();
+    var draggable = document.getElementById('image');
+    var over = document.getElementById('over');
+
+    var dragstartFired = 0;
+    draggable.addEventListener('dragstart', onDragStart);
+    function onDragStart(e) {
+      draggable.removeEventListener('dragstart', onDragStart);
+
+      var dt = e.dataTransfer;
+      dragstartFired++;
+
+      ok(true, "dragStart event fired");
+      var dtList = e.dataTransfer.items;
+      ok(dtList instanceof DataTransferItemList,
+         "DataTransfer.items returns a DataTransferItemList");
+
+      for (var i = 0; i < dtList.length; i++) {
+        var item = dtList[i];
+        ok(item instanceof DataTransferItem,
+           "operator[] returns DataTransferItem objects");
+        if (item.kind == "file") {
+          var file = item.getAsFile();
+          ok(file instanceof File, "getAsFile() returns File objects");
+        }
+      }
+
+      dtList.clear();
+      is(dtList.length, 0, "after .clear() DataTransferItemList should be empty");
+
+      dtList.add("this is some text", "text/plain");
+      dtList.add("<a href='www.mozilla.org'>this is a link</a>", "text/html");
+      dtList.add("http://www.mozilla.org", "text/uri-list");
+      dtList.add("this is custom-data", "custom-data");
+
+
+      var file = new File(['<a id="a"><b id="b">hey!</b></a>'], "myfile.html",
+                          {type: "text/html"});
+
+      dtList.add(file);
+
+      checkTypes(["text/plain", "text/html", "text/uri-list", "custom-data", "text/html"],
+                 dtList, "DataTransferItemList.add test");
+
+      var files = e.dataTransfer.files;
+      is(files.length, 1, "DataTransfer.files should contain the one file we added earlier");
+      is(files[0], file, "It should be the same file as the file we originally created");
+      is(file, e.dataTransfer.mozGetDataAt("text/html", 1),
+         "It should be stored in index 1 for mozGetDataAt");
+
+      var file2 = new File(['<a id="c"><b id="d">yo!</b></a>'], "myotherfile.html",
+                           {type: "text/html"});
+      dtList.add(file2);
+
+      is(files.length, 2, "The files property should have been updated in place");
+      is(files[1], file2, "It should be the same file as the file we originally created");
+      is(file2, e.dataTransfer.mozGetDataAt("text/html", 2),
+         "It should be stored in index 2 for mozGetDataAt");
+
+      var oldLength = dtList.length;
+      var randomString = "foo!";
+      e.dataTransfer.mozSetDataAt("random/string", randomString, 3);
+      is(oldLength, dtList.length,
+         "Adding a non-file entry to a non-zero index should not add an item to the items list");
+
+      var file3 = new File(['<a id="e"><b id="f">heya!</b></a>'], "yetanotherfile.html",
+                           {type: "text/html"});
+      e.dataTransfer.mozSetDataAt("random/string", file3, 3);
+      is(oldLength + 1, dtList.length,
+         "Replacing the entry with a file should add it to the list!");
+      is(dtList[oldLength].getAsFile(), file3, "It should be stored in the last index as a file");
+      is(dtList[oldLength].type, "random/string", "It should have the correct type");
+      is(dtList[oldLength].kind, "file", "It should have the correct kind");
+      is(files[files.length - 1], file3, "It should also be in the files list");
+
+      oldLength = dtList.length;
+      var nonstring = {};
+      e.dataTransfer.mozSetDataAt("jsobject", nonstring, 0);
+      is(oldLength + 1, dtList.length,
+         "Adding a non-string object using the mozAPIs to index 0 should add an item to the dataTransfer");
+      is(dtList[oldLength].type, "jsobject", "It should have the correct type");
+      is(dtList[oldLength].kind, "other", "It should have the correct kind");
+
+      // Clear the event's data and get it set up so we can read it later!
+      dtList.clear();
+
+      dtList.add(file);
+      dtList.add("this is some text", "text/plain");
+      is(e.dataTransfer.mozGetDataAt("text/html", 1), file);
+    }
+
+    var getAsStringCalled = 0;
+    var dragenterFired = 0;
+    over.addEventListener('dragenter', onDragEnter);
+    function onDragEnter(e) {
+      over.removeEventListener('dragenter', onDragEnter);
+
+      var dt = e.dataTransfer;
+      dragenterFired++;
+
+      readOnly(e);
+    }
+
+    var dropFired = 0;
+    over.addEventListener('drop', onDrop);
+    function onDrop(e) {
+      over.removeEventListener('drop', onDrop);
+
+      var dt = e.dataTransfer;
+      dropFired++;
+      e.preventDefault();
+
+      readOnly(e);
+    }
+
+
+    function readOnly(e) {
+      var dtList = e.dataTransfer.items;
+      var num = dtList.length;
+
+      // .clear() should have no effect
+      dtList.clear();
+      is(dtList.length, num,
+         ".clear() should have no effect on the object during a readOnly event");
+
+      // .remove(i) should throw InvalidStateError
+      for (var i = 0; i < dtList.length; i++) {
+        expectError(function() { dtList.remove(i); },
+                    "InvalidStateError", ".remove(" + i + ") during a readOnly event");
+      }
+
+      // .add() should return null and have no effect
+      var data = [["This is a plain string",  "text/plain"],
+                  ["This is <em>HTML!</em>",  "text/html"],
+                  ["http://www.mozilla.org/", "text/uri-list"],
+                  ["this is some custom data", "custom-data"]];
+
+      for (var i = 0; i < data.length; i++) {
+        is(dtList.add(data[i][0], data[i][1]), null,
+           ".add() should return null during a readOnly event");
+
+        is(dtList.length, num, ".add() should have no effect during a readOnly event");
+      }
+
+      // .add() with a file should return null and have no effect
+      var file = new File(['<a id="a"><b id="b">hey!</b></a>'], "myfile.html",
+                          {type: "text/html"});
+      is(dtList.add(file), null, ".add() with a file should return null during a readOnly event");
+      is(dtList.length, num, ".add() should have no effect during a readOnly event");
+
+      // We should be able to access the files
+      is(e.dataTransfer.files.length, 1, "Should be able to access files");
+      ok(e.dataTransfer.files[0], "File should be the same file!");
+      is(e.dataTransfer.items.length, 2, "Should be able to see there are 2 items");
+
+      is(e.dataTransfer.items[0].kind, "file", "First item should be a file");
+      is(e.dataTransfer.items[1].kind, "string", "Second item should be a string");
+
+      is(e.dataTransfer.items[0].type, "text/html", "first item should be text/html");
+      is(e.dataTransfer.items[1].type, "text/plain", "second item should be text/plain");
+
+      ok(e.dataTransfer.items[0].getAsFile(), "Should be able to get file");
+      e.dataTransfer.items[1].getAsString(function(s) {
+        getAsStringCalled++;
+        is(s, "this is some text", "Should provide the correct string");
+      });
+    }
+
+    synthesizeDrop(draggable, over, null, null);
+
+    // Wait for the getAsString callbacks to complete
+    yield spin();
+    is(getAsStringCalled, 2, "getAsString should be called twice");
+
+    // Sanity-check to make sure that the events were actually run
+    is(dragstartFired, 1, "dragstart fired");
+    is(dragenterFired, 1, "dragenter fired");
+    is(dropFired, 1, "drop fired");
+  });
+
+  function expectError(fn, eid, testid) {
+    var error = "";
+    try {
+      fn();
+    } catch (ex) {
+      error = ex.name;
+    }
+    is(error, eid, testid + " causes exception " + eid);
+  }
+
+  function checkTypes(aExpectedList, aDtList, aTestid) {
+    is(aDtList.length, aExpectedList.length, aTestid + " length test");
+    for (var i = 0; i < aExpectedList.length; i++) {
+      is(aDtList[i].type, aExpectedList[i], aTestid + " type " + i);
+    }
+  }
+</script>
+
+</body>
+</html>
--- a/dom/events/test/test_dragstart.html
+++ b/dom/events/test/test_dragstart.html
@@ -353,24 +353,29 @@ function test_DataTransfer(dt)
   checkOneDataItem(dt, ["text/plain", "text/html"],
                    ["First Item", "Changed with setData"], 0, "clearData type that does not exist item at index 0");
   checkOneDataItem(dt, ["text/unknown"],
                    ["Unknown type"], 1, "clearData type that does not exist item at index 1");
 
   expectError(() => dt.mozClearDataAt("text/plain", 3),
               "IndexSizeError", "clearData index too high with two items");
 
-  // ensure that clearData() removes all data associated with the first item
+  // ensure that clearData() removes all data associated with the first item, but doesn't
+  // shift the second item down into the first item's slot.
   dt.clearData();
-  is(dt.mozItemCount, 1, "clearData no argument with multiple items itemCount");
+  is(dt.mozItemCount, 2, "clearData no argument with multiple items itemCount");
+  checkOneDataItem(dt, [], [], 0,
+                   "clearData no argument with multiple items item at index 0");
   checkOneDataItem(dt, ["text/unknown"],
-                   ["Unknown type"], 0, "clearData no argument with multiple items item at index 1");
+                   ["Unknown type"], 1, "clearData no argument with multiple items item at index 1");
 
-  // remove tha remaining data
-  dt.mozClearDataAt("", 0);
+  // remove tha remaining data in index 1. As index 0 is empty at this point, this will actually
+  // drop mozItemCount to 0. (XXX: This is because of spec-compliance reasons related
+  // to the more-recent dt.item API. It's an unfortunate, but hopefully rare edge case)
+  dt.mozClearDataAt("", 1);
   is(dt.mozItemCount, 0, "all data cleared");
 
   // now check the effectAllowed and dropEffect properties
   is(dt.dropEffect, "none", "initial dropEffect");
   is(dt.effectAllowed, "uninitialized", "initial effectAllowed");
 
   ["copy", "none", "link", "", "other", "copyMove", "all", "uninitialized", "move"].forEach(
     function (i) {
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -231,8 +231,15 @@ PushMessageDecryptionFailure=The ServiceWorker for scope ‘%1$S’ encountered an error decrypting a push message: ‘%2$S’. For help with encryption, please see https://developer.mozilla.org/en-US/docs/Web/API/Push_API/Using_the_Push_API#Encryption
 # LOCALIZATION NOTE: %1$S is the type of a DOM event. 'passive' is a literal parameter from the DOM spec.
 PreventDefaultFromPassiveListenerWarning=Ignoring ‘preventDefault()’ call on event of type ‘%1$S’ from a listener registered as ‘passive’.
 FileLastModifiedDateWarning=File.lastModifiedDate is deprecated. Use File.lastModified instead.
 ChromeScriptedDOMParserWithoutPrincipal=Creating DOMParser without a principal is deprecated.
 IIRFilterChannelCountChangeWarning=IIRFilterNode channel count changes may produce audio glitches.
 BiquadFilterChannelCountChangeWarning=BiquadFilterNode channel count changes may produce audio glitches.
 # LOCALIZATION NOTE: %1$S is the unanimatable paced property.
 UnanimatablePacedProperty=Paced property ‘%1$S’ is not an animatable property.
+# LOCALIZATION NOTE: Do not translate ".jpeg"
+GenericImageNameJPEG=image.jpeg
+# LOCALIZATION NOTE: Do not translate ".gif"
+GenericImageNameGIF=image.gif
+# LOCALIZATION NOTE: Do not translate ".png"
+GenericImageNamePNG=image.png
+GenericFileName=file
--- a/dom/tests/mochitest/general/test_clipboard_events.html
+++ b/dom/tests/mochitest/general/test_clipboard_events.html
@@ -659,17 +659,17 @@ function checkCachedDataTransfer(cd, eve
   ok(oldtext != "Some Clipboard Text", "clipboard get using " + testprefix);
 
   var exh = false;
   try { cd.mozSetDataAt("text/plain", "Test Cache Data", 0); } catch (ex) { exh = true; }
   ok(eventtype == "paste" ? exh : !exh, "exception occured setting " + testprefix);
 
   var newtext = (eventtype == "paste") ? cd.getData("text/plain") :
                                          cd.mozGetDataAt("text/plain", 0);
-  is(newtext, (eventtype == "paste") ? "" : "Test Cache Data",
+  is(newtext, (eventtype == "paste") ? oldtext : "Test Cache Data",
      " clipboardData not changed using " + testprefix);
 
   is(getClipboardText(), "Some Clipboard Text", "clipboard not changed using " + testprefix);
 
   var exh = false;
   try { cd.mozClearDataAt("text/plain", 0); } catch (ex) { exh = true; }
   ok(eventtype == "paste" ? exh : !exh, "exception occured clearing " + testprefix);
 
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -381,16 +381,20 @@ var interfaceNamesInGlobalScope =
     "CustomEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "DataChannel",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "DataErrorEvent", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "DataTransfer",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    "DataTransferItem",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "DataTransferItemList",
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "DelayNode",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "DesktopNotification",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "DesktopNotificationCenter",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "DeviceLightEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/dom/webidl/DataTransfer.webidl
+++ b/dom/webidl/DataTransfer.webidl
@@ -7,21 +7,22 @@
  * http://www.whatwg.org/specs/web-apps/current-work/#the-datatransfer-interface
  */
 
 [ChromeConstructor(DOMString eventType, boolean isExternal)]
 interface DataTransfer {
            attribute DOMString dropEffect;
            attribute DOMString effectAllowed;
 
-  //readonly attribute DataTransferItemList items;
+  readonly attribute DataTransferItemList items;
 
   [Throws]
   void setDragImage(Element image, long x, long y);
 
+  [Throws]
   readonly attribute DOMStringList types;
   [Throws]
   DOMString getData(DOMString format);
   [Throws]
   void setData(DOMString format, DOMString data);
   [Throws]
   void clearData(optional DOMString format);
   [Throws]
new file mode 100644
--- /dev/null
+++ b/dom/webidl/DataTransferItem.webidl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is:
+ * https://html.spec.whatwg.org/multipage/interaction.html#the-datatransferitem-interface
+ */
+
+interface DataTransferItem {
+  readonly attribute DOMString kind;
+  readonly attribute DOMString type;
+  [Throws]
+  void getAsString(FunctionStringCallback? _callback);
+  [Throws]
+  File? getAsFile();
+};
+
+callback FunctionStringCallback = void (DOMString data);
new file mode 100644
--- /dev/null
+++ b/dom/webidl/DataTransferItemList.webidl
@@ -0,0 +1,22 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is:
+ * https://html.spec.whatwg.org/multipage/interaction.html#the-datatransferitemlist-interface
+ */
+
+interface DataTransferItemList {
+  readonly attribute unsigned long length;
+  [Throws]
+  getter DataTransferItem (unsigned long index);
+  [Throws]
+  DataTransferItem? add(DOMString data, DOMString type);
+  [Throws]
+  DataTransferItem? add(File data);
+  [Throws]
+  void remove(unsigned long index);
+  [Throws]
+  void clear();
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -105,16 +105,18 @@ WEBIDL_FILES = [
     'CSSRuleList.webidl',
     'CSSStyleDeclaration.webidl',
     'CSSStyleSheet.webidl',
     'CSSTransition.webidl',
     'CSSValue.webidl',
     'CSSValueList.webidl',
     'DataContainerEvent.webidl',
     'DataTransfer.webidl',
+    'DataTransferItem.webidl',
+    'DataTransferItemList.webidl',
     'DecoderDoctorNotification.webidl',
     'DedicatedWorkerGlobalScope.webidl',
     'DelayNode.webidl',
     'DesktopNotification.webidl',
     'DeviceMotionEvent.webidl',
     'DeviceStorage.webidl',
     'DeviceStorageAreaListener.webidl',
     'Directory.webidl',
--- a/editor/libeditor/nsEditorEventListener.cpp
+++ b/editor/libeditor/nsEditorEventListener.cpp
@@ -934,17 +934,21 @@ nsEditorEventListener::CanDrop(nsIDOMDra
     return false;
   }
 
   nsCOMPtr<nsIDOMDataTransfer> domDataTransfer;
   aEvent->GetDataTransfer(getter_AddRefs(domDataTransfer));
   nsCOMPtr<DataTransfer> dataTransfer = do_QueryInterface(domDataTransfer);
   NS_ENSURE_TRUE(dataTransfer, false);
 
-  RefPtr<DOMStringList> types = dataTransfer->Types();
+  ErrorResult err;
+  RefPtr<DOMStringList> types = dataTransfer->GetTypes(err);
+  if (NS_WARN_IF(err.Failed())) {
+    return false;
+  }
 
   // Plaintext editors only support dropping text. Otherwise, HTML and files
   // can be dropped as well.
   if (!types->Contains(NS_LITERAL_STRING(kTextMime)) &&
       !types->Contains(NS_LITERAL_STRING(kMozTextInternal)) &&
       (mEditor->IsPlaintextEditor() ||
        (!types->Contains(NS_LITERAL_STRING(kHTMLMime)) &&
         !types->Contains(NS_LITERAL_STRING(kFileMime))))) {
--- a/layout/forms/nsFileControlFrame.cpp
+++ b/layout/forms/nsFileControlFrame.cpp
@@ -259,17 +259,22 @@ nsFileControlFrame::DnDListener::HandleE
 
 /* static */ bool
 nsFileControlFrame::DnDListener::IsValidDropData(nsIDOMDataTransfer* aDOMDataTransfer)
 {
   nsCOMPtr<DataTransfer> dataTransfer = do_QueryInterface(aDOMDataTransfer);
   NS_ENSURE_TRUE(dataTransfer, false);
 
   // We only support dropping files onto a file upload control
-  RefPtr<DOMStringList> types = dataTransfer->Types();
+  ErrorResult rv;
+  RefPtr<DOMStringList> types = dataTransfer->GetTypes(rv);
+  if (NS_WARN_IF(rv.Failed())) {
+    return false;
+  }
+
   return types->Contains(NS_LITERAL_STRING("Files"));
 }
 
 /* static */ bool
 nsFileControlFrame::DnDListener::CanDropTheseFiles(nsIDOMDataTransfer* aDOMDataTransfer,
                                                    bool aSupportsMultiple)
 {
   nsCOMPtr<DataTransfer> dataTransfer = do_QueryInterface(aDOMDataTransfer);
--- a/testing/web-platform/meta/html/dom/interfaces.html.ini
+++ b/testing/web-platform/meta/html/dom/interfaces.html.ini
@@ -1700,70 +1700,16 @@
     expected: FAIL
 
   [Path2D interface: operation addPathByStrokingText(DOMString,CanvasDrawingStyles,SVGMatrix,Path2D,unrestricted double)]
     expected: FAIL
 
   [DataTransfer interface object length]
     expected: FAIL
 
-  [DataTransfer interface: attribute items]
-    expected: FAIL
-
-  [DataTransferItemList interface: existence and properties of interface object]
-    expected: FAIL
-
-  [DataTransferItemList interface object length]
-    expected: FAIL
-
-  [DataTransferItemList interface: existence and properties of interface prototype object]
-    expected: FAIL
-
-  [DataTransferItemList interface: existence and properties of interface prototype object's "constructor" property]
-    expected: FAIL
-
-  [DataTransferItemList interface: attribute length]
-    expected: FAIL
-
-  [DataTransferItemList interface: operation add(DOMString,DOMString)]
-    expected: FAIL
-
-  [DataTransferItemList interface: operation add(File)]
-    expected: FAIL
-
-  [DataTransferItemList interface: operation remove(unsigned long)]
-    expected: FAIL
-
-  [DataTransferItemList interface: operation clear()]
-    expected: FAIL
-
-  [DataTransferItem interface: existence and properties of interface object]
-    expected: FAIL
-
-  [DataTransferItem interface object length]
-    expected: FAIL
-
-  [DataTransferItem interface: existence and properties of interface prototype object]
-    expected: FAIL
-
-  [DataTransferItem interface: existence and properties of interface prototype object's "constructor" property]
-    expected: FAIL
-
-  [DataTransferItem interface: attribute kind]
-    expected: FAIL
-
-  [DataTransferItem interface: attribute type]
-    expected: FAIL
-
-  [DataTransferItem interface: operation getAsString(FunctionStringCallback)]
-    expected: FAIL
-
-  [DataTransferItem interface: operation getAsFile()]
-    expected: FAIL
-
   [Window interface: operation showModalDialog(DOMString,any)]
     disabled:
       if e10s: https://bugzilla.mozilla.org/show_bug.cgi?id=981796
 
   [Window interface: attribute onautocomplete]
     expected: FAIL
 
   [Window interface: attribute onautocompleteerror]
@@ -2531,22 +2477,16 @@
     expected: FAIL
 
   [CanvasProxy interface object name]
     expected: FAIL
 
   [DrawingStyle interface object name]
     expected: FAIL
 
-  [DataTransferItemList interface object name]
-    expected: FAIL
-
-  [DataTransferItem interface object name]
-    expected: FAIL
-
   [ApplicationCache interface object name]
     expected: FAIL
 
   [PortCollection interface object name]
     expected: FAIL
 
   [WorkerGlobalScope interface object name]
     expected: FAIL
--- a/widget/gonk/nsClipboard.cpp
+++ b/widget/gonk/nsClipboard.cpp
@@ -267,22 +267,28 @@ nsClipboard::GetData(nsITransferable *aT
         RefPtr<gfx::DataSourceSurface> image = mClipboard->GetImage();
 
         // Encode according to MIME type.
         RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(image, image->GetSize());
         nsCOMPtr<imgIContainer> imageContainer(image::ImageOps::CreateFromDrawable(drawable));
         nsCOMPtr<imgITools> imgTool = do_GetService(NS_IMGTOOLS_CID);
 
         nsCOMPtr<nsIInputStream> byteStream;
-        imgTool->EncodeImage(imageContainer, flavorStr, EmptyString(), getter_AddRefs(byteStream));
+        nsresult rv = imgTool->EncodeImage(imageContainer,
+                                           flavorStr,
+                                           EmptyString(),
+                                           getter_AddRefs(byteStream));
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          continue;
+        }
 
         // Set transferable.
-        nsresult rv = aTransferable->SetTransferData(flavorStr,
-                                                     byteStream,
-                                                     sizeof(nsIInputStream*));
+        rv = aTransferable->SetTransferData(flavorStr,
+                                            byteStream,
+                                            sizeof(nsIInputStream*));
         if (NS_WARN_IF(NS_FAILED(rv))) {
           continue;
         }
         break;
       }
     }
   }