merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 05 Jul 2016 16:03:24 +0200
changeset 303695 dbb31bcad5a1f60a35b5600ea1578d9b9fa55237
parent 303694 b2e48cc9d3a02fc172910ce8b29f83c19db20777 (current diff)
parent 303642 c71004cf2ebfe2f07b93ee08b1f9f553c35322d2 (diff)
child 303696 f08c54971dd185850d9f2abae42d604f5e820918
child 303748 9b428173a0889f5a25e7a6e855f2d1726207a723
child 303751 32c95171d0da188c731d0570117d424483ed790e
push id79141
push usercbook@mozilla.com
push dateTue, 05 Jul 2016 14:07:42 +0000
treeherdermozilla-inbound@f08c54971dd1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
dom/html/test/forms/test_experimental_forms_pref.html
testing/web-platform/meta/fetch/api/policies/referrer-origin-worker.html.ini
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -225,16 +225,17 @@ const SEC_ERROR_BASE          = Ci.nsINS
 const MOZILLA_PKIX_ERROR_BASE = Ci.nsINSSErrorsService.MOZILLA_PKIX_ERROR_BASE;
 
 const SEC_ERROR_EXPIRED_CERTIFICATE                = SEC_ERROR_BASE + 11;
 const SEC_ERROR_UNKNOWN_ISSUER                     = SEC_ERROR_BASE + 13;
 const SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE         = SEC_ERROR_BASE + 30;
 const SEC_ERROR_OCSP_FUTURE_RESPONSE               = SEC_ERROR_BASE + 131;
 const SEC_ERROR_OCSP_OLD_RESPONSE                  = SEC_ERROR_BASE + 132;
 const MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE = MOZILLA_PKIX_ERROR_BASE + 5;
+const MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE = MOZILLA_PKIX_ERROR_BASE + 6;
 
 const PREF_BLOCKLIST_CLOCK_SKEW_SECONDS = "services.blocklist.clock_skew_seconds";
 
 const PREF_SSL_IMPACT_ROOTS = ["security.tls.version.", "security.ssl3."];
 
 const PREF_SSL_IMPACT = PREF_SSL_IMPACT_ROOTS.reduce((prefs, root) => {
   return prefs.concat(Services.prefs.getChildList(root));
 }, []);
@@ -267,30 +268,31 @@ var AboutNetAndCertErrorListener = {
         this.onCertErrorDetails(msg);
         break;
     }
   },
 
   onCertErrorDetails(msg) {
     let div = content.document.getElementById("certificateErrorText");
     div.textContent = msg.data.info;
+    let learnMoreLink = content.document.getElementById("learnMoreLink");
 
     switch (msg.data.code) {
       case SEC_ERROR_UNKNOWN_ISSUER:
-        let learnMoreLink = content.document.getElementById("learnMoreLink");
         learnMoreLink.href = "https://support.mozilla.org/kb/troubleshoot-SEC_ERROR_UNKNOWN_ISSUER";
         break;
 
       // in case the certificate expired we make sure the system clock
       // matches settings server (kinto) time
       case SEC_ERROR_EXPIRED_CERTIFICATE:
       case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
       case SEC_ERROR_OCSP_FUTURE_RESPONSE:
       case SEC_ERROR_OCSP_OLD_RESPONSE:
       case MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE:
+      case MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE:
 
         // use blocklist stats if available
         if (Services.prefs.getPrefType(PREF_BLOCKLIST_CLOCK_SKEW_SECONDS)) {
           let difference = Services.prefs.getIntPref(PREF_BLOCKLIST_CLOCK_SKEW_SECONDS);
 
           // if the difference is more than a day
           if (Math.abs(difference) > 60 * 60 * 24) {
             let formatter = new Intl.DateTimeFormat();
@@ -306,17 +308,18 @@ var AboutNetAndCertErrorListener = {
               .textContent = actualDate;
 
             content.document.getElementById("errorShortDesc")
               .style.display = "none";
             content.document.getElementById("wrongSystemTimePanel")
               .style.display = "block";
           }
         }
-
+        let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "time-errors";
+        learnMoreLink.setAttribute("href", url);
         break;
     }
   },
 
   handleEvent: function(aEvent) {
     if (!this.isAboutNetError && !this.isAboutCertError) {
       return;
     }
--- a/browser/base/content/test/general/browser_aboutCertError.js
+++ b/browser/base/content/test/general/browser_aboutCertError.js
@@ -120,22 +120,24 @@ add_task(function* checkWrongSystemTimeW
     info("Loading and waiting for the cert error");
     yield certErrorLoaded;
 
     return yield ContentTask.spawn(browser, null, function* () {
       let doc = content.document;
       let div = doc.getElementById("wrongSystemTimePanel");
       let systemDateDiv = doc.getElementById("wrongSystemTime_systemDate");
       let actualDateDiv = doc.getElementById("wrongSystemTime_actualDate");
+      let learnMoreLink = doc.getElementById("learnMoreLink");
 
       return {
         divDisplay: content.getComputedStyle(div).display,
         text: div.textContent,
         systemDate: systemDateDiv.textContent,
-        actualDate: actualDateDiv.textContent
+        actualDate: actualDateDiv.textContent,
+        learnMoreLink: learnMoreLink.href
       };
     });
   }
 
   let formatter = new Intl.DateTimeFormat();
 
   // pretend we have a positively skewed (ahead) system time
   let serverDate = new Date("2015/10/27");
@@ -150,16 +152,17 @@ add_task(function* checkWrongSystemTimeW
   let message = yield Task.spawn(setUpPage);
 
   isnot(message.divDisplay, "none", "Wrong time message information is visible");
   ok(message.text.includes("because your clock appears to show the wrong time"),
      "Correct error message found");
   ok(message.text.includes("expired.example.com"), "URL found in error message");
   ok(message.systemDate.includes(localDateFmt), "correct local date displayed");
   ok(message.actualDate.includes(serverDateFmt), "correct server date displayed");
+  ok(message.learnMoreLink.includes("time-errors"), "time-errors in the Learn More URL");
 
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
 
   // pretend we have a negatively skewed (behind) system time
   serverDate = new Date();
   serverDate.setYear(serverDate.getFullYear() + 1);
   serverDateFmt = formatter.format(serverDate);
 
--- a/browser/base/content/test/general/browser_contextmenu_input.js
+++ b/browser/base/content/test/general/browser_contextmenu_input.js
@@ -215,17 +215,17 @@ add_task(function* test_search_input() {
      "context-selectall",   null,
      "---",                 null,
      "spell-check-enabled", true],
     {skipFocusChange: true}
   );
 });
 
 add_task(function* test_datetime_month_week_datetimelocal_input_todos() {
-  for (let type of ["datetime", "month", "week", "datetime-local"]) {
+  for (let type of ["datetime", "week", "datetime-local"]) {
     let returnedType = yield ContentTask.spawn(gBrowser.selectedBrowser, type, function*(type) {
       let doc = content.document;
       let input = doc.getElementById("input_" + type);
       return input.type;
     });
     todo_is(returnedType, type, `TODO: add test for ${type} input fields`);
   }
 });
--- a/browser/modules/PluginContent.jsm
+++ b/browser/modules/PluginContent.jsm
@@ -48,18 +48,16 @@ PluginContent.prototype = {
     global.addEventListener("unload",                this);
 
     global.addMessageListener("BrowserPlugins:ActivatePlugins", this);
     global.addMessageListener("BrowserPlugins:NotificationShown", this);
     global.addMessageListener("BrowserPlugins:ContextMenuCommand", this);
     global.addMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this);
     global.addMessageListener("BrowserPlugins:CrashReportSubmitted", this);
     global.addMessageListener("BrowserPlugins:Test:ClearCrashData", this);
-
-    Services.obs.addObserver(this, "Plugin::HiddenPluginTouched", false);
   },
 
   uninit: function() {
     let global = this.global;
 
     global.removeEventListener("PluginBindingAttached", this, true);
     global.removeEventListener("PluginCrashed",         this, true);
     global.removeEventListener("PluginOutdated",        this, true);
@@ -72,18 +70,16 @@ PluginContent.prototype = {
     global.removeMessageListener("BrowserPlugins:ActivatePlugins", this);
     global.removeMessageListener("BrowserPlugins:NotificationShown", this);
     global.removeMessageListener("BrowserPlugins:ContextMenuCommand", this);
     global.removeMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this);
     global.removeMessageListener("BrowserPlugins:CrashReportSubmitted", this);
     global.removeMessageListener("BrowserPlugins:Test:ClearCrashData", this);
     delete this.global;
     delete this.content;
-
-    Services.obs.removeObserver(this, "Plugin::HiddenPluginTouched");
   },
 
   receiveMessage: function (msg) {
     switch (msg.name) {
       case "BrowserPlugins:ActivatePlugins":
         this.activatePlugins(msg.data.pluginInfo, msg.data.newState);
         break;
       case "BrowserPlugins:NotificationShown":
@@ -115,25 +111,16 @@ PluginContent.prototype = {
       case "BrowserPlugins:Test:ClearCrashData":
         // This message should ONLY ever be sent by automated tests.
         if (Services.prefs.getBoolPref("plugins.testmode")) {
           this.pluginCrashData.clear();
         }
     }
   },
 
-  observe: function observe(aSubject, aTopic, aData) {
-    let pluginTag = aSubject.QueryInterface(Ci.nsIPluginTag);
-    if (aTopic == "Plugin::HiddenPluginTouched") {
-      this._showClickToPlayNotification(pluginTag, false);
-    } else {
-      Cu.reportError("unknown topic observed: " + aTopic);
-    }
-  },
-
   onPageShow: function (event) {
     // Ignore events that aren't from the main document.
     if (!this.content || event.target != this.content.document) {
       return;
     }
 
     // The PluginClickToPlay events are not fired when navigating using the
     // BF cache. |persisted| is true when the page is loaded from the
@@ -202,55 +189,16 @@ PluginContent.prototype = {
              pluginName: pluginName,
              pluginTag: pluginTag,
              permissionString: permissionString,
              fallbackType: fallbackType,
              blocklistState: blocklistState,
            };
   },
 
-  _getPluginInfoForTag: function (pluginTag, tagMimetype) {
-    let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
-
-    let pluginName = gNavigatorBundle.GetStringFromName("pluginInfo.unknownPlugin");
-    let permissionString = null;
-    let blocklistState = null;
-
-    if (pluginTag) {
-      pluginName = BrowserUtils.makeNicePluginName(pluginTag.name);
-
-      permissionString = pluginHost.getPermissionStringForTag(pluginTag);
-      blocklistState = pluginTag.blocklistState;
-
-      // Convert this from nsIPluginTag so it can be serialized.
-      let properties = ["name", "description", "filename", "version", "enabledState", "niceName"];
-      let pluginTagCopy = {};
-      for (let prop of properties) {
-        pluginTagCopy[prop] = pluginTag[prop];
-      }
-      pluginTag = pluginTagCopy;
-
-      // Make state-softblocked == state-notblocked for our purposes,
-      // they have the same UI. STATE_OUTDATED should not exist for plugin
-      // items, but let's alias it anyway, just in case.
-      if (blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED ||
-          blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
-        blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
-      }
-    }
-
-    return { mimetype: tagMimetype,
-             pluginName: pluginName,
-             pluginTag: pluginTag,
-             permissionString: permissionString,
-             fallbackType: null,
-             blocklistState: blocklistState,
-           };
-  },
-
   /**
    * Update the visibility of the plugin overlay.
    */
   setVisibility : function (plugin, overlay, shouldShow) {
     overlay.classList.toggle("visible", shouldShow);
     if (shouldShow) {
       overlay.removeAttribute("dismissed");
     }
@@ -757,23 +705,17 @@ PluginContent.prototype = {
     }
 
     let pluginData = this.pluginData;
 
     let principal = this.content.document.nodePrincipal;
     let location = this.content.document.location.href;
 
     for (let p of plugins) {
-      let pluginInfo;
-      if (p instanceof Ci.nsIPluginTag) {
-        let mimeType = p.getMimeTypes() > 0 ? p.getMimeTypes()[0] : null;
-        pluginInfo = this._getPluginInfoForTag(p, mimeType);
-      } else {
-        pluginInfo = this._getPluginInfo(p);
-      }
+      let pluginInfo = this._getPluginInfo(p);
       if (pluginInfo.permissionString === null) {
         Cu.reportError("No permission string for active plugin.");
         continue;
       }
       if (pluginData.has(pluginInfo.permissionString)) {
         continue;
       }
 
--- a/build/autoconf/ffi.m4
+++ b/build/autoconf/ffi.m4
@@ -38,20 +38,24 @@ if test "$MOZ_BUILD_APP" != js -o -n "$J
       ac_configure_args="$ac_configure_args $var='`eval echo \\${${var}}`'"
     done
     old_cflags="$CFLAGS"
     # The libffi sources (especially the ARM ones) are written expecting gas
     # syntax, and clang's integrated assembler doesn't handle all of gas syntax.
     if test -n "$CLANG_CC" -a "$CPU_ARCH" = arm; then
       CFLAGS="-no-integrated-as $CFLAGS"
     fi
+    ac_configure_args="$ac_configure_args --build=$build --host=$target"
     if test "$CROSS_COMPILE"; then
-      export CPPFLAGS CFLAGS LDFLAGS
+      ac_configure_args="$ac_configure_args \
+                         CFLAGS=\"$CFLAGS\" \
+                         CPPFLAGS=\"$CPPFLAGS\" \
+                         LDFLAGS=\"$LDFLAGS\""
     fi
-    ac_configure_args="$ac_configure_args --build=$build --host=$target"
+    CFLAGS="$old_cflags"
     if test "$_MSC_VER"; then
       # Use a wrapper script for cl and ml that looks more like gcc.
       # autotools can't quite handle an MSVC build environment yet.
       LDFLAGS=
       CFLAGS=
       ac_configure_args="$ac_configure_args LD=link CPP=\"$CC -nologo -EP\" \
                          CXXCPP=\"$CXX -nologo -EP\" SHELL=sh.exe"
       flags=
@@ -78,14 +82,13 @@ if test "$MOZ_BUILD_APP" != js -o -n "$J
 
     # Use a separate cache file for libffi, since it does things differently
     # from our configure.
     old_config_files=$CONFIG_FILES
     unset CONFIG_FILES
     AC_OUTPUT_SUBDIRS(js/src/ctypes/libffi)
     ac_configure_args="$_SUBDIR_CONFIG_ARGS"
     CONFIG_FILES=$old_config_files
-    CFLAGS="$old_cflags"
   fi
 
 fi
 ])
 
--- a/dom/base/ScriptSettings.cpp
+++ b/dom/base/ScriptSettings.cpp
@@ -300,17 +300,17 @@ AutoJSAPI::~AutoJSAPI()
     // ScriptSettingsStack, so shouldn't pop.
     MOZ_ASSERT(ScriptSettingsStack::Top() != this);
     return;
   }
 
   ReportException();
 
   if (mOldWarningReporter.isSome()) {
-    JS::SetWarningReporter(JS_GetRuntime(cx()), mOldWarningReporter.value());
+    JS::SetWarningReporter(cx(), mOldWarningReporter.value());
   }
 
   // Leave the request before popping.
   if (mIsMainThread) {
     mAutoRequest.reset();
   }
 
   ScriptSettingsStack::Pop(this);
@@ -340,20 +340,19 @@ AutoJSAPI::InitInternal(nsIGlobalObject*
     // needed on worker threads, and we're hoping to kill it on the main thread
     // too.
     mAutoRequest.emplace(mCx);
   }
   mAutoNullableCompartment.emplace(mCx, aGlobal);
 
   ScriptSettingsStack::Push(this);
 
-  JSRuntime* rt = JS_GetRuntime(aCx);
-  mOldWarningReporter.emplace(JS::GetWarningReporter(rt));
+  mOldWarningReporter.emplace(JS::GetWarningReporter(aCx));
 
-  JS::SetWarningReporter(rt, WarningOnlyErrorReporter);
+  JS::SetWarningReporter(aCx, WarningOnlyErrorReporter);
 
 #ifdef DEBUG
   if (haveException) {
     JS::Rooted<JS::Value> exn(aCx);
     JS_GetPendingException(aCx, &exn);
 
     JS_ClearPendingException(aCx);
     if (exn.isObject()) {
--- 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/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -2688,17 +2688,17 @@ nsDocument::ApplySettingsFromCSP(bool aS
       bool hasReferrerPolicy = false;
       uint32_t referrerPolicy = mozilla::net::RP_Default;
       rv = csp->GetReferrerPolicy(&referrerPolicy, &hasReferrerPolicy);
       NS_ENSURE_SUCCESS_VOID(rv);
       if (hasReferrerPolicy) {
         mReferrerPolicy = static_cast<ReferrerPolicy>(referrerPolicy);
         mReferrerPolicySet = true;
       }
- 
+
       // Set up 'block-all-mixed-content' if not already inherited
       // from the parent context or set by any other CSP.
       if (!mBlockAllMixedContent) {
         rv = csp->GetBlockAllMixedContent(&mBlockAllMixedContent);
         NS_ENSURE_SUCCESS_VOID(rv);
       }
       if (!mBlockAllMixedContentPreloads) {
         mBlockAllMixedContentPreloads = mBlockAllMixedContent;
@@ -13370,16 +13370,21 @@ void
 nsIDocument::SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder)
 {
   mCachedEncoder = aEncoder;
 }
 
 void
 nsIDocument::SetContentTypeInternal(const nsACString& aType)
 {
+  if (!IsHTMLOrXHTML() && mDefaultElementType == kNameSpaceID_None &&
+      aType.EqualsLiteral("application/xhtml+xml")) {
+    mDefaultElementType = kNameSpaceID_XHTML;
+  }
+
   mCachedEncoder = nullptr;
   mContentType = aType;
 }
 
 nsILoadContext*
 nsIDocument::GetLoadContext() const
 {
   return mDocumentContainer;
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -2449,17 +2449,17 @@ nsJSContext::EnsureStatics()
 
   // Set up the asm.js cache callbacks
   static const JS::AsmJSCacheOps asmJSCacheOps = {
     AsmJSCacheOpenEntryForRead,
     asmjscache::CloseEntryForRead,
     AsmJSCacheOpenEntryForWrite,
     asmjscache::CloseEntryForWrite
   };
-  JS::SetAsmJSCacheOps(sRuntime, &asmJSCacheOps);
+  JS::SetAsmJSCacheOps(JS_GetContext(sRuntime), &asmJSCacheOps);
 
   // Set these global xpconnect options...
   Preferences::RegisterCallbackAndCall(SetMemoryHighWaterMarkPrefChangedCallback,
                                        "javascript.options.mem.high_water_mark");
 
   Preferences::RegisterCallbackAndCall(SetMemoryMaxPrefChangedCallback,
                                        "javascript.options.mem.max");
 
--- a/dom/base/nsPluginArray.cpp
+++ b/dom/base/nsPluginArray.cpp
@@ -14,18 +14,16 @@
 #include "nsIDocShell.h"
 #include "nsIWebNavigation.h"
 #include "nsPluginHost.h"
 #include "nsPluginTags.h"
 #include "nsIObserverService.h"
 #include "nsIWeakReference.h"
 #include "mozilla/Services.h"
 #include "nsIInterfaceRequestorUtils.h"
-#include "nsIPermissionManager.h"
-#include "nsIDocument.h"
 #include "nsContentUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsPluginArray::nsPluginArray(nsPIDOMWindowInner* aWindow)
   : mWindow(aWindow)
 {
@@ -70,18 +68,17 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsPluginArray,
                                       mWindow,
-                                      mPlugins,
-                                      mCTPPlugins)
+                                      mPlugins)
 
 static void
 GetPluginMimeTypes(const nsTArray<RefPtr<nsPluginElement> >& aPlugins,
                    nsTArray<RefPtr<nsMimeType> >& aMimeTypes)
 {
   for (uint32_t i = 0; i < aPlugins.Length(); ++i) {
     nsPluginElement *plugin = aPlugins[i];
     aMimeTypes.AppendElements(plugin->MimeTypes());
@@ -151,17 +148,16 @@ nsPluginArray::Refresh(bool aReloadDocum
     // the both arrays contain the same plugin tags (though as
     // different types).
     if (newPluginTags.Length() == mPlugins.Length()) {
       return;
     }
   }
 
   mPlugins.Clear();
-  mCTPPlugins.Clear();
 
   nsCOMPtr<nsIDOMNavigator> navigator = mWindow->GetNavigator();
 
   if (!navigator) {
     return;
   }
 
   static_cast<mozilla::dom::Navigator*>(navigator.get())->RefreshMIMEArray();
@@ -227,23 +223,16 @@ nsPluginArray::NamedGetter(const nsAStri
   if (!AllowPlugins() || ResistFingerprinting()) {
     return nullptr;
   }
 
   EnsurePlugins();
 
   nsPluginElement* plugin = FindPlugin(mPlugins, aName);
   aFound = (plugin != nullptr);
-  if (!aFound) {
-    nsPluginElement* hiddenPlugin = FindPlugin(mCTPPlugins, aName);
-    if (hiddenPlugin) {
-      nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-      obs->NotifyObservers(hiddenPlugin->PluginTag(), "Plugin::HiddenPluginTouched", nsString(aName).get());
-    }
-  }
   return plugin;
 }
 
 uint32_t
 nsPluginArray::Length()
 {
   if (!AllowPlugins() || ResistFingerprinting()) {
     return 0;
@@ -295,58 +284,34 @@ operator<(const RefPtr<nsPluginElement>&
 {
   // Sort plugins alphabetically by name.
   return lhs->PluginTag()->Name() < rhs->PluginTag()->Name();
 }
 
 void
 nsPluginArray::EnsurePlugins()
 {
-  if (!mPlugins.IsEmpty() || !mCTPPlugins.IsEmpty()) {
+  if (!mPlugins.IsEmpty()) {
     // We already have an array of plugin elements.
     return;
   }
 
   RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
   if (!pluginHost) {
     // We have no plugin host.
     return;
   }
 
   nsTArray<nsCOMPtr<nsIInternalPluginTag> > pluginTags;
   pluginHost->GetPlugins(pluginTags);
 
   // need to wrap each of these with a nsPluginElement, which is
   // scriptable.
   for (uint32_t i = 0; i < pluginTags.Length(); ++i) {
-    nsCOMPtr<nsPluginTag> pluginTag = do_QueryInterface(pluginTags[i]);
-    if (!pluginTag) {
-      mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
-    } else if (pluginTag->IsActive()) {
-      uint32_t permission = nsIPermissionManager::ALLOW_ACTION;
-      if (pluginTag->IsClicktoplay()) {
-        nsCString name;
-        pluginTag->GetName(name);
-        if (NS_LITERAL_CSTRING("Shockwave Flash").Equals(name)) {
-          RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
-          nsCString permString;
-          nsresult rv = pluginHost->GetPermissionStringForTag(pluginTag, 0, permString);
-          if (rv == NS_OK) {
-            nsIPrincipal* principal = mWindow->GetExtantDoc()->NodePrincipal();
-            nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
-            permMgr->TestPermissionFromPrincipal(principal, permString.get(), &permission);
-          }
-        }
-      }
-      if (permission == nsIPermissionManager::ALLOW_ACTION) {
-        mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
-      } else {
-        mCTPPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
-      }
-    }
+    mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
   }
 
   // Alphabetize the enumeration order of non-hidden plugins to reduce
   // fingerprintable entropy based on plugins' installation file times.
   mPlugins.Sort();
 }
 
 // nsPluginElement implementation.
--- a/dom/base/nsPluginArray.h
+++ b/dom/base/nsPluginArray.h
@@ -55,20 +55,16 @@ public:
 private:
   virtual ~nsPluginArray();
 
   bool AllowPlugins() const;
   void EnsurePlugins();
 
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
   nsTArray<RefPtr<nsPluginElement> > mPlugins;
-  /* A separate list of click-to-play plugins that we don't tell content
-   * about but keep track of so we can still prompt the user to click to play.
-   */
-  nsTArray<RefPtr<nsPluginElement> > mCTPPlugins;
 };
 
 class nsPluginElement final : public nsISupports,
                               public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsPluginElement)
--- a/dom/camera/CameraPreviewMediaStream.cpp
+++ b/dom/camera/CameraPreviewMediaStream.cpp
@@ -23,18 +23,18 @@ static const TrackID TRACK_VIDEO = 2;
 
 void
 FakeMediaStreamGraph::DispatchToMainThreadAfterStreamStateUpdate(already_AddRefed<nsIRunnable> aRunnable)
 {
   nsCOMPtr<nsIRunnable> task = aRunnable;
   NS_DispatchToMainThread(task);
 }
 
-CameraPreviewMediaStream::CameraPreviewMediaStream(DOMMediaStream* aWrapper)
-  : ProcessedMediaStream(aWrapper)
+CameraPreviewMediaStream::CameraPreviewMediaStream()
+  : ProcessedMediaStream()
   , mMutex("mozilla::camera::CameraPreviewMediaStream")
   , mInvalidatePending(0)
   , mDiscardedFrames(0)
   , mRateLimit(false)
   , mTrackCreated(false)
 {
   SetGraphImpl(
       MediaStreamGraph::GetInstance(
--- a/dom/camera/CameraPreviewMediaStream.h
+++ b/dom/camera/CameraPreviewMediaStream.h
@@ -35,17 +35,17 @@ protected:
  * A camera preview requests no delay and no buffering stream,
  * but the SourceMediaStream does not support it.
  */
 class CameraPreviewMediaStream : public ProcessedMediaStream
 {
   typedef mozilla::layers::Image Image;
 
 public:
-  explicit CameraPreviewMediaStream(DOMMediaStream* aWrapper);
+  CameraPreviewMediaStream();
 
   virtual void AddAudioOutput(void* aKey) override;
   virtual void SetAudioOutputVolume(void* aKey, float aVolume) override;
   virtual void RemoveAudioOutput(void* aKey) override;
   virtual void AddVideoOutput(VideoFrameContainer* aContainer) override;
   virtual void RemoveVideoOutput(VideoFrameContainer* aContainer) override;
   virtual void Suspend() override {}
   virtual void Resume() override {}
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -254,17 +254,17 @@ nsDOMCameraControl::nsDOMCameraControl(u
   , mGetCameraPromise(aPromise)
   , mWindow(aWindow)
   , mPreviewState(CameraControlListener::kPreviewStopped)
   , mRecording(false)
   , mRecordingStoppedDeferred(false)
   , mSetInitialConfig(false)
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  mInput = new CameraPreviewMediaStream(this);
+  mInput = new CameraPreviewMediaStream();
   mOwnedStream = mInput;
 
   BindToOwner(aWindow);
 
   RefPtr<DOMCameraConfiguration> initialConfig =
     new DOMCameraConfiguration(aInitialConfig);
 
   // Create and initialize the underlying camera.
--- 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,90 @@ 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);
+
+    if (item->ChromeOnly() && !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+      continue;
+    }
+
+    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 +444,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,37 +533,39 @@ 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);
+
+    bool addFile = false;
+    for (uint32_t i = 0; i < items.Length(); i++) {
+      if (items[i]->ChromeOnly() && !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+        continue;
+      }
+
+      nsAutoString type;
+      items[i]->GetType(type);
+      if (NS_WARN_IF(!types->Add(type))) {
+        aRv.Throw(NS_ERROR_FAILURE);
+        return nullptr;
+      }
+
+      if (items[i]->Kind() == DataTransferItem::KIND_FILE) {
+        addFile = true;
       }
     }
 
     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
@@ -616,90 +592,84 @@ 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) &&
-      !nsContentUtils::IsSystemPrincipal(aSubjectPrincipal)) {
-    uint32_t count = item.Length();
-    for (uint32_t i = 0; i < count; i++) {
-      if (item[i].mFormat.EqualsLiteral(kFileMime)) {
-        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 we have chrome only content, and we aren't chrome, don't allow access
+  if (!nsContentUtils::IsSystemPrincipal(aSubjectPrincipal) && item->ChromeOnly()) {
+    return NS_OK;
+  }
+
+  if (item->Principal() && checkFormatItemPrincipal &&
+      !aSubjectPrincipal->Subsumes(item->Principal())) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
 
-      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<nsIVariant> data = item->Data();
+  if (!data) {
+    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 +703,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 +761,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 +854,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 +903,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 +941,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 +1011,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 +1059,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 +1117,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 +1177,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,83 +1233,61 @@ 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,
+                                 /* aInsertOnly = */ false,
+                                 /* aHidden= */ false,
+                                 rv);
+  return rv.StealNSResult();
 }
 
 void
 DataTransfer::SetDataWithPrincipalFromOtherProcess(const nsAString& aFormat,
                                                    nsIVariant* aData,
                                                    uint32_t aIndex,
-                                                   nsIPrincipal* aPrincipal)
+                                                   nsIPrincipal* aPrincipal,
+                                                   bool aHidden)
 {
   if (aFormat.EqualsLiteral(kCustomTypesMime)) {
     FillInExternalCustomTypes(aData, aIndex, aPrincipal);
   } else {
-    SetDataWithPrincipal(aFormat, aData, aIndex, aPrincipal);
+    nsAutoString format;
+    GetRealFormat(aFormat, format);
+
+    ErrorResult rv;
+    RefPtr<DataTransferItem> item =
+      mItems->SetDataWithPrincipal(format, aData, aIndex, aPrincipal,
+                                   /* aInsertOnly = */ false, aHidden, rv);
+    NS_WARN_IF(rv.Failed());
   }
 }
 
 void
 DataTransfer::GetRealFormat(const nsAString& aInFormat,
                             nsAString& aOutFormat) const
 {
   // treat text/unicode as equivalent to text/plain
@@ -1377,36 +1302,58 @@ DataTransfer::GetRealFormat(const nsAStr
   if (lowercaseFormat.EqualsLiteral("url")) {
     aOutFormat.AssignLiteral("text/uri-list");
     return;
   }
 
   aOutFormat.Assign(lowercaseFormat);
 }
 
-void
+nsresult
 DataTransfer::CacheExternalData(const char* aFormat, uint32_t aIndex,
-                                nsIPrincipal* aPrincipal)
+                                nsIPrincipal* aPrincipal, bool aHidden)
 {
+  ErrorResult rv;
+  RefPtr<DataTransferItem> item;
+
   if (strcmp(aFormat, kUnicodeMime) == 0) {
-    SetDataWithPrincipal(NS_LITERAL_STRING("text/plain"), nullptr, aIndex,
-                         aPrincipal);
-    return;
+    item = mItems->SetDataWithPrincipal(NS_LITERAL_STRING("text/plain"), nullptr,
+                                        aIndex, aPrincipal, false, aHidden, rv);
+    if (NS_WARN_IF(rv.Failed())) {
+      return rv.StealNSResult();
+    }
+    return NS_OK;
   }
 
   if (strcmp(aFormat, kURLDataMime) == 0) {
-    SetDataWithPrincipal(NS_LITERAL_STRING("text/uri-list"), nullptr, aIndex,
-                         aPrincipal);
-    return;
+    item = mItems->SetDataWithPrincipal(NS_LITERAL_STRING("text/uri-list"), nullptr,
+                                        aIndex, aPrincipal, false, aHidden, rv);
+    if (NS_WARN_IF(rv.Failed())) {
+      return rv.StealNSResult();
+    }
+    return NS_OK;
   }
 
-  SetDataWithPrincipal(NS_ConvertUTF8toUTF16(aFormat), nullptr, aIndex,
-                       aPrincipal);
+  nsAutoString format;
+  GetRealFormat(NS_ConvertUTF8toUTF16(aFormat), format);
+  item = mItems->SetDataWithPrincipal(format, nullptr, aIndex,
+                                      aPrincipal, false, aHidden, rv);
+  if (NS_WARN_IF(rv.Failed())) {
+    return rv.StealNSResult();
+  }
+  return NS_OK;
 }
 
+// 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, kPNGImageMime, kJPEGImageMime,
+                           kGIFImageMime };
+
 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.
@@ -1425,34 +1372,37 @@ DataTransfer::CacheExternalDragFormats()
   // 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* formats[] = { kFileMime, kHTMLMime, kRTFMime,
                             kURLMime, kURLDataMime, kUnicodeMime };
 
   uint32_t count;
   dragSession->GetNumDropItems(&count);
   for (uint32_t c = 0; c < count; c++) {
+    bool hasFileData = false;
+    dragSession->IsDataFlavorSupported(kFileMime, &hasFileData);
+
     // First, check for the special format that holds custom types.
     bool supported;
     dragSession->IsDataFlavorSupported(kCustomTypesMime, &supported);
     if (supported) {
       FillInExternalCustomTypes(c, sysPrincipal);
     }
 
     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, /* hidden = */ f && hasFileData);
       }
     }
   }
 }
 
 void
 DataTransfer::CacheExternalClipboardFormats()
 {
@@ -1468,151 +1418,75 @@ DataTransfer::CacheExternalClipboardForm
   if (!clipboard || mClipboardType < 0) {
     return;
   }
 
   nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
   nsCOMPtr<nsIPrincipal> sysPrincipal;
   ssm->GetSystemPrincipal(getter_AddRefs(sysPrincipal));
 
+  // Check if the clipboard has any files
+  bool hasFileData = false;
+  const char *fileMime[] = { kFileMime };
+  clipboard->HasDataMatchingFlavors(fileMime, 1, mClipboardType, &hasFileData);
+
   // 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.
   // Note that the loop below assumes that kCustomTypesMime will be first.
   const char* formats[] = { kCustomTypesMime, kFileMime, kHTMLMime, kRTFMime,
-                            kURLMime, kURLDataMime, kUnicodeMime };
+                            kURLMime, kURLDataMime, kUnicodeMime, kPNGImageMime,
+                            kJPEGImageMime, kGIFImageMime };
 
   for (uint32_t f = 0; f < mozilla::ArrayLength(formats); ++f) {
     // check each format one at a time
     bool supported;
     clipboard->HasDataMatchingFlavors(&(formats[f]), 1, mClipboardType,
                                       &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) {
       if (f == 0) {
         FillInExternalCustomTypes(0, sysPrincipal);
       } else {
-        CacheExternalData(formats[f], 0, sysPrincipal);
+        // If we aren't the file data, and we have file data, we want to be hidden
+        CacheExternalData(formats[f], 0, sysPrincipal, /* hidden = */ f != 1 && hasFileData);
       }
     }
   }
 }
 
 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
@@ -240,56 +231,50 @@ public:
                                 uint32_t aIndex,
                                 nsIPrincipal* aPrincipal);
 
   // Variation of SetDataWithPrincipal with handles extracting
   // kCustomTypesMime data into separate types.
   void SetDataWithPrincipalFromOtherProcess(const nsAString& aFormat,
                                             nsIVariant* aData,
                                             uint32_t aIndex,
-                                            nsIPrincipal* aPrincipal);
+                                            nsIPrincipal* aPrincipal,
+                                            bool aHidden);
 
   // returns a weak reference to the drag image
   Element* GetDragImage(int32_t* aX, int32_t* aY) const
   {
     *aX = mDragImageX;
     *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);
+  nsresult CacheExternalData(const char* aFormat, uint32_t aIndex,
+                             nsIPrincipal* aPrincipal, bool aHidden);
 
   // 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 +316,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,360 @@
+/* -*- 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" },
+  { kJPEGImageMime, "GenericImageNameJPEG" },
+  { kGIFImageMime, "GenericImageNameGIF" },
+  { kPNGImageMime, "GenericImageNamePNG" },
+};
+
+} // 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;
+  it->mChromeOnly = mChromeOnly;
+
+  return it.forget();
+}
+
+void
+DataTransferItem::SetType(const nsAString& aType)
+{
+  mType = aType;
+}
+
+void
+DataTransferItem::SetData(nsIVariant* aData)
+{
+  // Invalidate our file cache, we will regenerate it with the new data
+  mCachedFile = nullptr;
+
+  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) {
+    // Check if we have one of the supported file data formats
+    if (nsCOMPtr<nsIDOMBlob>(do_QueryInterface(supports)) ||
+        nsCOMPtr<BlobImpl>(do_QueryInterface(supports)) ||
+        nsCOMPtr<nsIFile>(do_QueryInterface(supports))) {
+      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;
+  }
+
+  // Fill the variant
+  RefPtr<nsVariantCC> variant = new nsVariantCC();
+
+  eKind oldKind = Kind();
+  if (oldKind == KIND_FILE) {
+    // Because this is an external piece of data, mType is one of kFileMime,
+    // kPNGImageMime, kJPEGImageMime, or kGIFImageMime. Some of these types
+    // are passed in as a nsIInputStream which must be converted to a
+    // dom::File before storing.
+    if (nsCOMPtr<nsIInputStream> istream = do_QueryInterface(data)) {
+      RefPtr<File> file = CreateFileFromInputStream(istream);
+      if (NS_WARN_IF(!file)) {
+        return;
+      }
+      data = do_QueryObject(file);
+    }
+
+    variant->SetAsISupports(data);
+  } else {
+    // We have an external piece of string data. Extract it and store it in the variant
+    MOZ_ASSERT(oldKind == KIND_STRING);
+
+    nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data);
+    if (supportsstr) {
+      nsAutoString str;
+      supportsstr->GetData(str);
+      variant->SetAsAString(str);
+    } else {
+      nsCOMPtr<nsISupportsCString> supportscstr = do_QueryInterface(data);
+      if (supportscstr) {
+        nsAutoCString str;
+        supportscstr->GetData(str);
+        variant->SetAsACString(str);
+      }
+    }
+  }
+
+  SetData(variant);
+
+#ifdef DEBUG
+  if (oldKind != Kind()) {
+    NS_WARNING("Clipboard data provided by the OS does not match predicted kind");
+  }
+#endif
+}
+
+already_AddRefed<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,
+             "File objects should be stored as nsISupports variants");
+  if (aRv.Failed() || !supports) {
+    return nullptr;
+  }
+
+  // Generate the dom::File from the stored data, caching it so that the
+  // same object is returned in the future.
+  if (!mCachedFile) {
+    if (nsCOMPtr<nsIDOMBlob> domBlob = do_QueryInterface(supports)) {
+      Blob* blob = static_cast<Blob*>(domBlob.get());
+      mCachedFile = blob->ToFile();
+    } else if (nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(supports)) {
+      MOZ_ASSERT(blobImpl->IsFile());
+      mCachedFile = File::Create(GetParentObject(), blobImpl);
+    } else if (nsCOMPtr<nsIFile> ifile = do_QueryInterface(supports)) {
+      mCachedFile = File::CreateFromFile(GetParentObject(), ifile);
+    } else {
+      MOZ_ASSERT(false, "One of the above code paths should be taken");
+    }
+  }
+
+  RefPtr<File> file = mCachedFile;
+  return file.forget();
+}
+
+already_AddRefed<File>
+DataTransferItem::CreateFileFromInputStream(nsIInputStream* aStream)
+{
+  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;
+  nsresult rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+                                                   key, fileName);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+
+  uint64_t available;
+  rv = aStream->Available(&available);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+
+  void* data = nullptr;
+  rv = NS_ReadInputStreamToBuffer(aStream, &data, available);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+
+  return File::CreateMemoryFile(GetParentObject(), data, available, fileName,
+                                mType, PR_Now());
+}
+
+void
+DataTransferItem::GetAsString(FunctionStringCallback* aCallback,
+                              ErrorResult& aRv)
+{
+  if (!aCallback || mKind != KIND_STRING) {
+    return;
+  }
+
+  // Theoretically this should be done inside of the runnable, as it might be an
+  // expensive operation on some systems. However, it doesn't really matter as
+  // in order to get a DataTransferItem, we need to call
+  // DataTransferItemList::IndexedGetter which already calls the Data method,
+  // meaning that this operation is basically free.
+  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(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,140 @@
+/* -*- 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), mChromeOnly(false), 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(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();
+
+  bool ChromeOnly() const
+  {
+    return mChromeOnly;
+  }
+  void SetChromeOnly(bool aChromeOnly)
+  {
+    mChromeOnly = aChromeOnly;
+  }
+
+private:
+  ~DataTransferItem() {}
+  already_AddRefed<File> CreateFileFromInputStream(nsIInputStream* aStream);
+
+  // The index in the 2d mIndexedItems array
+  uint32_t mIndex;
+
+  bool mChromeOnly;
+  eKind mKind;
+  nsString mType;
+  nsCOMPtr<nsIVariant> mData;
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+  RefPtr<DataTransferItemList> mParent;
+
+  // File cache for nsIFile application/x-moz-file entries.
+  RefPtr<File> mCachedFile;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_DataTransferItem_h */
new file mode 100644
--- /dev/null
+++ b/dom/events/DataTransferItemList.cpp
@@ -0,0 +1,590 @@
+/* -*- 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(),
+                         /* aInsertOnly = */ true,
+                         /* aHidden = */ false,
+                         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, false, 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,
+                                           bool aHidden,
+                                           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, aHidden);
+
+  if (item->Kind() == DataTransferItem::KIND_FILE) {
+    RegenerateFiles();
+  }
+
+  return item.forget();
+}
+
+DataTransferItem*
+DataTransferItemList::AppendNewItem(uint32_t aIndex,
+                                    const nsAString& aType,
+                                    nsIVariant* aData,
+                                    nsIPrincipal* aPrincipal,
+                                    bool aHidden)
+{
+  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);
+  item->SetChromeOnly(aHidden);
+
+  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 (!aHidden && (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,117 @@
+/* -*- 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, bool aHidden, 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,
+                                  bool aHidden);
+  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]
--- a/dom/events/test/mochitest.ini
+++ b/dom/events/test/mochitest.ini
@@ -195,8 +195,9 @@ support-files =
 [test_offsetxy.html]
 [test_eventhandler_scoping.html]
 [test_bug1013412.html]
 skip-if = buildapp == 'b2g' # no wheel events on b2g
 [test_dom_activate_event.html]
 [test_bug1264380.html]
 run-if = (e10s && os != "win") # Bug 1270043, crash at windows platforms; Bug1264380 comment 20, nsDragService::InvokeDragSessionImpl behaves differently among platform implementations in non-e10s mode which prevents us to check the validity of nsIDragService::getCurrentSession() consistently via synthesize mouse clicks in non-e10s mode.
 [test_passive_listeners.html]
+[test_paste_image.html]
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_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -298,19 +298,18 @@ const kEventConstructors = {
   MediaKeyMessageEvent:                          { create: function (aName, aProps) {
                                                          return new MediaKeyMessageEvent(aName, aProps);
                                                        },
                                              },
   MediaStreamEvent:                          { create: function (aName, aProps) {
                                                          return new MediaStreamEvent(aName, aProps);
                                                        },
                                              },
-  MediaStreamTrackEvent:                     { create: function (aName, aProps) {
-                                                         return new MediaStreamTrackEvent(aName, aProps);
-                                                       },
+  MediaStreamTrackEvent:                     {
+                                               // Difficult to test required arguments.
                                              },
   MessageEvent:                              { create: function (aName, aProps) {
                                                          var e = new MessageEvent("messageevent", { bubbles: aProps.bubbles,
                                                              cancelable: aProps.cancelable, data: aProps.data, origin: aProps.origin,
                                                              lastEventId: aProps.lastEventId, source: aProps.source });
                                                          return e;
                                                        },
                                              },
--- 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) {
new file mode 100644
--- /dev/null
+++ b/dom/events/test/test_paste_image.html
@@ -0,0 +1,196 @@
+<html><head>
+<title>Test for bug 891247</title>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+
+<script class="testbody" type="application/javascript">
+  function ImageTester() {
+    var counter = 0;
+    var images = [];
+    var that = this;
+
+    this.add = function(aFile) {
+      images.push(aFile);
+    };
+
+    this.test = function() {
+      for (var i = 0; i < images.length; i++) {
+        testImageSize(images[i]);
+      }
+    };
+
+    this.returned = function() {
+      counter++;
+      info("returned=" + counter + " images.length=" + images.length);
+      if (counter == images.length) {
+        info("test finish");
+        SimpleTest.finish();
+      }
+    };
+
+    function testImageSize(aFile) {
+      var source = window.URL.createObjectURL(aFile);
+      var image = new Image();
+      image.src = source;
+      var imageTester = that;
+      image.onload = function() {
+        is(this.width, 62, "Check generated image width");
+        is(this.height, 71, "Check generated image height");
+        if (aFile.type == "image/gif") {
+          // this test fails for image/jpeg and image/png because the images
+          // generated are slightly different
+          testImageCanvas(image);
+        }
+
+        imageTester.returned();
+      }
+
+      document.body.appendChild(image);
+    };
+
+    function testImageCanvas(aImage) {
+      var canvas = drawToCanvas(aImage);
+
+      var refImage = document.getElementById('image');
+      var refCanvas = drawToCanvas(refImage);
+
+      is(canvas.toDataURL(), refCanvas.toDataURL(), "Image should map pixel-by-pixel");
+    }
+
+    function drawToCanvas(aImage) {
+      var canvas = document.createElement("CANVAS");
+      document.body.appendChild(canvas);
+      canvas.width = aImage.width;
+      canvas.height = aImage.height;
+      canvas.getContext('2d').drawImage(aImage, 0, 0);
+      return canvas;
+    }
+  }
+
+  function copyImage(aImageId) {
+    // selection of the node
+    var node = document.getElementById(aImageId);
+    var webnav = SpecialPowers.wrap(window)
+                 .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+                 .getInterface(SpecialPowers.Ci.nsIWebNavigation)
+
+    var docShell = webnav.QueryInterface(SpecialPowers.Ci.nsIDocShell);
+
+    // let's copy the node
+    var documentViewer = docShell.contentViewer
+                         .QueryInterface(SpecialPowers.Ci.nsIContentViewerEdit);
+    documentViewer.setCommandNode(node);
+    documentViewer.copyImage(documentViewer.COPY_IMAGE_ALL);
+  }
+
+  function doTest() {
+    SimpleTest.waitForExplicitFinish();
+
+    copyImage('image');
+
+    //--------- now check the content of the clipboard
+    var clipboard = SpecialPowers.Cc["@mozilla.org/widget/clipboard;1"]
+                                 .getService(SpecialPowers.Ci.nsIClipboard);
+    // does the clipboard contain text/unicode data ?
+    ok(clipboard.hasDataMatchingFlavors(["text/unicode"], 1, clipboard.kGlobalClipboard),
+       "clipboard contains unicode text");
+    // does the clipboard contain text/html data ?
+    ok(clipboard.hasDataMatchingFlavors(["text/html"], 1, clipboard.kGlobalClipboard),
+       "clipboard contains html text");
+    // does the clipboard contain image data ?
+    ok(clipboard.hasDataMatchingFlavors(["image/png"], 1, clipboard.kGlobalClipboard),
+       "clipboard contains image");
+
+    window.addEventListener("paste", onPaste);
+
+    var textarea = SpecialPowers.wrap(document.getElementById('textarea'));
+    textarea.focus();
+    textarea.editor.paste(clipboard.kGlobalClipboard);
+  }
+
+  function onPaste(e) {
+    var imageTester = new ImageTester;
+    testFiles(e, imageTester);
+    testItems(e, imageTester);
+    imageTester.test();
+  }
+
+  function testItems(e, imageTester) {
+    var items = e.clipboardData.items;
+    is(items, e.clipboardData.items,
+       "Getting @items twice should return the same object");
+    var haveFiles = false;
+    ok(items instanceof DataTransferItemList, "@items implements DataTransferItemList");
+    ok(items.length > 0, "@items is not empty");
+    for (var i = 0; i < items.length; i++) {
+      var item = items[i];
+      ok(item instanceof DataTransferItem, "each element of @items must implement DataTransferItem");
+      if (item.kind == "file") {
+        var file = item.getAsFile();
+        ok(file instanceof File, ".getAsFile() returns a File object");
+        ok(file.size > 0, "Files shouldn't have size 0");
+        imageTester.add(file);
+      }
+    }
+  }
+
+  function testFiles(e, imageTester) {
+    var files = e.clipboardData.files;
+
+    is(files, e.clipboardData.files,
+       "Getting the files array twice should return the same array");
+    ok(files.length > 0, "There should be at least one file in the clipboard");
+    for (var i = 0; i < files.length; i++) {
+      var file = files[i];
+      ok(file instanceof File, ".files should contain only File objects");
+      ok(file.size > 0, "This file shouldn't have size 0");
+      if (file.name == "image.png") {
+        is(file.type, "image/png", "This file should be a image/png");
+      } else if (file.name == "image.jpeg") {
+        is(file.type, "image/jpeg", "This file should be a image/jpeg");
+      } else if (file.name == "image.gif") {
+        is(file.type, "image/gif", "This file should be a image/gif");
+      } else {
+        info("file.name=" + file.name);
+        ok(false, "Unexpected file name");
+      }
+
+      testSlice(file);
+      imageTester.add(file);
+      // Adding the same image again so we can test concurrency
+      imageTester.add(file);
+    }
+  }
+
+  function testSlice(aFile) {
+    var blob = aFile.slice();
+    ok(blob instanceof Blob, ".slice returns a blob");
+    is(blob.size, aFile.size, "the blob has the same size");
+
+    blob = aFile.slice(123123);
+    is(blob.size, 0, ".slice overflow check");
+
+    blob = aFile.slice(123, 123141);
+    is(blob.size, aFile.size - 123, ".slice @size check");
+
+    blob = aFile.slice(123, 12);
+    is(blob.size, 0, ".slice @size check 2");
+
+    blob = aFile.slice(124, 134, "image/png");
+    is(blob.size, 10, ".slice @size check 3");
+    is(blob.type, "image/png", ".slice @type check");
+  }
+
+</script>
+<body onload="doTest();">
+  <img id="image" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAABHCA
+  IAAADQjmMaAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3goUAwAgSAORBwAAABl0RVh0Q29
+  tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAABPSURBVGje7c4BDQAACAOga//OmuMbJGAurTbq
+  6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6s31B0IqAY2/t
+  QVCAAAAAElFTkSuQmCC" />
+  <form>
+    <textarea id="textarea"></textarea>
+  </form>
+</body>
+</html>
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -247,21 +247,25 @@ FetchDriver::HttpFetch()
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Set the same headers.
     SetRequestHeaders(httpChan);
 
     // Step 2. Set the referrer.
     nsAutoString referrer;
     mRequest->GetReferrer(referrer);
+
+    // The Referrer Policy in Request can be used to override a referrer policy
+    // associated with an environment settings object.
+    // If there's no Referrer Policy in the request, it should be inherited
+    // from environment.
     ReferrerPolicy referrerPolicy = mRequest->ReferrerPolicy_();
-    net::ReferrerPolicy net_referrerPolicy = net::RP_Unset;
+    net::ReferrerPolicy net_referrerPolicy = mRequest->GetEnvironmentReferrerPolicy();
     switch (referrerPolicy) {
     case ReferrerPolicy::_empty:
-      net_referrerPolicy = net::RP_Default;
       break;
     case ReferrerPolicy::No_referrer:
       net_referrerPolicy = net::RP_No_Referrer;
       break;
     case ReferrerPolicy::No_referrer_when_downgrade:
       net_referrerPolicy = net::RP_No_Referrer_When_Downgrade;
       break;
     case ReferrerPolicy::Origin:
@@ -289,22 +293,20 @@ FetchDriver::HttpFetch()
     } else {
       // From "Determine request's Referrer" step 3
       // "If request's referrer is a URL, let referrerSource be request's
       // referrer."
       nsCOMPtr<nsIURI> referrerURI;
       rv = NS_NewURI(getter_AddRefs(referrerURI), referrer, nullptr, nullptr);
       NS_ENSURE_SUCCESS(rv, rv);
 
-      uint32_t documentReferrerPolicy = mDocument ? mDocument->GetReferrerPolicy() :
-                                                    net::RP_Default;
       rv =
         httpChan->SetReferrerWithPolicy(referrerURI,
                                         referrerPolicy == ReferrerPolicy::_empty ?
-                                          documentReferrerPolicy :
+                                          mRequest->GetEnvironmentReferrerPolicy() :
                                           net_referrerPolicy);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     // Bug 1120722 - Authorization will be handled later.
     // Auth may require prompting, we don't support it yet.
     // The next patch in this same bug prevents this from aborting the request.
     // Credentials checks for CORS are handled by nsCORSListenerProxy,
--- a/dom/fetch/InternalRequest.cpp
+++ b/dom/fetch/InternalRequest.cpp
@@ -35,16 +35,17 @@ InternalRequest::GetRequestConstructorCo
   copy->mForceOriginHeader = true;
   // The "client" is not stored in our implementation. Fetch API users should
   // use the appropriate window/document/principal and other Gecko security
   // mechanisms as appropriate.
   copy->mSameOriginDataURL = true;
   copy->mPreserveContentCodings = true;
   // The default referrer is already about:client.
   copy->mReferrerPolicy = mReferrerPolicy;
+  copy->mEnvironmentReferrerPolicy = mEnvironmentReferrerPolicy;
 
   copy->mContentPolicyType = mContentPolicyTypeOverridden ?
                              mContentPolicyType :
                              nsIContentPolicy::TYPE_FETCH;
   copy->mMode = mMode;
   copy->mCredentialsMode = mCredentialsMode;
   copy->mCacheMode = mCacheMode;
   copy->mRedirectMode = mRedirectMode;
@@ -79,16 +80,17 @@ InternalRequest::Clone()
 
 InternalRequest::InternalRequest(const InternalRequest& aOther)
   : mMethod(aOther.mMethod)
   , mURLList(aOther.mURLList)
   , mHeaders(new InternalHeaders(*aOther.mHeaders))
   , mContentPolicyType(aOther.mContentPolicyType)
   , mReferrer(aOther.mReferrer)
   , mReferrerPolicy(aOther.mReferrerPolicy)
+  , mEnvironmentReferrerPolicy(aOther.mEnvironmentReferrerPolicy)
   , mMode(aOther.mMode)
   , mCredentialsMode(aOther.mCredentialsMode)
   , mResponseTainting(aOther.mResponseTainting)
   , mCacheMode(aOther.mCacheMode)
   , mRedirectMode(aOther.mRedirectMode)
   , mAuthenticationFlag(aOther.mAuthenticationFlag)
   , mForceOriginHeader(aOther.mForceOriginHeader)
   , mPreserveContentCodings(aOther.mPreserveContentCodings)
--- a/dom/fetch/InternalRequest.h
+++ b/dom/fetch/InternalRequest.h
@@ -6,16 +6,17 @@
 
 #ifndef mozilla_dom_InternalRequest_h
 #define mozilla_dom_InternalRequest_h
 
 #include "mozilla/dom/HeadersBinding.h"
 #include "mozilla/dom/InternalHeaders.h"
 #include "mozilla/dom/RequestBinding.h"
 #include "mozilla/LoadTainting.h"
+#include "mozilla/net/ReferrerPolicy.h"
 
 #include "nsIContentPolicy.h"
 #include "nsIInputStream.h"
 #include "nsISupportsImpl.h"
 #ifdef DEBUG
 #include "nsIURLParser.h"
 #include "nsNetCID.h"
 #include "nsServiceManagerUtils.h"
@@ -90,16 +91,17 @@ public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalRequest)
 
   explicit InternalRequest(const nsACString& aURL)
     : mMethod("GET")
     , mHeaders(new InternalHeaders(HeadersGuardEnum::None))
     , mContentPolicyType(nsIContentPolicy::TYPE_FETCH)
     , mReferrer(NS_LITERAL_STRING(kFETCH_CLIENT_REFERRER_STR))
     , mReferrerPolicy(ReferrerPolicy::_empty)
+    , mEnvironmentReferrerPolicy(net::RP_Default)
     , mMode(RequestMode::No_cors)
     , mCredentialsMode(RequestCredentials::Omit)
     , mResponseTainting(LoadTainting::Basic)
     , mCacheMode(RequestCache::Default)
     , mRedirectMode(RequestRedirect::Follow)
     , mAuthenticationFlag(false)
     , mForceOriginHeader(false)
     , mPreserveContentCodings(false)
@@ -127,16 +129,17 @@ public:
                   const nsAString& aReferrer,
                   ReferrerPolicy aReferrerPolicy,
                   nsContentPolicyType aContentPolicyType)
     : mMethod(aMethod)
     , mHeaders(aHeaders)
     , mContentPolicyType(aContentPolicyType)
     , mReferrer(aReferrer)
     , mReferrerPolicy(aReferrerPolicy)
+    , mEnvironmentReferrerPolicy(net::RP_Default)
     , mMode(aMode)
     , mCredentialsMode(aRequestCredentials)
     , mResponseTainting(LoadTainting::Basic)
     , mCacheMode(aCacheMode)
     , mRedirectMode(aRequestRedirect)
     , mAuthenticationFlag(false)
     , mForceOriginHeader(false)
     , mPreserveContentCodings(false)
@@ -261,16 +264,28 @@ public:
   }
 
   void
   SetReferrerPolicy(ReferrerPolicy aReferrerPolicy)
   {
     mReferrerPolicy = aReferrerPolicy;
   }
 
+  net::ReferrerPolicy
+  GetEnvironmentReferrerPolicy() const
+  {
+    return mEnvironmentReferrerPolicy;
+  }
+
+  void
+  SetEnvironmentReferrerPolicy(net::ReferrerPolicy aReferrerPolicy)
+  {
+    mEnvironmentReferrerPolicy = aReferrerPolicy;
+  }
+
   bool
   SkipServiceWorker() const
   {
     return mSkipServiceWorker;
   }
 
   void
   SetSkipServiceWorker()
@@ -487,16 +502,23 @@ private:
   nsContentPolicyType mContentPolicyType;
 
   // Empty string: no-referrer
   // "about:client": client (default)
   // URL: an URL
   nsString mReferrer;
   ReferrerPolicy mReferrerPolicy;
 
+  // This will be used for request created from Window or Worker contexts
+  // In case there's no Referrer Policy in Request, this will be passed to
+  // channel.
+  // The Environment Referrer Policy should be net::ReferrerPolicy so that it
+  // could be associated with nsIHttpChannel.
+  net::ReferrerPolicy mEnvironmentReferrerPolicy;
+
   RequestMode mMode;
   RequestCredentials mCredentialsMode;
   MOZ_INIT_OUTSIDE_CTOR LoadTainting mResponseTainting;
   RequestCache mCacheMode;
   RequestRedirect mRedirectMode;
 
   MOZ_INIT_OUTSIDE_CTOR bool mAuthenticationFlag;
   MOZ_INIT_OUTSIDE_CTOR bool mForceOriginHeader;
--- a/dom/fetch/Request.cpp
+++ b/dom/fetch/Request.cpp
@@ -408,16 +408,33 @@ Request::Constructor(const GlobalObject&
       request->SetReferrer(referrerURL);
     }
   }
 
   if (aInit.mReferrerPolicy.WasPassed()) {
     request->SetReferrerPolicy(aInit.mReferrerPolicy.Value());
   }
 
+  if (NS_IsMainThread()) {
+    nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
+    if (window) {
+      nsCOMPtr<nsIDocument> doc;
+      doc = window->GetExtantDoc();
+      if (doc) {
+        request->SetEnvironmentReferrerPolicy(doc->GetReferrerPolicy());
+      }
+    }
+  } else {
+    workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate();
+    if (worker) {
+      worker->AssertIsOnWorkerThread();
+      request->SetEnvironmentReferrerPolicy(worker->GetReferrerPolicy());
+    }
+  }
+
   if (mode != RequestMode::EndGuard_) {
     request->ClearCreatedByFetchEvent();
     request->SetMode(mode);
   }
 
   if (credentials != RequestCredentials::EndGuard_) {
     request->ClearCreatedByFetchEvent();
     request->SetCredentialsMode(credentials);
--- a/dom/html/HTMLFormControlsCollection.cpp
+++ b/dom/html/HTMLFormControlsCollection.cpp
@@ -42,16 +42,17 @@ HTMLFormControlsCollection::ShouldBeInEl
   case NS_FORM_INPUT_RESET :
   case NS_FORM_INPUT_PASSWORD :
   case NS_FORM_INPUT_RADIO :
   case NS_FORM_INPUT_SEARCH :
   case NS_FORM_INPUT_SUBMIT :
   case NS_FORM_INPUT_TEXT :
   case NS_FORM_INPUT_TEL :
   case NS_FORM_INPUT_URL :
+  case NS_FORM_INPUT_MONTH :
   case NS_FORM_INPUT_NUMBER :
   case NS_FORM_INPUT_RANGE :
   case NS_FORM_INPUT_DATE :
   case NS_FORM_INPUT_TIME :
   case NS_FORM_SELECT :
   case NS_FORM_TEXTAREA :
   case NS_FORM_FIELDSET :
   case NS_FORM_OBJECT :
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -160,31 +160,32 @@ static const nsAttrValue::EnumTable kInp
   { "checkbox", NS_FORM_INPUT_CHECKBOX },
   { "color", NS_FORM_INPUT_COLOR },
   { "date", NS_FORM_INPUT_DATE },
   { "email", NS_FORM_INPUT_EMAIL },
   { "file", NS_FORM_INPUT_FILE },
   { "hidden", NS_FORM_INPUT_HIDDEN },
   { "reset", NS_FORM_INPUT_RESET },
   { "image", NS_FORM_INPUT_IMAGE },
+  { "month", NS_FORM_INPUT_MONTH },
   { "number", NS_FORM_INPUT_NUMBER },
   { "password", NS_FORM_INPUT_PASSWORD },
   { "radio", NS_FORM_INPUT_RADIO },
   { "range", NS_FORM_INPUT_RANGE },
   { "search", NS_FORM_INPUT_SEARCH },
   { "submit", NS_FORM_INPUT_SUBMIT },
   { "tel", NS_FORM_INPUT_TEL },
   { "text", NS_FORM_INPUT_TEXT },
   { "time", NS_FORM_INPUT_TIME },
   { "url", NS_FORM_INPUT_URL },
   { 0 }
 };
 
 // Default type is 'text'.
-static const nsAttrValue::EnumTable* kInputDefaultType = &kInputTypeTable[16];
+static const nsAttrValue::EnumTable* kInputDefaultType = &kInputTypeTable[17];
 
 static const uint8_t NS_INPUT_INPUTMODE_AUTO              = 0;
 static const uint8_t NS_INPUT_INPUTMODE_NUMERIC           = 1;
 static const uint8_t NS_INPUT_INPUTMODE_DIGIT             = 2;
 static const uint8_t NS_INPUT_INPUTMODE_UPPERCASE         = 3;
 static const uint8_t NS_INPUT_INPUTMODE_LOWERCASE         = 4;
 static const uint8_t NS_INPUT_INPUTMODE_TITLECASE         = 5;
 static const uint8_t NS_INPUT_INPUTMODE_AUTOCAPITALIZED   = 6;
@@ -2132,19 +2133,19 @@ HTMLInputElement::SetWidth(uint32_t aWid
 NS_IMETHODIMP
 HTMLInputElement::GetValue(nsAString& aValue)
 {
   nsresult rv = GetValueInternal(aValue);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  // Don't return non-sanitized value for types that are experimental on mobile.
-  //  or date types
-  if (IsExperimentalMobileType(mType) || mType == NS_FORM_INPUT_DATE) {
+  // Don't return non-sanitized value for types that are experimental on mobile
+  // or datetime types
+  if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
     SanitizeValue(aValue);
   }
 
   return NS_OK;
 }
 
 nsresult
 HTMLInputElement::GetValueInternal(nsAString& aValue) const
@@ -2772,19 +2773,29 @@ HTMLInputElement::ApplyStep(int32_t aSte
 
   return rv;
 }
 
 /* static */
 bool
 HTMLInputElement::IsExperimentalMobileType(uint8_t aType)
 {
-  return aType == NS_FORM_INPUT_TIME ||
-    (aType == NS_FORM_INPUT_DATE &&
-     !Preferences::GetBool("dom.forms.datepicker", false));
+  return (aType == NS_FORM_INPUT_DATE &&
+    !Preferences::GetBool("dom.forms.datetime", false) &&
+    !Preferences::GetBool("dom.forms.datepicker", false)) ||
+    (aType == NS_FORM_INPUT_TIME &&
+     !Preferences::GetBool("dom.forms.datetime", false));
+}
+
+bool
+HTMLInputElement::IsDateTimeInputType(uint8_t aType)
+{
+  return aType == NS_FORM_INPUT_DATE || aType == NS_FORM_INPUT_TIME ||
+    aType == NS_FORM_INPUT_MONTH;
+
 }
 
 NS_IMETHODIMP
 HTMLInputElement::StepDown(int32_t n, uint8_t optional_argc)
 {
   return ApplyStep(optional_argc ? -n : -1);
 }
 
@@ -2965,18 +2976,18 @@ HTMLInputElement::MozSetDirectory(const 
   element->SetAsDirectory() = directory;
 
   SetFilesOrDirectories(array, true);
 }
 
 bool
 HTMLInputElement::MozIsTextField(bool aExcludePassword)
 {
-  // TODO: temporary until bug 773205 is fixed.
-  if (IsExperimentalMobileType(mType) || mType == NS_FORM_INPUT_DATE) {
+  // TODO: temporary until bug 888320 is fixed.
+  if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
     return false;
   }
 
   return IsSingleLineTextControl(aExcludePassword);
 }
 
 HTMLInputElement*
 HTMLInputElement::GetOwnerNumberControl()
@@ -4014,17 +4025,17 @@ HTMLInputElement::PreHandleEvent(EventCh
     GetValue(mFocusedValue);
   }
 
   // Fire onchange (if necessary), before we do the blur, bug 357684.
   if (aVisitor.mEvent->mMessage == eBlur) {
     // Experimental mobile types rely on the system UI to prevent users to not
     // set invalid values but we have to be extra-careful. Especially if the
     // option has been enabled on desktop.
-    if (IsExperimentalMobileType(mType) || mType == NS_FORM_INPUT_DATE) {
+    if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
       nsAutoString aValue;
       GetValueInternal(aValue);
       nsresult rv =
         SetValueInternal(aValue, nsTextEditorState::eSetValue_Internal);
       NS_ENSURE_SUCCESS(rv, rv);
     }
     FireChangeEventIfNeeded();
   }
@@ -4714,17 +4725,17 @@ HTMLInputElement::PostHandleEvent(EventC
            *     not submit, period.
            */
 
           if (aVisitor.mEvent->mMessage == eKeyPress &&
               keyEvent->mKeyCode == NS_VK_RETURN &&
                (IsSingleLineTextControl(false, mType) ||
                 mType == NS_FORM_INPUT_NUMBER ||
                 IsExperimentalMobileType(mType) ||
-                mType == NS_FORM_INPUT_DATE)) {
+                IsDateTimeInputType(mType))) {
             FireChangeEventIfNeeded();
             rv = MaybeSubmitForm(aVisitor.mPresContext);
             NS_ENSURE_SUCCESS(rv, rv);
           }
 
           if (aVisitor.mEvent->mMessage == eKeyPress &&
               mType == NS_FORM_INPUT_RANGE && !keyEvent->IsAlt() &&
               !keyEvent->IsControl() && !keyEvent->IsMeta() &&
@@ -5533,16 +5544,30 @@ HTMLInputElement::ParseTime(const nsAStr
                // NOTE: there is 10.0 instead of 10 and static_cast<int> because
                // some old [and stupid] compilers can't just do the right thing.
                fractionsSeconds * pow(10.0, static_cast<int>(3 - (aValue.Length() - 9)));
   }
 
   return true;
 }
 
+static bool
+IsDateTimeEnabled(int32_t aNewType)
+{
+  return (aNewType == NS_FORM_INPUT_DATE &&
+          (Preferences::GetBool("dom.forms.datetime", false) ||
+           Preferences::GetBool("dom.experimental_forms", false) ||
+           Preferences::GetBool("dom.forms.datepicker", false))) ||
+         (aNewType == NS_FORM_INPUT_TIME &&
+          (Preferences::GetBool("dom.forms.datetime", false) ||
+           Preferences::GetBool("dom.experimental_forms", false))) ||
+         (aNewType == NS_FORM_INPUT_MONTH &&
+          Preferences::GetBool("dom.forms.datetime", false));
+}
+
 bool
 HTMLInputElement::ParseAttribute(int32_t aNamespaceID,
                                  nsIAtom* aAttribute,
                                  const nsAString& aValue,
                                  nsAttrValue& aResult)
 {
   if (aNamespaceID == kNameSpaceID_None) {
     if (aAttribute == nsGkAtoms::type) {
@@ -5552,17 +5577,18 @@ HTMLInputElement::ParseAttribute(int32_t
       bool success = aResult.ParseEnumValue(aValue, kInputTypeTable, false);
       if (success) {
         newType = aResult.GetEnumValue();
         if ((IsExperimentalMobileType(newType) &&
              !Preferences::GetBool("dom.experimental_forms", false)) ||
             (newType == NS_FORM_INPUT_NUMBER &&
              !Preferences::GetBool("dom.forms.number", false)) ||
             (newType == NS_FORM_INPUT_COLOR &&
-             !Preferences::GetBool("dom.forms.color", false))) {
+             !Preferences::GetBool("dom.forms.color", false)) ||
+            (IsDateTimeInputType(newType) && !IsDateTimeEnabled(newType))) {
           newType = kInputDefaultType->value;
           aResult.SetTo(newType, &aValue);
         }
       } else {
         newType = kInputDefaultType->value;
       }
 
       if (newType != mType) {
@@ -6948,16 +6974,17 @@ HTMLInputElement::GetValueMode() const
     case NS_FORM_INPUT_TEL:
     case NS_FORM_INPUT_EMAIL:
     case NS_FORM_INPUT_URL:
     case NS_FORM_INPUT_NUMBER:
     case NS_FORM_INPUT_RANGE:
     case NS_FORM_INPUT_DATE:
     case NS_FORM_INPUT_TIME:
     case NS_FORM_INPUT_COLOR:
+    case NS_FORM_INPUT_MONTH:
       return VALUE_MODE_VALUE;
     default:
       NS_NOTYETIMPLEMENTED("Unexpected input type in GetValueMode()");
       return VALUE_MODE_VALUE;
 #else // DEBUG
     default:
       return VALUE_MODE_VALUE;
 #endif // DEBUG
@@ -6993,16 +7020,17 @@ HTMLInputElement::DoesReadOnlyApply() co
     case NS_FORM_INPUT_PASSWORD:
     case NS_FORM_INPUT_SEARCH:
     case NS_FORM_INPUT_TEL:
     case NS_FORM_INPUT_EMAIL:
     case NS_FORM_INPUT_URL:
     case NS_FORM_INPUT_NUMBER:
     case NS_FORM_INPUT_DATE:
     case NS_FORM_INPUT_TIME:
+    case NS_FORM_INPUT_MONTH:
       return true;
     default:
       NS_NOTYETIMPLEMENTED("Unexpected input type in DoesReadOnlyApply()");
       return true;
 #else // DEBUG
     default:
       return true;
 #endif // DEBUG
@@ -7030,58 +7058,59 @@ HTMLInputElement::DoesRequiredApply() co
     case NS_FORM_INPUT_PASSWORD:
     case NS_FORM_INPUT_SEARCH:
     case NS_FORM_INPUT_TEL:
     case NS_FORM_INPUT_EMAIL:
     case NS_FORM_INPUT_URL:
     case NS_FORM_INPUT_NUMBER:
     case NS_FORM_INPUT_DATE:
     case NS_FORM_INPUT_TIME:
+    case NS_FORM_INPUT_MONTH:
       return true;
     default:
       NS_NOTYETIMPLEMENTED("Unexpected input type in DoesRequiredApply()");
       return true;
 #else // DEBUG
     default:
       return true;
 #endif // DEBUG
   }
 }
 
 bool
 HTMLInputElement::PlaceholderApplies() const
 {
-  if (mType == NS_FORM_INPUT_DATE ||
-      mType == NS_FORM_INPUT_TIME) {
+  if (IsDateTimeInputType(mType)) {
     return false;
   }
 
   return IsSingleLineTextControl(false);
 }
 
 bool
 HTMLInputElement::DoesPatternApply() const
 {
   // TODO: temporary until bug 773205 is fixed.
-  if (IsExperimentalMobileType(mType)) {
+  if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
     return false;
   }
 
   return IsSingleLineTextControl(false);
 }
 
 bool
 HTMLInputElement::DoesMinMaxApply() const
 {
   switch (mType)
   {
     case NS_FORM_INPUT_NUMBER:
     case NS_FORM_INPUT_DATE:
     case NS_FORM_INPUT_TIME:
     case NS_FORM_INPUT_RANGE:
+    case NS_FORM_INPUT_MONTH:
     // TODO:
     // All date/time types.
       return true;
 #ifdef DEBUG
     case NS_FORM_INPUT_RESET:
     case NS_FORM_INPUT_SUBMIT:
     case NS_FORM_INPUT_IMAGE:
     case NS_FORM_INPUT_BUTTON:
@@ -7119,16 +7148,17 @@ HTMLInputElement::DoesAutocompleteApply(
     case NS_FORM_INPUT_TEL:
     case NS_FORM_INPUT_EMAIL:
     case NS_FORM_INPUT_PASSWORD:
     case NS_FORM_INPUT_DATE:
     case NS_FORM_INPUT_TIME:
     case NS_FORM_INPUT_NUMBER:
     case NS_FORM_INPUT_RANGE:
     case NS_FORM_INPUT_COLOR:
+    case NS_FORM_INPUT_MONTH:
       return true;
 #ifdef DEBUG
     case NS_FORM_INPUT_RESET:
     case NS_FORM_INPUT_SUBMIT:
     case NS_FORM_INPUT_IMAGE:
     case NS_FORM_INPUT_BUTTON:
     case NS_FORM_INPUT_RADIO:
     case NS_FORM_INPUT_CHECKBOX:
@@ -7296,17 +7326,18 @@ HTMLInputElement::HasPatternMismatch() c
   nsIDocument* doc = OwnerDoc();
 
   return !nsContentUtils::IsPatternMatching(value, pattern, doc);
 }
 
 bool
 HTMLInputElement::IsRangeOverflow() const
 {
-  if (!DoesMinMaxApply()) {
+  // TODO: this is temporary until bug 888324 is fixed.
+  if (!DoesMinMaxApply() || mType == NS_FORM_INPUT_MONTH) {
     return false;
   }
 
   Decimal maximum = GetMaximum();
   if (maximum.isNaN()) {
     return false;
   }
 
@@ -7316,17 +7347,18 @@ HTMLInputElement::IsRangeOverflow() cons
   }
 
   return value > maximum;
 }
 
 bool
 HTMLInputElement::IsRangeUnderflow() const
 {
-  if (!DoesMinMaxApply()) {
+  // TODO: this is temporary until bug 888324 is fixed.
+  if (!DoesMinMaxApply() || mType == NS_FORM_INPUT_MONTH) {
     return false;
   }
 
   Decimal minimum = GetMinimum();
   if (minimum.isNaN()) {
     return false;
   }
 
@@ -8295,17 +8327,18 @@ HTMLInputElement::UpdateHasRange()
 {
   /*
    * There is a range if min/max applies for the type and if the element
    * currently have a valid min or max.
    */
 
   mHasRange = false;
 
-  if (!DoesMinMaxApply()) {
+  // TODO: this is temporary until bug 888324 is fixed.
+  if (!DoesMinMaxApply() || mType == NS_FORM_INPUT_MONTH) {
     return;
   }
 
   Decimal minimum = GetMinimum();
   if (!minimum.isNaN()) {
     mHasRange = true;
     return;
   }
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -1017,27 +1017,35 @@ protected:
   /**
    * Returns if the min and max attributes apply for the current type.
    */
   bool DoesMinMaxApply() const;
 
   /**
    * Returns if the step attribute apply for the current type.
    */
-  bool DoesStepApply() const { return DoesMinMaxApply(); }
+  bool DoesStepApply() const
+  {
+    // TODO: this is temporary until bug 888324 is fixed.
+    return DoesMinMaxApply() && mType != NS_FORM_INPUT_MONTH;
+  }
 
   /**
    * Returns if stepDown and stepUp methods apply for the current type.
    */
   bool DoStepDownStepUpApply() const { return DoesStepApply(); }
 
   /**
    * Returns if valueAsNumber attribute applies for the current type.
    */
-  bool DoesValueAsNumberApply() const { return DoesMinMaxApply(); }
+  bool DoesValueAsNumberApply() const
+  {
+    // TODO: this is temporary until bug 888324 is fixed.
+    return DoesMinMaxApply() && mType != NS_FORM_INPUT_MONTH;
+  }
 
   /**
    * Returns if autocomplete attribute applies for the current type.
    */
   bool DoesAutocompleteApply() const;
 
   /**
    * Returns if the maxlength attribute applies for the current type.
@@ -1251,16 +1259,22 @@ protected:
    */
   nsresult ApplyStep(int32_t aStep);
 
   /**
    * Returns if the current type is an experimental mobile type.
    */
   static bool IsExperimentalMobileType(uint8_t aType);
 
+  /*
+   * Returns if the current type is one of the date/time input types: date,
+   * time and month. TODO: week and datetime-local.
+   */
+  static bool IsDateTimeInputType(uint8_t aType);
+
   /**
    * Flushes the layout frame tree to make sure we have up-to-date frames.
    */
   void FlushFrames();
 
   /**
    * Returns true if the element should prevent dispatching another DOMActivate.
    * This is used in situations where the anonymous subtree should already have
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -4980,16 +4980,28 @@ void HTMLMediaElement::FireTimeUpdate(bo
   // Here mTextTrackManager can be null if the cycle collector has unlinked
   // us before our parent. In that case UnbindFromTree will call us
   // when our parent is unlinked.
   if (mTextTrackManager) {
     mTextTrackManager->TimeMarchesOn();
   }
 }
 
+MediaStream* HTMLMediaElement::GetSrcMediaStream() const
+{
+  if (!mSrcStream) {
+    return nullptr;
+  }
+  if (mSrcStream->GetCameraStream()) {
+    // XXX Remove this check with CameraPreviewMediaStream per bug 1124630.
+    return mSrcStream->GetCameraStream();
+  }
+  return mSrcStream->GetPlaybackStream();
+}
+
 void HTMLMediaElement::GetCurrentSpec(nsCString& aString)
 {
   if (mLoadingSrc) {
     mLoadingSrc->GetSpec(aString);
   } else {
     aString.Truncate();
   }
 }
@@ -5815,10 +5827,76 @@ HTMLMediaElement::IsAudible() const
   // Silent audio track.
   if (!mIsAudioTrackAudible) {
     return false;
   }
 
   return true;
 }
 
+static const char* VisibilityString(Visibility aVisibility) {
+  switch(aVisibility) {
+    case Visibility::UNTRACKED: {
+      return "UNTRACKED";
+    }
+    case Visibility::NONVISIBLE: {
+      return "NONVISIBLE";
+    }
+    case Visibility::MAY_BECOME_VISIBLE: {
+      return "MAY_BECOME_VISIBLE";
+    }
+    case Visibility::IN_DISPLAYPORT: {
+      return "IN_DISPLAYPORT";
+    }
+  }
+
+  return "NAN";
+}
+
+// The visibility enumeration contains three states:
+// {NONVISIBLE, MAY_BECOME_VISIBLE, IN_DISPLAYPORT}.
+// Here, I implement a conservative mechanism:
+// (1) {MAY_BECOME_VISIBLE, IN_DISPLAYPORT} -> NONVISIBLE:
+//     notify the decoder to suspend.
+// (2) {NONVISIBLE, MAY_BECOME_VISIBLE} -> IN_DISPLAYPORT:
+//     notify the decoder to resume.
+// (3) IN_DISPLAYPORT -> MAY_BECOME_VISIBLE:
+//     do nothing here because users might scroll back immediately.
+// (4) NONVISIBLE -> MAY_BECOME_VISIBLE:
+//     notify the decoder to resume because users might continue their scroll
+//     direction and the video might be IN_DISPLAYPORT soon.
+void
+HTMLMediaElement::OnVisibilityChange(Visibility aOldVisibility,
+                                     Visibility aNewVisibility)
+{
+  LOG(LogLevel::Debug, ("OnVisibilityChange(): %s -> %s\n",
+      VisibilityString(aOldVisibility),VisibilityString(aNewVisibility)));
+
+  if (!mDecoder) {
+    return;
+  }
+
+  switch (aNewVisibility) {
+    case Visibility::UNTRACKED: {
+        MOZ_ASSERT_UNREACHABLE("Shouldn't notify for untracked visibility");
+        break;
+    }
+    case Visibility::NONVISIBLE: {
+      mDecoder->NotifyOwnerActivityChanged(false);
+      break;
+    }
+    case Visibility::MAY_BECOME_VISIBLE: {
+      if (aOldVisibility == Visibility::NONVISIBLE) {
+        mDecoder->NotifyOwnerActivityChanged(true);
+      } else if (aOldVisibility == Visibility::IN_DISPLAYPORT) {
+        // Do nothing.
+      }
+      break;
+    }
+    case Visibility::IN_DISPLAYPORT: {
+      mDecoder->NotifyOwnerActivityChanged(true);
+      break;
+    }
+  }
+
+}
 } // namespace dom
 } // namespace mozilla
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -50,16 +50,18 @@ class MediaResource;
 class MediaDecoder;
 class VideoFrameContainer;
 namespace dom {
 class MediaKeys;
 class TextTrack;
 class TimeRanges;
 class WakeLock;
 class MediaTrack;
+class MediaStreamTrack;
+class VideoStreamTrack;
 } // namespace dom
 } // namespace mozilla
 
 class AutoNotifyAudioChannelAgent;
 class nsIChannel;
 class nsIHttpChannel;
 class nsILoadGroup;
 class nsIRunnable;
@@ -398,27 +400,17 @@ public:
    */
   virtual void FireTimeUpdate(bool aPeriodic) final override;
 
   /**
    * This will return null if mSrcStream is null, or if mSrcStream is not
    * null but its GetPlaybackStream() returns null --- which can happen during
    * cycle collection unlinking!
    */
-  MediaStream* GetSrcMediaStream() const
-  {
-    if (!mSrcStream) {
-      return nullptr;
-    }
-    if (mSrcStream->GetCameraStream()) {
-      // XXX Remove this check with CameraPreviewMediaStream per bug 1124630.
-      return mSrcStream->GetCameraStream();
-    }
-    return mSrcStream->GetPlaybackStream();
-  }
+  MediaStream* GetSrcMediaStream() const;
 
   // WebIDL
 
   MediaError* GetError() const
   {
     return mError;
   }
 
--- a/dom/html/nsIFormControl.h
+++ b/dom/html/nsIFormControl.h
@@ -50,16 +50,17 @@ enum InputElementTypes {
   NS_FORM_INPUT_CHECKBOX,
   NS_FORM_INPUT_COLOR,
   NS_FORM_INPUT_DATE,
   NS_FORM_INPUT_EMAIL,
   NS_FORM_INPUT_FILE,
   NS_FORM_INPUT_HIDDEN,
   NS_FORM_INPUT_RESET,
   NS_FORM_INPUT_IMAGE,
+  NS_FORM_INPUT_MONTH,
   NS_FORM_INPUT_NUMBER,
   NS_FORM_INPUT_PASSWORD,
   NS_FORM_INPUT_RADIO,
   NS_FORM_INPUT_SEARCH,
   NS_FORM_INPUT_SUBMIT,
   NS_FORM_INPUT_TEL,
   NS_FORM_INPUT_TEXT,
   NS_FORM_INPUT_TIME,
@@ -261,16 +262,17 @@ nsIFormControl::IsSingleLineTextControl(
   return aType == NS_FORM_INPUT_TEXT ||
          aType == NS_FORM_INPUT_EMAIL ||
          aType == NS_FORM_INPUT_SEARCH ||
          aType == NS_FORM_INPUT_TEL ||
          aType == NS_FORM_INPUT_URL ||
          // TODO: those are temporary until bug 773205 is fixed.
          aType == NS_FORM_INPUT_DATE ||
          aType == NS_FORM_INPUT_TIME ||
+         aType == NS_FORM_INPUT_MONTH ||
          (!aExcludePassword && aType == NS_FORM_INPUT_PASSWORD);
 }
 
 bool
 nsIFormControl::IsSubmittableControl() const
 {
   // TODO: keygen should be in that list, see bug 101019.
   uint32_t type = GetType();
--- a/dom/html/test/forms/mochitest.ini
+++ b/dom/html/test/forms/mochitest.ini
@@ -6,17 +6,16 @@ support-files =
 
 [test_bug1039548.html]
 [test_button_attributes_reflection.html]
 [test_input_radio_radiogroup.html]
 [test_input_radio_required.html]
 [test_change_event.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_datalist_element.html]
-[test_experimental_forms_pref.html]
 [test_form_attribute-1.html]
 [test_form_attribute-2.html]
 [test_form_attribute-3.html]
 [test_form_attribute-4.html]
 [test_form_attributes_reflection.html]
 [test_form_named_getter_dynamic.html]
 [test_formaction_attribute.html]
 skip-if = buildapp == 'mulet'
@@ -56,16 +55,17 @@ skip-if = os == "android" || appname == 
 [test_input_range_key_events.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_input_range_mouse_and_touch_events.html]
 skip-if = (toolkit == 'gonk' && debug) #debug-only failure; bug 926546
 [test_input_range_rounding.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_input_sanitization.html]
 [test_input_textarea_set_value_no_scroll.html]
+[test_input_types_pref.html]
 [test_input_typing_sanitization.html]
 skip-if = buildapp == 'mulet'
 [test_input_untrusted_key_events.html]
 [test_input_url.html]
 [test_interactive_content_in_label.html]
 [test_label_control_attribute.html]
 [test_label_input_controls.html]
 [test_max_attribute.html]
--- a/dom/html/test/forms/test_input_attributes_reflection.html
+++ b/dom/html/test/forms/test_input_attributes_reflection.html
@@ -229,20 +229,20 @@ reflectString({
 });
 
 // .type
 reflectLimitedEnumerated({
   element: document.createElement("input"),
   attribute: "type",
   validValues: [ "hidden", "text", "search", "tel", "url", "email", "password",
                  "checkbox", "radio", "file", "submit", "image", "reset",
-                 "button", "date", "time", "number", "range", "color" ],
+                 "button", "date", "time", "number", "range", "color", "month" ],
   invalidValues: [ "this-is-probably-a-wrong-type", "", "tulip" ],
   defaultValue: "text",
-  unsupportedValues: [ "datetime", "month", "week", "datetime-local" ]
+  unsupportedValues: [ "datetime", "week", "datetime-local" ]
 });
 
 // .defaultValue
 reflectString({
   element: document.createElement("input"),
   attribute: { idl: "defaultValue", content: "value" },
   otherValues: [ "foo\nbar", "foo\rbar", "foo\r\nbar" ],
 });
--- a/dom/html/test/forms/test_input_sanitization.html
+++ b/dom/html/test/forms/test_input_sanitization.html
@@ -74,17 +74,17 @@ var inputTypes =
 [
   "text", "password", "search", "tel", "hidden", "checkbox", "radio",
   "submit", "image", "reset", "button", "email", "url", "number", "date",
   "time", "range", "color"
 ];
 
 var todoTypes =
 [
-  "month", "week", "datetime", "datetime-local",
+  "week", "datetime", "datetime-local",
 ];
 
 var valueModeValue =
 [
   "text", "search", "url", "tel", "email", "password", "date", "datetime",
   "month", "week", "time", "datetime-local", "number", "range", "color",
 ];
 
rename from dom/html/test/forms/test_experimental_forms_pref.html
rename to dom/html/test/forms/test_input_types_pref.html
--- a/dom/html/test/forms/test_experimental_forms_pref.html
+++ b/dom/html/test/forms/test_input_types_pref.html
@@ -14,38 +14,85 @@ https://bugzilla.mozilla.org/show_bug.cg
 <p id="display"></p>
 <div id="content" style="display: none" >
 </div>
 <pre id="test">
 <script type="application/javascript">
 
   var input = document.createElement("input");
 
-  SimpleTest.waitForExplicitFinish();
-  SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", false], ["dom.forms.datepicker",false]]}, function() {
-    input.type = "date";
-    is(input.type, "text", "input type shouldn't be date when the experimental forms are disabled");
-    is(input.getAttribute('type'), "date", "input 'type' attribute should not change");
+  var testData = [
+    {
+      prefs: [["dom.forms.number", false]],
+      inputType: "number",
+      expectedType: "text"
+    }, {
+      prefs: [["dom.forms.number", true]],
+      inputType: "number",
+      expectedType: "number"
+    }, {
+      prefs: [["dom.forms.color", false]],
+      inputType: "color",
+      expectedType: "text"
+    }, {
+      prefs: [["dom.forms.color", true]],
+      inputType: "color",
+      expectedType: "color"
+    }, {
+      prefs: [["dom.experimental_forms", false], ["dom.forms.datepicker", false],
+              ["dom.forms.datetime", false]],
+      inputType: "date",
+      expectedType: "text"
+    }, {
+      prefs: [["dom.experimental_forms", true], ["dom.forms.datepicker", false],
+              ["dom.forms.datetime", false]],
+      inputType: "date",
+      expectedType: "date"
+    }, {
+      prefs: [["dom.experimental_forms", false], ["dom.forms.datepicker", true],
+              ["dom.forms.datetime", false]],
+      inputType: "date",
+      expectedType: "date"
+    }, {
+      prefs: [["dom.experimental_forms", false], ["dom.forms.datepicker", false],
+              ["dom.forms.datetime", true]],
+      inputType: "date",
+      expectedType: "date"
+    }, {
+      prefs: [["dom.forms.datetime", false]],
+      inputType: "month",
+      expectedType: "text"
+    }, {
+      prefs: [["dom.forms.datetime", true]],
+      inputType: "month",
+      expectedType: "month"
+    }
+  ];
 
-    SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms",true], ["dom.forms.datepicker",false]]}, function() {
-      // Change the type of input to text and then back to date,
-      // so that HTMLInputElement::ParseAttribute gets called with the pref enabled.
-      input.type = "text";
-      input.type = "date";
-      is(input.type, "date", "input type should be date when the experimental forms are enabled");
-      is(input.getAttribute('type'), "date", "input 'type' attribute should not change");
-
-      SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms",false], ["dom.forms.datepicker",true]]}, function() {
-        // Change the type of input to text and then back to date,
+  function testInputTypePreference(aData) {
+    return SpecialPowers.pushPrefEnv({'set': aData.prefs})
+      .then(() => {
+        // Change the type of input to text and then back to the tested input type,
         // so that HTMLInputElement::ParseAttribute gets called with the pref enabled.
         input.type = "text";
-        input.type = "date";
-        is(input.type, "date", "input type should be date when the datepicker is enabled");
-        is(input.getAttribute('type'), "date", "input 'type' attribute should not change");
+        input.type = aData.inputType;
+        is(input.type, aData.expectedType, "input type should be '" +
+           aData.expectedType + "'' when pref " + aData.prefs + " is set");
+        is(input.getAttribute('type'), aData.inputType,
+           "input 'type' attribute should not change");
+      });
+  }
+
+  SimpleTest.waitForExplicitFinish();
 
-        SimpleTest.finish();
-      });
-    });
-  });
+  let promise = Promise.resolve();
+  for (let i = 0; i < testData.length; i++) {
+    let data = testData[i];
+    promise = promise.then(() => testInputTypePreference(data));
+  }
+
+  promise.catch(error => ok(false, "Promise reject: " + error))
+    .then(() => SimpleTest.finish());
+
 </script>
 </pre>
 </body>
 </html>
--- a/dom/html/test/forms/test_input_typing_sanitization.html
+++ b/dom/html/test/forms/test_input_typing_sanitization.html
@@ -177,17 +177,16 @@ function runTest()
         '00:00:00.',
         '00:60',
         '10:58:99',
         ':19:10',
         '23:08:09.1012',
       ]
     },
     { type: 'week', todo: true },
-    { type: 'month', todo: true },
     { type: 'datetime', todo: true },
     { type: 'datetime-local', todo: true },
   ];
 
   for (test of data) {
     gCurrentTest = test;
 
     if (test.todo) {
--- a/dom/html/test/forms/test_max_attribute.html
+++ b/dom/html/test/forms/test_max_attribute.html
@@ -24,17 +24,18 @@ var data = [
   { type: 'text', apply: false },
   { type: 'search', apply: false },
   { type: 'tel', apply: false },
   { type: 'url', apply: false },
   { type: 'email', apply: false },
   { type: 'password', apply: false },
   { type: 'datetime', apply: true, todo: true },
   { type: 'date', apply: true },
-  { type: 'month', apply: true, todo: true },
+  // TODO: temporary set to false until bug 888324 is fixed.
+  { type: 'month', apply: false },
   { type: 'week', apply: true, todo: true },
   { type: 'time', apply: true },
   { type: 'datetime-local', apply: true, todo: true },
   { type: 'number', apply: true },
   { type: 'range', apply: true },
   { type: 'color', apply: false },
   { type: 'checkbox', apply: false },
   { type: 'radio', apply: false },
@@ -140,16 +141,19 @@ for (var test of data) {
     case 'range':
       // range is special, since setting max to -1 will make it invalid since
       // it's default would then be 0, meaning it suffers from overflow.
       input.max = '-1';
       checkValidity(input, false, apply, apply);
       // Now make it something that won't cause an error below:
       input.max = '10';
       break;
+    case 'month':
+      // TODO: this is temporary until bug 888324 is fixed.
+      break;
     default:
       ok(false, 'please, add a case for this new type (' + input.type + ')');
   }
 
   checkValidity(input, true, apply, apply);
 
   switch (input.type) {
     case 'text':
--- a/dom/html/test/forms/test_min_attribute.html
+++ b/dom/html/test/forms/test_min_attribute.html
@@ -24,17 +24,18 @@ var data = [
   { type: 'text', apply: false },
   { type: 'search', apply: false },
   { type: 'tel', apply: false },
   { type: 'url', apply: false },
   { type: 'email', apply: false },
   { type: 'password', apply: false },
   { type: 'datetime', apply: true, todo: true },
   { type: 'date', apply: true },
-  { type: 'month', apply: true, todo: true },
+  // TODO: temporary set to false until bug 888324 is fixed.
+  { type: 'month', apply: false },
   { type: 'week', apply: true, todo: true },
   { type: 'time', apply: true },
   { type: 'datetime-local', apply: true, todo: true },
   { type: 'number', apply: true },
   { type: 'range', apply: true },
   { type: 'color', apply: false },
   { type: 'checkbox', apply: false },
   { type: 'radio', apply: false },
@@ -136,16 +137,19 @@ for (var test of data) {
     case 'time':
       input.min = '20:20';
       break;
     case 'range':
       // range is special, since setting min to 999 will make it invalid since
       // it's default maximum is 100, its value would be 999, and it would
       // suffer from overflow.
       break;
+    case 'month':
+      // TODO: this is temporary until bug 888324 is fixed.
+      break;
     default:
       ok(false, 'please, add a case for this new type (' + input.type + ')');
   }
 
   // The element should still be valid and range should apply if it can.
   checkValidity(input, true, apply, apply);
 
   switch (input.type) {
@@ -330,16 +334,19 @@ for (var test of data) {
       checkValidity(input, true, apply, apply);
 
       input.min = '';
       checkValidity(input, true, apply, false);
 
       input.min = 'foo';
       checkValidity(input, true, apply, false);
       break;
+    case 'month':
+      // TODO: this is temporary until bug 888324 is fixed.
+      break;
     default:
       ok(false, 'write tests for ' + input.type);
   }
 
   // Cleaning up,
   input.removeAttribute('min');
   input.value = '';
 }
--- a/dom/html/test/forms/test_mozistextfield.html
+++ b/dom/html/test/forms/test_mozistextfield.html
@@ -47,26 +47,26 @@ var gInputTestData = [
   ['search',   true],
   ['email',    true],
   ['url',      true],
   ['number',   false],
   ['range',    false],
   ['date',     false],
   ['time',     false],
   ['color',    false],
+  ['month',    false],
 ];
 
 /**
  * TODO: the next types are not yet in the tree.
  * The value is only a suggestion.
  */
 var gInputTodoData = [
 /* type        expected result */
   ['datetime', false],
-  ['month',    false],
   ['week',     false],
   ['datetime-local', false],
 ];
 
 function checkMozIsTextFieldDefined(aElement, aResult)
 {
   var element = document.createElement(aElement);
 
--- a/dom/html/test/forms/test_pattern_attribute.html
+++ b/dom/html/test/forms/test_pattern_attribute.html
@@ -293,18 +293,18 @@ function checkPatternValidity(element)
 }
 
 var input = document.getElementById('i');
 
 // |validTypes| are the types which accept @pattern
 // and |invalidTypes| are the ones which do not accept it.
 var validTypes = Array('text', 'password', 'search', 'tel', 'email', 'url');
 var barredTypes = Array('hidden', 'reset', 'button');
-var invalidTypes = Array('checkbox', 'radio', 'file', 'number', 'range', 'date', 'time', 'color', 'submit', 'image');
-// TODO: 'datetime', 'month', 'week', and 'datetime-local'
+var invalidTypes = Array('checkbox', 'radio', 'file', 'number', 'range', 'date', 'time', 'color', 'submit', 'image', 'month');
+// TODO: 'datetime', 'week', and 'datetime-local'
 //       do not accept the @pattern too but are not implemented yet.
 
 for (type of validTypes) {
   input.type = type;
   completeValidityCheck(input, false);
   checkPatternValidity(input);
 }
 
--- a/dom/html/test/forms/test_required_attribute.html
+++ b/dom/html/test/forms/test_required_attribute.html
@@ -169,16 +169,18 @@ function checkInputRequiredValidity(type
   } else if (element.type == 'url') {
     SpecialPowers.wrap(element).value = 'http://mozilla.org/';
   } else if (element.type == 'number') {
     SpecialPowers.wrap(element).value = '42';
   } else if (element.type == 'date') {
     SpecialPowers.wrap(element).value = '2010-10-10';
   } else if (element.type == 'time') {
     SpecialPowers.wrap(element).value = '21:21';
+  } else if (element.type = 'month') {
+    SpecialPowers.wrap(element).value = '2010-10';
   } else {
     SpecialPowers.wrap(element).value = 'foo';
   }
   checkNotSufferingFromBeingMissing(element);
 
   SpecialPowers.wrap(element).value = '';
   checkSufferingFromBeingMissing(element, true);
 
@@ -357,20 +359,20 @@ for (type of typeBarredFromConstraintVal
 
 // Then, checks for the types which do not use the required attribute.
 var typeRequireNotApply = ['range', 'color', 'submit', 'image'];
 for (type of typeRequireNotApply) {
   checkInputRequiredNotApply(type, false);
 }
 
 // Now, checking for all types which accept the required attribute.
-// TODO: check 'datetime', 'month', 'week' and 'datetime-local'
+// TODO: check 'datetime', 'week' and 'datetime-local'
 //       when they will be implemented.
 var typeRequireApply = ["text", "password", "search", "tel", "email", "url",
-                        "number", "date", "time"];
+                        "number", "date", "time", "month"];
 
 for (type of typeRequireApply) {
   checkInputRequiredValidity(type);
 }
 
 checkInputRequiredValidityForCheckbox();
 checkInputRequiredValidityForRadio();
 checkInputRequiredValidityForFile();
--- a/dom/html/test/forms/test_step_attribute.html
+++ b/dom/html/test/forms/test_step_attribute.html
@@ -24,17 +24,18 @@ var data = [
   { type: 'text', apply: false },
   { type: 'search', apply: false },
   { type: 'tel', apply: false },
   { type: 'url', apply: false },
   { type: 'email', apply: false },
   { type: 'password', apply: false },
   { type: 'datetime', apply: true, todo: true },
   { type: 'date', apply: true },
-  { type: 'month', apply: true, todo: true },
+  // TODO: temporary set to false until bug 888324 is fixed.
+  { type: 'month', apply: false },
   { type: 'week', apply: true, todo: true },
   { type: 'time', apply: true },
   { type: 'datetime-local', apply: true, todo: true },
   { type: 'number', apply: true },
   { type: 'range', apply: true },
   { type: 'color', apply: false },
   { type: 'checkbox', apply: false },
   { type: 'radio', apply: false },
@@ -715,16 +716,19 @@ for (var test of data) {
           checkValidity(input, true, apply);
         } else {
           checkValidity(input, false, apply,
                         { low: test.low, high: test.high });
         }
       }
 
       break;
+    case 'month':
+      // TODO: this is temporary until bug 888324 is fixed.
+      break;
     default:
       ok(false, "Implement the tests for <input type='" + test.type + " >");
       break;
   }
 }
 
 </script>
 </pre>
--- a/dom/html/test/forms/test_valueasnumber_attribute.html
+++ b/dom/html/test/forms/test_valueasnumber_attribute.html
@@ -39,22 +39,23 @@ function checkAvailability()
     ["image", false],
     ["reset", false],
     ["button", false],
     ["number", true],
     ["range", true],
     ["date", true],
     ["time", true],
     ["color", false],
+    // TODO: temporary set to false until bug 888324 is fixed.
+    ["month", false],
   ];
 
   var todoList =
   [
     ["datetime", true],
-    ["month", true],
     ["week", true],
     ["datetime-local", true],
   ];
 
   var element = document.createElement('input');
 
   for (data of testData) {
     var exceptionCatched = false;
--- a/dom/html/test/test_bug590363.html
+++ b/dom/html/test/test_bug590363.html
@@ -34,21 +34,22 @@ var testData = [
   [ "email",    true ],
   [ "search",   true ],
   [ "password", true ],
   [ "number",   true ],
   [ "date",     true ],
   [ "time",     true ],
   [ "range",    true ],
   [ "color",    true ],
+  [ 'month',    true ]
   // 'file' is treated separatly.
 ];
 
 var todoTypes = [
-  "datetime", "month", "week", "datetime-local"
+  "datetime", "week", "datetime-local"
 ];
 
 var nonTrivialSanitizing = [ 'number', 'date', 'time', 'color' ];
 
 var length = testData.length;
 for (var i=0; i<length; ++i) {
   for (var j=0; j<length; ++j) {
     var e = document.createElement('input');
@@ -77,16 +78,18 @@ for (var i=0; i<length; ++i) {
                nonTrivialSanitizing.indexOf(testData[j][0]) != -1) {
       expectedValue = '';
     } else if (testData[i][0] == 'number' || testData[j][0] == 'number') {
       expectedValue = '42';
     } else if (testData[i][0] == 'date' || testData[j][0] == 'date') {
       expectedValue = '2012-12-21';
     } else if (testData[i][0] == 'time' || testData[j][0] == 'time') {
       expectedValue = '21:21';
+    } else if (testData[i][0] == 'month' || testData[j][0] == 'month') {
+      expectedValue = '2013-03';
     } else {
       expectedValue = "foo";
     }
     e.value = expectedValue;
 
     e.type = testData[j][0];
     is(e.value, expectedValue, ".value should still return the same value after " +
        "changing type from " + testData[i][0] + " to " + testData[j][0]);
--- a/dom/html/test/test_bug598643.html
+++ b/dom/html/test/test_bug598643.html
@@ -33,19 +33,19 @@ function testFileControl(aElement)
      "the file control shouldn't suffer from being too long");
 }
 
 var types = [
   // These types can be too long.
   [ "text", "email", "password", "url", "search", "tel" ],
   // These types can't be too long.
   [ "radio", "checkbox", "submit", "button", "reset", "image", "hidden",
-    'number', 'range', 'date', 'time', 'color' ],
+    'number', 'range', 'date', 'time', 'color', 'month' ],
   // These types can't be too long but are not implemented yet.
-  [ "datetime", "month", "week", 'datetime-local' ]
+  [ "datetime", "week", 'datetime-local' ]
 ];
 
 var input = document.createElement("input");
 input.maxLength = 1;
 input.value = "foo";
 
 // Too long types.
 for (type of types[0]) {
--- a/dom/interfaces/html/nsIDOMHTMLMediaElement.idl
+++ b/dom/interfaces/html/nsIDOMHTMLMediaElement.idl
@@ -18,18 +18,21 @@
  * @status UNDER_DEVELOPMENT
  */
 
 // undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK
 %{C++
 #ifdef GetCurrentTime
 #undef GetCurrentTime
 #endif
+#include "Visibility.h"
 %}
 
+native Visibility(mozilla::Visibility);
+
 [uuid(c041d76c-15ce-47ad-b61d-e8755a6db638)]
 interface nsIDOMHTMLMediaElement : nsISupports
 {
   // error state
   readonly attribute nsIDOMMediaError error;
 
   // network state
            attribute DOMString src;
@@ -124,9 +127,19 @@ interface nsIDOMHTMLMediaElement : nsISu
    attribute DOMString mozAudioChannelType;
 
   // In addition the media element has this new events:
   // * onmozinterruptbegin - called when the media element is interrupted
   //   because of the audiochannel manager.
   // * onmozinterruptend - called when the interruption is concluded
 
   [notxpcom] boolean isVideo();
+
+  /**
+   * Called by layout to announce when the frame associated with this content
+   * has changed its visibility state.
+   *
+   * @param aOldVisibility    The previous visibility state.
+   * @param aNewVisibility    The new visibility state.
+   */
+  [noscript, notxpcom] void onVisibilityChange(in Visibility aOldVisibility,
+                                               in Visibility aNewVisibility);
 };
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -3287,16 +3287,29 @@ ContentChild::RecvInvokeDragSession(nsTA
   nsCOMPtr<nsIDragService> dragService =
     do_GetService("@mozilla.org/widget/dragservice;1");
   if (dragService) {
     dragService->StartDragSession();
     nsCOMPtr<nsIDragSession> session;
     dragService->GetCurrentSession(getter_AddRefs(session));
     if (session) {
       session->SetDragAction(aAction);
+      // Check if we are receiving any file objects. If we are we will want
+      // to hide any of the other objects coming in from content.
+      bool hasFiles = false;
+      for (uint32_t i = 0; i < aTransfers.Length() && !hasFiles; ++i) {
+        auto& items = aTransfers[i].items();
+        for (uint32_t j = 0; j < items.Length() && !hasFiles; ++j) {
+          if (items[j].data().type() == IPCDataTransferData::TPBlobChild) {
+            hasFiles = true;
+          }
+        }
+      }
+
+      // Add the entries from the IPC to the new DataTransfer
       nsCOMPtr<DataTransfer> dataTransfer =
         new DataTransfer(nullptr, eDragStart, false, -1);
       for (uint32_t i = 0; i < aTransfers.Length(); ++i) {
         auto& items = aTransfers[i].items();
         for (uint32_t j = 0; j < items.Length(); ++j) {
           const IPCDataTransferItem& item = items[j];
           RefPtr<nsVariantCC> variant = new nsVariantCC();
           if (item.data().type() == IPCDataTransferData::TnsString) {
@@ -3308,19 +3321,21 @@ ContentChild::RecvInvokeDragSession(nsTA
             Unused << DeallocShmem(data);
           } else if (item.data().type() == IPCDataTransferData::TPBlobChild) {
             BlobChild* blob = static_cast<BlobChild*>(item.data().get_PBlobChild());
             RefPtr<BlobImpl> blobImpl = blob->GetBlobImpl();
             variant->SetAsISupports(blobImpl);
           } else {
             continue;
           }
+          // We should hide this data from content if we have a file, and we aren't a file.
+          bool hidden = hasFiles && item.data().type() != IPCDataTransferData::TPBlobChild;
           dataTransfer->SetDataWithPrincipalFromOtherProcess(
             NS_ConvertUTF8toUTF16(item.flavor()), variant, i,
-            nsContentUtils::GetSystemPrincipal());
+            nsContentUtils::GetSystemPrincipal(), hidden);
         }
       }
       session->SetDataTransfer(dataTransfer);
     }
   }
   return true;
 }
 
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1809,17 +1809,17 @@ TabChild::HandleLongTap(const CSSPoint& 
 }
 
 bool
 TabChild::NotifyAPZStateChange(const ViewID& aViewId,
                                const layers::GeckoContentController::APZStateChange& aChange,
                                const int& aArg)
 {
   mAPZEventState->ProcessAPZStateChange(GetDocument(), aViewId, aChange, aArg);
-  if (aChange == layers::GeckoContentController::APZStateChange::TransformEnd) {
+  if (aChange == layers::GeckoContentController::APZStateChange::eTransformEnd) {
     // This is used by tests to determine when the APZ is done doing whatever
     // it's doing. XXX generify this as needed when writing additional tests.
     nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
     observerService->NotifyObservers(nullptr, "APZ:TransformEnd", nullptr);
   }
   return true;
 }
 
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -3349,19 +3349,24 @@ TabParent::AddInitialDnDDataTo(DataTrans
           variant->SetAsACString(nsDependentCString(data.get<char>(), data.Size<char>()));
         }
 
         mozilla::Unused << DeallocShmem(item.data().get_Shmem());
       }
 
       // Using system principal here, since once the data is on parent process
       // side, it can be handled as being from browser chrome or OS.
+
+      // We set aHidden to false, as we don't need to worry about hiding data
+      // from content in the parent process where there is no content.
+      // XXX: Nested Content Processes may change this
       aDataTransfer->SetDataWithPrincipalFromOtherProcess(NS_ConvertUTF8toUTF16(item.flavor()),
                                                           variant, i,
-                                                          nsContentUtils::GetSystemPrincipal());
+                                                          nsContentUtils::GetSystemPrincipal(),
+                                                          /* aHidden = */ false);
     }
   }
   mInitialDataTransferItems.Clear();
 }
 
 void
 TabParent::TakeDragVisualization(RefPtr<mozilla::gfx::SourceSurface>& aSurface,
                                  int32_t& aDragAreaX, int32_t& aDragAreaY)
--- 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/media/AudioCaptureStream.cpp
+++ b/dom/media/AudioCaptureStream.cpp
@@ -24,18 +24,18 @@ using namespace mozilla::dom;
 using namespace mozilla::gfx;
 
 namespace mozilla
 {
 
 // We are mixing to mono until PeerConnection can accept stereo
 static const uint32_t MONO = 1;
 
-AudioCaptureStream::AudioCaptureStream(DOMMediaStream* aWrapper, TrackID aTrackId)
-  : ProcessedMediaStream(aWrapper), mTrackId(aTrackId), mStarted(false), mTrackCreated(false)
+AudioCaptureStream::AudioCaptureStream(TrackID aTrackId)
+  : ProcessedMediaStream(), mTrackId(aTrackId), mStarted(false), mTrackCreated(false)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_COUNT_CTOR(AudioCaptureStream);
   mMixer.AddCallback(this);
 }
 
 AudioCaptureStream::~AudioCaptureStream()
 {
--- a/dom/media/AudioCaptureStream.h
+++ b/dom/media/AudioCaptureStream.h
@@ -18,17 +18,17 @@ class DOMMediaStream;
 
 /**
  * See MediaStreamGraph::CreateAudioCaptureStream.
  */
 class AudioCaptureStream : public ProcessedMediaStream,
                            public MixerCallbackReceiver
 {
 public:
-  explicit AudioCaptureStream(DOMMediaStream* aWrapper, TrackID aTrackId);
+  explicit AudioCaptureStream(TrackID aTrackId);
   virtual ~AudioCaptureStream();
 
   void Start();
 
   void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override;
 
 protected:
   void MixerCallback(AudioDataValue* aMixedBuffer, AudioSampleFormat aFormat,
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -5,16 +5,17 @@
 
 #include "DOMMediaStream.h"
 #include "nsContentUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIScriptError.h"
 #include "nsIUUIDGenerator.h"
 #include "nsPIDOMWindow.h"
 #include "mozilla/dom/MediaStreamBinding.h"
+#include "mozilla/dom/MediaStreamTrackEvent.h"
 #include "mozilla/dom/LocalMediaStreamBinding.h"
 #include "mozilla/dom/AudioNode.h"
 #include "AudioChannelAgent.h"
 #include "mozilla/dom/AudioTrack.h"
 #include "mozilla/dom/AudioTrackList.h"
 #include "mozilla/dom/VideoTrack.h"
 #include "mozilla/dom/VideoTrackList.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
@@ -93,20 +94,20 @@ DOMMediaStream::TrackPort::GetSource() c
 
 TrackID
 DOMMediaStream::TrackPort::GetSourceTrackId() const
 {
   return mInputPort ? mInputPort->GetSourceTrackId() : TRACK_INVALID;
 }
 
 already_AddRefed<Pledge<bool>>
-DOMMediaStream::TrackPort::BlockSourceTrackId(TrackID aTrackId)
+DOMMediaStream::TrackPort::BlockSourceTrackId(TrackID aTrackId, BlockingMode aBlockingMode)
 {
   if (mInputPort) {
-    return mInputPort->BlockSourceTrackId(aTrackId);
+    return mInputPort->BlockSourceTrackId(aTrackId, aBlockingMode);
   }
   RefPtr<Pledge<bool>> rejected = new Pledge<bool>();
   rejected->Reject(NS_ERROR_FAILURE);
   return rejected.forget();
 }
 
 NS_IMPL_CYCLE_COLLECTION(DOMMediaStream::TrackPort, mTrack)
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DOMMediaStream::TrackPort, AddRef)
@@ -137,17 +138,17 @@ public:
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     if (!mStream) {
       return;
     }
 
     MediaStreamTrack* track =
-      mStream->FindOwnedDOMTrack(aInputStream, aInputTrackID);
+      mStream->FindOwnedDOMTrack(aInputStream, aInputTrackID, aTrackID);
     if (!track) {
       // Track had not been created on main thread before, create it now.
       NS_WARN_IF_FALSE(!mStream->mTracks.IsEmpty(),
                        "A new track was detected on the input stream; creating "
                        "a corresponding MediaStreamTrack. Initial tracks "
                        "should be added manually to immediately and "
                        "synchronously be available to JS.");
       RefPtr<MediaStreamTrackSource> source;
@@ -160,26 +161,27 @@ public:
         nsIDocument* doc = window ? window->GetExtantDoc() : nullptr;
         nsIPrincipal* principal = doc ? doc->NodePrincipal() : nullptr;
         source = new BasicUnstoppableTrackSource(principal);
       }
       track = mStream->CreateDOMTrack(aTrackID, aType, source);
     }
   }
 
-  void DoNotifyTrackEnded(MediaStream* aInputStream, TrackID aInputTrackID)
+  void DoNotifyTrackEnded(MediaStream* aInputStream, TrackID aInputTrackID,
+                          TrackID aTrackID)
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     if (!mStream) {
       return;
     }
 
     RefPtr<MediaStreamTrack> track =
-      mStream->FindOwnedDOMTrack(aInputStream, aInputTrackID);
+      mStream->FindOwnedDOMTrack(aInputStream, aInputTrackID, aTrackID);
     NS_ASSERTION(track, "Owned MediaStreamTracks must be known by the DOMMediaStream");
     if (track) {
       LOG(LogLevel::Debug, ("DOMMediaStream %p MediaStreamTrack %p ended at the source. Marking it ended.",
                             mStream, track.get()));
       track->NotifyEnded();
     }
   }
 
@@ -192,19 +194,19 @@ public:
     if (aTrackEvents & TRACK_EVENT_CREATED) {
       nsCOMPtr<nsIRunnable> runnable =
         NewRunnableMethod<TrackID, MediaSegment::Type, MediaStream*, TrackID>(
           this, &OwnedStreamListener::DoNotifyTrackCreated,
           aID, aQueuedMedia.GetType(), aInputStream, aInputTrackID);
       aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
     } else if (aTrackEvents & TRACK_EVENT_ENDED) {
       nsCOMPtr<nsIRunnable> runnable =
-        NewRunnableMethod<MediaStream*, TrackID>(
+        NewRunnableMethod<MediaStream*, TrackID, TrackID>(
           this, &OwnedStreamListener::DoNotifyTrackEnded,
-          aInputStream, aInputTrackID);
+          aInputStream, aInputTrackID, aID);
       aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
     }
   }
 
 private:
   // These fields may only be accessed on the main thread
   DOMMediaStream* mStream;
 };
@@ -409,16 +411,18 @@ DOMMediaStream::Destroy()
   if (mOwnedStream) {
     mOwnedStream->UnregisterUser();
     mOwnedStream = nullptr;
   }
   if (mInputStream) {
     mInputStream->UnregisterUser();
     mInputStream = nullptr;
   }
+  mRunOnTracksAvailable.Clear();
+  mTrackListeners.Clear();
 }
 
 JSObject*
 DOMMediaStream::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return dom::MediaStreamBinding::Wrap(aCx, this, aGivenProto);
 }
 
@@ -593,17 +597,21 @@ DOMMediaStream::RemoveTrack(MediaStreamT
     LOG(LogLevel::Debug, ("DOMMediaStream %p does not contain track %p", this, &aTrack));
     return;
   }
 
   // If the track comes from a TRACK_ANY input port (i.e., mOwnedPort), we need
   // to block it in the port. Doing this for a locked track is still OK as it
   // will first block the track, then destroy the port. Both cause the track to
   // end.
-  BlockPlaybackTrack(toRemove);
+  // If the track has already ended, it's input port might be gone, so in those
+  // cases blocking the underlying track should be avoided.
+  if (!aTrack.Ended()) {
+    BlockPlaybackTrack(toRemove);
+  }
 
   DebugOnly<bool> removed = mTracks.RemoveElement(toRemove);
   MOZ_ASSERT(removed);
 
   NotifyTrackRemoved(&aTrack);
 
   LOG(LogLevel::Debug, ("DOMMediaStream %p Removed track %p", this, &aTrack));
 }
@@ -770,39 +778,39 @@ bool
 DOMMediaStream::IsFinished()
 {
   return !mPlaybackStream || mPlaybackStream->IsFinished();
 }
 
 void
 DOMMediaStream::InitSourceStream(MediaStreamGraph* aGraph)
 {
-  InitInputStreamCommon(aGraph->CreateSourceStream(nullptr), aGraph);
+  InitInputStreamCommon(aGraph->CreateSourceStream(), aGraph);
   InitOwnedStreamCommon(aGraph);
   InitPlaybackStreamCommon(aGraph);
 }
 
 void
 DOMMediaStream::InitTrackUnionStream(MediaStreamGraph* aGraph)
 {
-  InitInputStreamCommon(aGraph->CreateTrackUnionStream(nullptr), aGraph);
+  InitInputStreamCommon(aGraph->CreateTrackUnionStream(), aGraph);
   InitOwnedStreamCommon(aGraph);
   InitPlaybackStreamCommon(aGraph);
 }
 
 void
 DOMMediaStream::InitAudioCaptureStream(nsIPrincipal* aPrincipal, MediaStreamGraph* aGraph)
 {
   const TrackID AUDIO_TRACK = 1;
 
   RefPtr<BasicUnstoppableTrackSource> audioCaptureSource =
     new BasicUnstoppableTrackSource(aPrincipal, MediaSourceEnum::AudioCapture);
 
   AudioCaptureStream* audioCaptureStream =
-    static_cast<AudioCaptureStream*>(aGraph->CreateAudioCaptureStream(this, AUDIO_TRACK));
+    static_cast<AudioCaptureStream*>(aGraph->CreateAudioCaptureStream(AUDIO_TRACK));
   InitInputStreamCommon(audioCaptureStream, aGraph);
   InitOwnedStreamCommon(aGraph);
   InitPlaybackStreamCommon(aGraph);
   CreateDOMTrack(AUDIO_TRACK, MediaSegment::AUDIO, audioCaptureSource);
   audioCaptureStream->Start();
 }
 
 void
@@ -815,34 +823,32 @@ DOMMediaStream::InitInputStreamCommon(Me
   mInputStream->RegisterUser();
 }
 
 void
 DOMMediaStream::InitOwnedStreamCommon(MediaStreamGraph* aGraph)
 {
   MOZ_ASSERT(!mPlaybackStream, "Owned stream must be initialized before playback stream");
 
-  // We pass null as the wrapper since it is only used to signal finished
-  // streams. This is only needed for the playback stream.
-  mOwnedStream = aGraph->CreateTrackUnionStream(nullptr);
+  mOwnedStream = aGraph->CreateTrackUnionStream();
   mOwnedStream->SetAutofinish(true);
   mOwnedStream->RegisterUser();
   if (mInputStream) {
     mOwnedPort = mOwnedStream->AllocateInputPort(mInputStream);
   }
 
   // Setup track listeners
   mOwnedListener = new OwnedStreamListener(this);
   mOwnedStream->AddListener(mOwnedListener);
 }
 
 void
 DOMMediaStream::InitPlaybackStreamCommon(MediaStreamGraph* aGraph)
 {
-  mPlaybackStream = aGraph->CreateTrackUnionStream(this);
+  mPlaybackStream = aGraph->CreateTrackUnionStream();
   mPlaybackStream->SetAutofinish(true);
   mPlaybackStream->RegisterUser();
   if (mOwnedStream) {
     mPlaybackPort = mPlaybackStream->AllocateInputPort(mOwnedStream);
   }
 
   mPlaybackListener = new PlaybackStreamListener(this);
   mPlaybackStream->AddListener(mPlaybackListener);
@@ -996,16 +1002,19 @@ DOMMediaStream::CreateDOMTrack(TrackID a
 
   mOwnedTracks.AppendElement(
     new TrackPort(mOwnedPort, track, TrackPort::InputPortOwnership::EXTERNAL));
 
   mTracks.AppendElement(
     new TrackPort(mPlaybackPort, track, TrackPort::InputPortOwnership::EXTERNAL));
 
   NotifyTrackAdded(track);
+
+  DispatchTrackEvent(NS_LITERAL_STRING("addtrack"), track);
+
   return track;
 }
 
 already_AddRefed<MediaStreamTrack>
 DOMMediaStream::CloneDOMTrack(MediaStreamTrack& aTrack,
                               TrackID aCloneTrackID)
 {
   MOZ_RELEASE_ASSERT(mOwnedStream);
@@ -1030,16 +1039,27 @@ DOMMediaStream::CloneDOMTrack(MediaStrea
     new TrackPort(inputPort, newTrack, TrackPort::InputPortOwnership::OWNED));
 
   mTracks.AppendElement(
     new TrackPort(mPlaybackPort, newTrack, TrackPort::InputPortOwnership::EXTERNAL));
 
   NotifyTrackAdded(newTrack);
 
   newTrack->SetEnabled(aTrack.Enabled());
+  newTrack->SetReadyState(aTrack.ReadyState());
+
+  if (aTrack.Ended()) {
+    // For extra suspenders, make sure that we don't forward data by mistake
+    // to the clone when the original has already ended.
+    // We only block END_EXISTING to allow any pending clones to end.
+    RefPtr<Pledge<bool, nsresult>> blockingPledge =
+      inputPort->BlockSourceTrackId(inputTrackID,
+                                    BlockingMode::END_EXISTING);
+    Unused << blockingPledge;
+  }
 
   return newTrack.forget();
 }
 
 static DOMMediaStream::TrackPort*
 FindTrackPortAmongTracks(const MediaStreamTrack& aTrack,
                          const nsTArray<RefPtr<DOMMediaStream::TrackPort>>& aTracks)
 {
@@ -1048,24 +1068,26 @@ FindTrackPortAmongTracks(const MediaStre
       return info;
     }
   }
   return nullptr;
 }
 
 MediaStreamTrack*
 DOMMediaStream::FindOwnedDOMTrack(MediaStream* aInputStream,
-                                  TrackID aInputTrackID) const
+                                  TrackID aInputTrackID,
+                                  TrackID aTrackID) const
 {
   MOZ_RELEASE_ASSERT(mOwnedStream);
 
   for (const RefPtr<TrackPort>& info : mOwnedTracks) {
     if (info->GetInputPort() &&
         info->GetInputPort()->GetSource() == aInputStream &&
-        info->GetTrack()->mInputTrackID == aInputTrackID) {
+        info->GetTrack()->mInputTrackID == aInputTrackID &&
+        (aTrackID == TRACK_ANY || info->GetTrack()->mTrackID == aTrackID)) {
       // This track is owned externally but in our playback stream.
       return info->GetTrack();
     }
   }
   return nullptr;
 }
 
 DOMMediaStream::TrackPort*
@@ -1104,34 +1126,16 @@ DOMMediaStream::FindPlaybackDOMTrack(Med
 
 DOMMediaStream::TrackPort*
 DOMMediaStream::FindPlaybackTrackPort(const MediaStreamTrack& aTrack) const
 {
   return FindTrackPortAmongTracks(aTrack, mTracks);
 }
 
 void
-DOMMediaStream::NotifyMediaStreamGraphShutdown()
-{
-  // No more tracks will ever be added, so just clear these callbacks now
-  // to prevent leaks.
-  mNotifiedOfMediaStreamGraphShutdown = true;
-  mRunOnTracksAvailable.Clear();
-  mTrackListeners.Clear();
-  mConsumersToKeepAlive.Clear();
-}
-
-void
-DOMMediaStream::NotifyStreamFinished()
-{
-  MOZ_ASSERT(IsFinished());
-  mConsumersToKeepAlive.Clear();
-}
-
-void
 DOMMediaStream::OnTracksAvailable(OnTracksAvailableCallback* aRunnable)
 {
   if (mNotifiedOfMediaStreamGraphShutdown) {
     // No more tracks will ever be added, so just delete the callback now.
     delete aRunnable;
     return;
   }
   mRunOnTracksAvailable.AppendElement(aRunnable);
@@ -1222,30 +1226,48 @@ DOMMediaStream::NotifyTrackRemoved(const
     mTrackListeners[i]->NotifyTrackRemoved(aTrack);
   }
 
   // Don't call RecomputePrincipal here as the track may still exist in the
   // playback stream in the MediaStreamGraph. It will instead be called when the
   // track has been confirmed removed by the graph. See BlockPlaybackTrack().
 }
 
+nsresult
+DOMMediaStream::DispatchTrackEvent(const nsAString& aName,
+                                   const RefPtr<MediaStreamTrack>& aTrack)
+{
+  MOZ_ASSERT(aName == NS_LITERAL_STRING("addtrack"),
+             "Only 'addtrack' is supported at this time");
+
+  MediaStreamTrackEventInit init;
+  init.mTrack = aTrack;
+
+  RefPtr<MediaStreamTrackEvent> event =
+    MediaStreamTrackEvent::Constructor(this, aName, init);
+
+  return DispatchTrustedEvent(event);
+}
+
 void
 DOMMediaStream::CreateAndAddPlaybackStreamListener(MediaStream* aStream)
 {
   MOZ_ASSERT(GetCameraStream(), "I'm a hack. Only DOMCameraControl may use me.");
   mPlaybackListener = new PlaybackStreamListener(this);
   aStream->AddListener(mPlaybackListener);
 }
 
 void
 DOMMediaStream::BlockPlaybackTrack(TrackPort* aTrack)
 {
   MOZ_ASSERT(aTrack);
   ++mTracksPendingRemoval;
-  RefPtr<Pledge<bool>> p = aTrack->BlockSourceTrackId(aTrack->GetTrack()->mTrackID);
+  RefPtr<Pledge<bool>> p =
+    aTrack->BlockSourceTrackId(aTrack->GetTrack()->mTrackID,
+                               BlockingMode::CREATION);
   RefPtr<DOMMediaStream> self = this;
   p->Then([self] (const bool& aIgnore) { self->NotifyPlaybackTrackBlocked(); },
           [] (const nsresult& aIgnore) { NS_ERROR("Could not remove track from MSG"); }
   );
 }
 
 void
 DOMMediaStream::NotifyPlaybackTrackBlocked()
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -29,16 +29,18 @@ class DOMHwMediaStream;
 class DOMLocalMediaStream;
 class DOMMediaStream;
 class MediaStream;
 class MediaInputPort;
 class MediaStreamDirectListener;
 class MediaStreamGraph;
 class ProcessedMediaStream;
 
+enum class BlockingMode;
+
 namespace dom {
 class AudioNode;
 class HTMLCanvasElement;
 class MediaStreamTrack;
 class MediaStreamTrackSource;
 class AudioStreamTrack;
 class VideoStreamTrack;
 class AudioTrack;
@@ -299,17 +301,18 @@ public:
     MediaInputPort* GetInputPort() const { return mInputPort; }
     MediaStreamTrack* GetTrack() const { return mTrack; }
 
     /**
      * Blocks aTrackId from going into mInputPort unless the port has been
      * destroyed. Returns a pledge that gets resolved when the MediaStreamGraph
      * has applied the block in the playback stream.
      */
-    already_AddRefed<media::Pledge<bool, nsresult>> BlockSourceTrackId(TrackID aTrackId);
+    already_AddRefed<media::Pledge<bool, nsresult>>
+    BlockSourceTrackId(TrackID aTrackId, BlockingMode aBlockingMode);
 
   private:
     RefPtr<MediaInputPort> mInputPort;
     RefPtr<MediaStreamTrack> mTrack;
 
     // Defines if we've been given ownership of the input port or if it's owned
     // externally. The owner is responsible for destroying the port.
     const InputPortOwnership mOwnership;
@@ -355,16 +358,18 @@ public:
   void GetTracks(nsTArray<RefPtr<MediaStreamTrack> >& aTracks) const;
   MediaStreamTrack* GetTrackById(const nsAString& aId) const;
   void AddTrack(MediaStreamTrack& aTrack);
   void RemoveTrack(MediaStreamTrack& aTrack);
 
   /** Identical to CloneInternal(TrackForwardingOption::EXPLICIT) */
   already_AddRefed<DOMMediaStream> Clone();
 
+  IMPL_EVENT_HANDLER(addtrack)
+
   // NON-WebIDL
 
   /**
    * Option to provide to CloneInternal() of which tracks should be forwarded
    * from the source stream (`this`) to the returned stream clone.
    *
    * CURRENT forwards the tracks currently in the source stream's track set.
    * ALL     forwards like EXPLICIT plus any and all future tracks originating
@@ -385,20 +390,27 @@ public:
 
   /**
    * Returns true if this DOMMediaStream owns aTrack.
    */
   bool OwnsTrack(const MediaStreamTrack& aTrack) const;
 
   /**
    * Returns the corresponding MediaStreamTrack if it's in our mOwnedStream.
-   * aInputTrackID should match the track's TrackID in its input stream.
+   * aInputTrackID should match the track's TrackID in its input stream,
+   * and aTrackID the TrackID in mOwnedStream.
+   *
+   * When aTrackID is not supplied or set to TRACK_ANY, we return the first
+   * MediaStreamTrack that matches the given input track. Note that there may
+   * be multiple MediaStreamTracks matching the same input track, but that they
+   * in that case all share the same MediaStreamTrackSource.
    */
   MediaStreamTrack* FindOwnedDOMTrack(MediaStream* aInputStream,
-                                      TrackID aInputTrackID) const;
+                                      TrackID aInputTrackID,
+                                      TrackID aTrackID = TRACK_ANY) const;
 
   /**
    * Returns the TrackPort connecting aTrack's input stream to mOwnedStream,
    * or nullptr if aTrack is not owned by this DOMMediaStream.
    */
   TrackPort* FindOwnedTrackPort(const MediaStreamTrack& aTrack) const;
 
   /**
@@ -461,27 +473,16 @@ public:
 
   /**
    * Remove an added PrincipalChangeObserver from this stream.
    *
    * Returns true if it was successfully removed.
    */
   bool RemovePrincipalChangeObserver(dom::PrincipalChangeObserver<DOMMediaStream>* aObserver);
 
-  /**
-   * Called when this stream's MediaStreamGraph has been shut down. Normally
-   * MSGs are only shut down when all streams have been removed, so this
-   * will only be called during a forced shutdown due to application exit.
-   */
-  void NotifyMediaStreamGraphShutdown();
-  /**
-   * Called when the main-thread state of the MediaStream goes to finished.
-   */
-  void NotifyStreamFinished();
-
   // Webrtc allows the remote side to name a stream whatever it wants, and we
   // need to surface this to content.
   void AssignId(const nsAString& aID) { mID = aID; }
 
   /**
    * Create a DOMMediaStream whose underlying input stream is a SourceMediaStream.
    */
   static already_AddRefed<DOMMediaStream> CreateSourceStreamAsInput(nsPIDOMWindowInner* aWindow,
@@ -508,17 +509,22 @@ public:
   {
     mLogicalStreamStartTime = aTime;
   }
 
   /**
    * Called for each track in our owned stream to indicate to JS that we
    * are carrying that track.
    *
-   * Creates a MediaStreamTrack, adds it to mTracks and returns it.
+   * Creates a MediaStreamTrack, adds it to mTracks, raises "addtrack" and
+   * returns it.
+   *
+   * Note that "addtrack" is raised synchronously and only has an effect if
+   * this MediaStream is already exposed to script. For spec compliance this is
+   * to be called from an async task.
    */
   MediaStreamTrack* CreateDOMTrack(TrackID aTrackID, MediaSegment::Type aType,
                                    MediaStreamTrackSource* aSource);
 
   /**
    * Creates a MediaStreamTrack cloned from aTrack, adds it to mTracks and
    * returns it.
    * aCloneTrackID is the TrackID the new track will get in mOwnedStream.
@@ -533,23 +539,21 @@ public:
   // available!
   // We only care about track additions, we'll fire the notification even if
   // some of the tracks have been removed.
   // Takes ownership of aCallback.
   void OnTracksAvailable(OnTracksAvailableCallback* aCallback);
 
   /**
    * Add an nsISupports object that this stream will keep alive as long as
-   * the stream is not finished.
+   * the stream itself is alive.
    */
   void AddConsumerToKeepAlive(nsISupports* aConsumer)
   {
-    if (!IsFinished() && !mNotifiedOfMediaStreamGraphShutdown) {
-      mConsumersToKeepAlive.AppendElement(aConsumer);
-    }
+    mConsumersToKeepAlive.AppendElement(aConsumer);
   }
 
   // Registers a track listener to this MediaStream, for listening to changes
   // to our track set. The caller must call UnregisterTrackListener before
   // being destroyed, so we don't hold on to a dead pointer. Main thread only.
   void RegisterTrackListener(TrackListener* aListener);
 
   // Unregisters a track listener from this MediaStream. The caller must call
@@ -589,16 +593,20 @@ protected:
   void NotifyTracksCreated();
 
   // Dispatches NotifyTrackAdded() to all registered track listeners.
   void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack);
 
   // Dispatches NotifyTrackRemoved() to all registered track listeners.
   void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack);
 
+  // Dispatches "addtrack" or "removetrack".
+  nsresult DispatchTrackEvent(const nsAString& aName,
+                              const RefPtr<MediaStreamTrack>& aTrack);
+
   class OwnedStreamListener;
   friend class OwnedStreamListener;
 
   class PlaybackStreamListener;
   friend class PlaybackStreamListener;
 
   // XXX Bug 1124630. Remove with CameraPreviewMediaStream.
   void CreateAndAddPlaybackStreamListener(MediaStream*);
@@ -659,28 +667,33 @@ protected:
   // Number of MediaStreamTracks that have been removed on main thread but are
   // waiting to be removed on MediaStreamGraph thread.
   size_t mTracksPendingRemoval;
 
   // The interface through which we can query the stream producer for
   // track sources.
   RefPtr<MediaStreamTrackSourceGetter> mTrackSourceGetter;
 
+  // Listener tracking changes to mOwnedStream. We use this to notify the
+  // MediaStreamTracks we own about state changes.
   RefPtr<OwnedStreamListener> mOwnedListener;
+
+  // Listener tracking changes to mPlaybackStream. This drives state changes
+  // in this DOMMediaStream and notifications to mTrackListeners.
   RefPtr<PlaybackStreamListener> mPlaybackListener;
 
   nsTArray<nsAutoPtr<OnTracksAvailableCallback> > mRunOnTracksAvailable;
 
   // Set to true after MediaStreamGraph has created tracks for mPlaybackStream.
   bool mTracksCreated;
 
   nsString mID;
 
-  // Keep these alive until the stream finishes
-  nsTArray<nsCOMPtr<nsISupports> > mConsumersToKeepAlive;
+  // Keep these alive while the stream is alive.
+  nsTArray<nsCOMPtr<nsISupports>> mConsumersToKeepAlive;
 
   bool mNotifiedOfMediaStreamGraphShutdown;
 
   // The track listeners subscribe to changes in this stream's track set.
   nsTArray<TrackListener*> mTrackListeners;
 
 private:
   void NotifyPrincipalChanged();
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -832,17 +832,17 @@ public:
     if (mAudioDevice &&
         mAudioDevice->GetMediaSource() == MediaSourceEnum::AudioCapture) {
       // It should be possible to pipe the capture stream to anything. CORS is
       // not a problem here, we got explicit user content.
       nsCOMPtr<nsIPrincipal> principal = window->GetExtantDoc()->NodePrincipal();
       domStream =
         DOMMediaStream::CreateAudioCaptureStreamAsInput(window, principal, msg);
 
-      stream = msg->CreateSourceStream(nullptr); // Placeholder
+      stream = msg->CreateSourceStream(); // Placeholder
       msg->RegisterCaptureStreamForWindow(
             mWindowID, domStream->GetInputStream()->AsProcessedStream());
       window->SetAudioCapture(true);
     } else {
       class LocalTrackSource : public MediaStreamTrackSource
       {
       public:
         LocalTrackSource(nsIPrincipal* aPrincipal,
--- a/dom/media/MediaRecorder.cpp
+++ b/dom/media/MediaRecorder.cpp
@@ -462,17 +462,17 @@ public:
 
   void Start()
   {
     LOG(LogLevel::Debug, ("Session.Start %p", this));
     MOZ_ASSERT(NS_IsMainThread());
 
     // Create a Track Union Stream
     MediaStreamGraph* gm = mRecorder->GetSourceMediaStream()->Graph();
-    mTrackUnionStream = gm->CreateTrackUnionStream(nullptr);
+    mTrackUnionStream = gm->CreateTrackUnionStream();
     MOZ_ASSERT(mTrackUnionStream, "CreateTrackUnionStream failed");
 
     mTrackUnionStream->SetAutofinish(true);
 
     DOMMediaStream* domStream = mRecorder->Stream();
     if (domStream) {
       // Get the available tracks from the DOMMediaStream.
       // The callback will report back tracks that we have to connect to
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -20,17 +20,16 @@
 #include "ImageContainer.h"
 #include "AudioCaptureStream.h"
 #include "AudioChannelService.h"
 #include "AudioNodeStream.h"
 #include "AudioNodeExternalInputStream.h"
 #include "mozilla/dom/AudioContextBinding.h"
 #include "mozilla/media/MediaUtils.h"
 #include <algorithm>
-#include "DOMMediaStream.h"
 #include "GeckoProfiler.h"
 #include "mozilla/unused.h"
 #include "mozilla/media/MediaUtils.h"
 #ifdef MOZ_WEBRTC
 #include "AudioOutputObserver.h"
 #endif
 #include "mtransport/runnable_utils.h"
 
@@ -1617,20 +1616,16 @@ MediaStreamGraphImpl::ApplyStreamUpdate(
 
   MediaStream* stream = aUpdate->mStream;
   if (!stream)
     return;
   stream->mMainThreadCurrentTime = aUpdate->mNextMainThreadCurrentTime;
   stream->mMainThreadFinished = aUpdate->mNextMainThreadFinished;
 
   if (stream->ShouldNotifyStreamFinished()) {
-    if (stream->mWrapper) {
-      stream->mWrapper->NotifyStreamFinished();
-    }
-
     stream->NotifyMainThreadListeners();
   }
 }
 
 void
 MediaStreamGraphImpl::ForceShutDown(ShutdownTicket* aShutdownTicket)
 {
   NS_ASSERTION(NS_IsMainThread(), "Must be called on main thread");
@@ -1695,20 +1690,23 @@ public:
     } else {
       // The graph is not empty.  We must be in a forced shutdown, or a
       // non-realtime graph that has finished processing.  Some later
       // AppendMessage will detect that the manager has been emptied, and
       // delete it.
       NS_ASSERTION(mGraph->mForceShutDown || !mGraph->mRealtime,
                    "Not in forced shutdown?");
       for (MediaStream* stream : mGraph->AllStreams()) {
-        DOMMediaStream* s = stream->GetWrapper();
-        if (s) {
-          s->NotifyMediaStreamGraphShutdown();
+        // Clean up all MediaSegments since we cannot release Images too
+        // late during shutdown.
+        if (SourceMediaStream* source = stream->AsSourceStream()) {
+          // Finishing a SourceStream prevents new data from being appended.
+          source->Finish();
         }
+        stream->GetStreamTracks().Clear();
       }
 
       mGraph->mLifecycleState =
         MediaStreamGraphImpl::LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION;
     }
     return NS_OK;
   }
 private:
@@ -1978,52 +1976,45 @@ MediaStreamGraphImpl::AppendMessage(Uniq
     }
     return;
   }
 
   mCurrentTaskMessageQueue.AppendElement(Move(aMessage));
   EnsureRunInStableState();
 }
 
-MediaStream::MediaStream(DOMMediaStream* aWrapper)
+MediaStream::MediaStream()
   : mTracksStartTime(0)
   , mStartBlocking(GRAPH_TIME_MAX)
   , mSuspendedCount(0)
   , mFinished(false)
   , mNotifiedFinished(false)
   , mNotifiedBlocked(false)
   , mHasCurrentData(false)
   , mNotifiedHasCurrentData(false)
-  , mWrapper(aWrapper)
   , mMainThreadCurrentTime(0)
   , mMainThreadFinished(false)
   , mFinishedNotificationSent(false)
   , mMainThreadDestroyed(false)
   , mNrOfMainThreadUsers(0)
   , mGraph(nullptr)
   , mAudioChannelType(dom::AudioChannel::Normal)
 {
   MOZ_COUNT_CTOR(MediaStream);
-  // aWrapper should not already be connected to a MediaStream! It needs
-  // to be hooked up to this stream, and since this stream is only just
-  // being created now, aWrapper must not be connected to anything.
-  NS_ASSERTION(!aWrapper || !aWrapper->GetPlaybackStream(),
-               "Wrapper already has another media stream hooked up to it!");
 }
 
 size_t
 MediaStream::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
 {
   size_t amount = 0;
 
   // Not owned:
   // - mGraph - Not reported here
   // - mConsumers - elements
   // Future:
-  // - mWrapper
   // - mVideoOutputs - elements
   // - mLastPlayedVideoFrame
   // - mListeners - elements
   // - mAudioOutputStream - elements
 
   amount += mTracks.SizeOfExcludingThis(aMallocSizeOf);
   amount += mAudioOutputs.ShallowSizeOfExcludingThis(aMallocSizeOf);
   amount += mVideoOutputs.ShallowSizeOfExcludingThis(aMallocSizeOf);
@@ -2160,17 +2151,16 @@ MediaStream::Destroy()
       mStream->RemoveAllListenersImpl();
       auto graph = mStream->GraphImpl();
       mStream->DestroyImpl();
       graph->RemoveStreamGraphThread(mStream);
     }
     void RunDuringShutdown() override
     { Run(); }
   };
-  mWrapper = nullptr;
   GraphImpl()->AppendMessage(MakeUnique<Message>(this));
   // Message::RunDuringShutdown may have removed this stream from the graph,
   // but our kungFuDeathGrip above will have kept this stream alive if
   // necessary.
   mMainThreadDestroyed = true;
 }
 
 void
@@ -2370,16 +2360,41 @@ MediaStream::Resume()
 }
 
 void
 MediaStream::AddListenerImpl(already_AddRefed<MediaStreamListener> aListener)
 {
   MediaStreamListener* listener = *mListeners.AppendElement() = aListener;
   listener->NotifyBlockingChanged(GraphImpl(),
     mNotifiedBlocked ? MediaStreamListener::BLOCKED : MediaStreamListener::UNBLOCKED);
+
+  for (StreamTracks::TrackIter it(mTracks); !it.IsEnded(); it.Next()) {
+    MediaStream* inputStream = nullptr;
+    TrackID inputTrackID = TRACK_INVALID;
+    if (ProcessedMediaStream* ps = AsProcessedStream()) {
+      // The only ProcessedMediaStream where we should have listeners is
+      // TrackUnionStream - it's what's used as owned stream in DOMMediaStream,
+      // the only main-thread exposed stream type.
+      // TrackUnionStream guarantees that each of its tracks has an input track.
+      // Other types do not implement GetInputStreamFor() and will return null.
+      inputStream = ps->GetInputStreamFor(it->GetID());
+      MOZ_ASSERT(inputStream);
+      inputTrackID = ps->GetInputTrackIDFor(it->GetID());
+      MOZ_ASSERT(IsTrackIDExplicit(inputTrackID));
+    }
+
+    uint32_t flags = MediaStreamListener::TRACK_EVENT_CREATED;
+    if (it->IsEnded()) {
+      flags |= MediaStreamListener::TRACK_EVENT_ENDED;
+    }
+    nsAutoPtr<MediaSegment> segment(it->GetSegment()->CreateEmptyClone());
+    listener->NotifyQueuedTrackChanges(Graph(), it->GetID(), it->GetEnd(),
+                                       flags, *segment,
+                                       inputStream, inputTrackID);
+  }
   if (mNotifiedFinished) {
     listener->NotifyEvent(GraphImpl(), MediaStreamListener::EVENT_FINISHED);
   }
   if (mNotifiedHasCurrentData) {
     listener->NotifyHasCurrentData(GraphImpl());
   }
 }
 
@@ -3142,57 +3157,60 @@ MediaInputPort::Graph()
 void
 MediaInputPort::SetGraphImpl(MediaStreamGraphImpl* aGraph)
 {
   MOZ_ASSERT(!mGraph || !aGraph, "Should only be set once");
   mGraph = aGraph;
 }
 
 void
-MediaInputPort::BlockSourceTrackIdImpl(TrackID aTrackId)
+MediaInputPort::BlockSourceTrackIdImpl(TrackID aTrackId, BlockingMode aBlockingMode)
 {
-  mBlockedTracks.AppendElement(aTrackId);
+  mBlockedTracks.AppendElement(Pair<TrackID, BlockingMode>(aTrackId, aBlockingMode));
 }
 
 already_AddRefed<Pledge<bool>>
-MediaInputPort::BlockSourceTrackId(TrackID aTrackId)
+MediaInputPort::BlockSourceTrackId(TrackID aTrackId, BlockingMode aBlockingMode)
 {
   class Message : public ControlMessage {
   public:
     explicit Message(MediaInputPort* aPort,
                      TrackID aTrackId,
+                     BlockingMode aBlockingMode,
                      already_AddRefed<nsIRunnable> aRunnable)
       : ControlMessage(aPort->GetDestination()),
-        mPort(aPort), mTrackId(aTrackId), mRunnable(aRunnable) {}
+        mPort(aPort), mTrackId(aTrackId), mBlockingMode(aBlockingMode),
+        mRunnable(aRunnable) {}
     void Run() override
     {
-      mPort->BlockSourceTrackIdImpl(mTrackId);
+      mPort->BlockSourceTrackIdImpl(mTrackId, mBlockingMode);
       if (mRunnable) {
         mStream->Graph()->DispatchToMainThreadAfterStreamStateUpdate(mRunnable.forget());
       }
     }
     void RunDuringShutdown() override
     {
       Run();
     }
     RefPtr<MediaInputPort> mPort;
     TrackID mTrackId;
+    BlockingMode mBlockingMode;
     nsCOMPtr<nsIRunnable> mRunnable;
   };
 
   MOZ_ASSERT(IsTrackIDExplicit(aTrackId),
              "Only explicit TrackID is allowed");
 
   RefPtr<Pledge<bool>> pledge = new Pledge<bool>();
   nsCOMPtr<nsIRunnable> runnable = NewRunnableFrom([pledge]() {
     MOZ_ASSERT(NS_IsMainThread());
     pledge->Resolve(true);
     return NS_OK;
   });
-  GraphImpl()->AppendMessage(MakeUnique<Message>(this, aTrackId, runnable.forget()));
+  GraphImpl()->AppendMessage(MakeUnique<Message>(this, aTrackId, aBlockingMode, runnable.forget()));
   return pledge.forget();
 }
 
 already_AddRefed<MediaInputPort>
 ProcessedMediaStream::AllocateInputPort(MediaStream* aStream, TrackID aTrackID,
                                         TrackID aDestTrackID,
                                         uint16_t aInputNumber, uint16_t aOutputNumber,
                                         nsTArray<TrackID>* aBlockedTracks)
@@ -3225,17 +3243,17 @@ ProcessedMediaStream::AllocateInputPort(
              "Only TRACK_ANY and explicit ID are allowed for destination track");
   MOZ_ASSERT(aTrackID != TRACK_ANY || aDestTrackID == TRACK_ANY,
              "Generic MediaInputPort cannot produce a single destination track");
   RefPtr<MediaInputPort> port =
     new MediaInputPort(aStream, aTrackID, this, aDestTrackID,
                        aInputNumber, aOutputNumber);
   if (aBlockedTracks) {
     for (TrackID trackID : *aBlockedTracks) {
-      port->BlockSourceTrackIdImpl(trackID);
+      port->BlockSourceTrackIdImpl(trackID, BlockingMode::CREATION);
     }
   }
   port->SetGraphImpl(GraphImpl());
   GraphImpl()->AppendMessage(MakeUnique<Message>(port));
   return port.forget();
 }
 
 void
@@ -3519,36 +3537,35 @@ MediaStreamGraphImpl::CollectReports(nsI
   }
 
 #undef REPORT
 
   return NS_OK;
 }
 
 SourceMediaStream*
-MediaStreamGraph::CreateSourceStream(DOMMediaStream* aWrapper)
+MediaStreamGraph::CreateSourceStream()
 {
-  SourceMediaStream* stream = new SourceMediaStream(aWrapper);
+  SourceMediaStream* stream = new SourceMediaStream();
   AddStream(stream);
   return stream;
 }
 
 ProcessedMediaStream*
-MediaStreamGraph::CreateTrackUnionStream(DOMMediaStream* aWrapper)
+MediaStreamGraph::CreateTrackUnionStream()
 {
-  TrackUnionStream* stream = new TrackUnionStream(aWrapper);
+  TrackUnionStream* stream = new TrackUnionStream();
   AddStream(stream);
   return stream;
 }
 
 ProcessedMediaStream*
-MediaStreamGraph::CreateAudioCaptureStream(DOMMediaStream* aWrapper,
-                                           TrackID aTrackId)
+MediaStreamGraph::CreateAudioCaptureStream(TrackID aTrackId)
 {
-  AudioCaptureStream* stream = new AudioCaptureStream(aWrapper, aTrackId);
+  AudioCaptureStream* stream = new AudioCaptureStream(aTrackId);
   AddStream(stream);
   return stream;
 }
 
 void
 MediaStreamGraph::AddStream(MediaStream* aStream)
 {
   NS_ADDREF(aStream);
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -18,17 +18,16 @@
 #include "nsIRunnable.h"
 #include "StreamTracks.h"
 #include "VideoFrameContainer.h"
 #include "VideoSegment.h"
 #include "MainThreadUtils.h"
 #include "nsAutoPtr.h"
 #include "nsAutoRef.h"
 #include <speex/speex_resampler.h>
-#include "DOMMediaStream.h"
 
 class nsIRunnable;
 
 template <>
 class nsAutoRefTraits<SpeexResamplerState> : public nsPointerRefTraits<SpeexResamplerState>
 {
   public:
   static void Release(SpeexResamplerState* aState) { speex_resampler_destroy(aState); }
@@ -74,17 +73,26 @@ namespace media {
  *
  * Media decoding, audio processing and media playback use thread-safe APIs to
  * the media graph to ensure they can continue while the main thread is blocked.
  *
  * When the graph is changed, we may need to throw out buffered data and
  * reprocess it. This is triggered automatically by the MediaStreamGraph.
  */
 
+class AudioNodeEngine;
+class AudioNodeExternalInputStream;
+class AudioNodeStream;
+class CameraPreviewMediaStream;
+class MediaInputPort;
+class MediaStream;
 class MediaStreamGraph;
+class MediaStreamGraphImpl;
+class ProcessedMediaStream;
+class SourceMediaStream;
 
 /**
  * This is a base class for media graph thread listener callbacks.
  * Override methods to be notified of audio or video data or changes in stream
  * state.
  *
  * This can be used by stream recorders or network connections that receive
  * stream input. It could also be used for debugging.
@@ -434,25 +442,16 @@ struct AudioNodeSizes
 {
   AudioNodeSizes() : mDomNode(0), mStream(0), mEngine(0), mNodeType() {}
   size_t mDomNode;
   size_t mStream;
   size_t mEngine;
   nsCString mNodeType;
 };
 
-class MediaStreamGraphImpl;
-class SourceMediaStream;
-class ProcessedMediaStream;
-class MediaInputPort;
-class AudioNodeEngine;
-class AudioNodeExternalInputStream;
-class AudioNodeStream;
-class CameraPreviewMediaStream;
-
 /**
  * Helper struct for binding a track listener to a specific TrackID.
  */
 template<typename Listener>
 struct TrackBound
 {
   RefPtr<Listener> mListener;
   TrackID mTrackID;
@@ -528,17 +527,17 @@ struct TrackBound
 #undef GetCurrentTime
 #endif
 
 class MediaStream : public mozilla::LinkedListElement<MediaStream>
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaStream)
 
-  explicit MediaStream(DOMMediaStream* aWrapper);
+  MediaStream();
 
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~MediaStream()
   {
     MOZ_COUNT_DTOR(MediaStream);
     NS_ASSERTION(mMainThreadDestroyed, "Should have been destroyed already");
     NS_ASSERTION(mMainThreadListeners.IsEmpty(),
@@ -793,22 +792,16 @@ public:
    * Find track by track id.
    */
   StreamTracks::Track* FindTrack(TrackID aID);
 
   StreamTracks::Track* EnsureTrack(TrackID aTrack);
 
   virtual void ApplyTrackDisabling(TrackID aTrackID, MediaSegment* aSegment, MediaSegment* aRawSegment = nullptr);
 
-  DOMMediaStream* GetWrapper()
-  {
-    NS_ASSERTION(NS_IsMainThread(), "Only use DOMMediaStream on main thread");
-    return mWrapper;
-  }
-
   // Return true if the main thread needs to observe updates from this stream.
   virtual bool MainThreadNeedsUpdates() const
   {
     return true;
   }
 
   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
@@ -933,18 +926,16 @@ protected:
    * unblock it until there's more data.
    */
   bool mHasCurrentData;
   /**
    * True if mHasCurrentData is true and we've notified listeners.
    */
   bool mNotifiedHasCurrentData;
 
-  // This state is only used on the main thread.
-  DOMMediaStream* mWrapper;
   // Main-thread views of state
   StreamTime mMainThreadCurrentTime;
   bool mMainThreadFinished;
   bool mFinishedNotificationSent;
   bool mMainThreadDestroyed;
   int mNrOfMainThreadUsers;
 
   // Our media stream graph.  null if destroyed on the graph thread.
@@ -957,18 +948,18 @@ protected:
  * This is a stream into which a decoder can write audio and video.
  *
  * Audio and video can be written on any thread, but you probably want to
  * always write from the same thread to avoid unexpected interleavings.
  */
 class SourceMediaStream : public MediaStream
 {
 public:
-  explicit SourceMediaStream(DOMMediaStream* aWrapper) :
-    MediaStream(aWrapper),
+  explicit SourceMediaStream() :
+    MediaStream(),
     mMutex("mozilla::media::SourceMediaStream"),
     mUpdateKnownTracksTime(0),
     mPullEnabled(false),
     mUpdateFinished(false),
     mNeedsMixing(false)
   {}
 
   SourceMediaStream* AsSourceStream() override { return this; }
@@ -1180,16 +1171,33 @@ protected:
   nsTArray<RefPtr<MediaStreamDirectListener> > mDirectListeners;
   nsTArray<TrackBound<MediaStreamTrackDirectListener>> mDirectTrackListeners;
   bool mPullEnabled;
   bool mUpdateFinished;
   bool mNeedsMixing;
 };
 
 /**
+ * The blocking mode decides how a track should be blocked in a MediaInputPort.
+ */
+enum class BlockingMode
+{
+  /**
+   * BlockingMode CREATION blocks the source track from being created
+   * in the destination. It'll end if it already exists.
+   */
+  CREATION,
+  /**
+   * BlockingMode END_EXISTING allows a track to be created in the destination
+   * but will end it before any data has been passed through.
+   */
+  END_EXISTING,
+};
+
+/**
  * Represents a connection between a ProcessedMediaStream and one of its
  * input streams.
  * We make these refcounted so that stream-related messages with MediaInputPort*
  * pointers can be sent to the main thread safely.
  *
  * A port can be locked to a specific track in the source stream, in which case
  * only this track will be forwarded to the destination stream. TRACK_ANY
  * can used to signal that all tracks shall be forwarded.
@@ -1260,26 +1268,49 @@ public:
   TrackID GetDestinationTrackId() { return mDestTrack; }
 
   /**
    * Block aTrackId in the source stream from being passed through the port.
    * Consumers will interpret this track as ended.
    * Returns a pledge that resolves on the main thread after the track block has
    * been applied by the MSG.
    */
-  already_AddRefed<media::Pledge<bool, nsresult>> BlockSourceTrackId(TrackID aTrackId);
+  already_AddRefed<media::Pledge<bool, nsresult>> BlockSourceTrackId(TrackID aTrackId,
+                                                                     BlockingMode aBlockingMode);
 private:
-  void BlockSourceTrackIdImpl(TrackID aTrackId);
+  void BlockSourceTrackIdImpl(TrackID aTrackId, BlockingMode aBlockingMode);
 
 public:
-  // Returns true if aTrackId has not been blocked and this port has not
-  // been locked to another track.
+  // Returns true if aTrackId has not been blocked for any reason and this port
+  // has not been locked to another track.
   bool PassTrackThrough(TrackID aTrackId) {
-    return !mBlockedTracks.Contains(aTrackId) &&
-           (mSourceTrack == TRACK_ANY || mSourceTrack == aTrackId);
+    bool blocked = false;
+    for (auto pair : mBlockedTracks) {
+      if (pair.first() == aTrackId &&
+          (pair.second() == BlockingMode::CREATION ||
+           pair.second() == BlockingMode::END_EXISTING)) {
+        blocked = true;
+        break;
+      }
+    }
+    return !blocked && (mSourceTrack == TRACK_ANY || mSourceTrack == aTrackId);
+  }
+
+  // Returns true if aTrackId has not been blocked for track creation and this
+  // port has not been locked to another track.
+  bool AllowCreationOf(TrackID aTrackId) {
+    bool blocked = false;
+    for (auto pair : mBlockedTracks) {
+      if (pair.first() == aTrackId &&
+          pair.second() == BlockingMode::CREATION) {
+        blocked = true;
+        break;
+      }
+    }
+    return !blocked && (mSourceTrack == TRACK_ANY || mSourceTrack == aTrackId);
   }
 
   uint16_t InputNumber() const { return mInputNumber; }
   uint16_t OutputNumber() const { return mOutputNumber; }
 
   // Call on graph manager thread
   struct InputInterval {
     GraphTime mStart;
@@ -1324,32 +1355,34 @@ private:
   MediaStream* mSource;
   TrackID mSourceTrack;
   ProcessedMediaStream* mDest;
   TrackID mDestTrack;
   // The input and output numbers are optional, and are currently only used by
   // Web Audio.
   const uint16_t mInputNumber;
   const uint16_t mOutputNumber;
-  nsTArray<TrackID> mBlockedTracks;
+
+  typedef Pair<TrackID, BlockingMode> BlockedTrack;
+  nsTArray<BlockedTrack> mBlockedTracks;
 
   // Our media stream graph
   MediaStreamGraphImpl* mGraph;
 };
 
 /**
  * This stream processes zero or more input streams in parallel to produce
  * its output. The details of how the output is produced are handled by
  * subclasses overriding the ProcessInput method.
  */
 class ProcessedMediaStream : public MediaStream
 {
 public:
-  explicit ProcessedMediaStream(DOMMediaStream* aWrapper)
-    : MediaStream(aWrapper), mAutofinish(false), mCycleMarker(0)
+  explicit ProcessedMediaStream()
+    : MediaStream(), mAutofinish(false), mCycleMarker(0)
   {}
 
   // Control API.
   /**
    * Allocates a new input port attached to source aStream.
    * This stream can be removed by calling MediaInputPort::Remove().
    *
    * The input port is tied to aTrackID in the source stream.
@@ -1400,16 +1433,18 @@ public:
   bool HasInputPort(MediaInputPort* aPort)
   {
     return mInputs.Contains(aPort);
   }
   uint32_t InputPortCount()
   {
     return mInputs.Length();
   }
+  virtual MediaStream* GetInputStreamFor(TrackID aTrackID) { return nullptr; }
+  virtual TrackID GetInputTrackIDFor(TrackID aTrackID) { return TRACK_NONE; }
   void DestroyImpl() override;
   /**
    * This gets called after we've computed the blocking states for all
    * streams (mBlocked is up to date up to mStateComputedTime).
    * Also, we've produced output for all streams up to this one. If this stream
    * is not in a cycle, then all its source streams have produced data.
    * Generate output from aFrom to aTo.
    * This will be called on streams that have finished. Most stream types should
@@ -1496,37 +1531,36 @@ public:
   }
   virtual void CloseAudioInput(AudioDataListener *aListener) {}
 
   // Control API.
   /**
    * Create a stream that a media decoder (or some other source of
    * media data, such as a camera) can write to.
    */
-  SourceMediaStream* CreateSourceStream(DOMMediaStream* aWrapper);
+  SourceMediaStream* CreateSourceStream();
   /**
    * Create a stream that will form the union of the tracks of its input
    * streams.
    * A TrackUnionStream contains all the tracks of all its input streams.
    * Adding a new input stream makes that stream's tracks immediately appear as new
    * tracks starting at the time the input stream was added.
    * Removing an input stream makes the output tracks corresponding to the
    * removed tracks immediately end.
    * For each added track, the track ID of the output track is the track ID
    * of the input track or one plus the maximum ID of all previously added
    * tracks, whichever is greater.
    * TODO at some point we will probably need to add API to select
    * particular tracks of each input stream.
    */
-  ProcessedMediaStream* CreateTrackUnionStream(DOMMediaStream* aWrapper);
+  ProcessedMediaStream* CreateTrackUnionStream();
   /**
    * Create a stream that will mix all its audio input.
    */
-  ProcessedMediaStream* CreateAudioCaptureStream(DOMMediaStream* aWrapper,
-                                                 TrackID aTrackId);
+  ProcessedMediaStream* CreateAudioCaptureStream(TrackID aTrackId);
 
   /**
    * Add a new stream to the graph.  Main thread.
    */
   void AddStream(MediaStream* aStream);
 
   /* From the main thread, ask the MSG to send back an event when the graph
    * thread is running, and audio is being processed. */
--- a/dom/media/MediaStreamGraphImpl.h
+++ b/dom/media/MediaStreamGraphImpl.h
@@ -12,16 +12,17 @@
 
 #include "mozilla/Monitor.h"
 #include "mozilla/TimeStamp.h"
 #include "nsIMemoryReporter.h"
 #include "nsIThread.h"
 #include "nsIRunnable.h"
 #include "nsIAsyncShutdown.h"
 #include "Latency.h"
+#include "mozilla/Services.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WeakPtr.h"
 #include "GraphDriver.h"
 #include "AudioMixer.h"
 
 namespace mozilla {
 
 template <typename T>
--- a/dom/media/MediaStreamTrack.cpp
+++ b/dom/media/MediaStreamTrack.cpp
@@ -110,17 +110,18 @@ protected:
 };
 
 MediaStreamTrack::MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID,
                                    TrackID aInputTrackID,
                                    MediaStreamTrackSource* aSource)
   : mOwningStream(aStream), mTrackID(aTrackID),
     mInputTrackID(aInputTrackID), mSource(aSource),
     mPrincipal(aSource->GetPrincipal()),
-    mEnded(false), mEnabled(true), mRemote(aSource->IsRemote()), mStopped(false)
+    mReadyState(MediaStreamTrackState::Live),
+    mEnabled(true), mRemote(aSource->IsRemote())
 {
 
   if (!gMediaStreamTrackLog) {
     gMediaStreamTrackLog = PR_NewLogModule("MediaStreamTrack");
   }
 
   GetSource().RegisterSink(this);
 
@@ -211,18 +212,18 @@ MediaStreamTrack::SetEnabled(bool aEnabl
   GetOwnedStream()->SetTrackEnabled(mTrackID, aEnabled);
 }
 
 void
 MediaStreamTrack::Stop()
 {
   LOG(LogLevel::Info, ("MediaStreamTrack %p Stop()", this));
 
-  if (mStopped) {
-    LOG(LogLevel::Warning, ("MediaStreamTrack %p Already stopped", this));
+  if (Ended()) {
+    LOG(LogLevel::Warning, ("MediaStreamTrack %p Already ended", this));
     return;
   }
 
   if (mRemote) {
     LOG(LogLevel::Warning, ("MediaStreamTrack %p is remote. Can't be stopped.", this));
     return;
   }
 
@@ -231,20 +232,20 @@ MediaStreamTrack::Stop()
     return;
   }
 
   mSource->UnregisterSink(this);
 
   MOZ_ASSERT(mOwningStream, "Every MediaStreamTrack needs an owning DOMMediaStream");
   DOMMediaStream::TrackPort* port = mOwningStream->FindOwnedTrackPort(*this);
   MOZ_ASSERT(port, "A MediaStreamTrack must exist in its owning DOMMediaStream");
-  RefPtr<Pledge<bool>> p = port->BlockSourceTrackId(mInputTrackID);
+  RefPtr<Pledge<bool>> p = port->BlockSourceTrackId(mInputTrackID, BlockingMode::CREATION);
   Unused << p;
 
-  mStopped = true;
+  mReadyState = MediaStreamTrackState::Ended;
 }
 
 already_AddRefed<Promise>
 MediaStreamTrack::ApplyConstraints(const MediaTrackConstraints& aConstraints,
                                    ErrorResult &aRv)
 {
   if (MOZ_LOG_TEST(gMediaStreamTrackLog, LogLevel::Info)) {
     nsString str;
@@ -345,16 +346,32 @@ MediaStreamTrack::Clone()
 
   MediaStreamGraph* graph = Graph();
   newStream->InitOwnedStreamCommon(graph);
   newStream->InitPlaybackStreamCommon(graph);
 
   return newStream->CloneDOMTrack(*this, mTrackID);
 }
 
+void
+MediaStreamTrack::NotifyEnded()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (Ended()) {
+    return;
+  }
+
+  LOG(LogLevel::Info, ("MediaStreamTrack %p ended", this));
+
+  mReadyState = MediaStreamTrackState::Ended;
+
+  DispatchTrustedEvent(NS_LITERAL_STRING("ended"));
+}
+
 DOMMediaStream*
 MediaStreamTrack::GetInputDOMStream()
 {
   MediaStreamTrack* originalTrack =
     mOriginalTrack ? mOriginalTrack.get() : this;
   MOZ_RELEASE_ASSERT(originalTrack->mOwningStream);
   return originalTrack->mOwningStream;
 }
--- a/dom/media/MediaStreamTrack.h
+++ b/dom/media/MediaStreamTrack.h
@@ -261,20 +261,39 @@ public:
   void GetId(nsAString& aID) const;
   void GetLabel(nsAString& aLabel) { GetSource().GetLabel(aLabel); }
   bool Enabled() { return mEnabled; }
   void SetEnabled(bool aEnabled);
   void Stop();
   already_AddRefed<Promise>
   ApplyConstraints(const dom::MediaTrackConstraints& aConstraints, ErrorResult &aRv);
   already_AddRefed<MediaStreamTrack> Clone();
+  MediaStreamTrackState ReadyState() { return mReadyState; }
 
-  bool Ended() const { return mEnded; }
-  // Notifications from the MediaStreamGraph
-  void NotifyEnded() { mEnded = true; }
+  IMPL_EVENT_HANDLER(ended)
+
+  /**
+   * Convenience (and legacy) method for when ready state is "ended".
+   */
+  bool Ended() const { return mReadyState == MediaStreamTrackState::Ended; }
+
+  /**
+   * Forces the ready state to a particular value, for instance when we're
+   * cloning an already ended track.
+   */
+  void SetReadyState(MediaStreamTrackState aState) { mReadyState = aState; }
+
+  /**
+   * Notified by the MediaStreamGraph, through our owning MediaStream on the
+   * main thread.
+   *
+   * Note that this sets the track to ended and raises the "ended" event
+   * synchronously.
+   */
+  void NotifyEnded();
 
   /**
    * Get this track's principal.
    */
   nsIPrincipal* GetPrincipal() const { return mPrincipal; }
 
   /**
    * Called by the PrincipalHandleListener when this track's PrincipalHandle changes on
@@ -393,18 +412,17 @@ protected:
   TrackID mTrackID;
   TrackID mInputTrackID;
   RefPtr<MediaStreamTrackSource> mSource;
   RefPtr<MediaStreamTrack> mOriginalTrack;
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsIPrincipal> mPendingPrincipal;
   RefPtr<PrincipalHandleListener> mPrincipalHandleListener;
   nsString mID;
-  bool mEnded;
+  MediaStreamTrackState mReadyState;
   bool mEnabled;
   const bool mRemote;
-  bool mStopped;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* MEDIASTREAMTRACK_H_ */
--- a/dom/media/StreamTracks.cpp
+++ b/dom/media/StreamTracks.cpp
@@ -98,20 +98,20 @@ StreamTracks::ForgetUpTo(StreamTime aTim
   const StreamTime minChunkSize = 2400;
   if (aTime < mForgottenTime + minChunkSize) {
     return;
   }
   mForgottenTime = aTime;
 
   for (uint32_t i = 0; i < mTracks.Length(); ++i) {
     Track* track = mTracks[i];
-    if (track->IsEnded() && track->GetEnd() <= aTime) {
-      mTracks.RemoveElementAt(i);
-      mTracksDirty = true;
-      --i;
-      continue;
-    }
     StreamTime forgetTo = std::min(track->GetEnd() - 1, aTime);
     track->ForgetUpTo(forgetTo);
   }
 }
 
+void
+StreamTracks::Clear()
+{
+  mTracks.Clear();
+}
+
 } // namespace mozilla
--- a/dom/media/StreamTracks.h
+++ b/dom/media/StreamTracks.h
@@ -298,16 +298,21 @@ public:
 
   /**
    * Forget stream data before aTime; they will no longer be needed.
    * Also can forget entire tracks that have ended at or before aTime.
    * Can't be used to forget beyond GetEnd().
    */
   void ForgetUpTo(StreamTime aTime);
   /**
+   * Clears out all Tracks and the data they are holding.
+   * MediaStreamGraph calls this during forced shutdown.
+   */
+  void Clear();
+  /**
    * Returns the latest time passed to ForgetUpTo.
    */
   StreamTime GetForgottenDuration()
   {
     return mForgottenTime;
   }
 
   bool GetAndResetTracksDirty()
--- a/dom/media/TrackUnionStream.cpp
+++ b/dom/media/TrackUnionStream.cpp
@@ -40,18 +40,18 @@ namespace mozilla {
 
 #ifdef STREAM_LOG
 #undef STREAM_LOG
 #endif
 
 LazyLogModule gTrackUnionStreamLog("TrackUnionStream");
 #define STREAM_LOG(type, msg) MOZ_LOG(gTrackUnionStreamLog, type, msg)
 
-TrackUnionStream::TrackUnionStream(DOMMediaStream* aWrapper) :
-  ProcessedMediaStream(aWrapper), mNextAvailableTrackID(1)
+TrackUnionStream::TrackUnionStream() :
+  ProcessedMediaStream(), mNextAvailableTrackID(1)
 {
 }
 
   void TrackUnionStream::RemoveInput(MediaInputPort* aPort)
   {
     STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p removing input %p", this, aPort));
     for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) {
       if (mTrackMap[i].mInputPort == aPort) {
@@ -102,17 +102,17 @@ TrackUnionStream::TrackUnionStream(DOMMe
             } else {
               CopyTrackData(tracks.get(), j, aFrom, aTo, &trackFinished);
             }
             mappedTracksFinished[j] = trackFinished;
             mappedTracksWithMatchingInputTracks[j] = true;
             break;
           }
         }
-        if (!found && mInputs[i]->PassTrackThrough(tracks->GetID())) {
+        if (!found && mInputs[i]->AllowCreationOf(tracks->GetID())) {
           bool trackFinished = false;
           trackAdded = true;
           uint32_t mapIndex = AddTrack(mInputs[i], tracks.get(), aFrom);
           CopyTrackData(tracks.get(), mapIndex, aFrom, aTo, &trackFinished);
           mappedTracksFinished.AppendElement(trackFinished);
           mappedTracksWithMatchingInputTracks.AppendElement(true);
         }
       }
@@ -369,16 +369,40 @@ TrackUnionStream::SetTrackEnabledImpl(Tr
           listener->IncreaseDisabled();
         }
       }
     }
   }
   MediaStream::SetTrackEnabledImpl(aTrackID, aEnabled);
 }
 
+MediaStream*
+TrackUnionStream::GetInputStreamFor(TrackID aTrackID)
+{
+  for (TrackMapEntry& entry : mTrackMap) {
+    if (entry.mOutputTrackID == aTrackID && entry.mInputPort) {
+      return entry.mInputPort->GetSource();
+    }
+  }
+
+  return nullptr;
+}
+
+TrackID
+TrackUnionStream::GetInputTrackIDFor(TrackID aTrackID)
+{
+  for (TrackMapEntry& entry : mTrackMap) {
+    if (entry.mOutputTrackID == aTrackID) {
+      return entry.mInputTrackID;
+    }
+  }
+
+  return TRACK_NONE;
+}
+
 void
 TrackUnionStream::AddDirectTrackListenerImpl(already_AddRefed<MediaStreamTrackDirectListener> aListener,
                                              TrackID aTrackID)
 {
   RefPtr<MediaStreamTrackDirectListener> listener = aListener;
 
   for (TrackMapEntry& entry : mTrackMap) {
     if (entry.mOutputTrackID == aTrackID) {
--- a/dom/media/TrackUnionStream.h
+++ b/dom/media/TrackUnionStream.h
@@ -12,23 +12,26 @@
 
 namespace mozilla {
 
 /**
  * See MediaStreamGraph::CreateTrackUnionStream.
  */
 class TrackUnionStream : public ProcessedMediaStream {
 public:
-  explicit TrackUnionStream(DOMMediaStream* aWrapper);
+  explicit TrackUnionStream();
 
   void RemoveInput(MediaInputPort* aPort) override;
   void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override;
 
   void SetTrackEnabledImpl(TrackID aTrackID, bool aEnabled) override;
 
+  MediaStream* GetInputStreamFor(TrackID aTrackID) override;
+  TrackID GetInputTrackIDFor(TrackID aTrackID) override;
+
 protected:
   // Only non-ended tracks are allowed to persist in this map.
   struct TrackMapEntry {
     // mEndOfConsumedInputTicks is the end of the input ticks that we've consumed.
     // 0 if we haven't consumed any yet.
     StreamTime mEndOfConsumedInputTicks;
     // mEndOfLastInputIntervalInInputStream is the timestamp for the end of the
     // previous interval which was unblocked for both the input and output
--- a/dom/media/mediasink/DecodedStream.cpp
+++ b/dom/media/mediasink/DecodedStream.cpp
@@ -157,17 +157,17 @@ DecodedStreamData::DecodedStreamData(Out
                                      PlaybackInfoInit&& aInit,
                                      MozPromiseHolder<GenericPromise>&& aPromise)
   : mAudioFramesWritten(0)
   , mNextVideoTime(aInit.mStartTime)
   , mNextAudioTime(aInit.mStartTime)
   , mHaveSentFinish(false)
   , mHaveSentFinishAudio(false)
   , mHaveSentFinishVideo(false)
-  , mStream(aOutputStreamManager->Graph()->CreateSourceStream(nullptr))
+  , mStream(aOutputStreamManager->Graph()->CreateSourceStream())
   // DecodedStreamGraphListener will resolve this promise.
   , mListener(new DecodedStreamGraphListener(mStream, Move(aPromise)))
   // mPlaying is initially true because MDSM won't start playback until playing
   // becomes true. This is consistent with the settings of AudioSink.
   , mPlaying(true)
   , mEOSVideoCompensation(false)
   , mOutputStreamManager(aOutputStreamManager)
 {
--- a/dom/media/tests/mochitest/head.js
+++ b/dom/media/tests/mochitest/head.js
@@ -437,18 +437,18 @@ function checkMediaStreamTrackCloneAgain
      "Track clone's kind should be same as the original's");
   is(clone.enabled, original.enabled,
      "Track clone's kind should be same as the original's");
 }
 
 /*** Utility methods */
 
 /** The dreadful setTimeout, use sparingly */
-function wait(time) {
-  return new Promise(r => setTimeout(r, time));
+function wait(time, message) {
+  return new Promise(r => setTimeout(() => r(message), time));
 }
 
 /** The even more dreadful setInterval, use even more sparingly */
 function waitUntil(func, time) {
   return new Promise(resolve => {
     var interval = setInterval(() => {
       if (func())  {
         clearInterval(interval);
@@ -478,18 +478,18 @@ var addFinallyToPromise = promise => {
     );
   }
   return promise;
 }
 
 /** Use event listener to call passed-in function on fire until it returns true */
 var listenUntil = (target, eventName, onFire) => {
   return new Promise(resolve => target.addEventListener(eventName,
-                                                        function callback() {
-    var result = onFire();
+                                                        function callback(event) {
+    var result = onFire(event);
     if (result) {
       target.removeEventListener(eventName, callback, false);
       resolve(result);
     }
   }, false));
 };
 
 /* Test that a function throws the right error */
@@ -586,16 +586,37 @@ function createOneShotEventWrapper(wrapp
   obj[onx] = e => {
     info(wrapper + ': "on' + event + '" event fired');
     e.wrapper = wrapper;
     wrapper[onx](e);
     wrapper[onx] = unexpected;
   };
 }
 
+/**
+ * Returns a promise that resolves when `target` has raised an event with the
+ * given name. Cancel the returned promise by passing in a `cancelPromise` and
+ * resolve it.
+ *
+ * @param {object} target
+ *        The target on which the event should occur.
+ * @param {string} name
+ *        The name of the event that should occur.
+ * @param {promise} cancelPromise
+ *        A promise that on resolving rejects the returned promise,
+ *        so we can avoid logging results after a test has finished.
+ */
+function haveEvent(target, name, cancelPromise) {
+  var listener;
+  var p = Promise.race([
+    (cancelPromise || new Promise()).then(e => Promise.reject(e)),
+    new Promise(resolve => target.addEventListener(name, listener = resolve))
+  ]);
+  return p.then(event => (target.removeEventListener(name, listener), event));
+};
 
 /**
  * This class executes a series of functions in a continuous sequence.
  * Promise-bearing functions are executed after the previous promise completes.
  *
  * @constructor
  * @param {object} framework
  *        A back reference to the framework which makes use of the class. It is
--- a/dom/media/tests/mochitest/mediaStreamPlayback.js
+++ b/dom/media/tests/mochitest/mediaStreamPlayback.js
@@ -50,27 +50,37 @@ MediaStreamPlayback.prototype = {
     var elem = this.mediaElement;
     var waitForEnded = () => new Promise(resolve => {
       elem.addEventListener('ended', function ended() {
         elem.removeEventListener('ended', ended);
         resolve();
       });
     });
 
-    // TODO (bug 910249) Also check that all the tracks are local.
-    this.mediaStream.getTracks().forEach(t => t.stop());
+    var noTrackEnded = Promise.all(this.mediaStream.getTracks().map(t => {
+      let onNextLoop = wait(0);
+      let p = Promise.race([
+        onNextLoop,
+        haveEvent(t, "ended", onNextLoop)
+          .then(() => Promise.reject("Unexpected ended event for track " + t.id),
+                () => Promise.resolve())
+      ]);
+      t.stop();
+      return p;
+    }));
 
     // XXX (bug 1208316) When we implement MediaStream.active, do not stop
     // the stream. We just do it now so the media element will raise 'ended'.
     if (!this.mediaStream.stop) {
       return;
     }
     this.mediaStream.stop();
     return timeout(waitForEnded(), ENDED_TIMEOUT_LENGTH, "ended event never fired")
-             .then(() => ok(true, "ended event successfully fired"));
+             .then(() => ok(true, "ended event successfully fired"))
+             .then(() => noTrackEnded);
   },
 
   /**
    * Starts media with a media stream, runs it until a canplaythrough and
    * timeupdate event fires, and stops the media.
    *
    * @param {Boolean} isResume specifies if this media element is being resumed
    *                           from a previous run
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -39,16 +39,17 @@ skip-if = toolkit == 'gonk' || buildapp 
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g emulator seems to be too slow (Bug 1016498 and 1008080)
 [test_dataChannel_noOffer.html]
 [test_enumerateDevices.html]
 skip-if = buildapp == 'mulet'
 [test_getUserMedia_audioCapture.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version == '18' # b2g emulator seems to be too slow (Bug 1016498 and 1008080), android(Bug 1189784, timeouts on 4.3 emulator)
 [test_getUserMedia_addTrackRemoveTrack.html]
 skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # debug-only failure
+[test_getUserMedia_addtrack_removetrack_events.html]
 [test_getUserMedia_basicAudio.html]
 skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # debug-only failure
 [test_getUserMedia_basicVideo.html]
 skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # debug-only failure
 [test_getUserMedia_basicVideo_playAfterLoadedmetadata.html]
 skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # debug-only failure
 [test_getUserMedia_basicScreenshare.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' # no screenshare on b2g/android # Bug 1141029 Mulet parity with B2G Desktop for TC
@@ -74,19 +75,22 @@ skip-if = toolkit == 'gonk' || buildapp 
 [test_getUserMedia_spinEventLoop.html]
 skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # copied from basicAudio
 [test_getUserMedia_stopAudioStream.html]
 [test_getUserMedia_stopAudioStreamWithFollowupAudio.html]
 [test_getUserMedia_stopVideoAudioStream.html]
 [test_getUserMedia_stopVideoAudioStreamWithFollowupVideoAudio.html]
 [test_getUserMedia_stopVideoStream.html]
 [test_getUserMedia_stopVideoStreamWithFollowupVideo.html]
+[test_getUserMedia_trackEnded.html]
 [test_getUserMedia_peerIdentity.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 1021776, too --ing slow on b2g)
 [test_peerConnection_addIceCandidate.html]
+[test_peerConnection_addtrack_removetrack_events.html]
+skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_basicAudio.html]
 skip-if = toolkit == 'gonk' # B2G emulator is too slow to handle a two-way audio call reliably
 [test_peerConnection_basicAudioNATSrflx.html]
 skip-if = toolkit == 'gonk' || toolkit == 'android' # B2G emulator is too slow to handle a two-way audio call reliably, websockets don't work on android (bug 1266217)
 [test_peerConnection_basicAudioNATRelay.html]
 skip-if = toolkit == 'gonk' || toolkit == 'android' # B2G emulator is too slow to handle a two-way audio call reliably, websockets don't work on android (bug 1266217)
 [test_peerConnection_basicAudioNATRelayTCP.html]
 skip-if = toolkit == 'gonk' || toolkit == 'android' # B2G emulator is too slow to handle a two-way audio call reliably, websockets don't work on android (bug 1266217)
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -129,23 +129,39 @@ function timerGuard(p, time, message) {
 PeerConnectionTest.prototype.closePC = function() {
   info("Closing peer connections");
 
   var closeIt = pc => {
     if (!pc || pc.signalingState === "closed") {
       return Promise.resolve();
     }
 
-    return new Promise(resolve => {
-      pc.onsignalingstatechange = e => {
-        is(e.target.signalingState, "closed", "signalingState is closed");
-        resolve();
-      };
-      pc.close();
-    });
+    var promise = Promise.all([
+      new Promise(resolve => {
+        pc.onsignalingstatechange = e => {
+          is(e.target.signalingState, "closed", "signalingState is closed");
+          resolve();
+        };
+      }),
+      Promise.all(pc._pc.getReceivers()
+        .filter(receiver => receiver.track.readyState == "live")
+        .map(receiver => {
+          info("Waiting for track " + receiver.track.id + " (" +
+               receiver.track.kind + ") to end.");
+          return haveEvent(receiver.track, "ended", wait(50000))
+            .then(event => {
+              is(event.target, receiver.track, "Event target should be the correct track");
+              info("ended fired for track " + receiver.track.id);
+            }, e => e ? Promise.reject(e)
+                      : ok(false, "ended never fired for track " +
+                                    receiver.track.id));
+        }))
+    ]);
+    pc.close();
+    return promise;
   };
 
   return timerGuard(Promise.all([
     closeIt(this.pcLocal),
     closeIt(this.pcRemote)
   ]), 60000, "failed to close peer connection");
 };
 
@@ -445,29 +461,32 @@ PeerConnectionTest.prototype.updateChain
 
 /**
  * Start running the tests as assigned to the command chain.
  */
 PeerConnectionTest.prototype.run = function() {
   /* We have to modify the chain here to allow tests which modify the default
    * test chain instantiating a PeerConnectionTest() */
   this.updateChainSteps();
+  var finished = () => {
+    if (window.SimpleTest) {
+      networkTestFinished();
+    } else {
+      finish();
+    }
+  };
   return this.chain.execute()
     .then(() => this.close())
-    .then(() => {
-      if (window.SimpleTest) {
-        networkTestFinished();
-      } else {
-        finish();
-      }
-    })
     .catch(e =>
-           ok(false, 'Error in test execution: ' + e +
-              ((typeof e.stack === 'string') ?
-               (' ' + e.stack.split('\n').join(' ... ')) : '')));
+      ok(false, 'Error in test execution: ' + e +
+         ((typeof e.stack === 'string') ?
+          (' ' + e.stack.split('\n').join(' ... ')) : '')))
+    .then(() => finished())
+    .catch(e =>
+      ok(false, "Error in finished()"));
 };
 
 /**
  * Routes ice candidates from one PCW to the other PCW
  */
 PeerConnectionTest.prototype.iceCandidateHandler = function(caller, candidate) {
   info("Received: " + JSON.stringify(candidate) + " from " + caller);
 
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_getUserMedia_addtrack_removetrack_events.html
@@ -0,0 +1,109 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+createHTML({
+  title: "MediaStream's 'addtrack' and 'removetrack' events shouldn't fire on manual operations",
+  bug: "1208328"
+});
+
+var spinEventLoop = () => new Promise(r => setTimeout(r, 0));
+
+var stream;
+var clone;
+var newStream;
+
+var addTrack = track => {
+  info("Adding track " + track.id);
+  stream.addTrack(track);
+};
+var removeTrack = track => {
+  info("Removing track " + track.id);
+  stream.removeTrack(track);
+};
+var stopTrack = track => {
+  if (track.readyState == "live") {
+    info("Stopping track " + track.id);
+  }
+  track.stop();
+};
+
+runTest(() => getUserMedia({audio: true, video: true})
+  .then(s => {
+    stream = s;
+    clone = s.clone();
+    stream.addEventListener("addtrack", function onAddtrack(event) {
+      ok(false, "addtrack fired unexpectedly for track " + event.track.id);
+    });
+    stream.addEventListener("removetrack", function onRemovetrack(event) {
+      ok(false, "removetrack fired unexpectedly for track " + event.track.id);
+    });
+
+    return getUserMedia({audio: true, video: true});
+  })
+  .then(s => {
+    newStream = s;
+
+    info("Stopping an original track");
+    stopTrack(stream.getTracks()[0]);
+
+    return spinEventLoop();
+  })
+  .then(() => {
+    info("Removing original tracks");
+    stream.getTracks().forEach(t => stream.removeTrack(t));
+
+    return spinEventLoop();
+  })
+  .then(() => {
+    info("Adding other gUM tracks");
+    newStream.getTracks().forEach(t => addTrack(t))
+
+    return spinEventLoop();
+  })
+  .then(() => {
+    info("Adding cloned tracks");
+    let clone = stream.clone();
+    clone.getTracks().forEach(t => addTrack(t));
+
+    return spinEventLoop();
+  })
+  .then(() => {
+    info("Removing a clone");
+    removeTrack(clone.getTracks()[0]);
+
+    return spinEventLoop();
+  })
+  .then(() => {
+    info("Stopping clones");
+    clone.getTracks().forEach(t => stopTrack(t));
+
+    return spinEventLoop();
+  })
+  .then(() => {
+    info("Stopping originals");
+    stream.getTracks().forEach(t => stopTrack(t));
+
+    return spinEventLoop();
+  })
+  .then(() => {
+    info("Removing remaining tracks");
+    stream.getTracks().forEach(t => removeTrack(t));
+
+    return spinEventLoop();
+  })
+  .then(() => {
+      // Test MediaStreamTrackEvent required args here.
+      mustThrowWith("MediaStreamTrackEvent without required args",
+                    "TypeError", () => new MediaStreamTrackEvent("addtrack", {}));
+  }));
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/tests/mochitest/test_getUserMedia_mediaStreamTrackClone.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_mediaStreamTrackClone.html
@@ -47,17 +47,20 @@
       var cloneStream = new MediaStream();
       cloneStream.addTrack(inceptionClone);
 
       // cloneStream is now essentially the same as stream.clone();
       checkMediaStreamCloneAgainstOriginal(cloneStream, stream);
 
       var test = createMediaElement('video', 'testClonePlayback');
       var playback = new MediaStreamPlayback(test, cloneStream);
-      return playback.playMediaWithMediaStreamTracksStop(false);
+      return playback.playMediaWithMediaStreamTracksStop(false)
+        .then(() => info("Testing that clones of ended tracks are ended"))
+        .then(() => cloneStream.clone().getTracks().forEach(t =>
+          is(t.readyState, "ended", "Track " + t.id + " should be ended")));
     })
     .then(() => getUserMedia({audio: true, video: true})).then(stream => {
       info("Test adding many track clones to the original stream");
 
       const LOOPS = 3;
       for (var i = 0; i < LOOPS; i++) {
         stream.getTracks().forEach(t => stream.addTrack(t.clone()));
       }
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_getUserMedia_trackEnded.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<iframe id="iframe" srcdoc="
+  <script type='application/javascript'>
+  document.gUM = (constraints, success, failure) =>
+    navigator.mediaDevices.getUserMedia(constraints).then(success, failure);
+  </script>">
+</iframe>
+<script type="application/javascript">
+  "use strict";
+
+  createHTML({
+    title: "getUserMedia MediaStreamTrack 'ended' event on navigating",
+    bug: "1208373",
+  });
+
+  runTest(() => {
+    let iframe = document.getElementById("iframe");
+    let stream;
+    // We're passing callbacks into a method in the iframe here, because
+    // a Promise created in the iframe is unusable after the iframe has
+    // navigated away (see bug 1269400 for details).
+    return new Promise((resolve, reject) =>
+        iframe.contentDocument.gUM({audio: true, video: true}, resolve, reject))
+      .then(s => {
+        // We're cloning a stream containing identical tracks (an original
+        // and its clone) to test that ended works both for originals
+        // clones when they're both owned by the same MediaStream.
+        // (Bug 1274221)
+        stream = new MediaStream([].concat(s.getTracks(), s.getTracks())
+                                   .map(t => t.clone())).clone();
+        var allTracksEnded = Promise.all(stream.getTracks().map(t => {
+          info("Set up ended handler for track " + t.id);
+          return haveEvent(t, "ended", wait(5000))
+            .then(event => {
+              info("ended handler invoked for track " + t.id);
+              is(event.target, t, "Target should be correct");
+            }, e => e ? Promise.reject(e)
+                      : ok(false, "ended event never raised for track " + t.id));
+        }));
+        stream.getTracks().forEach(t =>
+          is(t.readyState, "live",
+             "Non-ended track should have readyState 'live'"));
+        iframe.srcdoc = "";
+        info("iframe has been reset. Waiting for tracks to end.");
+        return allTracksEnded;
+      })
+      .then(() => stream.getTracks().forEach(t =>
+        is(t.readyState, "ended",
+           "Ended track should have readyState 'ended'")));
+  });
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_addtrack_removetrack_events.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+createHTML({
+  title: "MediaStream's 'addtrack' and 'removetrack' events with gUM",
+  bug: "1208328"
+});
+
+runNetworkTest(function (options) {
+  let test = new PeerConnectionTest(options);
+  let eventsPromise;
+  addRenegotiation(test.chain,
+    [
+      function PC_LOCAL_SWAP_VIDEO_TRACKS(test) {
+        return getUserMedia({video: true}).then(stream => {
+          let localStream = test.pcLocal._pc.getLocalStreams()[0];
+          let remoteStream = test.pcRemote._pc.getRemoteStreams()[0];
+
+          let newTrack = stream.getTracks()[0];
+
+          let videoSenderIndex =
+            test.pcLocal._pc.getSenders().findIndex(s => s.track.kind == "video");
+          isnot(videoSenderIndex, -1, "Should have video sender");
+
+          test.pcLocal.removeSender(videoSenderIndex);
+          test.pcLocal.attachLocalTrack(stream.getTracks()[0], localStream);
+
+          let onNextLoop = wait(0);
+          eventsPromise = haveEvent(remoteStream, "addtrack", wait(50000, "No addtrack event"))
+            .then(trackEvent => {
+              ok(trackEvent instanceof MediaStreamTrackEvent,
+                 "Expected event to be instance of MediaStreamTrackEvent");
+              is(trackEvent.type, "addtrack",
+                 "Expected addtrack event type");
+              is(trackEvent.track.id, newTrack.id, "Expected track in event");
+              is(trackEvent.track.readyState, "live",
+                 "added track should be live");
+            })
+            .then(() => haveEvent(remoteStream, "addtrack", onNextLoop)
+              .then(() => Promise.reject("Unexpected addtrack event for remote stream " + remoteStream.id),
+                    () => Promise.resolve())
+            );
+          remoteStream.addEventListener("removetrack",
+                                        function onRemovetrack(trackEvent) {
+            ok(false, "UA shouldn't raise 'removetrack' when receiving peer connection");
+          })
+        });
+      },
+    ],
+    [
+      function PC_REMOTE_CHECK_EVENTS(test) {
+        return eventsPromise;
+      },
+    ]
+  );
+
+  test.setMediaConstraints([{audio: true, video: true}], []);
+  test.run();
+});
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html
+++ b/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html
@@ -68,18 +68,18 @@ function startTest(media, token) {
       return test.chain.execute();
     });
   })
   // Handle both MediaErrors (with the `code` attribute) and other errors.
   .catch(e => ok(false, "Error (" + e + ")" +
                         (e.code ? " (code=" + e.code + ")" : "") +
                         " in test for " + media.name +
                         (e.stack ? ":\n" + e.stack : "")))
+  .then(() => test && test.close())
   .then(() => {
-    if (test) { test.close(); }
     removeNodeAndSource(video);
     manager.finished(token);
   })
   .catch(e => ok(false, "Error (" + e + ") during shutdown."));
 };
 
 </script>
 </pre>
--- a/dom/media/webaudio/AudioNodeStream.cpp
+++ b/dom/media/webaudio/AudioNodeStream.cpp
@@ -24,17 +24,17 @@ namespace mozilla {
  * for offline audio contexts.
  * Each chunk in the track is a single block of WEBAUDIO_BLOCK_SIZE samples.
  * Note: This must be a different value than MEDIA_STREAM_DEST_TRACK_ID
  */
 
 AudioNodeStream::AudioNodeStream(AudioNodeEngine* aEngine,
                                  Flags aFlags,
                                  TrackRate aSampleRate)
-  : ProcessedMediaStream(nullptr),
+  : ProcessedMediaStream(),
     mEngine(aEngine),
     mSampleRate(aSampleRate),
     mFlags(aFlags),
     mNumberOfInputChannels(2),
     mIsActive(aEngine->IsActive()),
     mMarkAsFinishedAfterThisBlock(false),
     mAudioParamStream(false),
     mPassThrough(false)
--- a/dom/media/webaudio/MediaStreamAudioSourceNode.cpp
+++ b/dom/media/webaudio/MediaStreamAudioSourceNode.cpp
@@ -13,20 +13,17 @@
 #include "mozilla/CORSMode.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamAudioSourceNode)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaStreamAudioSourceNode)
-  if (tmp->mInputStream) {
-    tmp->mInputStream->UnregisterTrackListener(tmp);
-  }
-  tmp->DetachFromTrack();
+  tmp->Destroy();
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputStream)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputTrack)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(AudioNode)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaStreamAudioSourceNode, AudioNode)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputStream)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputTrack)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
@@ -75,17 +72,30 @@ MediaStreamAudioSourceNode::Init(DOMMedi
   AudioNodeEngine* engine = new MediaStreamAudioSourceNodeEngine(this);
   mStream = AudioNodeExternalInputStream::Create(graph, engine);
   mInputStream->AddConsumerToKeepAlive(static_cast<nsIDOMEventTarget*>(this));
 
   mInputStream->RegisterTrackListener(this);
   AttachToFirstTrack(mInputStream);
 }
 
-MediaStreamAudioSourceNode::~MediaStreamAudioSourceNode() {}
+void
+MediaStreamAudioSourceNode::Destroy()
+{
+  if (mInputStream) {
+    mInputStream->UnregisterTrackListener(this);
+    mInputStream = nullptr;
+  }
+  DetachFromTrack();
+}
+
+MediaStreamAudioSourceNode::~MediaStreamAudioSourceNode()
+{
+  Destroy();
+}
 
 void
 MediaStreamAudioSourceNode::AttachToTrack(const RefPtr<MediaStreamTrack>& aTrack)
 {
   MOZ_ASSERT(!mInputTrack);
   if (!mStream) {
     return;
   }
@@ -118,18 +128,22 @@ MediaStreamAudioSourceNode::AttachToFirs
   aMediaStream->GetAudioTracks(tracks);
 
   for (const RefPtr<AudioStreamTrack>& track : tracks) {
     if (track->Ended()) {
       continue;
     }
 
     AttachToTrack(track);
+    MarkActive();
     return;
   }
+
+  // There was no track available. We'll allow the node to be garbage collected.
+  MarkInactive();
 }
 
 void
 MediaStreamAudioSourceNode::NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack)
 {
   if (mInputTrack) {
     return;
   }
--- a/dom/media/webaudio/MediaStreamAudioSourceNode.h
+++ b/dom/media/webaudio/MediaStreamAudioSourceNode.h
@@ -84,16 +84,17 @@ public:
   void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override;
 
   // From PrincipalChangeObserver<MediaStreamTrack>.
   void PrincipalChanged(MediaStreamTrack* aMediaStreamTrack) override;
 
 protected:
   explicit MediaStreamAudioSourceNode(AudioContext* aContext);
   void Init(DOMMediaStream* aMediaStream, ErrorResult& aRv);
+  void Destroy();
   virtual ~MediaStreamAudioSourceNode();
 
 private:
   RefPtr<MediaInputPort> mInputPort;
   RefPtr<DOMMediaStream> mInputStream;
 
   // On construction we set this to the first audio track of mInputStream.
   RefPtr<MediaStreamTrack> mInputTrack;
--- a/dom/media/webaudio/test/mochitest.ini
+++ b/dom/media/webaudio/test/mochitest.ini
@@ -140,16 +140,17 @@ tags=capturestream
 skip-if = toolkit == 'android' # bug 1145816
 [test_mediaElementAudioSourceNodeCrossOrigin.html]
 tags=capturestream
 skip-if = toolkit == 'android' # bug 1145816
 [test_mediaStreamAudioDestinationNode.html]
 [test_mediaStreamAudioSourceNode.html]
 [test_mediaStreamAudioSourceNodeCrossOrigin.html]
 tags=capturestream
+[test_mediaStreamAudioSourceNodeNoGC.html]
 [test_mediaStreamAudioSourceNodePassThrough.html]
 [test_mediaStreamAudioSourceNodeResampling.html]
 tags=capturestream
 [test_mixingRules.html]
 skip-if = toolkit == 'android' # bug 1091965
 [test_mozaudiochannel.html]
 # Android: bug 1061675; OSX 10.6: bug 1097721
 skip-if = (toolkit == 'gonk' && !debug) || (toolkit == 'android') || (os == 'mac' && os_version == '10.6')
new file mode 100644
--- /dev/null
+++ b/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeNoGC.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<meta charset="utf-8">
+<head>
+  <title>Test that MediaStreamAudioSourceNode and its input MediaStream stays alive while there are active tracks</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("gUM and WebAudio data is async to main thread. " +
+                               "We need a timeout to see that something does " +
+                               "NOT happen to data.");
+
+var context = new AudioContext();
+var analyser = context.createAnalyser();
+
+function wait(millis, resolveWithThis) {
+  return new Promise(resolve => setTimeout(() => resolve(resolveWithThis), millis));
+}
+
+function binIndexForFrequency(frequency) {
+  return 1 + Math.round(frequency * analyser.fftSize / context.sampleRate);
+}
+
+function waitForAudio(analysisFunction, cancelPromise) {
+  var data = new Uint8Array(analyser.frequencyBinCount);
+  var cancelled = false;
+  var cancelledMsg = "";
+  cancelPromise.then(msg => {
+    cancelled = true;
+    cancelledMsg = msg;
+  });
+  return new Promise((resolve, reject) => {
+    var loop = () => {
+      analyser.getByteFrequencyData(data);
+      if (cancelled) {
+        reject(new Error("waitForAudio cancelled: " + cancelledMsg));
+        return;
+      }
+      if (analysisFunction(data)) {
+        resolve();
+        return;
+      }
+      requestAnimationFrame(loop);
+    };
+    loop();
+  });
+}
+
+navigator.mediaDevices.getUserMedia({audio: true, fake: true})
+  .then(stream => {
+    stream.onended = () => ended = true;
+    let source = context.createMediaStreamSource(stream);
+    source.connect(analyser);
+    analyser.connect(context.destination);
+  })
+  .then(() => {
+    ok(true, "Waiting for audio to pass through the analyser")
+    return waitForAudio(arr => arr[binIndexForFrequency(1000)] > 200,
+                        wait(60000, "Timeout waiting for audio"));
+  })
+  .then(() => {
+    ok(true, "Audio was detected by the analyser. Forcing CC.");
+    SpecialPowers.forceCC();
+    SpecialPowers.forceGC();
+    SpecialPowers.forceCC();
+    SpecialPowers.forceGC();
+
+    info("Checking that GC didn't destroy the stream or source node");
+    return waitForAudio(arr => arr[binIndexForFrequency(1000)] < 50,
+                        wait(5000, "Timeout waiting for GC (timeout OK)"))
+                  .then(() => Promise.reject("Audio stopped unexpectedly"),
+                        () => Promise.resolve());
+  })
+  .then(() => {
+    ok(true, "Audio is still flowing");
+    SimpleTest.finish();
+  })
+  .catch(e => {
+    ok(false, "Error executing test: " + e + (e.stack ? "\n" + e.stack : ""));
+    SimpleTest.finish();
+  });
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/webrtc/MediaEngineDefault.cpp
+++ b/dom/media/webrtc/MediaEngineDefault.cpp
@@ -110,16 +110,17 @@ MediaEngineDefaultVideoSource::Allocate(
 
 nsresult
 MediaEngineDefaultVideoSource::Deallocate()
 {
   if (mState != kStopped && mState != kAllocated) {
     return NS_ERROR_FAILURE;
   }
   mState = kReleased;
+  mImage = nullptr;
   return NS_OK;
 }
 
 static void AllocateSolidColorFrame(layers::PlanarYCbCrData& aData,
                                     int aWidth, int aHeight,
                                     int aY, int aCb, int aCr)
 {
   MOZ_ASSERT(!(aWidth&1));
@@ -203,16 +204,17 @@ MediaEngineDefaultVideoSource::Stop(Sour
   aSource->EndTrack(aID);
   if (mHasFakeTracks) {
     for (int i = 0; i < kFakeVideoTrackCount; ++i) {
       aSource->EndTrack(kTrackCount + i);
     }
   }
 
   mState = kStopped;
+  mImage = nullptr;
   return NS_OK;
 }
 
 nsresult
 MediaEngineDefaultVideoSource::Restart(const dom::MediaTrackConstraints& aConstraints,
                                        const MediaEnginePrefs &aPrefs,
                                        const nsString& aDeviceId)
 {
--- a/dom/media/webspeech/synth/nsSpeechTask.cpp
+++ b/dom/media/webspeech/synth/nsSpeechTask.cpp
@@ -157,17 +157,17 @@ nsSpeechTask::~nsSpeechTask()
   }
 }
 
 void
 nsSpeechTask::InitDirectAudio()
 {
   mStream = MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER,
                                           AudioChannel::Normal)->
-    CreateSourceStream(nullptr);
+    CreateSourceStream();
   mIndirectAudio = false;
   mInited = true;
 }
 
 void
 nsSpeechTask::InitIndirectAudio()
 {
   mIndirectAudio = true;
--- a/dom/plugins/base/nsIPluginHost.idl
+++ b/dom/plugins/base/nsIPluginHost.idl
@@ -95,28 +95,16 @@ interface nsIPluginHost : nsISupports
    *
    * @mimeType The MIME type we're interested in.
    * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE.
    */
   ACString getPermissionStringForType(in AUTF8String mimeType,
                                       [optional] in uint32_t excludeFlags);
 
   /**
-   * Get the "permission string" for the plugin.  This is a string that can be
-   * passed to the permission manager to see whether the plugin is allowed to
-   * run, for example.  This will typically be based on the plugin's "nice name"
-   * and its blocklist state.
-   *
-   * @tag The tage we're interested in
-   * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE.
-   */
-  ACString getPermissionStringForTag(in nsIPluginTag tag,
-                                     [optional] in uint32_t excludeFlags);
-
-  /**
    * Get the nsIPluginTag for this MIME type. This method works with both
    * enabled and disabled/blocklisted plugins, but an enabled plugin will
    * always be returned if available.
    *
    * A fake plugin tag, if one exists and is available, will be returned in
    * preference to NPAPI plugin tags unless excluded by the excludeFlags.
    *
    * @mimeType The MIME type we're interested in.
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -1105,41 +1105,33 @@ NS_IMETHODIMP
 nsPluginHost::GetPermissionStringForType(const nsACString &aMimeType,
                                          uint32_t aExcludeFlags,
                                          nsACString &aPermissionString)
 {
   nsCOMPtr<nsIPluginTag> tag;
   nsresult rv = GetPluginTagForType(aMimeType, aExcludeFlags,
                                     getter_AddRefs(tag));
   NS_ENSURE_SUCCESS(rv, rv);
-  return GetPermissionStringForTag(tag, aExcludeFlags, aPermissionString);
-}
-
-NS_IMETHODIMP
-nsPluginHost::GetPermissionStringForTag(nsIPluginTag* aTag,
-                                        uint32_t aExcludeFlags,
-                                        nsACString &aPermissionString)
-{
-  NS_ENSURE_TRUE(aTag, NS_ERROR_FAILURE);
+  NS_ENSURE_TRUE(tag, NS_ERROR_FAILURE);
 
   aPermissionString.Truncate();
   uint32_t blocklistState;
-  nsresult rv = aTag->GetBlocklistState(&blocklistState);
+  rv = tag->GetBlocklistState(&blocklistState);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (blocklistState == nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE ||
       blocklistState == nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE) {
     aPermissionString.AssignLiteral("plugin-vulnerable:");
   }
   else {
     aPermissionString.AssignLiteral("plugin:");
   }
 
   nsCString niceName;
-  rv = aTag->GetNiceName(niceName);
+  rv = tag->GetNiceName(niceName);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(!niceName.IsEmpty(), NS_ERROR_FAILURE);
 
   aPermissionString.Append(niceName);
 
   return NS_OK;
 }
 
--- a/dom/plugins/base/nsPluginTags.cpp
+++ b/dom/plugins/base/nsPluginTags.cpp
@@ -318,17 +318,17 @@ nsPluginTag::nsPluginTag(uint32_t aId,
 {
 }
 
 nsPluginTag::~nsPluginTag()
 {
   NS_ASSERTION(!mNext, "Risk of exhausting the stack space, bug 486349");
 }
 
-NS_IMPL_ISUPPORTS(nsPluginTag, nsPluginTag,  nsIInternalPluginTag, nsIPluginTag)
+NS_IMPL_ISUPPORTS(nsPluginTag, nsIPluginTag, nsIInternalPluginTag)
 
 void nsPluginTag::InitMime(const char* const* aMimeTypes,
                            const char* const* aMimeDescriptions,
                            const char* const* aExtensions,
                            uint32_t aVariantCount)
 {
   if (!aMimeTypes) {
     return;
--- a/dom/plugins/base/nsPluginTags.h
+++ b/dom/plugins/base/nsPluginTags.h
@@ -25,19 +25,16 @@ struct FakePluginTagInit;
 } // namespace dom
 } // namespace mozilla
 
 // An interface representing plugin tags internally.
 #define NS_IINTERNALPLUGINTAG_IID \
 { 0xe8fdd227, 0x27da, 0x46ee,     \
   { 0xbe, 0xf3, 0x1a, 0xef, 0x5a, 0x8f, 0xc5, 0xb4 } }
 
-#define NS_PLUGINTAG_IID \
-  { 0xcce2e8b9, 0x9702, 0x4d4b, \
-   { 0xbe, 0xa4, 0x7c, 0x1e, 0x13, 0x1f, 0xaf, 0x78 } }
 class nsIInternalPluginTag : public nsIPluginTag
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IINTERNALPLUGINTAG_IID)
 
   nsIInternalPluginTag();
   nsIInternalPluginTag(const char* aName, const char* aDescription,
                        const char* aFileName, const char* aVersion);
@@ -88,18 +85,16 @@ protected:
 };
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIInternalPluginTag, NS_IINTERNALPLUGINTAG_IID)
 
 // A linked-list of plugin information that is used for instantiating plugins
 // and reflecting plugin information into JavaScript.
 class nsPluginTag final : public nsIInternalPluginTag
 {
 public:
-  NS_DECLARE_STATIC_IID_ACCESSOR(NS_PLUGINTAG_IID)
-
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPLUGINTAG
 
   // These must match the STATE_* values in nsIPluginTag.idl
   enum PluginState {
     ePluginState_Disabled = 0,
     ePluginState_Clicktoplay = 1,
     ePluginState_Enabled = 2,
@@ -192,17 +187,16 @@ private:
                 const char* const* aExtensions,
                 uint32_t aVariantCount);
   void InitSandboxLevel();
   nsresult EnsureMembersAreUTF8();
   void FixupVersion();
 
   static uint32_t sNextId;
 };
-NS_DEFINE_STATIC_IID_ACCESSOR(nsPluginTag, NS_PLUGINTAG_IID)
 
 // A class representing "fake" plugin tags; that is plugin tags not
 // corresponding to actual NPAPI plugins.  In practice these are all
 // JS-implemented plugins; maybe we want a better name for this class?
 class nsFakePluginTag : public nsIInternalPluginTag,
                         public nsIFakePluginTag
 {
 public:
--- 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/MediaStream.webidl
+++ b/dom/webidl/MediaStream.webidl
@@ -38,12 +38,12 @@ interface MediaStream : EventTarget {
     sequence<MediaStreamTrack> getTracks ();
     MediaStreamTrack?          getTrackById (DOMString trackId);
     void                       addTrack (MediaStreamTrack track);
     void                       removeTrack (MediaStreamTrack track);
     MediaStream                clone ();
     // readonly    attribute boolean      active;
     //             attribute EventHandler onactive;
     //             attribute EventHandler oninactive;
-    //             attribute EventHandler onaddtrack;
+                attribute EventHandler onaddtrack;
     //             attribute EventHandler onremovetrack;
     readonly attribute double currentTime;
 };
--- a/dom/webidl/MediaStreamTrack.webidl
+++ b/dom/webidl/MediaStreamTrack.webidl
@@ -58,29 +58,34 @@ dictionary MediaTrackConstraintSet {
     ConstrainBoolean mozNoiseSuppression;
     ConstrainBoolean mozAutoGainControl;
 };
 
 dictionary MediaTrackConstraints : MediaTrackConstraintSet {
     sequence<MediaTrackConstraintSet> advanced;
 };
 
+enum MediaStreamTrackState {
+    "live",
+    "ended"
+};
+
 [Exposed=Window]
 interface MediaStreamTrack : EventTarget {
     readonly    attribute DOMString             kind;
     readonly    attribute DOMString             id;
     readonly    attribute DOMString             label;
                 attribute boolean               enabled;
 //  readonly    attribute boolean               muted;
 //              attribute EventHandler          onmute;
 //              attribute EventHandler          onunmute;
 //  readonly    attribute boolean               _readonly;
 //  readonly    attribute boolean               remote;
-//  readonly    attribute MediaStreamTrackState readyState;
-//                attribute EventHandler          onended;
+    readonly    attribute MediaStreamTrackState readyState;
+                attribute EventHandler          onended;
     MediaStreamTrack       clone ();
     void                   stop ();
 //  MediaTrackCapabilities getCapabilities ();
 //  MediaTrackConstraints  getConstraints ();
 //  MediaTrackSettings     getSettings ();
 
     [Throws]
     Promise<void>          applyConstraints (optional MediaTrackConstraints constraints);
--- a/dom/webidl/MediaStreamTrackEvent.webidl
+++ b/dom/webidl/MediaStreamTrackEvent.webidl
@@ -3,20 +3,17 @@
  * 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
  * http://dev.w3.org/2011/webrtc/editor/webrtc.html#mediastreamevent
  */
 
 dictionary MediaStreamTrackEventInit : EventInit {
-  MediaStreamTrack? track = null;
-  RTCRtpReceiver? receiver = null;
-  MediaStream? stream = null;
+    required MediaStreamTrack track;
 };
 
-[Pref="media.peerconnection.enabled",
- Constructor(DOMString type, optional MediaStreamTrackEventInit eventInitDict)]
+[Exposed=Window,
+ Constructor (DOMString type, MediaStreamTrackEventInit eventInitDict)]
 interface MediaStreamTrackEvent : Event {
-  readonly attribute RTCRtpReceiver? receiver;
-  readonly attribute MediaStreamTrack? track;
-  readonly attribute MediaStream? stream;
+    [SameObject]
+    readonly        attribute MediaStreamTrack track;
 };
--- 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/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -751,16 +751,18 @@ JSContext*
 InitJSContextForWorker(WorkerPrivate* aWorkerPrivate, JSRuntime* aRuntime)
 {
   aWorkerPrivate->AssertIsOnWorkerThread();
   NS_ASSERTION(!aWorkerPrivate->GetJSContext(), "Already has a context!");
 
   JSSettings settings;
   aWorkerPrivate->CopyJSSettings(settings);
 
+  JSContext* workerCx = JS_GetContext(aRuntime);
+
   JS::RuntimeOptionsRef(aRuntime) = settings.runtimeOptions;
 
   JSSettings::JSGCSettingsArray& gcSettings = settings.gcSettings;
 
   // This is the real place where we set the max memory for the runtime.
   for (uint32_t index = 0; index < ArrayLength(gcSettings); index++) {
     const JSSettings::JSGCSetting& setting = gcSettings[index];
     if (setting.IsSet()) {
@@ -779,30 +781,29 @@ InitJSContextForWorker(WorkerPrivate* aW
 
   // Set up the asm.js cache callbacks
   static const JS::AsmJSCacheOps asmJSCacheOps = {
     AsmJSCacheOpenEntryForRead,
     asmjscache::CloseEntryForRead,
     AsmJSCacheOpenEntryForWrite,
     asmjscache::CloseEntryForWrite
   };
-  JS::SetAsmJSCacheOps(aRuntime, &asmJSCacheOps);
-
-  JSContext* workerCx = JS_GetContext(aRuntime);
+  JS::SetAsmJSCacheOps(workerCx, &asmJSCacheOps);
+
   if (!JS::InitSelfHostedCode(workerCx)) {
     NS_WARNING("Could not init self-hosted code!");
     return nullptr;
   }
 
   JS_SetInterruptCallback(aRuntime, InterruptCallback);
 
   js::SetCTypesActivityCallback(aRuntime, CTypesActivityCallback);
 
 #ifdef JS_GC_ZEAL
-  JS_SetGCZeal(aRuntime, settings.gcZeal, settings.gcZealFrequency);
+  JS_SetGCZeal(workerCx, settings.gcZeal, settings.gcZealFrequency);
 #endif
 
   return workerCx;
 }
 
 static bool
 PreserveWrapper(JSContext *cx, JSObject *obj)
 {
@@ -883,21 +884,23 @@ public:
        return rv;
      }
 
     JSRuntime* rt = Runtime();
     MOZ_ASSERT(rt);
 
     JS_SetRuntimePrivate(rt, new WorkerThreadRuntimePrivate(mWorkerPrivate));
 
+    JSContext* cx = JS_GetContext(rt);
+
     js::SetPreserveWrapperCallback(rt, PreserveWrapper);
     JS_InitDestroyPrincipalsCallback(rt, DestroyWorkerPrincipals);
-    JS_SetWrapObjectCallbacks(rt, &WrapObjectCallbacks);
+    JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks);
     if (mWorkerPrivate->IsDedicatedWorker()) {
-      JS_SetFutexCanWait(rt);
+      JS_SetFutexCanWait(cx);
     }
 
     return NS_OK;
   }
 
   virtual void
   PrepareForForgetSkippable() override
   {
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -1197,16 +1197,26 @@ private:
           bool evalAllowed = false;
           bool reportEvalViolations = false;
           rv = csp->GetAllowsEval(&reportEvalViolations, &evalAllowed);
           NS_ENSURE_SUCCESS(rv, rv);
 
           mWorkerPrivate->SetCSP(csp);
           mWorkerPrivate->SetEvalAllowed(evalAllowed);
           mWorkerPrivate->SetReportCSPViolations(reportEvalViolations);
+
+          // Set ReferrerPolicy, default value is set in GetReferrerPolicy
+          bool hasReferrerPolicy = false;
+          uint32_t rp = mozilla::net::RP_Default;
+          rv = csp->GetReferrerPolicy(&rp, &hasReferrerPolicy);
+          NS_ENSURE_SUCCESS(rv, rv);
+
+          if (hasReferrerPolicy) {
+            mWorkerPrivate->SetReferrerPolicy(static_cast<net::ReferrerPolicy>(rp));
+          }
         }
       }
       if (parent) {
         // XHR Params Allowed
         mWorkerPrivate->SetXHRParamsAllowed(parent->XHRParamsAllowed());
       }
     }
 
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -1867,16 +1867,17 @@ TimerThreadEventTarget::IsOnCurrentThrea
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS(TimerThreadEventTarget, nsIEventTarget)
 
 WorkerLoadInfo::WorkerLoadInfo()
   : mWindowID(UINT64_MAX)
   , mServiceWorkerID(0)
+  , mReferrerPolicy(net::RP_Default)
   , mFromWindow(false)
   , mEvalAllowed(false)
   , mReportCSPViolations(false)
   , mXHRParamsAllowed(false)
   , mPrincipalIsSystem(false)
   , mIsInPrivilegedApp(false)
   , mIsInCertifiedApp(false)
   , mStorageAllowed(false)
@@ -1926,16 +1927,17 @@ WorkerLoadInfo::StealFrom(WorkerLoadInfo
 
   MOZ_ASSERT(!mPrincipalInfo);
   mPrincipalInfo = aOther.mPrincipalInfo.forget();
 
   mDomain = aOther.mDomain;
   mServiceWorkerCacheName = aOther.mServiceWorkerCacheName;
   mWindowID = aOther.mWindowID;
   mServiceWorkerID = aOther.mServiceWorkerID;
+  mReferrerPolicy = aOther.mReferrerPolicy;
   mFromWindow = aOther.mFromWindow;
   mEvalAllowed = aOther.mEvalAllowed;
   mReportCSPViolations = aOther.mReportCSPViolations;
   mXHRParamsAllowed = aOther.mXHRParamsAllowed;
   mPrincipalIsSystem = aOther.mPrincipalIsSystem;
   mIsInPrivilegedApp = aOther.mIsInPrivilegedApp;
   mIsInCertifiedApp = aOther.mIsInCertifiedApp;
   mStorageAllowed = aOther.mStorageAllowed;
@@ -3561,16 +3563,26 @@ WorkerPrivateParent<Derived>::SetPrincip
      appStatus == nsIPrincipal::APP_STATUS_PRIVILEGED);
   mLoadInfo.mIsInCertifiedApp = (appStatus == nsIPrincipal::APP_STATUS_CERTIFIED);
 
   aPrincipal->GetCsp(getter_AddRefs(mLoadInfo.mCSP));
 
   if (mLoadInfo.mCSP) {
     mLoadInfo.mCSP->GetAllowsEval(&mLoadInfo.mReportCSPViolations,
                                   &mLoadInfo.mEvalAllowed);
+    // Set ReferrerPolicy
+    bool hasReferrerPolicy = false;
+    uint32_t rp = mozilla::net::RP_Default;
+
+    nsresult rv = mLoadInfo.mCSP->GetReferrerPolicy(&rp, &hasReferrerPolicy);
+    NS_ENSURE_SUCCESS_VOID(rv);
+
+    if (hasReferrerPolicy) {
+      mLoadInfo.mReferrerPolicy = static_cast<net::ReferrerPolicy>(rp);
+    }
   } else {
     mLoadInfo.mEvalAllowed = true;
     mLoadInfo.mReportCSPViolations = false;
   }
 
   mLoadInfo.mLoadGroup = aLoadGroup;
 
   mLoadInfo.mPrincipalInfo = new PrincipalInfo();
@@ -6361,17 +6373,17 @@ WorkerPrivate::UpdateJSWorkerMemoryParam
 
 #ifdef JS_GC_ZEAL
 void
 WorkerPrivate::UpdateGCZealInternal(JSContext* aCx, uint8_t aGCZeal,
                                     uint32_t aFrequency)
 {
   AssertIsOnWorkerThread();
 
-  JS_SetGCZeal(JS_GetRuntime(aCx), aGCZeal, aFrequency);
+  JS_SetGCZeal(aCx, aGCZeal, aFrequency);
 
   for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
     mChildWorkers[index]->UpdateGCZeal(aGCZeal, aFrequency);
   }
 }
 #endif
 
 void
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -646,16 +646,28 @@ public:
 
   void
   SetCSP(nsIContentSecurityPolicy* aCSP)
   {
     AssertIsOnMainThread();
     mLoadInfo.mCSP = aCSP;
   }
 
+  net::ReferrerPolicy
+  GetReferrerPolicy() const
+  {
+    return mLoadInfo.mReferrerPolicy;
+  }
+
+  void
+  SetReferrerPolicy(net::ReferrerPolicy aReferrerPolicy)
+  {
+    mLoadInfo.mReferrerPolicy = aReferrerPolicy;
+  }
+
   bool
   IsEvalAllowed() const
   {
     return mLoadInfo.mEvalAllowed;
   }
 
   void
   SetEvalAllowed(bool aEvalAllowed)
--- a/dom/workers/Workers.h
+++ b/dom/workers/Workers.h
@@ -16,16 +16,17 @@
 #include "nsDebug.h"
 #include "nsString.h"
 #include "nsTArray.h"
 
 #include "nsILoadContext.h"
 #include "nsIWeakReferenceUtils.h"
 #include "nsIInterfaceRequestor.h"
 #include "mozilla/dom/ChannelInfo.h"
+#include "mozilla/net/ReferrerPolicy.h"
 
 #define BEGIN_WORKERS_NAMESPACE \
   namespace mozilla { namespace dom { namespace workers {
 #define END_WORKERS_NAMESPACE \
   } /* namespace workers */ } /* namespace dom */ } /* namespace mozilla */
 #define USING_WORKERS_NAMESPACE \
   using namespace mozilla::dom::workers;
 
@@ -256,16 +257,17 @@ struct WorkerLoadInfo
 
   nsString mServiceWorkerCacheName;
 
   ChannelInfo mChannelInfo;
 
   uint64_t mWindowID;
   uint64_t mServiceWorkerID;
 
+  net::ReferrerPolicy mReferrerPolicy;
   bool mFromWindow;
   bool mEvalAllowed;
   bool mReportCSPViolations;
   bool mXHRParamsAllowed;
   bool mPrincipalIsSystem;
   bool mIsInPrivilegedApp;
   bool mIsInCertifiedApp;
   bool mStorageAllowed;
--- 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/gfx/ipc/GfxMessageUtils.h
+++ b/gfx/ipc/GfxMessageUtils.h
@@ -1171,18 +1171,18 @@ struct ParamTraits<mozilla::gfx::FilterD
 };
 
 typedef mozilla::layers::GeckoContentController::APZStateChange APZStateChange;
 
 template <>
 struct ParamTraits<APZStateChange>
   : public ContiguousEnumSerializer<
              APZStateChange,
-             APZStateChange::TransformBegin,
-             APZStateChange::APZStateChangeSentinel>
+             APZStateChange::eTransformBegin,
+             APZStateChange::eSentinel>
 {};
 
 template<>
 struct ParamTraits<mozilla::layers::EventRegionsOverride>
   : public BitFlagsEnumSerializer<
             mozilla::layers::EventRegionsOverride,
             mozilla::layers::EventRegionsOverride::ALL_BITS>
 {};
--- a/gfx/layers/apz/public/GeckoContentController.h
+++ b/gfx/layers/apz/public/GeckoContentController.h
@@ -84,40 +84,43 @@ public:
    * APZ can then get the correct touch-sensitive region for each frame
    * directly from the layer.
    */
   virtual bool GetTouchSensitiveRegion(CSSRect* aOutRegion)
   {
     return false;
   }
 
-  enum APZStateChange {
+  enum class APZStateChange {
     /**
      * APZ started modifying the view (including panning, zooming, and fling).
      */
-    TransformBegin,
+    eTransformBegin,
     /**
      * APZ finished modifying the view.
      */
-    TransformEnd,
+    eTransformEnd,
     /**
      * APZ started a touch.
      * |aArg| is 1 if touch can be a pan, 0 otherwise.
      */
-    StartTouch,
+    eStartTouch,
     /**
      * APZ started a pan.
      */
-    StartPanning,
+    eStartPanning,
     /**
      * APZ finished processing a touch.
      * |aArg| is 1 if touch was a click, 0 otherwise.
      */
-    EndTouch,
-    APZStateChangeSentinel
+    eEndTouch,
+
+    // Sentinel value for IPC, this must be the last item in the enum and
+    // should not be used as an actual message value.
+    eSentinel
   };
   /**
    * General notices of APZ state changes for consumers.
    * |aGuid| identifies the APZC originating the state change.
    * |aChange| identifies the type of state change
    * |aArg| is used by some state changes to pass extra information (see
    *        the documentation for each state change above)
    */
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -1088,17 +1088,17 @@ nsEventStatus AsyncPanZoomController::On
     case PAN_MOMENTUM:
       CurrentTouchBlock()->GetOverscrollHandoffChain()->CancelAnimations(ExcludeOverscroll);
       MOZ_FALLTHROUGH;
     case NOTHING: {
       mX.StartTouch(point.x, aEvent.mTime);
       mY.StartTouch(point.y, aEvent.mTime);
       if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
         controller->NotifyAPZStateChange(
-            GetGuid(), APZStateChange::StartTouch,
+            GetGuid(), APZStateChange::eStartTouch,
             CurrentTouchBlock()->GetOverscrollHandoffChain()->CanBePanned(this));
       }
       SetState(TOUCHING);
       break;
     }
     case TOUCHING:
     case PANNING:
     case PANNING_LOCKED_X:
@@ -2045,17 +2045,17 @@ nsEventStatus AsyncPanZoomController::Ge
     }
   }
   return nsEventStatus_eIgnore;
 }
 
 void AsyncPanZoomController::OnTouchEndOrCancel() {
   if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
     controller->NotifyAPZStateChange(
-        GetGuid(), APZStateChange::EndTouch, CurrentTouchBlock()->SingleTapOccurred());
+        GetGuid(), APZStateChange::eEndTouch, CurrentTouchBlock()->SingleTapOccurred());
   }
 }
 
 nsEventStatus AsyncPanZoomController::OnSingleTapUp(const TapGestureInput& aEvent) {
   APZC_LOG("%p got a single-tap-up in state %d\n", this, mState);
   // If mZoomConstraints.mAllowDoubleTapZoom is true we wait for a call to OnSingleTapConfirmed before
   // sending event to content
   if (!(mZoomConstraints.mAllowDoubleTapZoom && CurrentTouchBlock()->TouchActionAllowsDoubleTapZoom())) {
@@ -2268,17 +2268,17 @@ nsEventStatus AsyncPanZoomController::St
       SetState(PANNING);
     } else {
       HandlePanning(angle);
     }
   }
 
   if (IsInPanningState()) {
     if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
-      controller->NotifyAPZStateChange(GetGuid(), APZStateChange::StartPanning);
+      controller->NotifyAPZStateChange(GetGuid(), APZStateChange::eStartPanning);
     }
     return nsEventStatus_eConsumeNoDefault;
   }
   // Don't consume an event that didn't trigger a panning.
   return nsEventStatus_eIgnore;
 }
 
 void AsyncPanZoomController::UpdateWithTouchAtDevicePoint(const MultiTouchInput& aEvent) {
@@ -3644,27 +3644,27 @@ void AsyncPanZoomController::DispatchSta
     if (mNotificationBlockers > 0) {
       return;
     }
   }
 
   if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
     if (!IsTransformingState(aOldState) && IsTransformingState(aNewState)) {
       controller->NotifyAPZStateChange(
-          GetGuid(), APZStateChange::TransformBegin);
+          GetGuid(), APZStateChange::eTransformBegin);
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
       // Let the compositor know about scroll state changes so it can manage
       // windowed plugins.
       if (mCompositorBridgeParent) {
         mCompositorBridgeParent->ScheduleHideAllPluginWindows();
       }
 #endif
     } else if (IsTransformingState(aOldState) && !IsTransformingState(aNewState)) {
       controller->NotifyAPZStateChange(
-          GetGuid(), APZStateChange::TransformEnd);
+          GetGuid(), APZStateChange::eTransformEnd);
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
       if (mCompositorBridgeParent) {
         mCompositorBridgeParent->ScheduleShowAllPluginWindows();
       }
 #endif
     }
   }
 }
--- a/gfx/layers/apz/test/gtest/TestBasic.cpp
+++ b/gfx/layers/apz/test/gtest/TestBasic.cpp
@@ -201,27 +201,27 @@ TEST_F(APZCBasicTester, PanningTransform
   // go into overscroll, and finally snap-back to recover from overscroll.
   // Again, ensure we only get one set of state change notifications for
   // this entire procedure.
 
   MockFunction<void(std::string checkPointName)> check;
   {
     InSequence s;
     EXPECT_CALL(check, Call("Simple pan"));
-    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartTouch,_)).Times(1);
-    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformBegin,_)).Times(1);
-    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartPanning,_)).Times(1);
-    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::EndTouch,_)).Times(1);
-    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformEnd,_)).Times(1);
+    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::eStartTouch,_)).Times(1);
+    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::eTransformBegin,_)).Times(1);
+    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::eStartPanning,_)).Times(1);
+    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::eEndTouch,_)).Times(1);
+    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::eTransformEnd,_)).Times(1);
     EXPECT_CALL(check, Call("Complex pan"));
-    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartTouch,_)).Times(1);
-    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformBegin,_)).Times(1);
-    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartPanning,_)).Times(1);
-    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::EndTouch,_)).Times(1);
-    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformEnd,_)).Times(1);
+    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::eStartTouch,_)).Times(1);
+    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::eTransformBegin,_)).Times(1);
+    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::eStartPanning,_)).Times(1);
+    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::eEndTouch,_)).Times(1);
+    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::eTransformEnd,_)).Times(1);
     EXPECT_CALL(check, Call("Done"));
   }
 
   check.Call("Simple pan");
   ApzcPanNoFling(apzc, 50, 25);
   check.Call("Complex pan");
   Pan(apzc, 25, 45);
   apzc->AdvanceAnimationsUntilEnd();
--- a/gfx/layers/apz/util/APZEventState.cpp
+++ b/gfx/layers/apz/util/APZEventState.cpp
@@ -371,17 +371,17 @@ APZEventState::ProcessMouseEvent(const W
 void
 APZEventState::ProcessAPZStateChange(const nsCOMPtr<nsIDocument>& aDocument,
                                      ViewID aViewId,
                                      APZStateChange aChange,
                                      int aArg)
 {
   switch (aChange)
   {
-  case APZStateChange::TransformBegin:
+  case APZStateChange::eTransformBegin:
   {
     nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aViewId);
     if (sf) {
       sf->SetTransformingByAPZ(true);
     }
     nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sf);
     if (scrollbarMediator) {
       scrollbarMediator->ScrollbarActivityStarted();
@@ -392,17 +392,17 @@ APZEventState::ProcessAPZStateChange(con
       if (docshell && sf) {
         nsDocShell* nsdocshell = static_cast<nsDocShell*>(docshell.get());
         nsdocshell->NotifyAsyncPanZoomStarted();
       }
     }
     mActiveAPZTransforms++;
     break;
   }
-  case APZStateChange::TransformEnd:
+  case APZStateChange::eTransformEnd:
   {
     mActiveAPZTransforms--;
     nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aViewId);
     if (sf) {
       sf->SetTransformingByAPZ(false);
     }
     nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sf);
     if (scrollbarMediator) {
@@ -413,36 +413,37 @@ APZEventState::ProcessAPZStateChange(con
       nsCOMPtr<nsIDocShell> docshell(aDocument->GetDocShell());
       if (docshell && sf) {
         nsDocShell* nsdocshell = static_cast<nsDocShell*>(docshell.get());
         nsdocshell->NotifyAsyncPanZoomStopped();
       }
     }
     break;
   }
-  case APZStateChange::StartTouch:
+  case APZStateChange::eStartTouch:
   {
     mActiveElementManager->HandleTouchStart(aArg);
     break;
   }
-  case APZStateChange::StartPanning:
+  case APZStateChange::eStartPanning:
   {
     // The user started to pan, so we don't want anything to be :active.
     mActiveElementManager->ClearActivation();
     break;
   }
-  case APZStateChange::EndTouch:
+  case APZStateChange::eEndTouch:
   {
     mEndTouchIsClick = aArg;
     mActiveElementManager->HandleTouchEnd();
     break;
   }
-  default:
-    // APZStateChange has a 'sentinel' value, and the compiler complains
-    // if an enumerator is not handled and there is no 'default' case.
+  case APZStateChange::eSentinel:
+    // Should never happen, but we want this case branch to stop the compiler
+    // whining about unhandled values.
+    MOZ_ASSERT(false);
     break;
   }
 }
 
 void
 APZEventState::ProcessClusterHit()
 {
   // If we hit a cluster of links then we shouldn't activate any of them,
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -311,18 +311,17 @@ ClientTiledLayerBuffer::GetContentType(S
                                           gfxContentType::COLOR_ALPHA;
   SurfaceMode mode = mPaintedLayer->GetSurfaceMode();
 
   if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
 #if defined(MOZ_GFX_OPTIMIZE_MOBILE) || defined(MOZ_WIDGET_GONK)
     mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA;
 #else
     if (!mPaintedLayer->GetParent() ||
-        !mPaintedLayer->GetParent()->SupportsComponentAlphaChildren() ||
-        !gfxPrefs::TiledDrawTargetEnabled()) {
+        !mPaintedLayer->GetParent()->SupportsComponentAlphaChildren()) {
       mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA;
     } else {
       content = gfxContentType::COLOR;
     }
 #endif
   } else if (mode == SurfaceMode::SURFACE_OPAQUE) {
 #if defined(MOZ_GFX_OPTIMIZE_MOBILE) || defined(MOZ_WIDGET_GONK)
     if (IsLowPrecision()) {
@@ -771,60 +770,16 @@ ClientMultiTiledLayerBuffer::PaintThebes
   mCallback = aCallback;
   mCallbackData = aCallbackData;
   mWasLastPaintProgressive = aIsProgressive;
 
 #ifdef GFX_TILEDLAYER_PREF_WARNINGS
   long start = PR_IntervalNow();
 #endif
 
-  if (!gfxPrefs::TiledDrawTargetEnabled()) {
-    if (!aPaintRegion.IsEmpty()) {
-
-      RefPtr<gfxContext> ctxt;
-
-      const IntRect bounds = aPaintRegion.GetBounds();
-      {
-        PROFILER_LABEL("ClientMultiTiledLayerBuffer", "PaintThebesSingleBufferAlloc",
-          js::ProfileEntry::Category::GRAPHICS);
-
-        mSinglePaintDrawTarget =
-          gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
-            gfx::IntSize(ceilf(bounds.width * mResolution),
-                         ceilf(bounds.height * mResolution)),
-            gfxPlatform::GetPlatform()->Optimal2DFormatForContent(
-              GetContentType()));
-
-        if (!mSinglePaintDrawTarget || !mSinglePaintDrawTarget->IsValid()) {
-          return;
-        }
-
-        ctxt = gfxContext::CreateOrNull(mSinglePaintDrawTarget);
-        MOZ_ASSERT(ctxt); // already checked draw target above
-
-        mSinglePaintBufferOffset = nsIntPoint(bounds.x, bounds.y);
-      }
-      ctxt->NewPath();
-      ctxt->SetMatrix(
-        ctxt->CurrentMatrix().Scale(mResolution, mResolution).
-                              Translate(-bounds.x, -bounds.y));
-#ifdef GFX_TILEDLAYER_PREF_WARNINGS
-      if (PR_IntervalNow() - start > 3) {
-        printf_stderr("Slow alloc %i\n", PR_IntervalNow() - start);
-      }
-      start = PR_IntervalNow();
-#endif
-      PROFILER_LABEL("ClientMultiTiledLayerBuffer", "PaintThebesSingleBufferDraw",
-        js::ProfileEntry::Category::GRAPHICS);
-
-      mCallback(mPaintedLayer, ctxt, aPaintRegion, aDirtyRegion,
-                DrawRegionClip::NONE, nsIntRegion(), mCallbackData);
-    }
-  }
-
 #ifdef GFX_TILEDLAYER_PREF_WARNINGS
   if (PR_IntervalNow() - start > 30) {
     const IntRect bounds = aPaintRegion.GetBounds();
     printf_stderr("Time to draw %i: %i, %i, %i, %i\n", PR_IntervalNow() - start, bounds.x, bounds.y, bounds.width, bounds.height);
     if (aPaintRegion.IsComplex()) {
       printf_stderr("Complex region\n");
       for (auto iter = aPaintRegion.RectIter(); !iter.Done(); iter.Next()) {
         const IntRect& rect = iter.Get();
@@ -847,17 +802,16 @@ ClientMultiTiledLayerBuffer::PaintThebes
     const IntRect bounds = aPaintRegion.GetBounds();
     printf_stderr("Time to tile %i: %i, %i, %i, %i\n", PR_IntervalNow() - start, bounds.x, bounds.y, bounds.width, bounds.height);
   }
 #endif
 
   mLastPaintContentType = GetContentType(&mLastPaintSurfaceMode);
   mCallback = nullptr;
   mCallbackData = nullptr;
-  mSinglePaintDrawTarget = nullptr;
 }
 
 void PadDrawTargetOutFromRegion(RefPtr<DrawTarget> drawTarget, nsIntRegion &region)
 {
   struct LockedBits {
     uint8_t *data;
     IntSize size;
     int32_t stride;
@@ -1006,17 +960,17 @@ void ClientMultiTiledLayerBuffer::Update
       }
 
       TileClient& tile = mRetainedTiles[i];
       if (!ValidateTile(tile, GetTileOffset(tilePosition), tileDrawRegion)) {
         gfxCriticalError() << "ValidateTile failed";
       }
     }
 
-    if (gfxPrefs::TiledDrawTargetEnabled() && mMoz2DTiles.size() > 0) {
+    if (mMoz2DTiles.size() > 0) {
       gfx::TileSet tileset;
       for (size_t i = 0; i < mMoz2DTiles.size(); ++i) {
         mMoz2DTiles[i].mTileOrigin -= mTilingOrigin;
       }
       tileset.mTiles = &mMoz2DTiles[0];
       tileset.mTileCount = mMoz2DTiles.size();
       RefPtr<DrawTarget> drawTarget = gfx::Factory::CreateTiledDrawTarget(tileset);
       if (!drawTarget || !drawTarget->IsValid()) {
@@ -1104,19 +1058,16 @@ ClientMultiTiledLayerBuffer::ValidateTil
       gfxPlatform::GetPlatform()->Optimal2DFormatForContent(content),
       TextureFlags::DISALLOW_BIGIMAGE | TextureFlags::IMMEDIATE_UPLOAD));
   }
   aTile.SetCompositableClient(mCompositableClient);
 
   nsIntRegion offsetScaledDirtyRegion = aDirtyRegion.MovedBy(-aTileOrigin);
   offsetScaledDirtyRegion.ScaleRoundOut(mResolution, mResolution);
 
-  bool usingTiledDrawTarget = gfxPrefs::TiledDrawTargetEnabled();
-  MOZ_ASSERT(usingTiledDrawTarget || !!mSinglePaintDrawTarget);
-
   nsIntRegion extraPainted;
   RefPtr<TextureClient> backBufferOnWhite;
   RefPtr<TextureClient> backBuffer =
     aTile.GetBackBuffer(offsetScaledDirtyRegion,
                         content, mode,
                         extraPainted,
                         &backBufferOnWhite);
 
@@ -1125,130 +1076,61 @@ ClientMultiTiledLayerBuffer::ValidateTil
   extraPainted.MoveBy(aTileOrigin);
   extraPainted.And(extraPainted, mNewValidRegion);
   mPaintedRegion.Or(mPaintedRegion, extraPainted);
 
   if (!backBuffer) {
     return false;
   }
 
-  if (usingTiledDrawTarget) {
-    gfx::Tile moz2DTile;
-    RefPtr<DrawTarget> dt = backBuffer->BorrowDrawTarget();
-    RefPtr<DrawTarget> dtOnWhite;
-    if (backBufferOnWhite) {
-      dtOnWhite = backBufferOnWhite->BorrowDrawTarget();
-      moz2DTile.mDrawTarget = Factory::CreateDualDrawTarget(dt, dtOnWhite);
-    } else {
-      moz2DTile.mDrawTarget = dt;
-    }
-    moz2DTile.mTileOrigin = gfx::IntPoint(aTileOrigin.x, aTileOrigin.y);
-    if (!dt || (backBufferOnWhite && !dtOnWhite)) {
-      aTile.DiscardBuffers();
-      return false;
-    }
-
-    mMoz2DTiles.push_back(moz2DTile);
-    mTilingOrigin.x = std::min(mTilingOrigin.x, moz2DTile.mTileOrigin.x);
-    mTilingOrigin.y = std::min(mTilingOrigin.y, moz2DTile.mTileOrigin.y);
-
-    for (auto iter = aDirtyRegion.RectIter(); !iter.Done(); iter.Next()) {
-      const IntRect& dirtyRect = iter.Get();
-      gfx::Rect drawRect(dirtyRect.x - aTileOrigin.x,
-                         dirtyRect.y - aTileOrigin.y,
-                         dirtyRect.width,
-                         dirtyRect.height);
-      drawRect.Scale(mResolution);
-
-      // Mark the newly updated area as invalid in the front buffer
-      aTile.mInvalidFront.Or(aTile.mInvalidFront,
-        IntRect(NS_lroundf(drawRect.x), NS_lroundf(drawRect.y),
-                  drawRect.width, drawRect.height));
-
-      if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
-        dt->FillRect(drawRect, ColorPattern(Color(0.0, 0.0, 0.0, 1.0)));
-        dtOnWhite->FillRect(drawRect, ColorPattern(Color(1.0, 1.0, 1.0, 1.0)));
-      } else if (content == gfxContentType::COLOR_ALPHA) {
-        dt->ClearRect(drawRect);
-      }
-    }
-
-    // The new buffer is now validated, remove the dirty region from it.
-    aTile.mInvalidBack.SubOut(offsetScaledDirtyRegion);
-
-    aTile.Flip();
-
-    return true;
+  gfx::Tile moz2DTile;
+  RefPtr<DrawTarget> dt = backBuffer->BorrowDrawTarget();
+  RefPtr<DrawTarget> dtOnWhite;
+  if (backBufferOnWhite) {
+    dtOnWhite = backBufferOnWhite->BorrowDrawTarget();
+    moz2DTile.mDrawTarget = Factory::CreateDualDrawTarget(dt, dtOnWhite);
+  } else {
+    moz2DTile.mDrawTarget = dt;
+  }
+  moz2DTile.mTileOrigin = gfx::IntPoint(aTileOrigin.x, aTileOrigin.y);
+  if (!dt || (backBufferOnWhite && !dtOnWhite)) {
+    aTile.DiscardBuffers();
+    return false;
   }
 
-  // Single paint buffer case:
-
-  MOZ_ASSERT(!backBufferOnWhite, "Component alpha only supported with TiledDrawTarget");
+  mMoz2DTiles.push_back(moz2DTile);
+  mTilingOrigin.x = std::min(mTilingOrigin.x, moz2DTile.mTileOrigin.x);
+  mTilingOrigin.y = std::min(mTilingOrigin.y, moz2DTile.mTileOrigin.y);
 
-  // We must not keep a reference to the DrawTarget after it has been unlocked,
-  // make sure these are null'd before unlocking as destruction of the context
-  // may cause the target to be flushed.
-  RefPtr<DrawTarget> drawTarget = backBuffer->BorrowDrawTarget();
-  drawTarget->SetTransform(Matrix());
-
-  // XXX Perhaps we should just copy the bounding rectangle here?
-  RefPtr<gfx::SourceSurface> source = mSinglePaintDrawTarget->Snapshot();
   for (auto iter = aDirtyRegion.RectIter(); !iter.Done(); iter.Next()) {
     const IntRect& dirtyRect = iter.Get();
-#ifdef GFX_TILEDLAYER_PREF_WARNINGS
-    printf_stderr(" break into subdirtyRect %i, %i, %i, %i\n",
-                  dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height);
-#endif
     gfx::Rect drawRect(dirtyRect.x - aTileOrigin.x,
                        dirtyRect.y - aTileOrigin.y,
                        dirtyRect.width,
                        dirtyRect.height);
     drawRect.Scale(mResolution);
 
-    gfx::IntRect copyRect(NS_lroundf((dirtyRect.x - mSinglePaintBufferOffset.x) * mResolution),
-                          NS_lroundf((dirtyRect.y - mSinglePaintBufferOffset.y) * mResolution),
-                          drawRect.width,
-                          drawRect.height);
-    gfx::IntPoint copyTarget(NS_lroundf(drawRect.x), NS_lroundf(drawRect.y));
-    drawTarget->CopySurface(source, copyRect, copyTarget);
+    // Mark the newly updated area as invalid in the front buffer
+    aTile.mInvalidFront.Or(aTile.mInvalidFront,
+      IntRect(NS_lroundf(drawRect.x), NS_lroundf(drawRect.y),
+                drawRect.width, drawRect.height));
 
-    // Mark the newly updated area as invalid in the front buffer
-    aTile.mInvalidFront.Or(aTile.mInvalidFront, IntRect(copyTarget.x, copyTarget.y, copyRect.width, copyRect.height));
+    if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
+      dt->FillRect(drawRect, ColorPattern(Color(0.0, 0.0, 0.0, 1.0)));
+      dtOnWhite->FillRect(drawRect, ColorPattern(Color(1.0, 1.0, 1.0, 1.0)));
+    } else if (content == gfxContentType::COLOR_ALPHA) {
+      dt->ClearRect(drawRect);
+    }
   }
 
   // The new buffer is now validated, remove the dirty region from it.
   aTile.mInvalidBack.SubOut(offsetScaledDirtyRegion);
 
-#ifdef GFX_TILEDLAYER_DEBUG_OVERLAY
-  DrawDebugOverlay(drawTarget, aTileOrigin.x * mResolution,
-                   aTileOrigin.y * GetPresShellResolution(), GetTileLength(), GetTileLength());
-#endif
-
-  drawTarget = nullptr;
-
-  nsIntRegion tileRegion =
-    IntRect(aTileOrigin.x, aTileOrigin.y,
-              GetScaledTileSize().width, GetScaledTileSize().height);
-  // Intersect this area with the portion that's invalid.
-  tileRegion.SubOut(GetValidRegion());
-  tileRegion.SubOut(aDirtyRegion); // Has now been validated
-
-  backBuffer->SetWaste(tileRegion.Area() * mResolution * mResolution);
-
   aTile.Flip();
 
-  // Note, we don't call UpdatedTexture. The Updated function is called manually
-  // by the TiledContentHost before composition.
-
-  if (backBuffer->HasIntermediateBuffer()) {
-    // If our new buffer has an internal buffer, we don't want to keep another
-    // TextureClient around unnecessarily, so discard the back-buffer.
-    aTile.DiscardBackBuffer();
-  }
-
   return true;
 }
 
 /**
  * This function takes the transform stored in aTransformToCompBounds
  * (which was generated in GetTransformToAncestorsParentLayer), and
  * modifies it with the ViewTransform from the compositor side so that
  * it reflects what the compositor is actually rendering. This operation
--- a/gfx/layers/client/TiledContentClient.h
+++ b/gfx/layers/client/TiledContentClient.h
@@ -438,19 +438,16 @@ private:
   ClientLayerManager* mManager;
   LayerManager::DrawPaintedLayerCallback mCallback;
   void* mCallbackData;
 
   // The region that will be made valid during Update(). Once Update() is
   // completed then this is identical to mValidRegion.
   nsIntRegion mNewValidRegion;
 
-  // The DrawTarget we use when UseSinglePaintBuffer() above is true.
-  RefPtr<gfx::DrawTarget>       mSinglePaintDrawTarget;
-  nsIntPoint                    mSinglePaintBufferOffset;
   SharedFrameMetricsHelper*  mSharedFrameMetricsHelper;
   // When using Moz2D's CreateTiledDrawTarget we maintain a list of gfx::Tiles
   std::vector<gfx::Tile> mMoz2DTiles;
   /**
    * While we're adding tiles, this is used to keep track of the position of
    * the top-left of the top-left-most tile.  When we come to wrap the tiles in
    * TiledDrawTarget we subtract the value of this member from each tile's
    * offset so that all the tiles have a positive offset, then add a
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -480,17 +480,16 @@ private:
 
   // We allow for configurable and rectangular tile size to avoid wasting memory on devices whose
   // screen size does not align nicely to the default tile size. Although layers can be any size,
   // they are often the same size as the screen, especially for width.
   DECL_GFX_PREF(Once, "layers.tile-width",                     LayersTileWidth, int32_t, 256);
   DECL_GFX_PREF(Once, "layers.tile-height",                    LayersTileHeight, int32_t, 256);
   DECL_GFX_PREF(Once, "layers.tile-max-pool-size",             LayersTileMaxPoolSize, uint32_t, (uint32_t)50);
   DECL_GFX_PREF(Once, "layers.tile-shrink-pool-timeout",       LayersTileShrinkPoolTimeout, uint32_t, (uint32_t)1000);
-  DECL_GFX_PREF(Once, "layers.tiled-drawtarget.enabled",       TiledDrawTargetEnabled, bool, false);
   DECL_GFX_PREF(Once, "layers.tiles.adjust",                   LayersTilesAdjust, bool, true);
   DECL_GFX_PREF(Once, "layers.tiles.edge-padding",             TileEdgePaddingEnabled, bool, true);
   DECL_GFX_PREF(Live, "layers.tiles.fade-in.enabled",          LayerTileFadeInEnabled, bool, false);
   DECL_GFX_PREF(Live, "layers.tiles.fade-in.duration-ms",      LayerTileFadeInDuration, uint32_t, 250);
   DECL_GFX_PREF(Live, "layers.transaction.warning-ms",         LayerTransactionWarning, uint32_t, 200);
   DECL_GFX_PREF(Once, "layers.uniformity-info",                UniformityInfo, bool, false);
   DECL_GFX_PREF(Once, "layers.use-image-offscreen-surfaces",   UseImageOffscreenSurfaces, bool, true);
   DECL_GFX_PREF(Live, "layers.single-tile.enabled",            LayersSingleTileEnabled, bool, true);
--- a/ipc/testshell/XPCShellEnvironment.cpp
+++ b/ipc/testshell/XPCShellEnvironment.cpp
@@ -254,17 +254,17 @@ static bool
 GCZeal(JSContext *cx, unsigned argc, JS::Value *vp)
 {
   CallArgs args = CallArgsFromVp(argc, vp);
 
   uint32_t zeal;
   if (!ToUint32(cx, args.get(0), &zeal))
     return false;
 
-  JS_SetGCZeal(JS_GetRuntime(cx), uint8_t(zeal), JS_DEFAULT_ZEAL_FREQ);
+  JS_SetGCZeal(cx, uint8_t(zeal), JS_DEFAULT_ZEAL_FREQ);
   return true;
 }
 #endif
 
 const JSFunctionSpec gGlobalFunctions[] =
 {
     JS_FS("print",           Print,          0,0),
     JS_FS("load",            Load,           1,0),
@@ -369,22 +369,22 @@ XPCShellEnvironment::ProcessFile(JSConte
         options.setFileAndLine("typein", startline);
         JS::Rooted<JSScript*> script(cx);
         if (JS_CompileScript(cx, buffer, strlen(buffer), options, &script)) {
             JS::WarningReporter older;
 
             ok = JS_ExecuteScript(cx, script, &result);
             if (ok && !result.isUndefined()) {
                 /* Suppress warnings from JS::ToString(). */
-                older = JS::SetWarningReporter(JS_GetRuntime(cx), nullptr);
+                older = JS::SetWarningReporter(cx, nullptr);
                 str = JS::ToString(cx, result);
                 JSAutoByteString bytes;
                 if (str)
                     bytes.encodeLatin1(cx, str);
-                JS::SetWarningReporter(JS_GetRuntime(cx), older);
+                JS::SetWarningReporter(cx, older);
 
                 if (!!bytes)
                     fprintf(stdout, "%s\n", bytes.ptr());
                 else
                     ok = false;
             }
         }
     } while (!hitEOF && !env->IsQuitting());
@@ -590,22 +590,22 @@ XPCShellEnvironment::EvaluateString(cons
 
   if (aResult) {
       aResult->Truncate();
   }
 
   JS::Rooted<JS::Value> result(cx);
   bool ok = JS_ExecuteScript(cx, script, &result);
   if (ok && !result.isUndefined()) {
-      JS::WarningReporter old = JS::SetWarningReporter(JS_GetRuntime(cx), nullptr);
+      JS::WarningReporter old = JS::SetWarningReporter(cx, nullptr);
       JSString* str = JS::ToString(cx, result);
       nsAutoJSString autoStr;
       if (str)
           autoStr.init(cx, str);
-      JS::SetWarningReporter(JS_GetRuntime(cx), old);
+      JS::SetWarningReporter(cx, old);
 
       if (!autoStr.IsEmpty() && aResult) {
           aResult->Assign(autoStr);
       }
   }
 
   return true;
 }
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -715,17 +715,17 @@ GCZeal(JSContext* cx, unsigned argc, Val
     }
 
     uint32_t frequency = JS_DEFAULT_ZEAL_FREQ;
     if (args.length() >= 2) {
         if (!ToUint32(cx, args.get(1), &frequency))
             return false;
     }
 
-    JS_SetGCZeal(cx->runtime(), (uint8_t)zeal, frequency);
+    JS_SetGCZeal(cx, (uint8_t)zeal, frequency);
     args.rval().setUndefined();
     return true;
 }
 
 static bool
 ScheduleGC(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
@@ -1305,17 +1305,17 @@ OOMTest(JSContext* cx, unsigned argc, Va
         JS_ReportError(cx, "Nested call to oomTest() is not allowed.");
         return false;
     }
     rt->runningOOMTest = true;
 
     MOZ_ASSERT(!cx->isExceptionPending());
     rt->hadOutOfMemory = false;
 
-    JS_SetGCZeal(cx->runtime(), 0, JS_DEFAULT_ZEAL_FREQ);
+    JS_SetGCZeal(cx, 0, JS_DEFAULT_ZEAL_FREQ);
 
     for (unsigned thread = threadStart; thread < threadEnd; thread++) {
         if (verbose)
             fprintf(stderr, "thread %d\n", thread);
 
         HelperThreadState().waitForAllThreads();
         js::oom::targetThread = thread;
 
@@ -1980,17 +1980,17 @@ SetJitCompilerOption(JSContext* cx, unsi
     {
         js::jit::JitActivationIterator iter(cx->runtime());
         if (!iter.done()) {
             JS_ReportError(cx, "Can't turn off JITs with JIT code on the stack.");
             return false;
         }
     }
 
-    JS_SetGlobalJitCompilerOption(cx->runtime(), opt, uint32_t(number));
+    JS_SetGlobalJitCompilerOption(cx, opt, uint32_t(number));
 
     args.rval().setUndefined();
     return true;
 }
 
 static bool
 GetJitCompilerOptions(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -1998,17 +1998,17 @@ GetJitCompilerOptions(JSContext* cx, uns
     RootedObject info(cx, JS_NewPlainObject(cx));
     if (!info)
         return false;
 
     RootedValue value(cx);
 
 #define JIT_COMPILER_MATCH(key, string)                                \
     opt = JSJITCOMPILER_ ## key;                                       \
-    value.setInt32(JS_GetGlobalJitCompilerOption(cx->runtime(), opt)); \
+    value.setInt32(JS_GetGlobalJitCompilerOption(cx, opt));            \
     if (!JS_SetProperty(cx, info, string, value))                      \
         return false;
 
     JSJitCompilerOption opt = JSJITCOMPILER_NOT_AN_OPTION;
     JIT_COMPILER_OPTIONS(JIT_COMPILER_MATCH);
 #undef JIT_COMPILER_MATCH
 
     args.rval().setObject(*info);
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -7417,17 +7417,17 @@ bool CClosure::ArgClosure::operator()(JS
                                               js::CTYPES_CALLBACK_END);
 
   RootedObject typeObj(cx, cinfo->typeObj);
   RootedObject thisObj(cx, cinfo->thisObj);
   RootedValue jsfnVal(cx, ObjectValue(*cinfo->jsfnObj));
   AssertSameCompartment(cx, cinfo->jsfnObj);
 
 
-  JS_AbortIfWrongThread(JS_GetRuntime(cx));
+  JS_AbortIfWrongThread(cx);
 
   // Assert that our CIFs agree.
   FunctionInfo* fninfo = FunctionType::GetFunctionInfo(typeObj);
   MOZ_ASSERT(cif == &fninfo->mCIF);
 
   TypeCode typeCode = CType::GetTypeCode(fninfo->mReturnType);
 
   // Initialize the result to zero, in case something fails. Small integer types
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -376,17 +376,17 @@ js::TenuringTracer::TenuringTracer(JSRun
 #define TIME_START(name) int64_t timestampStart_##name = enableProfiling_ ? PRMJ_Now() : 0
 #define TIME_END(name) int64_t timestampEnd_##name = enableProfiling_ ? PRMJ_Now() : 0
 #define TIME_TOTAL(name) (timestampEnd_##name - timestampStart_##name)
 
 void
 js::Nursery::collect(JSRuntime* rt, JS::gcreason::Reason reason, ObjectGroupList* pretenureGroups)
 {
     MOZ_ASSERT(!rt->mainThread.suppressGC);
-    JS_AbortIfWrongThread(rt);
+    MOZ_RELEASE_ASSERT(CurrentThreadCanAccessRuntime(rt));
 
     StoreBuffer& sb = rt->gc.storeBuffer;
     if (!isEnabled() || isEmpty()) {
         /*
          * Our barriers are not always exact, and there may be entries in the
          * storebuffer even when the nursery is disabled or empty. It's not
          * safe to keep these entries as they may refer to tenured cells which
          * may be freed after this point.
--- a/js/src/gdb/gdb-tests.cpp
+++ b/js/src/gdb/gdb-tests.cpp
@@ -68,17 +68,17 @@ main(int argc, const char** argv)
 {
     if (!JS_Init()) return 1;
     JSRuntime* runtime = checkPtr(JS_NewRuntime(1024 * 1024));
     JS_SetGCParameter(runtime, JSGC_MAX_BYTES, 0xffffffff);
     JS_SetNativeStackQuota(runtime, 5000000);
 
     JSContext* cx = JS_GetContext(runtime);
     checkBool(JS::InitSelfHostedCode(cx));
-    JS::SetWarningReporter(runtime, reportWarning);
+    JS::SetWarningReporter(cx, reportWarning);
 
     JSAutoRequest ar(cx);
 
     /* Create the global object. */
     JS::CompartmentOptions options;
     options.behaviors().setVersion(JSVERSION_LATEST);
 
     RootedObject global(cx, checkPtr(JS_NewGlobalObject(cx, &global_class,
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/gc/bug1283169.js
@@ -0,0 +1,4 @@
+if (helperThreadCount() === 0)
+    quit(0);
+startgc(45);
+offThreadCompileScript("print(1)");
--- a/js/src/jsapi-tests/testBug604087.cpp
+++ b/js/src/jsapi-tests/testBug604087.cpp
@@ -86,13 +86,13 @@ BEGIN_TEST(testBug604087)
 
     JS::RootedObject next(cx);
     {
         JSAutoCompartment ac(cx, compartment2);
         next = js::Wrapper::New(cx, compartment2, &js::Wrapper::singleton, options);
         CHECK(next);
     }
 
-    JS_SetWrapObjectCallbacks(JS_GetRuntime(cx), &WrapObjectCallbacks);
+    JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks);
     CHECK(JS_TransplantObject(cx, outerObj, next));
     return true;
 }
 END_TEST(testBug604087)
--- a/js/src/jsapi-tests/testGCFinalizeCallback.cpp
+++ b/js/src/jsapi-tests/testGCFinalizeCallback.cpp
@@ -94,17 +94,17 @@ BEGIN_TEST(testGCFinalizeCallback)
     CHECK(checkFinalizeStatus());
     CHECK(checkFinalizeIsCompartmentGC(true));
 
 #ifdef JS_GC_ZEAL
 
     /* Full GC with reset due to new compartment, becoming compartment GC. */
 
     FinalizeCalls = 0;
-    JS_SetGCZeal(rt, 9, 1000000);
+    JS_SetGCZeal(cx, 9, 1000000);
     JS::PrepareForFullGC(rt);
     js::SliceBudget budget(js::WorkBudget(1));
     rt->gc.startDebugGC(GC_NORMAL, budget);
     CHECK(rt->gc.state() == js::gc::MARK);
     CHECK(rt->gc.isFullGc());
 
     JS::RootedObject global4(cx, createTestGlobal());
     budget = js::SliceBudget(js::WorkBudget(1));
@@ -115,17 +115,17 @@ BEGIN_TEST(testGCFinalizeCallback)
     CHECK(!rt->gc.isFullGc());
     CHECK(checkMultipleGroups());
     CHECK(checkFinalizeStatus());
 
     for (unsigned i = 0; i < FinalizeCalls - 1; ++i)
         CHECK(!IsCompartmentGCBuffer[i]);
     CHECK(IsCompartmentGCBuffer[FinalizeCalls - 1]);
 
-    JS_SetGCZeal(rt, 0, 0);
+    JS_SetGCZeal(cx, 0, 0);
 
 #endif
 
     /*
      * Make some use of the globals here to ensure the compiler doesn't optimize
      * them away in release builds, causing the compartments to be collected and
      * the test to fail.
      */
--- a/js/src/jsapi-tests/testGCMarking.cpp
+++ b/js/src/jsapi-tests/testGCMarking.cpp
@@ -81,17 +81,17 @@ BEGIN_TEST(testTracingIncomingCCWs)
 END_TEST(testTracingIncomingCCWs)
 
 BEGIN_TEST(testIncrementalRoots)
 {
     JSRuntime* rt = cx->runtime();
 
 #ifdef JS_GC_ZEAL
     // Disable zeal modes because this test needs to control exactly when the GC happens.
-    JS_SetGCZeal(rt, 0, 100);
+    JS_SetGCZeal(cx, 0, 100);
 #endif
 
     // Construct a big object graph to mark. In JS, the resulting object graph
     // is equivalent to:
     //
     //   leaf = {};
     //   leaf2 = {};
     //   root = { 'obj': { 'obj': ... { 'obj': leaf, 'leaf2': leaf2 } ... } }
--- a/js/src/jsapi-tests/testXDR.cpp
+++ b/js/src/jsapi-tests/testXDR.cpp
@@ -17,17 +17,17 @@ GetBuildId(JS::BuildIdCharVector* buildI
 {
     const char buildid[] = "testXDR";
     return buildId->append(buildid, sizeof(buildid));
 }
 
 static JSScript*
 FreezeThaw(JSContext* cx, JS::HandleScript script)
 {
-    JS::SetBuildIdOp(cx->runtime(), GetBuildId);
+    JS::SetBuildIdOp(cx, GetBuildId);
 
     // freeze
     uint32_t nbytes;
     void* memory = JS_EncodeScript(cx, script, &nbytes);
     if (!memory)
         return nullptr;
 
     // thaw
--- a/js/src/jsapi-tests/tests.h
+++ b/js/src/jsapi-tests/tests.h
@@ -284,17 +284,17 @@ class JSAPITest
 
         JS_SetNativeStackQuota(rt, MAX_STACK_SIZE);
     }
 
     virtual JSRuntime * createRuntime() {
         JSRuntime* rt = JS_NewRuntime(8L * 1024 * 1024);
         if (!rt)
             return nullptr;
-        JS::SetWarningReporter(rt, &reportWarning);
+        JS::SetWarningReporter(JS_GetContext(rt), &reportWarning);
         setNativeStackQuota(rt);
         return rt;
     }
 
     virtual void destroyRuntime() {
         MOZ_RELEASE_ASSERT(!cx);
         MOZ_RELEASE_ASSERT(rt);
         JS_DestroyRuntime(rt);
@@ -431,24 +431,24 @@ class AutoLeaveZeal
     JSContext* cx_;
     uint32_t zealBits_;
     uint32_t frequency_;
 
   public:
     explicit AutoLeaveZeal(JSContext* cx) : cx_(cx) {
         uint32_t dummy;
         JS_GetGCZealBits(cx_, &zealBits_, &frequency_, &dummy);
-        JS_SetGCZeal(JS_GetRuntime(cx_), 0, 0);
+        JS_SetGCZeal(cx_, 0, 0);
         JS::PrepareForFullGC(JS_GetRuntime(cx_));
         JS::GCForReason(JS_GetRuntime(cx_), GC_SHRINK, JS::gcreason::DEBUG_GC);
     }
     ~AutoLeaveZeal() {
         for (size_t i = 0; i < sizeof(zealBits_) * 8; i++) {
             if (zealBits_ & (1 << i))
-                JS_SetGCZeal(JS_GetRuntime(cx_), i, frequency_);
+                JS_SetGCZeal(cx_, i, frequency_);
         }
 
 #ifdef DEBUG
         uint32_t zealBitsAfter, frequencyAfter, dummy;
         JS_GetGCZealBits(cx_, &zealBitsAfter, &frequencyAfter, &dummy);
         MOZ_ASSERT(zealBitsAfter == zealBits_);
         MOZ_ASSERT(frequencyAfter == frequency_);
 #endif
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -294,20 +294,20 @@ JS_GetPositiveInfinityValue(JSContext* c
 
 JS_PUBLIC_API(Value)
 JS_GetEmptyStringValue(JSContext* cx)
 {
     return StringValue(cx->runtime()->emptyString);
 }
 
 JS_PUBLIC_API(JSString*)
-JS_GetEmptyString(JSRuntime* rt)
-{
-    MOZ_ASSERT(rt->emptyString);
-    return rt->emptyString;
+JS_GetEmptyString(JSContext* cx)
+{
+    MOZ_ASSERT(cx->emptyString());
+    return cx->emptyString();
 }
 
 namespace js {
 
 void
 AssertHeapIsIdle(JSRuntime* rt)
 {
     MOZ_ASSERT(!rt->isHeapBusy());
@@ -488,19 +488,19 @@ JS_GetRuntimePrivate(JSRuntime* rt)
 
 JS_PUBLIC_API(void)
 JS_SetRuntimePrivate(JSRuntime* rt, void* data)
 {
     rt->data = data;
 }
 
 JS_PUBLIC_API(void)
-JS_SetFutexCanWait(JSRuntime* rt)
-{
-    rt->fx.setCanWait(true);
+JS_SetFutexCanWait(JSContext* cx)
+{
+    cx->fx.setCanWait(true);
 }
 
 static void
 StartRequest(JSContext* cx)
 {
     JSRuntime* rt = cx->runtime();
     MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
 
@@ -652,50 +652,50 @@ JS::InitSelfHostedCode(JSContext* cx)
 
 JS_PUBLIC_API(const char*)
 JS_GetImplementationVersion(void)
 {
     return "JavaScript-C" MOZILLA_VERSION;
 }
 
 JS_PUBLIC_API(void)
-JS_SetDestroyCompartmentCallback(JSRuntime* rt, JSDestroyCompartmentCallback callback)
-{
-    rt->destroyCompartmentCallback = callback;
+JS_SetDestroyCompartmentCallback(JSContext* cx, JSDestroyCompartmentCallback callback)
+{
+    cx->destroyCompartmentCallback = callback;
 }
 
 JS_PUBLIC_API(void)
-JS_SetSizeOfIncludingThisCompartmentCallback(JSRuntime* rt,
+JS_SetSizeOfIncludingThisCompartmentCallback(JSContext* cx,
                                              JSSizeOfIncludingThisCompartmentCallback callback)
 {
-    rt->sizeOfIncludingThisCompartmentCallback = callback;
+    cx->sizeOfIncludingThisCompartmentCallback = callback;
 }
 
 JS_PUBLIC_API(void)
-JS_SetDestroyZoneCallback(JSRuntime* rt, JSZoneCallback callback)
-{
-    rt->destroyZoneCallback = callback;
+JS_SetDestroyZoneCallback(JSContext* cx, JSZoneCallback callback)
+{
+    cx->destroyZoneCallback = callback;
 }
 
 JS_PUBLIC_API(void)
-JS_SetSweepZoneCallback(JSRuntime* rt, JSZoneCallback callback)
-{
-    rt->sweepZoneCallback = callback;
+JS_SetSweepZoneCallback(JSContext* cx, JSZoneCallback callback)
+{
+    cx->sweepZoneCallback = callback;
 }
 
 JS_PUBLIC_API(void)
-JS_SetCompartmentNameCallback(JSRuntime* rt, JSCompartmentNameCallback callback)
-{
-    rt->compartmentNameCallback = callback;
+JS_SetCompartmentNameCallback(JSContext* cx, JSCompartmentNameCallback callback)
+{
+    cx->compartmentNameCallback = callback;
 }
 
 JS_PUBLIC_API(void)
-JS_SetWrapObjectCallbacks(JSRuntime* rt, const JSWrapObjectCallbacks* callbacks)
-{
-    rt->wrapObjectCallbacks = callbacks;
+JS_SetWrapObjectCallbacks(JSContext* cx, const JSWrapObjectCallbacks* callbacks)
+{
+    cx->wrapObjectCallbacks = callbacks;
 }
 
 JS_PUBLIC_API(JSCompartment*)
 JS_EnterCompartment(JSContext* cx, JSObject* target)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
 
@@ -4121,27 +4121,27 @@ JS_BufferIsCompilableUnit(JSContext* cx,
     // Return true on any out-of-memory error or non-EOF-related syntax error, so our
     // caller doesn't try to collect more buffered source.
     bool result = true;
 
     CompileOptions options(cx);
     Parser<frontend::FullParseHandler> parser(cx, &cx->tempLifoAlloc(),
                                               options, chars, length,
                                               /* foldConstants = */ true, nullptr, nullptr);
-    JS::WarningReporter older = JS::SetWarningReporter(cx->runtime(), nullptr);
+    JS::WarningReporter older = JS::SetWarningReporter(cx, nullptr);
     if (!parser.checkOptions() || !parser.parse()) {
         // We ran into an error. If it was because we ran out of source, we
         // return false so our caller knows to try to collect more buffered
         // source.
         if (parser.isUnexpectedEOF())
             result = false;
 
         cx->clearPendingException();
     }
-    JS::SetWarningReporter(cx->runtime(), older);
+    JS::SetWarningReporter(cx, older);
 
     js_free(chars);
     return result;
 }
 
 JS_PUBLIC_API(JSObject*)
 JS_GetGlobalFromScript(JSScript* script)
 {
@@ -5631,26 +5631,26 @@ JS_ReportOutOfMemory(JSContext* cx)
 
 JS_PUBLIC_API(void)
 JS_ReportAllocationOverflow(JSContext* cx)
 {
     ReportAllocationOverflow(cx);
 }
 
 JS_PUBLIC_API(JS::WarningReporter)
-JS::GetWarningReporter(JSRuntime* rt)
-{
-    return rt->warningReporter;
+JS::GetWarningReporter(JSContext* cx)
+{
+    return cx->warningReporter;
 }
 
 JS_PUBLIC_API(JS::WarningReporter)
-JS::SetWarningReporter(JSRuntime* rt, JS::WarningReporter reporter)
-{
-    WarningReporter older = rt->warningReporter;
-    rt->warningReporter = reporter;
+JS::SetWarningReporter(JSContext* cx, JS::WarningReporter reporter)
+{
+    WarningReporter older = cx->warningReporter;
+    cx->warningReporter = reporter;
     return older;
 }
 
 /************************************************************************/
 
 /*
  * Dates.
  */
@@ -5995,59 +5995,60 @@ JS_IsStopIteration(Value v)
 
 JS_PUBLIC_API(intptr_t)
 JS_GetCurrentThread()
 {
     return reinterpret_cast<intptr_t>(PR_GetCurrentThread());
 }
 
 extern MOZ_NEVER_INLINE JS_PUBLIC_API(void)
-JS_AbortIfWrongThread(JSRuntime* rt)
-{
-    if (!CurrentThreadCanAccessRuntime(rt))
+JS_AbortIfWrongThread(JSContext* cx)
+{
+    if (!CurrentThreadCanAccessRuntime(cx))
         MOZ_CRASH();
-    if (!js::TlsPerThreadData.get()->associatedWith(rt))
+    if (!js::TlsPerThreadData.get()->associatedWith(cx))
         MOZ_CRASH();
 }
 
 #ifdef JS_GC_ZEAL
 JS_PUBLIC_API(void)
 JS_GetGCZealBits(JSContext* cx, uint32_t* zealBits, uint32_t* frequency, uint32_t* nextScheduled)
 {
     cx->runtime()->gc.getZealBits(zealBits, frequency, nextScheduled);
 }
 
 JS_PUBLIC_API(void)
-JS_SetGCZeal(JSRuntime* rt, uint8_t zeal, uint32_t frequency)
-{
-    rt->gc.setZeal(zeal, frequency);
+JS_SetGCZeal(JSContext* cx, uint8_t zeal, uint32_t frequency)
+{
+    cx->gc.setZeal(zeal, frequency);
 }
 
 JS_PUBLIC_API(void)
 JS_ScheduleGC(JSContext* cx, uint32_t count)
 {
     cx->runtime()->gc.setNextScheduled(count);
 }
 #endif
 
 JS_PUBLIC_API(void)
-JS_SetParallelParsingEnabled(JSRuntime* rt, bool enabled)
-{
-    rt->setParallelParsingEnabled(enabled);
+JS_SetParallelParsingEnabled(JSContext* cx, bool enabled)
+{
+    cx->setParallelParsingEnabled(enabled);
 }
 
 JS_PUBLIC_API(void)
-JS_SetOffthreadIonCompilationEnabled(JSRuntime* rt, bool enabled)
-{
-    rt->setOffthreadIonCompilationEnabled(enabled);
+JS_SetOffthreadIonCompilationEnabled(JSContext* cx, bool enabled)
+{
+    cx->setOffthreadIonCompilationEnabled(enabled);
 }
 
 JS_PUBLIC_API(void)
-JS_SetGlobalJitCompilerOption(JSRuntime* rt, JSJitCompilerOption opt, uint32_t value)
-{
+JS_SetGlobalJitCompilerOption(JSContext* cx, JSJitCompilerOption opt, uint32_t value)
+{
+    JSRuntime* rt = cx->runtime();
     switch (opt) {
       case JSJITCOMPILER_BASELINE_WARMUP_TRIGGER:
         if (value == uint32_t(-1)) {
             jit::DefaultJitOptions defaultValues;
             value = defaultValues.baselineWarmUpThreshold;
         }
         jit::JitOptions.baselineWarmUpThreshold = value;
         break;
@@ -6127,19 +6128,20 @@ JS_SetGlobalJitCompilerOption(JSRuntime*
         jit::JitOptions.wasmTestMode = !!value;
         break;
       default:
         break;
     }
 }
 
 JS_PUBLIC_API(int)
-JS_GetGlobalJitCompilerOption(JSRuntime* rt, JSJitCompilerOption opt)
+JS_GetGlobalJitCompilerOption(JSContext* cx, JSJitCompilerOption opt)
 {
 #ifndef JS_CODEGEN_NONE
+    JSRuntime* rt = cx->runtime();
     switch (opt) {
       case JSJITCOMPILER_BASELINE_WARMUP_TRIGGER:
         return jit::JitOptions.baselineWarmUpThreshold;
       case JSJITCOMPILER_ION_WARMUP_TRIGGER:
         return jit::JitOptions.forcedDefaultIonWarmUpThreshold.isSome()
              ? jit::JitOptions.forcedDefaultIonWarmUpThreshold.ref()
              : jit::OptimizationInfo::CompilerWarmupThreshold;
       case JSJITCOMPILER_ION_FORCE_IC:
@@ -6473,25 +6475,25 @@ JS_DecodeInterpretedFunction(JSContext* 
     XDRDecoder decoder(cx, data, length);
     RootedFunction funobj(cx);
     if (!decoder.codeFunction(&funobj))
         return nullptr;
     return funobj;
 }
 
 JS_PUBLIC_API(void)
-JS::SetBuildIdOp(JSRuntime* rt, JS::BuildIdOp buildIdOp)
-{
-    rt->buildIdOp = buildIdOp;
+JS::SetBuildIdOp(JSContext* cx, JS::BuildIdOp buildIdOp)
+{
+    cx->runtime()->buildIdOp = buildIdOp;
 }
 
 JS_PUBLIC_API(void)
-JS::SetAsmJSCacheOps(JSRuntime* rt, const JS::AsmJSCacheOps* ops)
-{
-    rt->asmJSCacheOps = *ops;
+JS::SetAsmJSCacheOps(JSContext* cx, const JS::AsmJSCacheOps* ops)
+{
+    cx->runtime()->asmJSCacheOps = *ops;
 }
 
 char*
 JSAutoByteString::encodeLatin1(ExclusiveContext* cx, JSString* str)
 {
     mBytes = EncodeLatin1(cx, str);
     return mBytes;
 }
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -909,17 +909,17 @@ JS_GetNegativeInfinityValue(JSContext* c
 
 extern JS_PUBLIC_API(JS::Value)
 JS_GetPositiveInfinityValue(JSContext* cx);
 
 extern JS_PUBLIC_API(JS::Value)
 JS_GetEmptyStringValue(JSContext* cx);
 
 extern JS_PUBLIC_API(JSString*)
-JS_GetEmptyString(JSRuntime* rt);
+JS_GetEmptyString(JSContext* cx);
 
 extern JS_PUBLIC_API(bool)
 JS_ValueToObject(JSContext* cx, JS::HandleValue v, JS::MutableHandleObject objp);
 
 extern JS_PUBLIC_API(JSFunction*)
 JS_ValueToFunction(JSContext* cx, JS::HandleValue v);
 
 extern JS_PUBLIC_API(JSFunction*)
@@ -1003,17 +1003,17 @@ JS_SetRuntimePrivate(JSRuntime* rt, void
 
 extern JS_PUBLIC_API(void)
 JS_BeginRequest(JSContext* cx);
 
 extern JS_PUBLIC_API(void)
 JS_EndRequest(JSContext* cx);
 
 extern JS_PUBLIC_API(void)
-JS_SetFutexCanWait(JSRuntime* rt);
+JS_SetFutexCanWait(JSContext* cx);
 
 namespace js {
 
 void
 AssertHeapIsIdle(JSRuntime* rt);
 
 } /* namespace js */
 
@@ -1246,33 +1246,33 @@ JS_PUBLIC_API(bool)
 InitSelfHostedCode(JSContext* cx);
 
 } /* namespace JS */
 
 extern JS_PUBLIC_API(const char*)
 JS_GetImplementationVersion(void);
 
 extern JS_PUBLIC_API(void)
-JS_SetDestroyCompartmentCallback(JSRuntime* rt, JSDestroyCompartmentCallback callback);
+JS_SetDestroyCompartmentCallback(JSContext* cx, JSDestroyCompartmentCallback callback);
 
 extern JS_PUBLIC_API(void)
-JS_SetSizeOfIncludingThisCompartmentCallback(JSRuntime* rt,
+JS_SetSizeOfIncludingThisCompartmentCallback(JSContext* cx,
                                              JSSizeOfIncludingThisCompartmentCallback callback);
 
 extern JS_PUBLIC_API(void)
-JS_SetDestroyZoneCallback(JSRuntime* rt, JSZoneCallback callback);
+JS_SetDestroyZoneCallback(JSContext* cx, JSZoneCallback callback);
 
 extern JS_PUBLIC_API(void)
-JS_SetSweepZoneCallback(JSRuntime* rt, JSZoneCallback callback);
+JS_SetSweepZoneCallback(JSContext* cx, JSZoneCallback callback);
 
 extern JS_PUBLIC_API(void)
-JS_SetCompartmentNameCallback(JSRuntime* rt, JSCompartmentNameCallback callback);
+JS_SetCompartmentNameCallback(JSContext* cx, JSCompartmentNameCallback callback);
 
 extern JS_PUBLIC_API(void)
-JS_SetWrapObjectCallbacks(JSRuntime* rt, const JSWrapObjectCallbacks* callbacks);
+JS_SetWrapObjectCallbacks(JSContext* cx, const JSWrapObjectCallbacks* callbacks);
 
 extern JS_PUBLIC_API(void)
 JS_SetCompartmentPrivate(JSCompartment* compartment, void* data);
 
 extern JS_PUBLIC_API(void*)
 JS_GetCompartmentPrivate(JSCompartment* compartment);
 
 extern JS_PUBLIC_API(void)
@@ -5214,20 +5214,20 @@ class JSErrorReport
 #define JSREPORT_IS_STRICT_MODE_ERROR(flags) (((flags) &                      \
                                               JSREPORT_STRICT_MODE_ERROR) != 0)
 namespace JS {
 
 typedef void
 (* WarningReporter)(JSContext* cx, const char* message, JSErrorReport* report);
 
 extern JS_PUBLIC_API(WarningReporter)
-SetWarningReporter(JSRuntime* rt, WarningReporter reporter);
+SetWarningReporter(JSContext* cx, WarningReporter reporter);
 
 extern JS_PUBLIC_API(WarningReporter)
-GetWarningReporter(JSRuntime* rt);
+GetWarningReporter(JSContext* cx);
 
 extern JS_PUBLIC_API(bool)
 CreateError(JSContext* cx, JSExnType type, HandleObject stack,
             HandleString fileName, uint32_t lineNumber, uint32_t columnNumber,
             JSErrorReport* report, HandleString message, MutableHandleValue rval);
 
 /************************************************************************/
 
@@ -5497,27 +5497,27 @@ JS_ThrowStopIteration(JSContext* cx);
 
 extern JS_PUBLIC_API(bool)
 JS_IsStopIteration(JS::Value v);
 
 extern JS_PUBLIC_API(intptr_t)
 JS_GetCurrentThread();
 
 /**
- * A JS runtime always has an "owner thread". The owner thread is set when the
- * runtime is created (to the current thread) and practically all entry points
- * into the JS engine check that a runtime (or anything contained in the
- * runtime: context, compartment, object, etc) is only touched by its owner
+ * A JS context always has an "owner thread". The owner thread is set when the
+ * context is created (to the current thread) and practically all entry points
+ * into the JS engine check that a context (or anything contained in the
+ * context: runtime, compartment, object, etc) is only touched by its owner
  * thread. Embeddings may check this invariant outside the JS engine by calling
  * JS_AbortIfWrongThread (which will abort if not on the owner thread, even for
  * non-debug builds).
  */
 
 extern JS_PUBLIC_API(void)
-JS_AbortIfWrongThread(JSRuntime* rt);
+JS_AbortIfWrongThread(JSContext* cx);
 
 /************************************************************************/
 
 /**
  * A constructor can request that the JS engine create a default new 'this'
  * object of the given class, using the callee to determine parentage and
  * [[Prototype]].
  */
@@ -5528,27 +5528,27 @@ JS_NewObjectForConstructor(JSContext* cx
 
 #ifdef JS_GC_ZEAL
 #define JS_DEFAULT_ZEAL_FREQ 100
 
 extern JS_PUBLIC_API(void)
 JS_GetGCZealBits(JSContext* cx, uint32_t* zealBits, uint32_t* frequency, uint32_t* nextScheduled);
 
 extern JS_PUBLIC_API(void)
-JS_SetGCZeal(JSRuntime* rt, uint8_t zeal, uint32_t frequency);
+JS_SetGCZeal(JSContext* cx, uint8_t zeal, uint32_t frequency);
 
 extern JS_PUBLIC_API(void)
 JS_ScheduleGC(JSContext* cx, uint32_t count);
 #endif
 
 extern JS_PUBLIC_API(void)
-JS_SetParallelParsingEnabled(JSRuntime* rt, bool enabled);
+JS_SetParallelParsingEnabled(JSContext* cx, bool enabled);
 
 extern JS_PUBLIC_API(void)
-JS_SetOffthreadIonCompilationEnabled(JSRuntime* rt, bool enabled);
+JS_SetOffthreadIonCompilationEnabled(JSContext* cx, bool enabled);
 
 #define JIT_COMPILER_OPTIONS(Register)                                     \
     Register(BASELINE_WARMUP_TRIGGER, "baseline.warmup.trigger")           \
     Register(ION_WARMUP_TRIGGER, "ion.warmup.trigger")                     \
     Register(ION_GVN_ENABLE, "ion.gvn.enable")                             \
     Register(ION_FORCE_IC, "ion.forceinlineCaches")                        \
     Register(ION_ENABLE, "ion.enable")                                     \
     Register(BASELINE_ENABLE, "baseline.enable")                           \
@@ -5563,19 +5563,19 @@ typedef enum JSJitCompilerOption {
 
     JIT_COMPILER_OPTIONS(JIT_COMPILER_DECLARE)
 #undef JIT_COMPILER_DECLARE
 
     JSJITCOMPILER_NOT_AN_OPTION
 } JSJitCompilerOption;
 
 extern JS_PUBLIC_API(void)
-JS_SetGlobalJitCompilerOption(JSRuntime* rt, JSJitCompilerOption opt, uint32_t value);
+JS_SetGlobalJitCompilerOption(JSContext* cx, JSJitCompilerOption opt, uint32_t value);
 extern JS_PUBLIC_API(int)
-JS_GetGlobalJitCompilerOption(JSRuntime* rt, JSJitCompilerOption opt);
+JS_GetGlobalJitCompilerOption(JSContext* cx, JSJitCompilerOption opt);
 
 /**
  * Convert a uint32_t index into a jsid.
  */
 extern JS_PUBLIC_API(bool)
 JS_IndexToId(JSContext* cx, uint32_t index, JS::MutableHandleId);
 
 /**
@@ -5775,20 +5775,20 @@ struct AsmJSCacheOps
 {
     OpenAsmJSCacheEntryForReadOp openEntryForRead;
     CloseAsmJSCacheEntryForReadOp closeEntryForRead;
     OpenAsmJSCacheEntryForWriteOp openEntryForWrite;
     CloseAsmJSCacheEntryForWriteOp closeEntryForWrite;
 };
 
 extern JS_PUBLIC_API(void)
-SetAsmJSCacheOps(JSRuntime* rt, const AsmJSCacheOps* callbacks);
+SetAsmJSCacheOps(JSContext* cx, const AsmJSCacheOps* callbacks);
 
 extern JS_PUBLIC_API(void)
-SetBuildIdOp(JSRuntime* rt, BuildIdOp buildIdOp);
+SetBuildIdOp(JSContext* cx, BuildIdOp buildIdOp);
 
 /**
  * Convenience class for imitating a JS level for-of loop. Typical usage:
  *
  *     ForOfIterator it(cx);
  *     if (!it.init(iterable))
  *       return false;
  *     RootedValue val(cx);
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -112,30 +112,29 @@ js::NewContext(uint32_t maxBytes, uint32
     }
 
     return cx;
 }
 
 void
 js::DestroyContext(JSContext* cx)
 {
-    JSRuntime* rt = cx->runtime();
-    JS_AbortIfWrongThread(rt);
+    JS_AbortIfWrongThread(cx);
 
     if (cx->outstandingRequests != 0)
         MOZ_CRASH("Attempted to destroy a context while it is in a request.");
 
     cx->roots.checkNoGCRooters();
     cx->roots.finishPersistentRoots();
 
     /*
      * Dump remaining type inference results while we still have a context.
      * This printing depends on atoms still existing.
      */
-    for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next())
+    for (CompartmentsIter c(cx, SkipAtoms); !c.done(); c.next())
         PrintTypes(cx, c, false);
 
     js_delete_poison(cx);
 }
 
 void
 RootLists::checkNoGCRooters() {
 #ifdef DEBUG
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -244,23 +244,23 @@ js::CopyErrorReport(JSContext* cx, JSErr
 struct SuppressErrorsGuard
 {
     JSContext* cx;
     JS::WarningReporter prevReporter;
     JS::AutoSaveExceptionState prevState;
 
     explicit SuppressErrorsGuard(JSContext* cx)
       : cx(cx),
-        prevReporter(JS::SetWarningReporter(cx->runtime(), nullptr)),
+        prevReporter(JS::SetWarningReporter(cx, nullptr)),
         prevState(cx)
     {}
 
     ~SuppressErrorsGuard()
     {
-        JS::SetWarningReporter(cx->runtime(), prevReporter);
+        JS::SetWarningReporter(cx, prevReporter);
     }
 };
 
 // Cut off the stack if it gets too deep (most commonly for infinite recursion
 // errors).
 static const size_t MAX_REPORTED_STACK_DEPTH = 1u << 7;
 
 static bool
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -6235,17 +6235,17 @@ GCRuntime::scanZonesBeforeGC()
         zoneStats.compartmentCount++;
 
     return zoneStats;
 }
 
 void
 GCRuntime::checkCanCallAPI()
 {
-    JS_AbortIfWrongThread(rt);
+    MOZ_RELEASE_ASSERT(CurrentThreadCanAccessRuntime(rt));
 
     /* If we attempt to invoke the GC while we are running in the GC, assert. */
     MOZ_RELEASE_ASSERT(!rt->isHeapBusy());
 
     /* The engine never locks across anything that could GC. */
     MOZ_ASSERT(!rt->currentThreadHasExclusiveAccess());
 
     MOZ_ASSERT(isAllocAllowed());
@@ -6620,17 +6620,17 @@ AutoPrepareForTracing::AutoPrepareForTra
     session_.emplace(rt);
 }
 
 JSCompartment*
 js::NewCompartment(JSContext* cx, Zone* zone, JSPrincipals* principals,
                    const JS::CompartmentOptions& options)
 {
     JSRuntime* rt = cx->runtime();
-    JS_AbortIfWrongThread(rt);
+    JS_AbortIfWrongThread(cx);
 
     ScopedJSDeletePtr<Zone> zoneHolder;
     if (!zone) {
         zone = cx->new_<Zone>(rt);
         if (!zone)
             return nullptr;
 
         zoneHolder.reset(zone);
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -2938,24 +2938,25 @@ WorkerMain(void* arg)
 
     UniquePtr<ShellRuntime> sr = MakeUnique<ShellRuntime>(rt);
     if (!sr) {
         JS_DestroyRuntime(rt);
         js_delete(input);
         return;
     }
 
+    JSContext* cx = JS_GetContext(rt);
+
     sr->isWorker = true;
     JS_SetRuntimePrivate(rt, sr.get());
-    JS_SetFutexCanWait(rt);
-    JS::SetWarningReporter(rt, WarningReporter);
+    JS_SetFutexCanWait(cx);
+    JS::SetWarningReporter(cx, WarningReporter);
     JS_InitDestroyPrincipalsCallback(rt, ShellPrincipals::destroy);
     SetWorkerRuntimeOptions(rt);
 
-    JSContext* cx = JS_GetContext(rt);
     if (!JS::InitSelfHostedCode(cx)) {
         JS_DestroyRuntime(rt);
         js_delete(input);
         return;
     }
 
 #ifdef SPIDERMONKEY_PROMISE
     sr->jobQueue.init(cx, JobQueue(SystemAllocPolicy()));
@@ -7375,42 +7376,43 @@ main(int argc, char** argv, char** envp)
     JSRuntime* rt = JS_NewRuntime(JS::DefaultHeapMaxBytes, nurseryBytes);
     if (!rt)
         return 1;
 
     UniquePtr<ShellRuntime> sr = MakeUnique<ShellRuntime>(rt);
     if (!sr)
         return 1;
 
+    JSContext* cx = JS_GetContext(rt);
+
     JS_SetRuntimePrivate(rt, sr.get());
     // Waiting is allowed on the shell's main thread, for now.
-    JS_SetFutexCanWait(rt);
-    JS::SetWarningReporter(rt, WarningReporter);
+    JS_SetFutexCanWait(cx);
+    JS::SetWarningReporter(cx, WarningReporter);
     if (!SetRuntimeOptions(rt, op))
         return 1;
 
     JS_SetGCParameter(rt, JSGC_MAX_BYTES, 0xffffffff);
 
     size_t availMem = op.getIntOption("available-memory");
     if (availMem > 0)
         JS_SetGCParametersBasedOnAvailableMemory(rt, availMem);
 
     JS_SetTrustedPrincipals(rt, &ShellPrincipals::fullyTrusted);
     JS_SetSecurityCallbacks(rt, &ShellPrincipals::securityCallbacks);
     JS_InitDestroyPrincipalsCallback(rt, ShellPrincipals::destroy);
 
     JS_SetInterruptCallback(rt, ShellInterruptCallback);
-    JS::SetBuildIdOp(rt, ShellBuildId);
-    JS::SetAsmJSCacheOps(rt, &asmJSCacheOps);
+    JS::SetBuildIdOp(cx, ShellBuildId);
+    JS::SetAsmJSCacheOps(cx, &asmJSCacheOps);
 
     JS_SetNativeStackQuota(rt, gMaxStackSize);
 
     JS::dbg::SetDebuggerMallocSizeOf(rt, moz_malloc_size_of);
 
-    JSContext* cx = JS_GetContext(rt);
     if (!JS::InitSelfHostedCode(cx))
         return 1;
 
 #ifdef SPIDERMONKEY_PROMISE
     sr->jobQueue.init(cx, JobQueue(SystemAllocPolicy()));
     JS::SetEnqueuePromiseJobCallback(rt, ShellEnqueuePromiseJobCallback);
     JS::SetGetIncumbentGlobalCallback(rt, ShellGetIncumbentGlobalCallback);
 #endif // SPIDERMONKEY_PROMISE
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -4934,33 +4934,33 @@ Debugger::isCompilableUnit(JSContext* cx
 
     bool result = true;
 
     CompileOptions options(cx);
     frontend::Parser<frontend::FullParseHandler> parser(cx, &cx->tempLifoAlloc(),
                                                         options, chars.twoByteChars(),
                                                         length, /* foldConstants = */ true,
                                                         nullptr, nullptr);
-    JS::WarningReporter older = JS::SetWarningReporter(cx->runtime(), nullptr);
+    JS::WarningReporter older = JS::SetWarningReporter(cx, nullptr);
     if (!parser.checkOptions() || !parser.parse()) {
         // We ran into an error. If it was because we ran out of memory we report
         // it in the usual way.
         if (cx->isThrowingOutOfMemory()) {
-            JS::SetWarningReporter(cx->runtime(), older);
+            JS::SetWarningReporter(cx, older);
             return false;
         }
 
         // If it was because we ran out of source, we return false so our caller
         // knows to try to collect more [source].
         if (parser.isUnexpectedEOF())
             result = false;
 
         cx->clearPendingException();
     }
-    JS::SetWarningReporter(cx->runtime(), older);
+    JS::SetWarningReporter(cx, older);
     args.rval().setBoolean(result);
     return true;
 }
 
 
 bool
 Debugger::drainTraceLoggerScriptCalls(JSContext* cx, unsigned argc, Value* vp)
 {
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -302,16 +302,22 @@ ModuleParseTask::parse()
 void
 js::CancelOffThreadParses(JSRuntime* rt)
 {
     AutoLockHelperThreadState lock;
 
     if (!HelperThreadState().threads)
         return;
 
+#ifdef DEBUG
+    GlobalHelperThreadState::ParseTaskVector& waitingOnGC = HelperThreadState().parseWaitingOnGC();
+    for (size_t i = 0; i < waitingOnGC.length(); i++)
+        MOZ_ASSERT(!waitingOnGC[i]->runtimeMatches(rt));
+#endif
+
     // Instead of forcibly canceling pending parse tasks, just wait for all scheduled
     // and in progress ones to complete. Otherwise the final GC may not collect
     // everything due to zones being used off thread.
     while (true) {
         bool pending = false;
         GlobalHelperThreadState::ParseTaskVector& worklist = HelperThreadState().parseWorklist();
         for (size_t i = 0; i < worklist.length(); i++) {
             ParseTask* task = worklist[i];
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -35,16 +35,17 @@
 #include "jsobj.h"
 #include "jsscript.h"
 #include "jswatchpoint.h"
 #include "jswin.h"
 #include "jswrapper.h"
 
 #include "asmjs/WasmSignalHandlers.h"
 #include "builtin/Promise.h"
+#include "gc/GCInternals.h"
 #include "jit/arm/Simulator-arm.h"
 #include "jit/arm64/vixl/Simulator-vixl.h"
 #include "jit/IonBuilder.h"
 #include "jit/JitCompartment.h"
 #include "jit/mips32/Simulator-mips32.h"
 #include "jit/mips64/Simulator-mips64.h"
 #include "jit/PcScriptCache.h"
 #include "js/Date.h"
@@ -373,16 +374,23 @@ void
 JSRuntime::destroyRuntime()
 {
     MOZ_ASSERT(!isHeapBusy());
     MOZ_ASSERT(childRuntimeCount == 0);
 
     fx.destroyInstance();
 
     if (gcInitialized) {
+        /*
+         * Finish any in-progress GCs first. This ensures the parseWaitingOnGC
+         * list is empty in CancelOffThreadParses.
+         */
+        if (JS::IsIncrementalGCInProgress(this))
+            FinishGC(this);
+
         /* Free source hook early, as its destructor may want to delete roots. */
         sourceHook = nullptr;
 
         /*
          * Cancel any pending, in progress or completed Ion compilations and
          * parse tasks. Waiting for AsmJS and compression tasks is done
          * synchronously (on the main thread or during parse tasks), so no
          * explicit canceling is needed for these.
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2748,20 +2748,20 @@ class MOZ_STACK_CLASS AutoSelfHostingErr
 {
     JSContext* cx_;
     JS::WarningReporter oldReporter_;
 
   public:
     explicit AutoSelfHostingErrorReporter(JSContext* cx)
       : cx_(cx)
     {
-        oldReporter_ = JS::SetWarningReporter(cx_->runtime(), selfHosting_WarningReporter);
+        oldReporter_ = JS::SetWarningReporter(cx_, selfHosting_WarningReporter);
     }
     ~AutoSelfHostingErrorReporter() {
-        JS::SetWarningReporter(cx_->runtime(), oldReporter_);
+        JS::SetWarningReporter(cx_, oldReporter_);
 
         // Exceptions in self-hosted code will usually be printed to stderr in
         // ErrorToException, but not all exceptions are handled there. For
         // instance, ReportOutOfMemory will throw the "out of memory" string
         // without going through ErrorToException. We handle these other
         // exceptions here.
         MaybePrintAndClearPendingException(cx_, stderr);
     }
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -1734,17 +1734,17 @@ nsXPCComponents_utils_Sandbox::CallOrCon
     return NS_OK;
 }
 
 nsresult
 xpc::EvalInSandbox(JSContext* cx, HandleObject sandboxArg, const nsAString& source,
                    const nsACString& filename, int32_t lineNo,
                    JSVersion jsVersion, MutableHandleValue rval)
 {
-    JS_AbortIfWrongThread(JS_GetRuntime(cx));
+    JS_AbortIfWrongThread(cx);
     rval.set(UndefinedValue());
 
     bool waiveXray = xpc::WrapperFactory::HasWaiveXrayFlag(sandboxArg);
     RootedObject sandbox(cx, js::CheckedUnwrap(sandboxArg));
     if (!sandbox || !IsSandbox(sandbox)) {
         return NS_ERROR_INVALID_ARG;
     }
 
--- a/js/xpconnect/src/XPCCallContext.cpp
+++ b/js/xpconnect/src/XPCCallContext.cpp
@@ -240,17 +240,17 @@ XPCCallContext::GetCalleeClassInfo(nsICl
     nsCOMPtr<nsIClassInfo> rval = mWrapper ? mWrapper->GetClassInfo() : nullptr;
     rval.forget(aCalleeClassInfo);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 XPCCallContext::GetJSContext(JSContext * *aJSContext)
 {
-    JS_AbortIfWrongThread(JS_GetRuntime(mJSContext));
+    JS_AbortIfWrongThread(mJSContext);
     *aJSContext = mJSContext;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 XPCCallContext::GetArgc(uint32_t* aArgc)
 {
     *aArgc = (uint32_t) mArgc;
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -3021,17 +3021,17 @@ GENERATE_JSRUNTIMEOPTION_GETTER_SETTER(I
 
 #undef GENERATE_JSCONTEXTOPTION_GETTER_SETTER
 #undef GENERATE_JSRUNTIMEOPTION_GETTER_SETTER
 
 NS_IMETHODIMP
 nsXPCComponents_Utils::SetGCZeal(int32_t aValue, JSContext* cx)
 {
 #ifdef JS_GC_ZEAL
-    JS_SetGCZeal(JS_GetRuntime(cx), uint8_t(aValue), JS_DEFAULT_ZEAL_FREQ);
+    JS_SetGCZeal(cx, uint8_t(aValue), JS_DEFAULT_ZEAL_FREQ);
 #endif
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXPCComponents_Utils::NukeSandbox(HandleValue obj, JSContext* cx)
 {
     PROFILER_LABEL_FUNC(js::ProfileEntry::Category::JS);
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -1557,16 +1557,17 @@ void XPCJSRuntime::SystemIsBeingShutDown
 
 #define JS_OPTIONS_DOT_STR "javascript.options."
 
 static void
 ReloadPrefsCallback(const char* pref, void* data)
 {
     XPCJSRuntime* runtime = reinterpret_cast<XPCJSRuntime*>(data);
     JSRuntime* rt = runtime->Runtime();
+    JSContext* cx = runtime->Context();
 
     bool safeMode = false;
     nsCOMPtr<nsIXULRuntime> xr = do_GetService("@mozilla.org/xre/runtime;1");
     if (xr) {
         xr->GetInSafeMode(&safeMode);
     }
 
     bool useBaseline = Preferences::GetBool(JS_OPTIONS_DOT_STR "baselinejit") && !safeMode;
@@ -1606,38 +1607,38 @@ ReloadPrefsCallback(const char* pref, vo
 #endif
 
 #ifdef JS_GC_ZEAL
     int32_t zeal = Preferences::GetInt(JS_OPTIONS_DOT_STR "gczeal", -1);
     int32_t zeal_frequency =
         Preferences::GetInt(JS_OPTIONS_DOT_STR "gczeal.frequency",
                             JS_DEFAULT_ZEAL_FREQ);
     if (zeal >= 0) {
-        JS_SetGCZeal(rt, (uint8_t)zeal, zeal_frequency);
+        JS_SetGCZeal(cx, (uint8_t)zeal, zeal_frequency);
     }
 #endif // JS_GC_ZEAL
 
     JS::RuntimeOptionsRef(rt).setBaseline(useBaseline)
                              .setIon(useIon)
                              .setAsmJS(useAsmJS)
                              .setWasm(useWasm)
                              .setWasmAlwaysBaseline(useWasmBaseline)
                              .setThrowOnAsmJSValidationFailure(throwOnAsmJSValidationFailure)
                              .setNativeRegExp(useNativeRegExp)
                              .setAsyncStack(useAsyncStack)
                              .setThrowOnDebuggeeWouldRun(throwOnDebuggeeWouldRun)
                              .setDumpStackOnDebuggeeWouldRun(dumpStackOnDebuggeeWouldRun)
                              .setWerror(werror)
                              .setExtraWarnings(extraWarnings);
 
-    JS_SetParallelParsingEnabled(rt, parallelParsing);
-    JS_SetOffthreadIonCompilationEnabled(rt, offthreadIonCompilation);
-    JS_SetGlobalJitCompilerOption(rt, JSJITCOMPILER_BASELINE_WARMUP_TRIGGER,
+    JS_SetParallelParsingEnabled(cx, parallelParsing);
+    JS_SetOffthreadIonCompilationEnabled(cx, offthreadIonCompilation);
+    JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_WARMUP_TRIGGER,
                                   useBaselineEager ? 0 : -1);
-    JS_SetGlobalJitCompilerOption(rt, JSJITCOMPILER_ION_WARMUP_TRIGGER,
+    JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_WARMUP_TRIGGER,
                                   useIonEager ? 0 : -1);
 }
 
 XPCJSRuntime::~XPCJSRuntime()
 {
     // Elsewhere we abort immediately if XPCJSRuntime initialization fails.
     // Therefore the runtime must be non-null.
     MOZ_ASSERT(MaybeRuntime());
@@ -3449,16 +3450,18 @@ XPCJSRuntime::Initialize()
                                                       JS::DefaultNurseryBytes);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     MOZ_ASSERT(Runtime());
     JSRuntime* runtime = Runtime();
 
+    JSContext* cx = JS_GetContext(runtime);
+
     mUnprivilegedJunkScope.init(runtime, nullptr);
     mPrivilegedJunkScope.init(runtime, nullptr);
     mCompilationScope.init(runtime, nullptr);
 
     // these jsids filled in later when we have a JSContext to work with.
     mStrIDs[0] = JSID_VOID;
 
     auto rtPrivate = new PerThreadAtomCache();
@@ -3545,24 +3548,24 @@ XPCJSRuntime::Initialize()
     // default.
     (void) kDefaultStackQuota;
 
     JS_SetNativeStackQuota(runtime,
                            kStackQuota,
                            kStackQuota - kSystemCodeBuffer,
                            kStackQuota - kSystemCodeBuffer - kTrustedScriptBuffer);
 
-    JS_SetDestroyCompartmentCallback(runtime, CompartmentDestroyedCallback);
-    JS_SetSizeOfIncludingThisCompartmentCallback(runtime, CompartmentSizeOfIncludingThisCallback);
-    JS_SetCompartmentNameCallback(runtime, CompartmentNameCallback);
+    JS_SetDestroyCompartmentCallback(cx, CompartmentDestroyedCallback);
+    JS_SetSizeOfIncludingThisCompartmentCallback(cx, CompartmentSizeOfIncludingThisCallback);
+    JS_SetCompartmentNameCallback(cx, CompartmentNameCallback);
     mPrevGCSliceCallback = JS::SetGCSliceCallback(runtime, GCSliceCallback);
     JS_AddFinalizeCallback(runtime, FinalizeCallback, nullptr);
     JS_AddWeakPointerZoneGroupCallback(runtime, WeakPointerZoneGroupCallback, this);
     JS_AddWeakPointerCompartmentCallback(runtime, WeakPointerCompartmentCallback, this);
-    JS_SetWrapObjectCallbacks(runtime, &WrapObjectCallbacks);
+    JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks);
     js::SetPreserveWrapperCallback(runtime, PreserveWrapper);
 #ifdef MOZ_ENABLE_PROFILER_SPS
     if (PseudoStack* stack = mozilla_get_pseudo_stack())
         stack->sampleRuntime(runtime);
 #endif
     JS_SetAccumulateTelemetryCallback(runtime, AccumulateTelemetryCallback);
     js::SetActivityCallback(runtime, ActivityCallback, this);
     JS_SetInterruptCallback(runtime, InterruptCallback);
--- a/js/xpconnect/src/XPCShellImpl.cpp
+++ b/js/xpconnect/src/XPCShellImpl.cpp
@@ -239,17 +239,17 @@ ReadLine(JSContext* cx, unsigned argc, V
     RootedString str(cx);
 
     /* If a prompt was specified, construct the string */
     if (args.length() > 0) {
         str = JS::ToString(cx, args[0]);
         if (!str)
             return false;
     } else {
-        str = JS_GetEmptyString(JS_GetRuntime(cx));
+        str = JS_GetEmptyString(cx);
     }
 
     /* Get a line from the infile */
     JSAutoByteString strBytes(cx, str);
     if (!strBytes || !GetLine(cx, buf, gInFile, strBytes.ptr()))
         return false;
 
     /* Strip newline character added by GetLine() */
@@ -442,17 +442,17 @@ GC(JSContext* cx, unsigned argc, Value* 
 static bool
 GCZeal(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     uint32_t zeal;
     if (!ToUint32(cx, args.get(0), &zeal))
         return false;
 
-    JS_SetGCZeal(JS_GetRuntime(cx), uint8_t(zeal), JS_DEFAULT_ZEAL_FREQ);
+    JS_SetGCZeal(cx, uint8_t(zeal), JS_DEFAULT_ZEAL_FREQ);
     args.rval().setUndefined();
     return true;
 }
 #endif
 
 static bool
 SendCommand(JSContext* cx, unsigned argc, Value* vp)
 {
--- a/layout/base/AccessibleCaretManager.cpp
+++ b/layout/base/AccessibleCaretManager.cpp
@@ -943,16 +943,17 @@ AccessibleCaretManager::GetFrameForFirst
   nsDirection aDirection, int32_t* aOutOffset, nsINode** aOutNode,
   int32_t* aOutNodeOffset) const
 {
   if (!mPresShell) {
     return nullptr;
   }
 
   MOZ_ASSERT(GetCaretMode() == CaretMode::Selection);