Merge mozilla-central to fx-team
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 08 Jun 2016 12:15:05 +0200
changeset 301039 81f4cc3f6f4c21758b63605d69b47fa6cb3c142a
parent 300828 1401fcd673699f8d6e431ccb643ab46e166da2a9 (current diff)
parent 301038 f8ad071a6e14331d73fa44c8d3108bc2b66b2174 (diff)
child 301040 54354d5ade58598ba75e4fcbe29f2cae3d3cbfa1
push id19599
push usercbook@mozilla.com
push dateWed, 08 Jun 2016 10:16:21 +0000
treeherderfx-team@81f4cc3f6f4c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone50.0a1
Merge mozilla-central to fx-team
dom/battery/test/marionette/test_deprecated_battery_level.js
dom/battery/test/marionette/test_deprecated_battery_status_charging.js
dom/battery/test/marionette/test_deprecated_battery_status_discharging.js
dom/battery/test/marionette/test_deprecated_battery_status_full.js
dom/battery/test/marionette/test_deprecated_battery_status_not_charging.js
dom/battery/test/marionette/test_deprecated_battery_status_unknown.js
dom/battery/test/test_deprecated_battery_basics.html
dom/geolocation/nsGeoGridFuzzer.cpp
dom/geolocation/nsGeoGridFuzzer.h
dom/geolocation/nsGeolocationSettings.cpp
dom/geolocation/nsGeolocationSettings.h
js/src/jit-test/tests/wasm/spec/list.js
js/src/jsapi-tests/testUncaughtError.cpp
testing/docker/desktop-test/dot-config/pip/pip.conf
testing/docker/desktop-test/dot-config/user-dirs.dirs
testing/docker/desktop-test/dot-config/user-dirs.locale
testing/docker/desktop-test/dot-pulse/default.pa
--- a/accessible/base/AccIterator.h
+++ b/accessible/base/AccIterator.h
@@ -5,33 +5,35 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_a11y_AccIterator_h__
 #define mozilla_a11y_AccIterator_h__
 
 #include "DocAccessible.h"
 #include "Filters.h"
 
+#include <memory>
+
 class nsITreeView;
 
 namespace mozilla {
 namespace a11y {
 
 /**
  * AccIterable is a basic interface for iterators over accessibles.
  */
 class AccIterable
 {
 public:
   virtual ~AccIterable() { }
   virtual Accessible* Next() = 0;
 
 private:
   friend class Relation;
-  nsAutoPtr<AccIterable> mNextIter;
+  std::unique_ptr<AccIterable> mNextIter;
 };
 
 /**
  * Allows to iterate through accessible children or subtree complying with
  * filter function.
  */
 class AccIterator : public AccIterable
 {
--- a/accessible/base/Logging.cpp
+++ b/accessible/base/Logging.cpp
@@ -612,23 +612,29 @@ logging::SelChange(nsISelection* aSelect
 
   Stack();
 }
 
 void
 logging::TreeInfo(const char* aMsg, uint32_t aExtraFlags, ...)
 {
   if (IsEnabledAll(logging::eTree | aExtraFlags)) {
-    MsgBegin("TREE", aMsg);
-
     va_list vl;
     va_start(vl, aExtraFlags);
-    const char* descr = nullptr;
-    while ((descr = va_arg(vl, const char*))) {
-      AccessibleInfo(descr, va_arg(vl, Accessible*));
+    const char* descr = va_arg(vl, const char*);
+    if (descr) {
+      Accessible* acc = va_arg(vl, Accessible*);
+      MsgBegin("TREE", "%s; doc: %p", aMsg, acc ? acc->Document() : nullptr);
+      AccessibleInfo(descr, acc);
+      while ((descr = va_arg(vl, const char*))) {
+        AccessibleInfo(descr, va_arg(vl, Accessible*));
+      }
+    }
+    else {
+      MsgBegin("TREE", aMsg);
     }
     va_end(vl);
 
     MsgEnd();
 
     if (aExtraFlags & eStack) {
       Stack();
     }
@@ -636,17 +642,17 @@ logging::TreeInfo(const char* aMsg, uint
 }
 
 void
 logging::TreeInfo(const char* aMsg, uint32_t aExtraFlags,
                   const char* aMsg1, Accessible* aAcc,
                   const char* aMsg2, nsINode* aNode)
 {
   if (IsEnabledAll(logging::eTree | logging::eVerbose)) {
-    MsgBegin("TREE", aMsg);
+    MsgBegin("TREE", "%s; doc: %p", aMsg, aAcc ? aAcc->Document() : nullptr);
     AccessibleInfo(aMsg1, aAcc);
     Accessible* acc = aAcc->Document()->GetAccessible(aNode);
     if (acc) {
       AccessibleInfo(aMsg2, acc);
     }
     else {
       Node(aMsg2, aNode);
     }
@@ -654,17 +660,17 @@ logging::TreeInfo(const char* aMsg, uint
   }
 }
 
 
 void
 logging::TreeInfo(const char* aMsg, uint32_t aExtraFlags, Accessible* aParent)
 {
   if (IsEnabledAll(logging::eTree | aExtraFlags)) {
-    MsgBegin("TREE", aMsg);
+    MsgBegin("TREE", "%s; doc: %p", aMsg, aParent->Document());
     AccessibleInfo("container", aParent);
     for (uint32_t idx = 0; idx < aParent->ChildCount(); idx++) {
       AccessibleInfo("child", aParent->GetChildAt(idx));
     }
     MsgEnd();
   }
 }
 
--- a/accessible/base/Relation.h
+++ b/accessible/base/Relation.h
@@ -4,17 +4,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/. */
 
 #ifndef mozilla_a11y_relation_h_
 #define mozilla_a11y_relation_h_
 
 #include "AccIterator.h"
 
-#include "mozilla/Move.h"
+#include <memory>
 
 namespace mozilla {
 namespace a11y {
 
 /**
  * A collection of relation targets of a certain type.  Targets are computed
  * lazily while enumerating.
  */
@@ -46,19 +46,19 @@ public:
     mLastIter = aRH.mLastIter;
     aRH.mLastIter = nullptr;
     return *this;
   }
 
   inline void AppendIter(AccIterable* aIter)
   {
     if (mLastIter)
-      mLastIter->mNextIter = aIter;
+      mLastIter->mNextIter.reset(aIter);
     else
-      mFirstIter = aIter;
+      mFirstIter.reset(aIter);
 
     mLastIter = aIter;
   }
 
   /**
    * Append the given accessible to the set of related accessibles.
    */
   inline void AppendTarget(Accessible* aAcc)
@@ -79,31 +79,30 @@ public:
 
   /**
    * compute and return the next related accessible.
    */
   inline Accessible* Next()
   {
     Accessible* target = nullptr;
 
-    // a trick nsAutoPtr deletes what it used to point to when assigned to
     while (mFirstIter && !(target = mFirstIter->Next()))
-      mFirstIter = mFirstIter->mNextIter;
+      mFirstIter = std::move(mFirstIter->mNextIter);
 
     if (!mFirstIter)
       mLastIter = nullptr;
 
     return target;
   }
 
 private:
   Relation& operator = (const Relation&) = delete;
   Relation(const Relation&) = delete;
 
-  nsAutoPtr<AccIterable> mFirstIter;
+  std::unique_ptr<AccIterable> mFirstIter;
   AccIterable* mLastIter;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif
 
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -100,17 +100,18 @@ DocAccessibleParent::AddSubtree(ProxyAcc
 
   if (mAccessibles.Contains(newChild.ID())) {
     NS_ERROR("ID already in use");
     return 0;
   }
 
   auto role = static_cast<a11y::role>(newChild.Role());
   ProxyAccessible* newProxy =
-    new ProxyAccessible(newChild.ID(), aParent, this, role);
+    new ProxyAccessible(newChild.ID(), aParent, this, role,
+                        newChild.Interfaces());
   aParent->AddChildAt(aIdxInParent, newProxy);
   mAccessibles.PutEntry(newChild.ID())->mProxy = newProxy;
   ProxyCreated(newProxy, newChild.Interfaces());
 
   uint32_t accessibles = 1;
   uint32_t kids = newChild.ChildrenCount();
   for (uint32_t i = 0; i < kids; i++) {
     uint32_t consumed = AddSubtree(newProxy, aNewTree, aIdx + accessibles, i);
--- a/accessible/ipc/ProxyAccessible.h
+++ b/accessible/ipc/ProxyAccessible.h
@@ -19,24 +19,38 @@
 namespace mozilla {
 namespace a11y {
 
 class Accessible;
 class Attribute;
 class DocAccessibleParent;
 enum class RelationType;
 
+enum Interfaces
+{
+  HYPERTEXT = 1,
+  HYPERLINK = 1 << 1,
+  IMAGE = 1 << 2,
+  VALUE = 1 << 3,
+  TABLE = 1 << 4,
+  TABLECELL = 1 << 5,
+  DOCUMENT = 1 << 6,
+  SELECTION = 1 << 7,
+  ACTION = 1 << 8,
+};
+
 class ProxyAccessible
 {
 public:
 
   ProxyAccessible(uint64_t aID, ProxyAccessible* aParent,
-                  DocAccessibleParent* aDoc, role aRole) :
+                  DocAccessibleParent* aDoc, role aRole, uint32_t aInterfaces) :
      mParent(aParent), mDoc(aDoc), mWrapper(0), mID(aID), mRole(aRole),
-     mOuterDoc(false), mIsDoc(false)
+     mOuterDoc(false), mIsDoc(false),
+     mHasValue(aInterfaces & Interfaces::VALUE)
   {
     MOZ_COUNT_CTOR(ProxyAccessible);
   }
   ~ProxyAccessible()
   {
     MOZ_COUNT_DTOR(ProxyAccessible);
     MOZ_ASSERT(!mWrapper);
   }
@@ -401,41 +415,31 @@ public:
    * Return true if this proxy is a DocAccessibleParent.
    */
   bool IsDoc() const { return mIsDoc; }
   DocAccessibleParent* AsDoc() const { return IsDoc() ? mDoc : nullptr; }
 
 protected:
   explicit ProxyAccessible(DocAccessibleParent* aThisAsDoc) :
     mParent(nullptr), mDoc(aThisAsDoc), mWrapper(0), mID(0),
-    mRole(roles::DOCUMENT), mOuterDoc(false), mIsDoc(true)
+    mRole(roles::DOCUMENT), mOuterDoc(false), mIsDoc(true), mHasValue(false)
   { MOZ_COUNT_CTOR(ProxyAccessible); }
 
 protected:
   ProxyAccessible* mParent;
 
 private:
   nsTArray<ProxyAccessible*> mChildren;
   DocAccessibleParent* mDoc;
   uintptr_t mWrapper;
   uint64_t mID;
-  role mRole : 30;
+  role mRole : 29;
   bool mOuterDoc : 1;
-  const bool mIsDoc: 1;
-};
 
-enum Interfaces
-{
-  HYPERTEXT = 1,
-  HYPERLINK = 1 << 1,
-  IMAGE = 1 << 2,
-  VALUE = 1 << 3,
-  TABLE = 1 << 4,
-  TABLECELL = 1 << 5,
-  DOCUMENT = 1 << 6,
-  SELECTION = 1 << 7,
-  ACTION = 1 << 8,
+public:
+  const bool mIsDoc: 1;
+  const bool mHasValue: 1;
 };
 
 }
 }
 
 #endif
--- a/accessible/tests/browser/.eslintrc
+++ b/accessible/tests/browser/.eslintrc
@@ -6,20 +6,23 @@
   "globals": {
     // Content scripts have global 'content' object
     "content": true,
 
     // Defined in accessible/tests/mochitest/ common.js, name.js, states.js
     "prettyName": true,
     "statesToString": true,
     "eventTypeToString": true,
+    "testAttrs": true,
+    "testAbsentAttrs": true,
     "testName": true,
     "testDescr": true,
     "testStates": true,
     "testRelation": true,
+    "testValue": true,
     "testAccessibleTree": true,
     "isAccessible": true,
     "getAccessibleDOMNodeID": true,
 
     // Defined for all accessibility browser tests.
     "addAccessibleTask": true,
     "BrowserTestUtils": true,
     "ContentTask": true,
--- a/accessible/tests/browser/browser.ini
+++ b/accessible/tests/browser/browser.ini
@@ -9,21 +9,24 @@ support-files =
   doc_treeupdate_removal.xhtml
   doc_treeupdate_visibility.html
   doc_treeupdate_whitespace.html
   !/accessible/tests/mochitest/*.js
   !/accessible/tests/mochitest/letters.gif
   !/accessible/tests/mochitest/moz.png
 
 # Caching tests
+[browser_caching_attributes.js]
 [browser_caching_description.js]
 [browser_caching_name.js]
 skip-if = e10s
 [browser_caching_relations.js]
 [browser_caching_states.js]
+[browser_caching_value.js]
+skip-if = e10s # Bug 1276721: QueryInterface is not working with proxies.
 
 # Events tests
 [browser_events_caretmove.js]
 [browser_events_hide.js]
 [browser_events_show.js]
 [browser_events_statechange.js]
 [browser_events_textchange.js]
 
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_caching_attributes.js
@@ -0,0 +1,117 @@
+/* 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/. */
+
+'use strict';
+
+/* global EVENT_FOCUS */
+
+loadScripts({ name: 'attributes.js', dir: MOCHITESTS_DIR });
+
+/**
+ * Default textbox accessible attributes.
+ */
+const defaultAttributes = {
+  'margin-top': '0px',
+  'margin-right': '0px',
+  'margin-bottom': '0px',
+  'margin-left': '0px',
+  'text-align': 'start',
+  'text-indent': '0px',
+  'id': 'textbox',
+  'tag': 'input',
+  'display': 'inline'
+};
+
+/**
+ * Test data has the format of:
+ * {
+ *   desc        {String}       description for better logging
+ *   expected    {Object}       expected attributes for given accessibles
+ *   unexpected  {Object}       unexpected attributes for given accessibles
+ *
+ *   action      {?Function*}   an optional action that yields a change in
+ *                              attributes
+ *   attrs       {?Array}       an optional list of attributes to update
+ *   waitFor     {?Number}      an optional event to wait for
+ * }
+ */
+const attributesTests = [{
+  desc: 'Initiall accessible attributes',
+  expected: defaultAttributes,
+  unexpected: {
+    'line-number': '1',
+    'explicit-name': 'true',
+    'container-live': 'polite',
+    'live': 'polite'
+  }
+}, {
+  desc: '@line-number attribute is present when textbox is focused',
+  action: function*(browser) {
+    yield invokeFocus(browser, 'textbox');
+  },
+  waitFor: EVENT_FOCUS,
+  expected: Object.assign({}, defaultAttributes, { 'line-number': '1' }),
+  unexpected: {
+    'explicit-name': 'true',
+    'container-live': 'polite',
+    'live': 'polite'
+  }
+}, {
+  desc: '@aria-live sets container-live and live attributes',
+  attrs: [{
+    attr: 'aria-live',
+    value: 'polite'
+  }],
+  expected: Object.assign({}, defaultAttributes, {
+    'line-number': '1',
+    'container-live': 'polite',
+    'live': 'polite'
+  }),
+  unexpected: {
+    'explicit-name': 'true'
+  }
+}, {
+  desc: '@title attribute sets explicit-name attribute to true',
+  attrs: [{
+    attr: 'title',
+    value: 'textbox'
+  }],
+  expected: Object.assign({}, defaultAttributes, {
+    'line-number': '1',
+    'explicit-name': 'true',
+    'container-live': 'polite',
+    'live': 'polite'
+  }),
+  unexpected: {}
+}];
+
+/**
+ * Test caching of accessible object attributes
+ */
+addAccessibleTask(`
+  <input id="textbox" value="hello">`,
+  function* (browser, accDoc) {
+    let textbox = findAccessibleChildByID(accDoc, 'textbox');
+    for (let { desc, action, attrs, expected, waitFor, unexpected } of attributesTests) {
+      info(desc);
+      let onUpdate;
+
+      if (waitFor) {
+        onUpdate = waitForEvent(waitFor, 'textbox');
+      }
+
+      if (action) {
+        yield action(browser);
+      } else if (attrs) {
+        for (let { attr, value } of attrs) {
+          yield invokeSetAttribute(browser, 'textbox', attr, value);
+        }
+      }
+
+      yield onUpdate;
+      testAttrs(textbox, expected);
+      testAbsentAttrs(textbox, unexpected);
+    }
+  }
+);
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_caching_value.js
@@ -0,0 +1,155 @@
+/* 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/. */
+
+'use strict';
+
+/* global nsIAccessibleValue, EVENT_VALUE_CHANGE, EVENT_TEXT_VALUE_CHANGE */
+
+loadScripts({ name: 'value.js', dir: MOCHITESTS_DIR });
+
+/**
+ * Test data has the format of:
+ * {
+ *   desc      {String}       description for better logging
+ *   id        {String}       given accessible DOMNode ID
+ *   expected  {String}       expected value for a given accessible
+ *   action    {?Function*}   an optional action that yields a value change
+ *   attrs     {?Array}       an optional list of attributes to update
+ *   waitFor   {?Number}      an optional value change event to wait for
+ * }
+ */
+const valueTests = [{
+  desc: 'Initially value is set to 1st element of select',
+  id: 'select',
+  expected: '1st'
+}, {
+  desc: 'Value should update to 3rd when 3 is pressed',
+  id: 'select',
+  action: function*(browser) {
+    yield invokeFocus(browser, 'select');
+    yield BrowserTestUtils.synthesizeKey('3', {}, browser);
+  },
+  waitFor: EVENT_TEXT_VALUE_CHANGE,
+  expected: '3rd'
+}, {
+  desc: 'Initially value is set to @aria-valuenow for slider',
+  id: 'slider',
+  expected: ['5', 5, 0, 7, 0]
+}, {
+  desc: 'Value should change when @aria-valuenow is updated',
+  id: 'slider',
+  attrs: [{
+    attr: 'aria-valuenow',
+    value: '6'
+  }],
+  waitFor: EVENT_VALUE_CHANGE,
+  expected: ['6', 6, 0, 7, 0]
+}, {
+  desc: 'Value should change when @aria-valuetext is set',
+  id: 'slider',
+  attrs: [{
+    attr: 'aria-valuetext',
+    value: 'plain'
+  }],
+  waitFor: EVENT_TEXT_VALUE_CHANGE,
+  expected: ['plain', 6, 0, 7, 0]
+}, {
+  desc: 'Value should change when @aria-valuetext is updated',
+  id: 'slider',
+  attrs: [{
+    attr: 'aria-valuetext',
+    value: 'hey!'
+  }],
+  waitFor: EVENT_TEXT_VALUE_CHANGE,
+  expected: ['hey!', 6, 0, 7, 0]
+}, {
+  desc: 'Value should change to @aria-valuetext when @aria-valuenow is removed',
+  id: 'slider',
+  attrs: [{
+    attr: 'aria-valuenow'
+  }],
+  expected: ['hey!', 0, 0, 7, 0]
+}, {
+  desc: 'Initially value is not set for combobox',
+  id: 'combobox',
+  expected: ''
+}, {
+  desc: 'Value should change when @value attribute is updated',
+  id: 'combobox',
+  attrs: [{
+    attr: 'value',
+    value: 'hello'
+  }],
+  waitFor: EVENT_TEXT_VALUE_CHANGE,
+  expected: 'hello'
+}, {
+  desc: 'Initially value corresponds to @value attribute for progress',
+  id: 'progress',
+  expected: '22%'
+}, {
+  desc: 'Value should change when @value attribute is updated',
+  id: 'progress',
+  attrs: [{
+    attr: 'value',
+    value: '50'
+  }],
+  waitFor: EVENT_VALUE_CHANGE,
+  expected: '50%'
+}, {
+  desc: 'Initially value corresponds to @value attribute for range',
+  id: 'range',
+  expected: '6'
+}, {
+  desc: 'Value should change when slider is moved',
+  id: 'range',
+  action: function*(browser) {
+    yield invokeFocus(browser, 'range');
+    yield BrowserTestUtils.synthesizeKey('VK_LEFT', {}, browser);
+  },
+  waitFor: EVENT_VALUE_CHANGE,
+  expected: '5'
+}];
+
+/**
+ * Test caching of accessible object values
+ */
+addAccessibleTask(`
+  <div id="slider" role="slider" aria-valuenow="5"
+       aria-valuemin="0" aria-valuemax="7">slider</div>
+  <select id="select">
+    <option>1st</option>
+    <option>2nd</option>
+    <option>3rd</option>
+  </select>
+  <input id="combobox" role="combobox" aria-autocomplete="inline">
+  <progress id="progress" value="22" max="100"></progress>
+  <input type="range" id="range" min="0" max="10" value="6">`,
+  function* (browser, accDoc) {
+    for (let { desc, id, action, attrs, expected, waitFor } of valueTests) {
+      info(desc);
+      let acc = findAccessibleChildByID(accDoc, id);
+      let onUpdate;
+
+      if (waitFor) {
+        onUpdate = waitForEvent(waitFor, id);
+      }
+
+      if (action) {
+        yield action(browser);
+      } else if (attrs) {
+        for (let { attr, value } of attrs) {
+          yield invokeSetAttribute(browser, id, attr, value);
+        }
+      }
+
+      yield onUpdate;
+      if (Array.isArray(expected)) {
+        acc.QueryInterface(nsIAccessibleValue);
+        testValue(acc, ...expected);
+      } else {
+        is(acc.value, expected, `Correct value for ${prettyName(acc)}`);
+      }
+    }
+  }
+);
--- a/accessible/tests/browser/events.js
+++ b/accessible/tests/browser/events.js
@@ -5,28 +5,32 @@
 'use strict';
 
 /* global nsIAccessibleEvent, nsIAccessibleDocument,
           nsIAccessibleStateChangeEvent, nsIAccessibleTextChangeEvent */
 
 /* exported EVENT_REORDER, EVENT_SHOW, EVENT_TEXT_INSERTED, EVENT_TEXT_REMOVED,
             EVENT_DOCUMENT_LOAD_COMPLETE, EVENT_HIDE, EVENT_TEXT_CARET_MOVED,
             EVENT_DESCRIPTION_CHANGE, EVENT_NAME_CHANGE, EVENT_STATE_CHANGE,
+            EVENT_VALUE_CHANGE, EVENT_TEXT_VALUE_CHANGE, EVENT_FOCUS,
             waitForEvent, waitForMultipleEvents */
 
 const EVENT_DOCUMENT_LOAD_COMPLETE = nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE;
 const EVENT_HIDE = nsIAccessibleEvent.EVENT_HIDE;
 const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER;
 const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW;
 const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE;
 const EVENT_TEXT_CARET_MOVED = nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED;
 const EVENT_TEXT_INSERTED = nsIAccessibleEvent.EVENT_TEXT_INSERTED;
 const EVENT_TEXT_REMOVED = nsIAccessibleEvent.EVENT_TEXT_REMOVED;
 const EVENT_DESCRIPTION_CHANGE = nsIAccessibleEvent.EVENT_DESCRIPTION_CHANGE;
 const EVENT_NAME_CHANGE = nsIAccessibleEvent.EVENT_NAME_CHANGE;
+const EVENT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_VALUE_CHANGE;
+const EVENT_TEXT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_TEXT_VALUE_CHANGE;
+const EVENT_FOCUS = nsIAccessibleEvent.EVENT_FOCUS;
 
 /**
  * Describe an event in string format.
  * @param  {nsIAccessibleEvent}  event  event to strigify
  */
 function eventToString(event) {
   let type = eventTypeToString(event.eventType);
   let info = `Event type: ${type}`;
--- a/accessible/tests/mochitest/common.js
+++ b/accessible/tests/mochitest/common.js
@@ -286,16 +286,19 @@ function getAccessible(aAccOrElmOrID, aI
 
   if (!aInterfaces)
     return acc;
 
   if (!(aInterfaces instanceof Array))
     aInterfaces = [ aInterfaces ];
 
   for (var index = 0; index < aInterfaces.length; index++) {
+    if (acc instanceof aInterfaces[index]) {
+      continue;
+    }
     try {
       acc.QueryInterface(aInterfaces[index]);
     } catch (e) {
       if (!(aDoNotFailIf & DONOTFAIL_IF_NO_INTERFACE))
         ok(false, "Can't query " + aInterfaces[index] + " for " + aAccOrElmOrID);
 
       return null;
     }
--- a/accessible/xpcom/xpcAccessibleDocument.cpp
+++ b/accessible/xpcom/xpcAccessibleDocument.cpp
@@ -208,17 +208,22 @@ xpcAccessibleDocument::GetXPCAccessible(
   }
 
   xpcAccessibleGeneric* acc = mCache.GetWeak(aProxy);
   if (acc) {
     return acc;
   }
 
   // XXX support exposing optional interfaces.
-  acc = new xpcAccessibleGeneric(aProxy, 0);
+  uint8_t interfaces = 0;
+  if (aProxy->mHasValue) {
+    interfaces |= eValue;
+  }
+
+  acc = new xpcAccessibleGeneric(aProxy, interfaces);
   mCache.Put(aProxy, acc);
 
   return acc;
 }
 
 void
 xpcAccessibleDocument::Shutdown()
 {
--- a/accessible/xpcom/xpcAccessibleGeneric.h
+++ b/accessible/xpcom/xpcAccessibleGeneric.h
@@ -33,29 +33,18 @@ public:
     if (aInternal->IsSelect())
       mSupportedIfaces |= eSelectable;
     if (aInternal->HasNumericValue())
       mSupportedIfaces |= eValue;
     if (aInternal->IsLink())
       mSupportedIfaces |= eHyperLink;
   }
 
-  xpcAccessibleGeneric(ProxyAccessible* aProxy, uint32_t aInterfaces) :
-    mIntl(aProxy), mSupportedIfaces(0)
-  {
-    if (aInterfaces & Interfaces::SELECTION) {
-      mSupportedIfaces |= eSelectable;
-    }
-      if (aInterfaces & Interfaces::VALUE) {
-        mSupportedIfaces |= eValue;
-      }
-      if (aInterfaces & Interfaces::HYPERLINK) {
-        mSupportedIfaces |= eHyperLink;
-      }
-    }
+  xpcAccessibleGeneric(ProxyAccessible* aProxy, uint8_t aInterfaces) :
+    mIntl(aProxy), mSupportedIfaces(aInterfaces) {}
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(xpcAccessibleGeneric, nsIAccessible)
 
   // nsIAccessible
   virtual Accessible* ToInternalAccessible() const final override;
 
   // xpcAccessibleGeneric
--- a/browser/app/nsBrowserApp.cpp
+++ b/browser/app/nsBrowserApp.cpp
@@ -331,17 +331,17 @@ int main(int argc, char* argv[], char* e
 {
 #ifdef MOZ_BROWSER_CAN_BE_CONTENTPROC
   // We are launching as a content process, delegate to the appropriate
   // main
   if (argc > 1 && IsArg(argv[1], "contentproc")) {
 #if defined(XP_WIN) && defined(MOZ_SANDBOX)
     // We need to initialize the sandbox TargetServices before InitXPCOMGlue
     // because we might need the sandbox broker to give access to some files.
-    if (!sandboxing::GetInitializedTargetServices()) {
+    if (IsSandboxedProcess() && !sandboxing::GetInitializedTargetServices()) {
       Output("Failed to initialize the sandbox target services.");
       return 255;
     }
 #endif
 
     nsresult rv = InitXPCOMGlue(argv[0], nullptr);
     if (NS_FAILED(rv)) {
       return 255;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -826,41 +826,56 @@ function _loadURIWithFlags(browser, uri,
   let mustChangeProcess = gMultiProcessBrowser &&
                           !E10SUtils.canLoadURIInProcess(uri, process);
   if ((!wasRemote && !mustChangeProcess) ||
       (wasRemote && mustChangeProcess)) {
     browser.inLoadURI = true;
   }
   try {
     if (!mustChangeProcess) {
+      if (params.userContextId) {
+        browser.webNavigation.setOriginAttributesBeforeLoading({ userContextId: params.userContextId });
+      }
+
       browser.webNavigation.loadURIWithOptions(uri, flags,
                                                referrer, referrerPolicy,
                                                postData, null, null);
     } else {
       if (postData) {
         postData = NetUtil.readInputStreamToString(postData, postData.available());
       }
 
-      LoadInOtherProcess(browser, {
+      let loadParams = {
         uri: uri,
         flags: flags,
         referrer: referrer ? referrer.spec : null,
         referrerPolicy: referrerPolicy,
-        postData: postData,
-      });
+        postData: postData
+      }
+
+      if (params.userContextId) {
+        loadParams.userContextId = params.userContextId;
+      }
+
+      LoadInOtherProcess(browser, loadParams);
     }
   } catch (e) {
     // If anything goes wrong when switching remoteness, just switch remoteness
     // manually and load the URI.
     // We might lose history that way but at least the browser loaded a page.
     // This might be necessary if SessionStore wasn't initialized yet i.e.
     // when the homepage is a non-remote page.
     if (mustChangeProcess) {
       Cu.reportError(e);
       gBrowser.updateBrowserRemotenessByURL(browser, uri);
+
+      if (params.userContextId) {
+        browser.webNavigation.setOriginAttributesBeforeLoading({ userContextId: params.userContextId });
+      }
+
       browser.webNavigation.loadURIWithOptions(uri, flags, referrer, referrerPolicy,
                                                postData, null, null);
     } else {
       throw e;
     }
   } finally {
     if ((!wasRemote && !mustChangeProcess) ||
         (wasRemote && mustChangeProcess)) {
@@ -1121,29 +1136,32 @@ var gBrowserInit = {
         } catch(e) {
           Cu.reportError(e);
         }
       }
       // window.arguments[2]: referrer (nsIURI | string)
       //                 [3]: postData (nsIInputStream)
       //                 [4]: allowThirdPartyFixup (bool)
       //                 [5]: referrerPolicy (int)
+      //                 [6]: userContextId (int)
       else if (window.arguments.length >= 3) {
         let referrerURI = window.arguments[2];
         if (typeof(referrerURI) == "string") {
           try {
             referrerURI = makeURI(referrerURI);
           } catch (e) {
             referrerURI = null;
           }
         }
         let referrerPolicy = (window.arguments[5] != undefined ?
             window.arguments[5] : Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT);
+        let userContextId = (window.arguments[6] != undefined ?
+            window.arguments[6] : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID);
         loadURI(uriToLoad, referrerURI, window.arguments[3] || null,
-                window.arguments[4] || false, referrerPolicy);
+                window.arguments[4] || false, referrerPolicy, userContextId);
         window.focus();
       }
       // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3.
       // Such callers expect that window.arguments[0] is handled as a single URI.
       else {
         loadOneOrMoreURIs(uriToLoad);
       }
     }
@@ -1994,23 +2012,25 @@ function BrowserCloseTabOrWindow() {
 }
 
 function BrowserTryToCloseWindow()
 {
   if (WindowIsClosing())
     window.close();     // WindowIsClosing does all the necessary checks
 }
 
-function loadURI(uri, referrer, postData, allowThirdPartyFixup, referrerPolicy) {
+function loadURI(uri, referrer, postData, allowThirdPartyFixup, referrerPolicy,
+                 userContextId) {
   try {
     openLinkIn(uri, "current",
                { referrerURI: referrer,
                  referrerPolicy: referrerPolicy,
                  postData: postData,
-                 allowThirdPartyFixup: allowThirdPartyFixup });
+                 allowThirdPartyFixup: allowThirdPartyFixup,
+                 userContextId: userContextId });
   } catch (e) {}
 }
 
 /**
  * Given a urlbar value, discerns between URIs, keywords and aliases.
  *
  * @param url
  *        The urlbar value.
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -882,17 +882,22 @@ var UserContextIdNotifier = {
   },
 
   handleEvent(aEvent) {
     // When the window is created, we want to inform the tabbrowser about
     // the userContextId in use in order to update the UI correctly.
     // Just because we cannot change the userContextId from an active docShell,
     // we don't need to check DOMContentLoaded again.
     this.uninit();
-    let userContextId = content.document.nodePrincipal.originAttributes.userContextId;
+
+    // We use the docShell because content.document can have been loaded before
+    // setting the originAttributes.
+    let loadContext = docShell.QueryInterface(Ci.nsILoadContext);
+    let userContextId = loadContext.originAttributes.userContextId;
+
     sendAsyncMessage("Browser:WindowCreated", { userContextId });
   }
 };
 
 UserContextIdNotifier.init();
 
 ExtensionContent.init(this);
 addEventListener("unload", () => {
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -209,17 +209,17 @@ skip-if = buildapp == 'mulet' || e10s # 
 [browser_bug537474.js]
 [browser_bug550565.js]
 [browser_bug553455.js]
 skip-if = buildapp == 'mulet' # Bug 1066070 - I don't think either popup notifications nor addon install stuff works on mulet?
 [browser_bug555224.js]
 [browser_bug555767.js]
 [browser_bug559991.js]
 [browser_bug561636.js]
-skip-if = os == 'win' # bug 1057615
+skip-if = true # bug 1057615
 [browser_bug563588.js]
 [browser_bug565575.js]
 [browser_bug565667.js]
 skip-if = toolkit != "cocoa"
 [browser_bug567306.js]
 subsuite = clipboard
 [browser_bug575561.js]
 [browser_bug575830.js]
@@ -232,17 +232,17 @@ subsuite = clipboard
 [browser_bug581253.js]
 [browser_bug585558.js]
 [browser_bug585785.js]
 [browser_bug585830.js]
 [browser_bug590206.js]
 [browser_bug592338.js]
 [browser_bug594131.js]
 [browser_bug595507.js]
-skip-if = os == 'win' # bug 1057615
+skip-if = true # bug 1057615
 [browser_bug596687.js]
 [browser_bug597218.js]
 [browser_bug609700.js]
 [browser_bug623893.js]
 [browser_bug624734.js]
 [browser_bug633691.js]
 [browser_bug647886.js]
 skip-if = buildapp == 'mulet'
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -269,22 +269,27 @@ function openLinkIn(url, where, params) 
                             createInstance(Ci.nsISupportsString);
       referrerURISupports.data = aReferrerURI.spec;
     }
 
     var referrerPolicySupports = Cc["@mozilla.org/supports-PRUint32;1"].
                                  createInstance(Ci.nsISupportsPRUint32);
     referrerPolicySupports.data = aReferrerPolicy;
 
+    var userContextIdSupports = Cc["@mozilla.org/supports-PRUint32;1"].
+                                 createInstance(Ci.nsISupportsPRUint32);
+    userContextIdSupports.data = aUserContextId;
+
     sa.AppendElement(wuri);
     sa.AppendElement(charset);
     sa.AppendElement(referrerURISupports);
     sa.AppendElement(aPostData);
     sa.AppendElement(allowThirdPartyFixupSupports);
     sa.AppendElement(referrerPolicySupports);
+    sa.AppendElement(userContextIdSupports);
 
     let features = "chrome,dialog=no,all";
     if (aIsPrivate) {
       features += ",private";
     }
 
     Services.ww.openWindow(w || window, getBrowserURL(), null, features, sa);
     return;
@@ -347,16 +352,17 @@ function openLinkIn(url, where, params) 
       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ERROR_LOAD_CHANGES_RV;
     }
 
     w.gBrowser.loadURIWithFlags(url, {
       flags: flags,
       referrerURI: aNoReferrer ? null : aReferrerURI,
       referrerPolicy: aReferrerPolicy,
       postData: aPostData,
+      userContextId: aUserContextId
     });
     break;
   case "tabshifted":
     loadInBackground = !loadInBackground;
     // fall through
   case "tab":
     w.gBrowser.loadOneTab(url, {
       referrerURI: aReferrerURI,
--- a/browser/components/contextualidentity/test/browser/browser.ini
+++ b/browser/components/contextualidentity/test/browser/browser.ini
@@ -12,8 +12,9 @@ support-files =
 skip-if = os == "mac" || os == "win" # Intermittent failure - bug 1268276
 [browser_windowName.js]
 tags = openwindow
 [browser_windowOpen.js]
 tags = openwindow
 [browser_serviceworkers.js]
 [browser_broadcastchannel.js]
 [browser_blobUrl.js]
+[browser_imageCache.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_imageCache.js
@@ -0,0 +1,59 @@
+let Cu = Components.utils;
+let {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
+
+const NUM_USER_CONTEXTS = 3;
+
+let gHits = 0;
+
+let server = new HttpServer();
+server.registerPathHandler('/image.png', imageHandler);
+server.registerPathHandler('/file.html', fileHandler);
+server.start(-1);
+
+let BASE_URI = 'http://localhost:' + server.identity.primaryPort;
+let IMAGE_URI = BASE_URI + '/image.png';
+let FILE_URI = BASE_URI + '/file.html';
+
+function imageHandler(metadata, response) {
+  gHits++;
+  response.setHeader("Cache-Control", "max-age=10000", false);
+  response.setStatusLine(metadata.httpVersion, 200, "OK");
+  response.setHeader("Content-Type", "image/png", false);
+  var body = "iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII=";
+  response.bodyOutputStream.write(body, body.length);
+}
+
+function fileHandler(metadata, response) {
+  response.setStatusLine(metadata.httpVersion, 200, "OK");
+  response.setHeader("Content-Type", "text/html", false);
+  let body = `<html><body><image src=${IMAGE_URI}></body></html>`;
+  response.bodyOutputStream.write(body, body.length);
+}
+
+add_task(function* setup() {
+  // make sure userContext is enabled.
+  yield SpecialPowers.pushPrefEnv({"set": [["privacy.userContext.enabled", true]]});
+});
+
+// opens `uri' in a new tab with the provided userContextId and focuses it.
+// returns the newly opened tab
+function* openTabInUserContext(uri, userContextId) {
+  // open the tab in the correct userContextId
+  let tab = gBrowser.addTab(uri, {userContextId});
+
+  // select tab and make sure its browser is focused
+  gBrowser.selectedTab = tab;
+  tab.ownerDocument.defaultView.focus();
+
+  let browser = gBrowser.getBrowserForTab(tab);
+  yield BrowserTestUtils.browserLoaded(browser);
+  return tab;
+}
+
+add_task(function* test() {
+  for (let userContextId = 0; userContextId < NUM_USER_CONTEXTS; userContextId++) {
+    let tab = yield* openTabInUserContext(FILE_URI, userContextId);
+    gBrowser.removeTab(tab);
+  }
+  is(gHits, NUM_USER_CONTEXTS, "should get an image request for each user contexts");
+});
--- a/browser/components/feeds/FeedConverter.js
+++ b/browser/components/feeds/FeedConverter.js
@@ -250,18 +250,21 @@ FeedConverter.prototype = {
         // Store the result in the result service so that the display
         // page can access it.
         feedService.addFeedResult(result);
 
         // Now load the actual XUL document.
         let aboutFeedsURI = ios.newURI("about:feeds", null, null);
         chromeChannel = ios.newChannelFromURIWithLoadInfo(aboutFeedsURI, loadInfo);
         chromeChannel.originalURI = result.uri;
+
+        // carry the origin attributes from the channel that loaded the feed.
         chromeChannel.owner =
-          Services.scriptSecurityManager.createCodebasePrincipal(aboutFeedsURI, {});
+          Services.scriptSecurityManager.createCodebasePrincipal(aboutFeedsURI,
+                                                                 loadInfo.originAttributes);
       } else {
         chromeChannel = ios.newChannelFromURIWithLoadInfo(result.uri, loadInfo);
       }
 
       chromeChannel.loadGroup = this._request.loadGroup;
       chromeChannel.asyncOpen(this._listener, null);
     }
     finally {
@@ -504,26 +507,23 @@ GenericProtocolHandler.prototype = {
     // case we create a nested URI for the realURI) or feed://example.com, in
     // which case we create a nested URI for the real protocol which is http.
 
     let scheme = this._scheme + ":";
     if (spec.substr(0, scheme.length) != scheme)
       throw Cr.NS_ERROR_MALFORMED_URI;
 
     let prefix = spec.substr(scheme.length, 2) == "//" ? "http:" : "";
-    let inner = Cc["@mozilla.org/network/io-service;1"].
-                getService(Ci.nsIIOService).newURI(spec.replace(scheme, prefix),
-                                                   originalCharset, baseURI);
-    let netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
-    const URI_INHERITS_SECURITY_CONTEXT = Ci.nsIProtocolHandler
-                                            .URI_INHERITS_SECURITY_CONTEXT;
-    if (netutil.URIChainHasFlags(inner, URI_INHERITS_SECURITY_CONTEXT))
+    let inner = Services.io.newURI(spec.replace(scheme, prefix),
+                                   originalCharset, baseURI);
+
+    if (!["http", "https"].includes(inner.scheme))
       throw Cr.NS_ERROR_MALFORMED_URI;
 
-    let uri = netutil.newSimpleNestedURI(inner);
+    let uri = Services.io.QueryInterface(Ci.nsINetUtil).newSimpleNestedURI(inner);
     uri.spec = inner.spec.replace(prefix, scheme);
     return uri;
   },
 
   newChannel2(aUri, aLoadInfo) {
     let inner = aUri.QueryInterface(Ci.nsINestedURI).innerURI;
     let channel = Cc["@mozilla.org/network/io-service;1"].
                   getService(Ci.nsIIOService).
--- a/browser/components/feeds/test/unit/test_355473.js
+++ b/browser/components/feeds/test/unit/test_355473.js
@@ -30,12 +30,14 @@ function run_test() {
   do_check_true(httpFeedURI.equals(httpChannel.originalURI));
   do_check_true(httpsFeedURI.equals(httpsChannel.originalURI));
 
   // actually using the horrible mess that's a feed: URI is suicidal
   do_check_true(httpURI.equals(feedChannel.URI));
   do_check_true(httpURI.equals(httpChannel.URI));
   do_check_true(httpsURI.equals(httpsChannel.URI));
 
-  // check that we don't throw creating feed: URIs from file and ftp
-  var ftpFeedURI = ios.newURI("feed:ftp://example.com/feed.xml", null, null);
-  var fileFeedURI = ios.newURI("feed:file:///var/feed.xml", null, null);
+  // check that we throw creating feed: URIs from file and ftp
+  Assert.throws(function() { ios.newURI("feed:ftp://example.com/feed.xml", null, null); },
+      "Should throw an exception when trying to create a feed: URI with an ftp: inner");
+  Assert.throws(function() { ios.newURI("feed:file:///var/feed.xml", null, null); },
+      "Should throw an exception when trying to create a feed: URI with a file: inner");
 }
--- a/browser/components/privatebrowsing/test/browser/browser.ini
+++ b/browser/components/privatebrowsing/test/browser/browser.ini
@@ -9,16 +9,17 @@ support-files =
   browser_privatebrowsing_localStorage_page1.html
   browser_privatebrowsing_localStorage_page2.html
   browser_privatebrowsing_placesTitleNoUpdate.html
   browser_privatebrowsing_protocolhandler_page.html
   browser_privatebrowsing_windowtitle_page.html
   head.js
   popup.html
   title.sjs
+  empty_file.html
 
 [browser_privatebrowsing_DownloadLastDirWithCPS.js]
 [browser_privatebrowsing_about.js]
 tags = trackingprotection
 [browser_privatebrowsing_aboutHomeButtonAfterWindowClose.js]
 [browser_privatebrowsing_aboutSessionRestore.js]
 [browser_privatebrowsing_cache.js]
 [browser_privatebrowsing_certexceptionsui.js]
@@ -41,8 +42,9 @@ tags = trackingprotection
 [browser_privatebrowsing_sidebar.js]
 [browser_privatebrowsing_theming.js]
 [browser_privatebrowsing_ui.js]
 [browser_privatebrowsing_urlbarfocus.js]
 [browser_privatebrowsing_windowtitle.js]
 [browser_privatebrowsing_zoom.js]
 [browser_privatebrowsing_zoomrestore.js]
 [browser_privatebrowsing_newtab_from_popup.js]
+[browser_privatebrowsing_blobUrl.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_blobUrl.js
@@ -0,0 +1,48 @@
+"use strict";
+
+// Here we want to test that blob URLs are not available between private and
+// non-private browsing.
+
+const BASE_URI = "http://mochi.test:8888/browser/browser/components/"
+  + "privatebrowsing/test/browser/empty_file.html";
+
+add_task(function* test() {
+  info("Creating a normal window...");
+  let win = yield BrowserTestUtils.openNewBrowserWindow();
+  let tab = win.gBrowser.selectedBrowser;
+  tab.loadURI(BASE_URI);
+  yield BrowserTestUtils.browserLoaded(tab);
+
+  let blobURL;
+
+  info("Creating a blob URL...");
+  yield ContentTask.spawn(tab, null, function() {
+    return Promise.resolve(content.window.URL.createObjectURL(new content.window.Blob([123])));
+  }).then(newURL => { blobURL = newURL });
+
+  info("Blob URL: " + blobURL);
+
+  info("Creating a private window...");
+  let privateWin = yield BrowserTestUtils.openNewBrowserWindow({ private: true });
+  let privateTab = privateWin.gBrowser.selectedBrowser;
+  privateTab.loadURI(BASE_URI);
+  yield BrowserTestUtils.browserLoaded(privateTab);
+
+  yield ContentTask.spawn(privateTab, blobURL, function(url) {
+    return new Promise(resolve => {
+      var xhr = new content.window.XMLHttpRequest();
+      xhr.open("GET", url);
+      try {
+        xhr.send();
+        resolve("OpenSucceeded");
+      } catch(e) {
+        resolve("OpenThrew");
+      }
+    });
+  }).then(status => {
+    is(status, "OpenThrew", "Using a blob URI from one user context id in another should not work");
+  });
+
+  yield BrowserTestUtils.closeWindow(win);
+  yield BrowserTestUtils.closeWindow(privateWin);
+});
\ No newline at end of file
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent.js
@@ -9,37 +9,29 @@
 // Step 1: create new tab, load a page that sets test=value in non-private storage
 // Step 2: create a new tab, load a page that sets test2=value2 in private storage
 // Step 3: load a page in the tab from step 1 that checks the value of test2 is value2 and the total count in non-private storage is 1
 // Step 4: load a page in the tab from step 2 that checks the value of test is value and the total count in private storage is 1
 
 add_task(function test() {
   let prefix = 'http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent_page.html';
 
-  function setUsePrivateBrowsing(browser, val) {
-    return ContentTask.spawn(browser, val, function* (val) {
-      docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing = val;
-    });
-  };
-
   function getElts(browser) {
     return browser.contentTitle.split('|');
   };
 
   // Step 1
-  gBrowser.selectedTab = gBrowser.addTab(prefix + '?action=set&name=test&value=value&initial=true');
   let non_private_browser = gBrowser.selectedBrowser;
+  non_private_browser.loadURI(prefix + '?action=set&name=test&value=value&initial=true');
   yield BrowserTestUtils.browserLoaded(non_private_browser);
 
 
   // Step 2
-  gBrowser.selectedTab = gBrowser.addTab();
-  let private_browser = gBrowser.selectedBrowser;
-  yield BrowserTestUtils.browserLoaded(private_browser);
-  yield setUsePrivateBrowsing(private_browser, true);
+  let private_window = yield BrowserTestUtils.openNewBrowserWindow({ private : true });
+  let private_browser = private_window.getBrowser().selectedBrowser;
   private_browser.loadURI(prefix + '?action=set&name=test2&value=value2');
   yield BrowserTestUtils.browserLoaded(private_browser);
 
 
   // Step 3
   non_private_browser.loadURI(prefix + '?action=get&name=test2');
   yield BrowserTestUtils.browserLoaded(non_private_browser);
   let elts = yield getElts(non_private_browser);
@@ -50,35 +42,41 @@ add_task(function test() {
   // Step 4
   private_browser.loadURI(prefix + '?action=get&name=test');
   yield BrowserTestUtils.browserLoaded(private_browser);
   elts = yield getElts(private_browser);
   isnot(elts[0], 'value', "private window shouldn't see public storage");
   is(elts[1], '1', "private window should only see private items");
 
 
-  // Make the private tab public again, which should clear the
+  // Reopen the private window again, without privateBrowsing, which should clear the
   // the private storage.
-  yield setUsePrivateBrowsing(private_browser, false);
+  private_window.close();
+  private_window = yield BrowserTestUtils.openNewBrowserWindow({ private : false });
+  private_browser = null;
   yield new Promise(resolve => Cu.schedulePreciseGC(resolve));
+  private_browser = private_window.getBrowser().selectedBrowser;
 
   private_browser.loadURI(prefix + '?action=get&name=test2');
   yield BrowserTestUtils.browserLoaded(private_browser);
   elts = yield getElts(private_browser);
   isnot(elts[0], 'value2', "public window shouldn't see cleared private storage");
   is(elts[1], '1', "public window should only see public items");
 
 
   // Making it private again should clear the storage and it shouldn't
   // be able to see the old private storage as well.
-  yield setUsePrivateBrowsing(private_browser, true);
+  private_window.close();
+  private_window = yield BrowserTestUtils.openNewBrowserWindow({ private : true });
+  private_browser = null;
+  yield new Promise(resolve => Cu.schedulePreciseGC(resolve));
+  private_browser = private_window.getBrowser().selectedBrowser;
 
   private_browser.loadURI(prefix + '?action=set&name=test3&value=value3');
-  BrowserTestUtils.browserLoaded(private_browser);
+  yield BrowserTestUtils.browserLoaded(private_browser);
   elts = yield getElts(private_browser);
   is(elts[1], '1', "private window should only see new private items");
 
   // Cleanup.
   non_private_browser.loadURI(prefix + '?final=true');
   yield BrowserTestUtils.browserLoaded(non_private_browser);
-  gBrowser.removeCurrentTab();
-  gBrowser.removeCurrentTab();
+  private_window.close();
 });
new file mode 100644
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/empty_file.html
@@ -0,0 +1,1 @@
+<html><body></body></html>
\ No newline at end of file
--- a/browser/components/sessionstore/ContentRestore.jsm
+++ b/browser/components/sessionstore/ContentRestore.jsm
@@ -199,16 +199,21 @@ ContentRestoreInternal.prototype = {
         // same state it was before the load started then trigger the load.
         let referrer = loadArguments.referrer ?
                        Utils.makeURI(loadArguments.referrer) : null;
         let referrerPolicy = ('referrerPolicy' in loadArguments
             ? loadArguments.referrerPolicy
             : Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT);
         let postData = loadArguments.postData ?
                        Utils.makeInputStream(loadArguments.postData) : null;
+
+        if (loadArguments.userContextId) {
+          webNavigation.setOriginAttributesBeforeLoading({ userContextId: loadArguments.userContextId });
+        }
+
         webNavigation.loadURIWithOptions(loadArguments.uri, loadArguments.flags,
                                          referrer, referrerPolicy, postData,
                                          null, null);
       } else if (tabData.userTypedValue && tabData.userTypedClear) {
         // If the user typed a URL into the URL bar and hit enter right before
         // we crashed, we want to start loading that page again. A non-zero
         // userTypedClear value means that the load had started.
         // Load userTypedValue and fix up the URL if it's partial/broken.
--- a/browser/themes/shared/devedition.inc.css
+++ b/browser/themes/shared/devedition.inc.css
@@ -147,17 +147,17 @@
 
 #tabbrowser-tabs,
 #TabsToolbar,
 #browser-panel {
   background: var(--chrome-background-color);
   color: var(--chrome-color);
 }
 
-#navigator-toolbox::after {
+#navigator-toolbox:-moz-lwtheme::after {
   background: var(--chrome-navigator-toolbox-separator-color);
 }
 
 #navigator-toolbox > toolbar:not(#TabsToolbar):not(#toolbar-menubar),
 .browserContainer > findbar,
 #browser-bottombox {
   background-color: var(--chrome-secondary-background-color) !important;
   background-image: none !important;
--- a/build/autoconf/android.m4
+++ b/build/autoconf/android.m4
@@ -43,25 +43,25 @@ case "$target" in
 
     if test -d "$android_platform" ; then
         AC_MSG_RESULT([$android_platform])
     else
         AC_MSG_ERROR([not found. Please check your NDK. With the current configuration, it should be in $android_platform])
     fi
 
     CPPFLAGS="-idirafter $android_platform/usr/include $CPPFLAGS"
-    CFLAGS="-mandroid -fno-short-enums -fno-exceptions $CFLAGS"
-    CXXFLAGS="-mandroid -fno-short-enums -fno-exceptions $CXXFLAGS"
+    CFLAGS="-fno-short-enums -fno-exceptions $CFLAGS"
+    CXXFLAGS="-fno-short-enums -fno-exceptions $CXXFLAGS"
     ASFLAGS="-idirafter $android_platform/usr/include -DANDROID $ASFLAGS"
 
     dnl Add -llog by default, since we use it all over the place.
     dnl Add --allow-shlib-undefined, because libGLESv2 links to an
     dnl undefined symbol (present on the hardware, just not in the
     dnl NDK.)
-    LDFLAGS="-mandroid -L$android_platform/usr/lib -Wl,-rpath-link=$android_platform/usr/lib --sysroot=$android_platform -llog -Wl,--allow-shlib-undefined $LDFLAGS"
+    LDFLAGS="-L$android_platform/usr/lib -Wl,-rpath-link=$android_platform/usr/lib --sysroot=$android_platform -llog -Wl,--allow-shlib-undefined $LDFLAGS"
     ANDROID_PLATFORM="${android_platform}"
 
     AC_DEFINE(ANDROID)
     AC_SUBST(ANDROID_PLATFORM)
 
     ;;
 esac
 
--- a/build/autoconf/compiler-opts.m4
+++ b/build/autoconf/compiler-opts.m4
@@ -440,32 +440,34 @@ AC_DEFUN([MOZ_SET_WARNINGS_CFLAGS],
     _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Wpointer-arith"
     _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Wsign-compare"
     _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Wtype-limits"
     _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Wunreachable-code"
 
     # -Wclass-varargs - catches objects passed by value to variadic functions.
     # -Wloop-analysis - catches issues around loops
     # -Wnon-literal-null-conversion - catches expressions used as a null pointer constant
+    # -Wstring-conversion - catches string literals used in boolean expressions
     # -Wthread-safety - catches inconsistent use of mutexes
     #
     # XXX: at the time of writing, the version of clang used on the OS X test
     # machines has a bug that causes it to reject some valid files if both
     # -Wnon-literal-null-conversion and -Wsometimes-uninitialized are
     # specified. We work around this by instead using
     # -Werror=non-literal-null-conversion, but we only do that when
     # --enable-warnings-as-errors is specified so that no unexpected fatal
     # warnings are produced.
     MOZ_C_SUPPORTS_WARNING(-W, class-varargs, ac_c_has_wclass_varargs)
     MOZ_C_SUPPORTS_WARNING(-W, loop-analysis, ac_c_has_wloop_analysis)
 
     if test "$MOZ_ENABLE_WARNINGS_AS_ERRORS"; then
         MOZ_C_SUPPORTS_WARNING(-Werror=, non-literal-null-conversion, ac_c_has_non_literal_null_conversion)
     fi
 
+    MOZ_C_SUPPORTS_WARNING(-W, string-conversion, ac_c_has_wstring_conversion)
     MOZ_C_SUPPORTS_WARNING(-W, thread-safety, ac_c_has_wthread_safety)
 
     # Turn off some non-useful warnings that -Wall turns on.
 
     # Prevent the following GCC warnings from being treated as errors:
     # -Wmaybe-uninitialized - too many false positives
     # -Wdeprecated-declarations - we don't want our builds held hostage when a
     #   platform-specific API becomes deprecated.
@@ -506,16 +508,17 @@ AC_DEFUN([MOZ_SET_WARNINGS_CXXFLAGS],
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wtype-limits"
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wunreachable-code"
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wwrite-strings"
 
     # -Wclass-varargs - catches objects passed by value to variadic functions.
     # -Wimplicit-fallthrough - catches unintentional switch case fallthroughs
     # -Wloop-analysis - catches issues around loops
     # -Wnon-literal-null-conversion - catches expressions used as a null pointer constant
+    # -Wstring-conversion - catches string literals used in boolean expressions
     # -Wthread-safety - catches inconsistent use of mutexes
     #
     # XXX: at the time of writing, the version of clang used on the OS X test
     # machines has a bug that causes it to reject some valid files if both
     # -Wnon-literal-null-conversion and -Wsometimes-uninitialized are
     # specified. We work around this by instead using
     # -Werror=non-literal-null-conversion, but we only do that when
     # --enable-warnings-as-errors is specified so that no unexpected fatal
@@ -527,16 +530,17 @@ AC_DEFUN([MOZ_SET_WARNINGS_CXXFLAGS],
     MOZ_CXX_SUPPORTS_WARNING(-W, class-varargs, ac_cxx_has_wclass_varargs)
     MOZ_CXX_SUPPORTS_WARNING(-W, implicit-fallthrough, ac_cxx_has_wimplicit_fallthrough)
     MOZ_CXX_SUPPORTS_WARNING(-W, loop-analysis, ac_cxx_has_wloop_analysis)
 
     if test "$MOZ_ENABLE_WARNINGS_AS_ERRORS"; then
         MOZ_CXX_SUPPORTS_WARNING(-Werror=, non-literal-null-conversion, ac_cxx_has_non_literal_null_conversion)
     fi
 
+    MOZ_CXX_SUPPORTS_WARNING(-W, string-conversion, ac_cxx_has_wstring_conversion)
     MOZ_CXX_SUPPORTS_WARNING(-W, thread-safety, ac_cxx_has_wthread_safety)
 
     # Turn off some non-useful warnings that -Wall turns on.
 
     # -Wno-invalid-offsetof - we use offsetof on non-POD types frequently
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wno-invalid-offsetof"
 
     # -Wno-inline-new-delete - we inline 'new' and 'delete' in mozalloc
--- a/devtools/client/netmonitor/test/browser_net_security-state.js
+++ b/devtools/client/netmonitor/test/browser_net_security-state.js
@@ -8,26 +8,19 @@
  * state.
  */
 
 add_task(function* () {
   const EXPECTED_SECURITY_STATES = {
     "test1.example.com": "security-state-insecure",
     "example.com": "security-state-secure",
     "nocert.example.com": "security-state-broken",
-    "rc4.example.com": "security-state-weak",
     "localhost": "security-state-local",
   };
 
-  yield new promise(resolve => {
-    SpecialPowers.pushPrefEnv({"set": [
-      ["security.tls.insecure_fallback_hosts", "rc4.example.com"]
-    ]}, resolve);
-  });
-
   let [tab, debuggee, monitor] = yield initNetMonitor(CUSTOM_GET_URL);
   let { $, EVENTS, NetMonitorView } = monitor.panelWin;
   let { RequestsMenu } = NetMonitorView;
   RequestsMenu.lazyUpdate = false;
 
   yield performRequests();
 
   for (let item of RequestsMenu.items) {
@@ -46,17 +39,16 @@ add_task(function* () {
 
   yield teardown(monitor);
 
   /**
    * A helper that performs requests to
    *  - https://nocert.example.com (broken)
    *  - https://example.com (secure)
    *  - http://test1.example.com (insecure)
-   *  - https://rc4.example.com (partly secure)
    *  - http://localhost (local)
    * and waits until NetworkMonitor has handled all packets sent by the server.
    */
   function* performRequests() {
     // waitForNetworkEvents does not work for requests with security errors as
     // those only emit 9/13 events of a successful request.
     let done = waitForSecurityBrokenNetworkEvent();
 
@@ -75,27 +67,23 @@ add_task(function* () {
     debuggee.performRequests(1, "http://test1.example.com" + CORS_SJS_PATH);
     yield done;
 
     done = waitForNetworkEvents(monitor, 1);
     info("Requesting a resource over HTTPS.");
     debuggee.performRequests(1, "https://example.com" + CORS_SJS_PATH);
     yield done;
 
-    done = waitForNetworkEvents(monitor, 1);
-    info("Requesting a resource over HTTPS with RC4.");
-    debuggee.performRequests(1, "https://rc4.example.com" + CORS_SJS_PATH);
-    yield done;
-
     done = waitForSecurityBrokenNetworkEvent(true);
     info("Requesting a resource over HTTP to localhost.");
     debuggee.performRequests(1, "http://localhost" + CORS_SJS_PATH);
     yield done;
 
-    is(RequestsMenu.itemCount, 5, "Five events logged.");
+    const expectedCount = Object.keys(EXPECTED_SECURITY_STATES).length;
+    is(RequestsMenu.itemCount, expectedCount, expectedCount + " events logged.");
   }
 
   /**
    * Returns a promise that's resolved once a request with security issues is
    * completed.
    */
   function waitForSecurityBrokenNetworkEvent(networkError) {
     let awaitedEvents = [
--- a/devtools/client/netmonitor/test/browser_net_security-warnings.js
+++ b/devtools/client/netmonitor/test/browser_net_security-warnings.js
@@ -8,36 +8,24 @@
  */
 
 const TEST_CASES = [
   {
     desc: "no warnings",
     uri: "https://example.com" + CORS_SJS_PATH,
     warnCipher: false,
   },
-  {
-    desc: "cipher warning",
-    uri: "https://rc4.example.com" + CORS_SJS_PATH,
-    warnCipher: true,
-  },
 ];
 
 add_task(function* () {
   let [tab, debuggee, monitor] = yield initNetMonitor(CUSTOM_GET_URL);
   let { $, EVENTS, NetMonitorView } = monitor.panelWin;
   let { RequestsMenu, NetworkDetails } = NetMonitorView;
   RequestsMenu.lazyUpdate = false;
 
-  info("Enabling RC4 for the test.");
-  yield new promise(resolve => {
-    SpecialPowers.pushPrefEnv({"set": [
-      ["security.tls.insecure_fallback_hosts", "rc4.example.com"]
-    ]}, resolve);
-  });
-
   let cipher = $("#security-warning-cipher");
 
   for (let test of TEST_CASES) {
     info("Testing site with " + test.desc);
 
     info("Performing request to " + test.uri);
     debuggee.performRequests(1, test.uri);
     yield waitForNetworkEvents(monitor, 1);
--- a/devtools/client/webaudioeditor/test/browser_wa_destroy-node-01.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_destroy-node-01.js
@@ -5,47 +5,39 @@
  * Tests that the destruction node event is fired and that the nodes are no
  * longer stored internally in the tool, that the graph is updated properly, and
  * that selecting a soon-to-be dead node clears the inspector.
  *
  * All done in one test since this test takes a few seconds to clear GC.
  */
 
 add_task(function* () {
-  // Use a longer timeout as garbage collection event
-  // can be unpredictable.
-  requestLongerTimeout(2);
-
   let { target, panel } = yield initWebAudioEditor(DESTROY_NODES_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, gAudioNodes } = panelWin;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
   let destroyed = getN(gAudioNodes, "remove", 10);
 
-  forceCC();
-
   let [created] = yield Promise.all([
     getNSpread(gAudioNodes, "add", 13),
     waitForGraphRendered(panelWin, 13, 2)
   ]);
 
   // Flatten arrays of event arguments and take the first (AudioNodeModel)
   // and get its ID.
   let actorIDs = created.map(ev => ev[0].id);
 
   // Click a soon-to-be dead buffer node
   yield clickGraphNode(panelWin, actorIDs[5]);
 
-  // Wait for a tick before gc to prevent this test from intermittent timeout
-  // where the node never get collected.
-  yield DevToolsUtils.waitForTick();
+  // Force a CC in the child process to collect the orphaned nodes.
   forceCC();
 
   // Wait for destruction and graph to re-render
   yield Promise.all([destroyed, waitForGraphRendered(panelWin, 3, 2)]);
 
   // Test internal storage
   is(panelWin.gAudioNodes.length, 3, "All nodes should be GC'd except one gain, osc and dest node.");
 
--- a/devtools/client/webaudioeditor/test/head.js
+++ b/devtools/client/webaudioeditor/test/head.js
@@ -416,19 +416,22 @@ function countGraphObjects(win) {
     edges: win.document.querySelectorAll(".edgePaths > .edgePath").length
   };
 }
 
 /**
 * Forces cycle collection and GC, used in AudioNode destruction tests.
 */
 function forceCC() {
-  SpecialPowers.DOMWindowUtils.cycleCollect();
-  SpecialPowers.DOMWindowUtils.garbageCollect();
-  SpecialPowers.DOMWindowUtils.garbageCollect();
+  ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() {
+    Cu.forceGC();
+    Cu.forceCC();
+    Cu.forceGC();
+    Cu.forceCC();
+  });
 }
 
 /**
  * Takes a `values` array of automation value entries,
  * looking for the value at `time` seconds, checking
  * to see if the value is close to `expected`.
  */
 function checkAutomationValue(values, time, expected) {
--- a/devtools/client/webconsole/test/browser_webconsole_certificate_messages.js
+++ b/devtools/client/webconsole/test/browser_webconsole_certificate_messages.js
@@ -1,34 +1,26 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-// Tests that the Web Console shows weak crypto warnings (SHA-1 Certificate, SSLv3, and RC4)
+// Tests that the Web Console shows weak crypto warnings (SHA-1 Certificate)
 
 "use strict";
 
 const TEST_URI = "data:text/html;charset=utf8,Web Console weak crypto " +
                  "warnings test";
 const TEST_URI_PATH = "/browser/devtools/client/webconsole/test/" +
                       "test-certificate-messages.html";
 
 var gWebconsoleTests = [
   {url: "https://sha1ee.example.com" + TEST_URI_PATH,
    name: "SHA1 warning displayed successfully",
    warning: ["SHA-1"], nowarning: ["SSL 3.0", "RC4"]},
-  {url: "https://rc4.example.com" + TEST_URI_PATH,
-   name: "RC4 warning displayed successfully",
-   pref: [["security.tls.insecure_fallback_hosts", "rc4.example.com"]],
-   warning: ["RC4"], nowarning: ["SHA-1", "SSL 3.0"]},
-  {url: "https://rc4.example.com" + TEST_URI_PATH + "?",
-   name: "Unrestricted RC4 fallback worked",
-   pref: [["security.tls.unrestricted_rc4_fallback", true]],
-   warning: ["RC4"], nowarning: ["SHA-1", "SSL 3.0"]},
   {url: "https://sha256ee.example.com" + TEST_URI_PATH,
    name: "SSL warnings appropriately not present",
    warning: [], nowarning: ["SHA-1", "SSL 3.0", "RC4"]},
 ];
 const TRIGGER_MSG = "If you haven't seen ssl warnings yet, you won't";
 
 var gHud = undefined, gContentBrowser;
 var gCurrentTest;
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -9,16 +9,17 @@
 #include <algorithm>
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/Casting.h"
 #include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ChromeUtils.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/ProfileTimelineMarkerBinding.h"
 #include "mozilla/dom/ScreenOrientation.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
 #include "mozilla/dom/workers/ServiceWorkerManager.h"
 #include "mozilla/EventStateManager.h"
@@ -3659,17 +3660,17 @@ nsDocShell::FindItemWithName(const char1
     if (foundItem) {
       foundItem.swap(*aResult);
     }
     return NS_OK;
   }
 }
 
 void
-nsDocShell::AssertOriginAttributesMatchPrivateBrowsing(){
+nsDocShell::AssertOriginAttributesMatchPrivateBrowsing() {
   MOZ_ASSERT((mOriginAttributes.mPrivateBrowsingId != 0) == mInPrivateBrowsing);
 }
 
 nsresult
 nsDocShell::DoFindItemWithName(const char16_t* aName,
                                nsISupports* aRequestor,
                                nsIDocShellTreeItem* aOriginalRequestor,
                                nsIDocShellTreeItem** aResult)
@@ -7930,16 +7931,23 @@ nsDocShell::CreateAboutBlankContentViewe
   }
 
   AutoRestore<bool> creatingDocument(mCreatingDocument);
   mCreatingDocument = true;
 
   // mContentViewer->PermitUnload may release |this| docshell.
   nsCOMPtr<nsIDocShell> kungFuDeathGrip(this);
 
+  if (aPrincipal && !nsContentUtils::IsSystemPrincipal(aPrincipal) &&
+      mItemType != typeChrome) {
+    MOZ_ASSERT(ChromeUtils::IsOriginAttributesEqualIgnoringAddonId(
+      BasePrincipal::Cast(aPrincipal)->OriginAttributesRef(),
+      mOriginAttributes));
+  }
+
   // Make sure timing is created.  But first record whether we had it
   // already, so we don't clobber the timing for an in-progress load.
   bool hadTiming = mTiming;
   MaybeInitTiming();
   if (mContentViewer) {
     // We've got a content viewer already. Make sure the user
     // permits us to discard the current document and replace it
     // with about:blank. And also ensure we fire the unload events
@@ -10747,17 +10755,20 @@ nsDocShell::DoURILoad(nsIURI* aURI,
       new LoadInfo(loadingPrincipal, triggeringPrincipal, loadingNode,
                    securityFlags, aContentPolicyType);
 
   // We have to do this in case our OriginAttributes are different from the
   // OriginAttributes of the parent document. Or in case there isn't a
   // parent document.
   NeckoOriginAttributes neckoAttrs;
   neckoAttrs.InheritFromDocShellToNecko(GetOriginAttributes());
-  loadInfo->SetOriginAttributes(neckoAttrs);
+  rv = loadInfo->SetOriginAttributes(neckoAttrs);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
   if (!isSrcdoc) {
     rv = NS_NewChannelInternal(getter_AddRefs(channel),
                                aURI,
                                loadInfo,
                                nullptr,   // loadGroup
                                static_cast<nsIInterfaceRequestor*>(this),
                                loadFlags);
@@ -14121,50 +14132,79 @@ NS_IMETHODIMP
 nsDocShell::GetOriginAttributes(JSContext* aCx,
                                 JS::MutableHandle<JS::Value> aVal)
 {
   bool ok = ToJSValue(aCx, mOriginAttributes, aVal);
   NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
   return NS_OK;
 }
 
-void
+nsresult
 nsDocShell::SetOriginAttributes(const DocShellOriginAttributes& aAttrs)
 {
-  MOZ_ASSERT(mChildList.Length() == 0);
+  MOZ_ASSERT(mChildList.IsEmpty());
+  if (!mChildList.IsEmpty()) {
+    return NS_ERROR_FAILURE;
+  }
 
   // TODO: Bug 1273058 - mContentViewer should be null when setting origin
   // attributes.
   if (mContentViewer) {
     nsIDocument* doc = mContentViewer->GetDocument();
     if (doc) {
       nsIURI* uri = doc->GetDocumentURI();
-      MOZ_ASSERT(uri);
-      if (uri) {
-        nsAutoCString uriSpec;
-        uri->GetSpec(uriSpec);
-        MOZ_ASSERT(uriSpec.EqualsLiteral("about:blank"));
+      if (!uri) {
+        return NS_ERROR_FAILURE;
+      }
+      nsAutoCString uriSpec;
+      uri->GetSpec(uriSpec);
+      MOZ_ASSERT(uriSpec.EqualsLiteral("about:blank"));
+      if (!uriSpec.EqualsLiteral("about:blank")) {
+        return NS_ERROR_FAILURE;
       }
     }
   }
 
   mOriginAttributes = aAttrs;
+  SetPrivateBrowsing(mOriginAttributes.mPrivateBrowsingId > 0);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetOriginAttributesBeforeLoading(JS::Handle<JS::Value> aOriginAttributes)
+{
+  if (!aOriginAttributes.isObject()) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  AutoJSAPI jsapi;
+  jsapi.Init(&aOriginAttributes.toObject());
+  JSContext* cx = jsapi.cx();
+  if (NS_WARN_IF(!cx)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  DocShellOriginAttributes attrs;
+  if (!aOriginAttributes.isObject() || !attrs.Init(cx, aOriginAttributes)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  return SetOriginAttributes(attrs);
 }
 
 NS_IMETHODIMP
 nsDocShell::SetOriginAttributes(JS::Handle<JS::Value> aOriginAttributes,
                                 JSContext* aCx)
 {
   DocShellOriginAttributes attrs;
   if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
     return NS_ERROR_INVALID_ARG;
   }
 
-  SetOriginAttributes(attrs);
-  return NS_OK;
+  return SetOriginAttributes(attrs);
 }
 
 NS_IMETHODIMP
 nsDocShell::GetAppManifestURL(nsAString& aAppManifestURL)
 {
   uint32_t appId = nsIDocShell::GetAppId();
   if (appId != nsIScriptSecurityManager::NO_APP_ID &&
       appId != nsIScriptSecurityManager::UNKNOWN_APP_ID) {
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -272,17 +272,17 @@ public:
   bool InFrameSwap();
 
   const mozilla::DocShellOriginAttributes&
   GetOriginAttributes()
   {
     return mOriginAttributes;
   }
 
-  void SetOriginAttributes(const mozilla::DocShellOriginAttributes& aAttrs);
+  nsresult SetOriginAttributes(const mozilla::DocShellOriginAttributes& aAttrs);
 
   void GetInterceptedDocumentId(nsAString& aId)
   {
     aId = mInterceptedDocumentId;
   }
 
 private:
   // An observed docshell wrapper is created when recording markers is enabled.
--- a/docshell/base/nsIWebNavigation.idl
+++ b/docshell/base/nsIWebNavigation.idl
@@ -350,9 +350,15 @@ interface nsIWebNavigation : nsISupports
    * The referring URI for the currently loaded URI or null.
    */
   readonly attribute nsIURI referringURI;
 
   /**
    * The session history object used by this web navigation instance.
    */
   attribute nsISHistory sessionHistory;
+
+  /**
+   * Set an OriginAttributes dictionary in the docShell. This can be done only
+   * before loading any content.
+   */
+  void setOriginAttributesBeforeLoading(in jsval originAttributes);
 };
--- a/docshell/shistory/nsSHistory.cpp
+++ b/docshell/shistory/nsSHistory.cpp
@@ -1492,16 +1492,22 @@ nsSHistory::LoadURIWithOptions(const cha
                                nsIInputStream* aPostStream,
                                nsIInputStream* aExtraHeaderStream,
                                nsIURI* aBaseURI)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsSHistory::SetOriginAttributesBeforeLoading(JS::HandleValue aOriginAttributes)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsSHistory::LoadURI(const char16_t* aURI,
                     uint32_t aLoadFlags,
                     nsIURI* aReferringURI,
                     nsIInputStream* aPostStream,
                     nsIInputStream* aExtraHeaderStream)
 {
   return NS_OK;
 }
--- a/docshell/test/unit/test_pb_notification.js
+++ b/docshell/test/unit/test_pb_notification.js
@@ -1,16 +1,16 @@
 if (typeof Cc === "undefined")
   Cc = Components.classes;
 if (typeof Ci === "undefined")
   Ci = Components.interfaces;
 
 function destroy_transient_docshell() {
   var docshell = Cc["@mozilla.org/docshell;1"].createInstance(Ci.nsIDocShell);
-  docshell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing = true;
+  docshell.setOriginAttributes({privateBrowsingId : 1});
   do_test_pending();
   do_timeout(0, Components.utils.forceGC);
 }
 
 function run_test() {
   var obs = {
     observe: function(aSubject, aTopic, aData) {
       do_check_eq(aTopic, "last-pb-context-exited");
--- a/docshell/test/unit/test_privacy_transition.js
+++ b/docshell/test/unit/test_privacy_transition.js
@@ -12,12 +12,12 @@ var observer = {
   privateModeChanged: function(enabled) {
     gNotifications++;
   }
 }
 
 function run_test() {
   var docshell = Cc["@mozilla.org/docshell;1"].createInstance(Ci.nsIDocShell);
   docshell.addWeakPrivacyTransitionObserver(observer);
-  docshell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing = true;
-  docshell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing = false;
+  docshell.setOriginAttributes({ privateBrowsingId : 1 });
+  docshell.setOriginAttributes({ privateBrowsingId : 0 });
   do_check_eq(gNotifications, 2);
 }
\ No newline at end of file
--- a/dom/base/BodyUtil.cpp
+++ b/dom/base/BodyUtil.cpp
@@ -545,17 +545,16 @@ BodyUtil::ConsumeText(uint32_t aInputLen
 
 // static
 void
 BodyUtil::ConsumeJson(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
                        const nsString& aStr, ErrorResult& aRv)
 {
   aRv.MightThrowJSException();
 
-  AutoForceSetExceptionOnContext forceExn(aCx);
   JS::Rooted<JS::Value> json(aCx);
   if (!JS_ParseJSON(aCx, aStr.get(), aStr.Length(), &json)) {
     if (!JS_IsExceptionPending(aCx)) {
       aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
       return;
     }
 
     JS::Rooted<JS::Value> exn(aCx);
--- a/dom/base/ChromeUtils.cpp
+++ b/dom/base/ChromeUtils.cpp
@@ -178,10 +178,21 @@ ChromeUtils::IsOriginAttributesEqual(dom
   return aA.mAddonId == aB.mAddonId &&
          aA.mAppId == aB.mAppId &&
          aA.mInIsolatedMozBrowser == aB.mInIsolatedMozBrowser &&
          aA.mSignedPkg == aB.mSignedPkg &&
          aA.mUserContextId == aB.mUserContextId &&
          aA.mPrivateBrowsingId == aB.mPrivateBrowsingId;
 }
 
+/* static */ bool
+ChromeUtils::IsOriginAttributesEqualIgnoringAddonId(const dom::OriginAttributesDictionary& aA,
+                                                    const dom::OriginAttributesDictionary& aB)
+{
+  return aA.mAppId == aB.mAppId &&
+         aA.mInIsolatedMozBrowser == aB.mInIsolatedMozBrowser &&
+         aA.mSignedPkg == aB.mSignedPkg &&
+         aA.mUserContextId == aB.mUserContextId &&
+         aA.mPrivateBrowsingId == aB.mPrivateBrowsingId;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/ChromeUtils.h
+++ b/dom/base/ChromeUtils.h
@@ -82,14 +82,18 @@ public:
   FillNonDefaultOriginAttributes(dom::GlobalObject& aGlobal,
                                  const dom::OriginAttributesDictionary& aAttrs,
                                  dom::OriginAttributesDictionary& aNewAttrs);
 
   static bool
   IsOriginAttributesEqual(dom::GlobalObject& aGlobal,
                           const dom::OriginAttributesDictionary& aA,
                           const dom::OriginAttributesDictionary& aB);
+
+  static bool
+  IsOriginAttributesEqualIgnoringAddonId(const dom::OriginAttributesDictionary& aA,
+                                         const dom::OriginAttributesDictionary& aB);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_ChromeUtils__
--- a/dom/base/DOMParser.cpp
+++ b/dom/base/DOMParser.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/DOMParser.h"
 
 #include "nsIDOMDocument.h"
 #include "nsNetUtil.h"
 #include "nsIStreamListener.h"
 #include "nsStringStream.h"
+#include "nsIScriptError.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsCRT.h"
 #include "nsStreamUtils.h"
 #include "nsContentUtils.h"
 #include "nsDOMJSUtils.h"
 #include "nsError.h"
 #include "nsPIDOMWindow.h"
 #include "nsNullPrincipal.h"
@@ -316,34 +317,44 @@ DOMParser::ParseFromStream(nsIInputStrea
 }
 
 NS_IMETHODIMP
 DOMParser::Init(nsIPrincipal* principal, nsIURI* documentURI,
                 nsIURI* baseURI, nsIGlobalObject* aScriptObject)
 {
   NS_ENSURE_STATE(!mAttemptedInit);
   mAttemptedInit = true;
-  
   NS_ENSURE_ARG(principal || documentURI);
-
   mDocumentURI = documentURI;
   
   if (!mDocumentURI) {
     principal->GetURI(getter_AddRefs(mDocumentURI));
     // If we have the system principal, then we'll just use the null principals
     // uri.
     if (!mDocumentURI && !nsContentUtils::IsSystemPrincipal(principal)) {
       return NS_ERROR_INVALID_ARG;
     }
   }
 
   mScriptHandlingObject = do_GetWeakReference(aScriptObject);
   mPrincipal = principal;
   nsresult rv;
   if (!mPrincipal) {
+    // BUG 1237080 -- in this case we're getting a chrome privilege scripted
+    // DOMParser object creation without an explicit principal set.  This is
+    // now deprecated.
+    nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+                                    NS_LITERAL_CSTRING("DOM"),
+                                    nullptr,
+                                    nsContentUtils::eDOM_PROPERTIES,
+                                    "ChromeScriptedDOMParserWithoutPrincipal",
+                                    nullptr,
+                                    0,
+                                    documentURI);
+
     PrincipalOriginAttributes attrs;
     mPrincipal = BasePrincipal::CreateCodebasePrincipal(mDocumentURI, attrs);
     NS_ENSURE_TRUE(mPrincipal, NS_ERROR_FAILURE);
     mOriginalPrincipal = mPrincipal;
   } else {
     mOriginalPrincipal = mPrincipal;
     if (nsContentUtils::IsSystemPrincipal(mPrincipal)) {
       // Don't give DOMParsers the system principal.  Use a null
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -32,16 +32,17 @@
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 #include "BatteryManager.h"
 #include "mozilla/dom/DeviceStorageAreaListener.h"
 #include "mozilla/dom/PowerManager.h"
 #include "mozilla/dom/WakeLock.h"
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/CellBroadcast.h"
+#include "mozilla/dom/FlyWebPublishedServer.h"
 #include "mozilla/dom/FlyWebService.h"
 #include "mozilla/dom/IccManager.h"
 #include "mozilla/dom/InputPortManager.h"
 #include "mozilla/dom/MobileMessageManager.h"
 #include "mozilla/dom/Permissions.h"
 #include "mozilla/dom/Presentation.h"
 #include "mozilla/dom/ServiceWorkerContainer.h"
 #include "mozilla/dom/TCPSocket.h"
@@ -195,17 +196,16 @@ Navigator::Init()
   Preferences::AddUintVarCache(&sMaxVibrateMS,
                                "dom.vibrator.max_vibrate_ms", 10000);
   Preferences::AddUintVarCache(&sMaxVibrateListLen,
                                "dom.vibrator.max_vibrate_list_len", 128);
 }
 
 Navigator::Navigator(nsPIDOMWindowInner* aWindow)
   : mWindow(aWindow)
-  , mBatteryTelemetryReported(false)
 {
   MOZ_ASSERT(aWindow->IsInnerWindow(), "Navigator must get an inner window!");
 }
 
 Navigator::~Navigator()
 {
   Invalidate();
 }
@@ -299,17 +299,16 @@ Navigator::Invalidate()
   }
 
   if (mBatteryManager) {
     mBatteryManager->Shutdown();
     mBatteryManager = nullptr;
   }
 
   mBatteryPromise = nullptr;
-  mBatteryTelemetryReported = false;
 
 #ifdef MOZ_B2G_FM
   if (mFMRadio) {
     mFMRadio->Shutdown();
     mFMRadio = nullptr;
   }
 #endif
 
@@ -1600,57 +1599,49 @@ Navigator::GetBattery(ErrorResult& aRv)
     mBatteryManager->Init();
   }
 
   mBatteryPromise->MaybeResolve(mBatteryManager);
 
   return mBatteryPromise;
 }
 
-battery::BatteryManager*
-Navigator::GetDeprecatedBattery(ErrorResult& aRv)
-{
-  if (!mBatteryManager) {
-    if (!mWindow) {
-      aRv.Throw(NS_ERROR_UNEXPECTED);
-      return nullptr;
-    }
-    NS_ENSURE_TRUE(mWindow->GetDocShell(), nullptr);
-
-    mBatteryManager = new battery::BatteryManager(mWindow);
-    mBatteryManager->Init();
-  }
-
-  nsIDocument* doc = mWindow->GetDoc();
-  if (doc) {
-    doc->WarnOnceAbout(nsIDocument::eNavigatorBattery);
-  }
-
-  // Is this the first time this page has accessed navigator.battery?
-  if (!mBatteryTelemetryReported) {
-    // sample value 0 = navigator.battery
-    Telemetry::Accumulate(Telemetry::BATTERY_STATUS_COUNT, 0);
-    mBatteryTelemetryReported = true;
-  }
-
-  return mBatteryManager;
-}
-
 already_AddRefed<Promise>
 Navigator::PublishServer(const nsAString& aName,
                          const FlyWebPublishOptions& aOptions,
                          ErrorResult& aRv)
 {
   RefPtr<FlyWebService> service = FlyWebService::GetOrCreate();
   if (!service) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
-  return service->PublishServer(aName, aOptions, mWindow, aRv);
+  RefPtr<FlyWebPublishPromise> mozPromise =
+    service->PublishServer(aName, aOptions, mWindow);
+  MOZ_ASSERT(mozPromise);
+
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
+  ErrorResult result;
+  RefPtr<Promise> domPromise = Promise::Create(global, result);
+  if (result.Failed()) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  mozPromise->Then(AbstractThread::MainThread(),
+                   __func__,
+                   [domPromise] (FlyWebPublishedServer* aServer) {
+                     domPromise->MaybeResolve(aServer);
+                   },
+                   [domPromise] (nsresult aStatus) {
+                     domPromise->MaybeReject(aStatus);
+                   });
+
+  return domPromise.forget();
 }
 
 already_AddRefed<Promise>
 Navigator::GetFeature(const nsAString& aName, ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
   RefPtr<Promise> p = Promise::Create(go, aRv);
   if (aRv.Failed()) {
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -162,17 +162,16 @@ public:
   void RegisterContentHandler(const nsAString& aMIMEType, const nsAString& aURL,
                               const nsAString& aTitle, ErrorResult& aRv);
   nsMimeTypeArray* GetMimeTypes(ErrorResult& aRv);
   nsPluginArray* GetPlugins(ErrorResult& aRv);
   Permissions* GetPermissions(ErrorResult& aRv);
   // The XPCOM GetDoNotTrack is ok
   Geolocation* GetGeolocation(ErrorResult& aRv);
   Promise* GetBattery(ErrorResult& aRv);
-  battery::BatteryManager* GetDeprecatedBattery(ErrorResult& aRv);
 
   already_AddRefed<Promise> PublishServer(const nsAString& aName,
                                           const FlyWebPublishOptions& aOptions,
                                           ErrorResult& aRv);
   static void AppName(nsAString& aAppName, bool aUsePrefOverriddenValue);
 
   static nsresult GetPlatform(nsAString& aPlatform,
                               bool aUsePrefOverriddenValue);
@@ -398,16 +397,14 @@ private:
   RefPtr<time::TimeManager> mTimeManager;
   RefPtr<ServiceWorkerContainer> mServiceWorkerContainer;
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
   RefPtr<DeviceStorageAreaListener> mDeviceStorageAreaListener;
   RefPtr<Presentation> mPresentation;
 
   nsTArray<RefPtr<Promise> > mVRGetDevicesPromises;
   nsTArray<uint32_t> mRequestedVibrationPattern;
-
-  bool mBatteryTelemetryReported;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Navigator_h
--- a/dom/base/ScriptSettings.cpp
+++ b/dom/base/ScriptSettings.cpp
@@ -291,41 +291,33 @@ GetWebIDLCallerPrincipal()
     return nullptr;
   }
 
   return aes->mWebIDLCallerPrincipal;
 }
 
 AutoJSAPI::AutoJSAPI()
   : mCx(nullptr)
-  , mOldAutoJSAPIOwnsErrorReporting(false)
   , mIsMainThread(false) // For lack of anything better
 {
 }
 
 AutoJSAPI::~AutoJSAPI()
 {
   if (!mCx) {
     // No need to do anything here: we never managed to Init, so can't have an
     // exception on our (nonexistent) JSContext.  We also don't need to restore
     // any state on it.
     return;
   }
 
   ReportException();
 
-  // We need to do this _after_ processing the existing exception, because the
-  // JS engine can throw while doing that, and uses this bit to determine what
-  // to do in that case: squelch the exception if the bit is set, otherwise
-  // call the error reporter. Calling WarningOnlyErrorReporter with a
-  // non-warning will assert, so we need to make sure we do the former.
-  JS::ContextOptionsRef(cx()).setAutoJSAPIOwnsErrorReporting(mOldAutoJSAPIOwnsErrorReporting);
-
-  if (mOldErrorReporter.isSome()) {
-    JS_SetErrorReporter(JS_GetRuntime(cx()), mOldErrorReporter.value());
+  if (mOldWarningReporter.isSome()) {
+    JS::SetWarningReporter(JS_GetRuntime(cx()), mOldWarningReporter.value());
   }
 }
 
 void
 WarningOnlyErrorReporter(JSContext* aCx, const char* aMessage,
                          JSErrorReport* aRep);
 
 void
@@ -351,21 +343,19 @@ AutoJSAPI::InitInternal(nsIGlobalObject*
     JS::Rooted<JSObject*> global(JS_GetRuntime(aCx), aGlobal);
     mCxPusher.emplace(mCx);
     mAutoNullableCompartment.emplace(mCx, global);
   } else {
     mAutoNullableCompartment.emplace(mCx, aGlobal);
   }
 
   JSRuntime* rt = JS_GetRuntime(aCx);
-  mOldErrorReporter.emplace(JS_GetErrorReporter(rt));
+  mOldWarningReporter.emplace(JS::GetWarningReporter(rt));
 
-  mOldAutoJSAPIOwnsErrorReporting = JS::ContextOptionsRef(aCx).autoJSAPIOwnsErrorReporting();
-  JS::ContextOptionsRef(aCx).setAutoJSAPIOwnsErrorReporting(true);
-  JS_SetErrorReporter(rt, WarningOnlyErrorReporter);
+  JS::SetWarningReporter(rt, WarningOnlyErrorReporter);
 
 #ifdef DEBUG
   if (haveException) {
     JS::Rooted<JS::Value> exn(aCx);
     JS_GetPendingException(aCx, &exn);
 
     JS_ClearPendingException(aCx);
     if (exn.isObject()) {
@@ -427,18 +417,17 @@ AutoJSAPI::InitInternal(nsIGlobalObject*
     MOZ_ASSERT(false, "We had an exception; we should not have");
   }
 #endif // DEBUG
 }
 
 AutoJSAPI::AutoJSAPI(nsIGlobalObject* aGlobalObject,
                      bool aIsMainThread,
                      JSContext* aCx)
-  : mOldAutoJSAPIOwnsErrorReporting(false)
-  , mIsMainThread(aIsMainThread)
+  : mIsMainThread(aIsMainThread)
 {
   MOZ_ASSERT(aGlobalObject);
   MOZ_ASSERT(aGlobalObject->GetGlobalJSObject(), "Must have a JS global");
   MOZ_ASSERT(aCx);
   MOZ_ASSERT(aIsMainThread == NS_IsMainThread());
 
   InitInternal(aGlobalObject, aGlobalObject->GetGlobalJSObject(), aCx,
                aIsMainThread);
--- a/dom/base/ScriptSettings.h
+++ b/dom/base/ScriptSettings.h
@@ -306,21 +306,19 @@ private:
   // while we're being used.  This _could_ become a JS::Rooted<JSObject*> if we
   // grabbed our JSContext in our constructor instead of waiting for Init(), so
   // we could construct this at that point.  It might be worth it do to that.
   RefPtr<nsIGlobalObject> mGlobalObject;
   mozilla::Maybe<danger::AutoCxPusher> mCxPusher;
   mozilla::Maybe<JSAutoNullableCompartment> mAutoNullableCompartment;
   JSContext *mCx;
 
-  // Track state between the old and new error reporting modes.
-  bool mOldAutoJSAPIOwnsErrorReporting;
   // Whether we're mainthread or not; set when we're initialized.
   bool mIsMainThread;
-  Maybe<JSErrorReporter> mOldErrorReporter;
+  Maybe<JS::WarningReporter> mOldWarningReporter;
 
   void InitInternal(nsIGlobalObject* aGlobalObject, JSObject* aGlobal,
                     JSContext* aCx, bool aIsMainThread);
 
   AutoJSAPI(const AutoJSAPI&) = delete;
   AutoJSAPI& operator= (const AutoJSAPI&) = delete;
 };
 
--- a/dom/base/WebSocket.cpp
+++ b/dom/base/WebSocket.cpp
@@ -2602,21 +2602,21 @@ WebSocketImpl::GetStatus(nsresult* aStat
   AssertIsOnTargetThread();
 
   *aStatus = NS_OK;
   return NS_OK;
 }
 
 namespace {
 
-class CancelRunnable final : public WorkerRunnable
+class CancelRunnable final : public MainThreadWorkerRunnable
 {
 public:
   CancelRunnable(WorkerPrivate* aWorkerPrivate, WebSocketImpl* aImpl)
-    : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+    : MainThreadWorkerRunnable(aWorkerPrivate)
     , mImpl(aImpl)
   {
   }
 
   bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     aWorkerPrivate->AssertIsOnWorkerThread();
     aWorkerPrivate->ModifyBusyCountFromWorker(true);
@@ -2624,33 +2624,16 @@ public:
   }
 
   void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
                bool aRunResult) override
   {
     aWorkerPrivate->ModifyBusyCountFromWorker(false);
   }
 
-  bool
-  PreDispatch(WorkerPrivate* aWorkerPrivate) override
-  {
-    // We don't call WorkerRunnable::PreDispatch because it would assert the
-    // wrong thing about which thread we're on.
-    AssertIsOnMainThread();
-    return true;
-  }
-
-  void
-  PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
-  {
-    // We don't call WorkerRunnable::PostDispatch because it would assert the
-    // wrong thing about which thread we're on.
-    AssertIsOnMainThread();
-  }
-
 private:
   RefPtr<WebSocketImpl> mImpl;
 };
 
 } // namespace
 
 // Window closed, stop/reload button pressed, user navigated away from page, etc.
 NS_IMETHODIMP
--- a/dom/base/nsCopySupport.cpp
+++ b/dom/base/nsCopySupport.cpp
@@ -686,18 +686,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/nsDeprecatedOperationList.h
+++ b/dom/base/nsDeprecatedOperationList.h
@@ -41,10 +41,9 @@ DEPRECATED_OPERATION(Window_Controllers)
 DEPRECATED_OPERATION(ImportXULIntoContent)
 DEPRECATED_OPERATION(PannerNodeDoppler)
 DEPRECATED_OPERATION(NavigatorGetUserMedia)
 DEPRECATED_OPERATION(WebrtcDeprecatedPrefix)
 DEPRECATED_OPERATION(RTCPeerConnectionGetStreams)
 DEPRECATED_OPERATION(AppCache)
 DEPRECATED_OPERATION(PrefixedFullscreenAPI)
 DEPRECATED_OPERATION(LenientSetter)
-DEPRECATED_OPERATION(NavigatorBattery)
 DEPRECATED_OPERATION(FileLastModifiedDate)
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -2151,18 +2151,16 @@ nsFrameLoader::MaybeCreateDocShell()
   NS_ENSURE_STATE(parentContext);
 
   rv = parentContext->GetUsePrivateBrowsing(&isPrivate);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   attrs.SyncAttributesWithPrivateBrowsing(isPrivate);
 
-  nsDocShell::Cast(mDocShell)->SetOriginAttributes(attrs);
-
   if (OwnerIsMozBrowserOrAppFrame()) {
     // For inproc frames, set the docshell properties.
     nsAutoString name;
     if (mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, name)) {
       docShell->SetName(name);
     }
     mDocShell->SetFullscreenAllowed(
       mOwnerContent->HasAttr(kNameSpaceID_None, nsGkAtoms::allowfullscreen) ||
@@ -2173,22 +2171,25 @@ nsFrameLoader::MaybeCreateDocShell()
       mDocShell->GetHasLoadedNonBlankURI(&nonBlank);
       if (nonBlank) {
         nsContentUtils::ReportToConsoleNonLocalized(
           NS_LITERAL_STRING("We should not switch to Private Browsing after loading a document."),
           nsIScriptError::warningFlag,
           NS_LITERAL_CSTRING("mozprivatebrowsing"),
           nullptr);
       } else {
-        nsCOMPtr<nsILoadContext> context = do_GetInterface(mDocShell);
-        context->SetUsePrivateBrowsing(true);
+        // This handles the case where a frames private browsing is set by chrome flags
+        // and not inherited by its parent.
+        attrs.SyncAttributesWithPrivateBrowsing(isPrivate);
       }
     }
   }
 
+  nsDocShell::Cast(mDocShell)->SetOriginAttributes(attrs);
+
   ReallyLoadFrameScripts();
   InitializeBrowserAPI();
 
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   if (os) {
     os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
                         "inprocess-browser-shown", nullptr);
   }
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -302,16 +302,26 @@ int32_t gTimeoutCnt                     
 // The default shortest interval/timeout we permit
 #define DEFAULT_MIN_TIMEOUT_VALUE 4 // 4ms
 #define DEFAULT_MIN_BACKGROUND_TIMEOUT_VALUE 1000 // 1000ms
 static int32_t gMinTimeoutValue;
 static int32_t gMinBackgroundTimeoutValue;
 inline int32_t
 nsGlobalWindow::DOMMinTimeoutValue() const {
   bool isBackground = !mOuterWindow || mOuterWindow->IsBackground();
+  if (isBackground) {
+    // Don't use the background timeout value when there are audio contexts with
+    // active nodes, so that background audio can keep running smoothly.
+    for (const AudioContext* ctx : mAudioContexts) {
+      if (ctx->ActiveNodeCount() > 0) {
+        isBackground = false;
+        break;
+      }
+    }
+  }
   return
     std::max(isBackground ? gMinBackgroundTimeoutValue : gMinTimeoutValue, 0);
 }
 
 // The number of nested timeouts before we start clamping. HTML5 says 1, WebKit
 // uses 5.
 #define DOM_CLAMP_TIMEOUT_NESTING_LEVEL 5
 
--- a/dom/base/nsJSUtils.cpp
+++ b/dom/base/nsJSUtils.cpp
@@ -144,18 +144,16 @@ nsJSUtils::EvaluateString(JSContext* aCx
                           JS::CompileOptions& aCompileOptions,
                           const EvaluateOptions& aEvaluateOptions,
                           JS::MutableHandle<JS::Value> aRetValue,
                           void **aOffThreadToken)
 {
   PROFILER_LABEL("nsJSUtils", "EvaluateString",
     js::ProfileEntry::Category::JS);
 
-  MOZ_ASSERT(JS::ContextOptionsRef(aCx).autoJSAPIOwnsErrorReporting(),
-             "Caller must own error reporting");
   MOZ_ASSERT_IF(aCompileOptions.versionSet,
                 aCompileOptions.version != JSVERSION_UNKNOWN);
   MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
   MOZ_ASSERT(aSrcBuf.get());
   MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(aEvaluationGlobal) ==
              aEvaluationGlobal);
   MOZ_ASSERT_IF(aOffThreadToken, aCompileOptions.noScriptRval);
   MOZ_ASSERT(NS_IsMainThread());
@@ -273,18 +271,16 @@ nsJSUtils::CompileModule(JSContext* aCx,
                        JS::SourceBufferHolder& aSrcBuf,
                        JS::Handle<JSObject*> aEvaluationGlobal,
                        JS::CompileOptions &aCompileOptions,
                        JS::MutableHandle<JSObject*> aModule)
 {
   PROFILER_LABEL("nsJSUtils", "CompileModule",
     js::ProfileEntry::Category::JS);
 
-  MOZ_ASSERT(JS::ContextOptionsRef(aCx).autoJSAPIOwnsErrorReporting(),
-             "Caller must own error reporting");
   MOZ_ASSERT_IF(aCompileOptions.versionSet,
                 aCompileOptions.version != JSVERSION_UNKNOWN);
   MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
   MOZ_ASSERT(aSrcBuf.get());
   MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(aEvaluationGlobal) ==
              aEvaluationGlobal);
   MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx) == aEvaluationGlobal);
   MOZ_ASSERT(NS_IsMainThread());
@@ -300,18 +296,16 @@ nsJSUtils::CompileModule(JSContext* aCx,
 }
 
 nsresult
 nsJSUtils::ModuleDeclarationInstantiation(JSContext* aCx, JS::Handle<JSObject*> aModule)
 {
   PROFILER_LABEL("nsJSUtils", "ModuleDeclarationInstantiation",
     js::ProfileEntry::Category::JS);
 
-  MOZ_ASSERT(JS::ContextOptionsRef(aCx).autoJSAPIOwnsErrorReporting(),
-             "Caller must own error reporting");
   MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(nsContentUtils::IsInMicroTask());
 
   NS_ENSURE_TRUE(xpc::Scriptability::Get(aModule).Allowed(), NS_OK);
 
   if (!JS::ModuleDeclarationInstantiation(aCx, aModule)) {
     return NS_ERROR_FAILURE;
@@ -321,18 +315,16 @@ nsJSUtils::ModuleDeclarationInstantiatio
 }
 
 nsresult
 nsJSUtils::ModuleEvaluation(JSContext* aCx, JS::Handle<JSObject*> aModule)
 {
   PROFILER_LABEL("nsJSUtils", "ModuleEvaluation",
     js::ProfileEntry::Category::JS);
 
-  MOZ_ASSERT(JS::ContextOptionsRef(aCx).autoJSAPIOwnsErrorReporting(),
-             "Caller must own error reporting");
   MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(nsContentUtils::IsInMicroTask());
 
   NS_ENSURE_TRUE(xpc::Scriptability::Get(aModule).Allowed(), NS_OK);
 
   if (!JS::ModuleEvaluation(aCx, aModule)) {
     return NS_ERROR_FAILURE;
--- a/dom/battery/test/marionette/manifest.ini
+++ b/dom/battery/test/marionette/manifest.ini
@@ -2,14 +2,8 @@
 run-if = buildapp == 'b2g'
 
 [test_battery_level.js]
 [test_battery_status_charging.js]
 [test_battery_status_discharging.js]
 [test_battery_status_full.js]
 [test_battery_status_not_charging.js]
 [test_battery_status_unknown.js]
-[test_deprecated_battery_level.js]
-[test_deprecated_battery_status_charging.js]
-[test_deprecated_battery_status_discharging.js]
-[test_deprecated_battery_status_full.js]
-[test_deprecated_battery_status_not_charging.js]
-[test_deprecated_battery_status_unknown.js]
--- a/dom/battery/test/marionette/test_battery_status_full.js
+++ b/dom/battery/test/marionette/test_battery_status_full.js
@@ -1,14 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 MARIONETTE_TIMEOUT = 10000;
 
-var battery = window.navigator.battery;
+var battery;
 var fromStatus = "full";
 var fromCharging = true;
 
 function verifyInitialState() {
   window.navigator.getBattery().then(function (b) {
     battery = b;
     ok(battery, "battery");
     ok(battery.charging, "battery.charging");
--- a/dom/battery/test/marionette/test_battery_status_not_charging.js
+++ b/dom/battery/test/marionette/test_battery_status_not_charging.js
@@ -1,14 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 MARIONETTE_TIMEOUT = 10000;
 
-var battery = window.navigator.battery;
+var battery;
 var fromStatus = "not-charging";
 var fromCharging = false;
 
 function verifyInitialState() {
   window.navigator.getBattery().then(function (b) {
     battery = b;
     ok(battery, "battery");
     ok(battery.charging, "battery.charging");
--- a/dom/battery/test/marionette/test_battery_status_unknown.js
+++ b/dom/battery/test/marionette/test_battery_status_unknown.js
@@ -1,14 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 MARIONETTE_TIMEOUT = 10000;
 
-var battery = window.navigator.battery;
+var battery;
 var fromStatus = "unknown";
 var fromCharging = false;
 
 function verifyInitialState() {
   window.navigator.getBattery().then(function (b) {
     battery = b;
     ok(battery, "battery");
     ok(battery.charging, "battery.charging");
deleted file mode 100644
--- a/dom/battery/test/marionette/test_deprecated_battery_level.js
+++ /dev/null
@@ -1,68 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-MARIONETTE_TIMEOUT = 10000;
-
-var battery = window.navigator.battery;
-
-function verifyInitialState() {
-  ok(battery, "battery");
-  is(battery.level, 0.5, "battery.level");
-  runEmulatorCmd("power display", function (result) {
-    is(result.pop(), "OK", "power display successful");
-    ok(result.indexOf("capacity: 50") !== -1, "power capacity");
-    setUp();
-  });
-}
-
-function unexpectedEvent(event) {
-  ok(false, "Unexpected " + event.type + " event");
-}
-
-function setUp() {
-  battery.onchargingchange = unexpectedEvent;
-  battery.onlevelchange = unexpectedEvent;
-  levelUp();
-}
-
-function changeCapacity(capacity, changeExpected, nextFunction) {
-  log("Changing power capacity to " + capacity);
-  if (changeExpected) {
-    battery.onlevelchange = function (event) {
-     battery.onlevelchange = unexpectedEvent;
-     is(event.type, "levelchange", "event.type");
-     is(battery.level, capacity / 100, "battery.level");
-     nextFunction();
-    };
-    runEmulatorCmd("power capacity " + capacity);
-  }
-  else {
-    runEmulatorCmd("power capacity " + capacity, function () {
-      is(battery.level, capacity / 100, "battery.level");
-      nextFunction();
-    });
-  }
-}
-
-function levelUp() {
-  changeCapacity("90", true, levelDown);
-}
-
-function levelDown() {
-  changeCapacity("10", true, levelSame);
-}
-
-function levelSame() {
-  changeCapacity("10", false, cleanUp);
-}
-
-function cleanUp() {
-  battery.onchargingchange = null;
-  battery.onlevelchange = function () {
-    battery.onlevelchange = null;
-    finish();
-  };
-  runEmulatorCmd("power capacity 50");
-}
-
-verifyInitialState();
deleted file mode 100644
--- a/dom/battery/test/marionette/test_deprecated_battery_status_charging.js
+++ /dev/null
@@ -1,85 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-MARIONETTE_TIMEOUT = 10000;
-
-var battery = window.navigator.battery;
-var fromStatus = "charging";
-var fromCharging = true;
-
-function verifyInitialState() {
-  ok(battery, "battery");
-  ok(battery.charging, "battery.charging");
-  runEmulatorCmd("power display", function (result) {
-    is(result.pop(), "OK", "power display successful");
-    ok(result.indexOf("status: Charging") !== -1, "power status charging");
-    setUp();
-  });
-}
-
-function unexpectedEvent(event) {
-  ok(false, "Unexpected " + event.type + " event");
-}
-
-function setUp() {
-  battery.onchargingchange = unexpectedEvent;
-  battery.onlevelchange = unexpectedEvent;
-  toDischarging();
-}
-
-function resetStatus(charging, nextFunction) {
-  log("Resetting power status to " + fromStatus);
-  if (charging !== fromCharging) {
-    battery.onchargingchange = function () {
-      battery.onchargingchange = unexpectedEvent;
-      nextFunction();
-    };
-    runEmulatorCmd("power status " + fromStatus);
-  }
-  else {
-    runEmulatorCmd("power status " + fromStatus, nextFunction);
-  }
-}
-
-function changeStatus(toStatus, toCharging, nextFunction) {
-  log("Changing power status to " + toStatus);
-  if (fromCharging !== toCharging) {
-    battery.onchargingchange = function (event) {
-      battery.onchargingchange = unexpectedEvent;
-      is(event.type, "chargingchange", "event type");
-      is(battery.charging, toCharging, "battery.charging");
-      resetStatus(toCharging, nextFunction);
-    };
-    runEmulatorCmd("power status " + toStatus);
-  }
-  else {
-    runEmulatorCmd("power status " + toStatus, function () {
-      is(battery.charging, toCharging, "battery.charging");
-      resetStatus(toCharging, nextFunction);
-    });
-  }
-}
-
-function toDischarging() {
-  changeStatus("discharging", false, toFull);
-}
-
-function toFull() {
-  changeStatus("full", true, toNotCharging);
-}
-
-function toNotCharging() {
-  changeStatus("not-charging", false, toUnknown);
-}
-
-function toUnknown() {
-  changeStatus("unknown", false, cleanUp);
-}
-
-function cleanUp() {
-  battery.onchargingchange = null;
-  battery.onlevelchange = null;
-  finish();
-}
-
-verifyInitialState();
deleted file mode 100644
--- a/dom/battery/test/marionette/test_deprecated_battery_status_discharging.js
+++ /dev/null
@@ -1,93 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-MARIONETTE_TIMEOUT = 10000;
-
-var battery = window.navigator.battery;
-var fromStatus = "discharging";
-var fromCharging = false;
-
-function verifyInitialState() {
-  ok(battery, "battery");
-  ok(battery.charging, "battery.charging");
-  runEmulatorCmd("power display", function (result) {
-    is(result.pop(), "OK", "power display successful");
-    ok(result.indexOf("status: Charging") !== -1, "power status charging");
-    setUp();
-  });
-}
-
-function unexpectedEvent(event) {
-  ok(false, "Unexpected " + event.type + " event");
-}
-
-function setUp() {
-  battery.onchargingchange = function () {
-    battery.onchargingchange = unexpectedEvent;
-    toCharging();
-  };
-  battery.onlevelchange = unexpectedEvent;
-  log("Changing power status to " + fromStatus);
-  runEmulatorCmd("power status " + fromStatus);
-}
-
-function resetStatus(charging, nextFunction) {
-  log("Resetting power status to " + fromStatus);
-  if (charging !== fromCharging) {
-    battery.onchargingchange = function () {
-      battery.onchargingchange = unexpectedEvent;
-      nextFunction();
-    };
-    runEmulatorCmd("power status " + fromStatus);
-  }
-  else {
-    runEmulatorCmd("power status " + fromStatus, nextFunction);
-  }
-}
-
-function changeStatus(toStatus, toCharging, nextFunction) {
-  log("Changing power status to " + toStatus);
-  if (fromCharging !== toCharging) {
-    battery.onchargingchange = function (event) {
-      battery.onchargingchange = unexpectedEvent;
-      is(event.type, "chargingchange", "event type");
-      is(battery.charging, toCharging, "battery.charging");
-      resetStatus(toCharging, nextFunction);
-    };
-    runEmulatorCmd("power status " + toStatus);
-  }
-  else {
-    runEmulatorCmd("power status " + toStatus, function () {
-      is(battery.charging, toCharging, "battery.charging");
-      resetStatus(toCharging, nextFunction);
-    });
-  }
-}
-
-function toCharging() {
-  changeStatus("charging", true, toFull);
-}
-
-function toFull() {
-  changeStatus("full", true, toNotCharging);
-}
-
-function toNotCharging() {
-  changeStatus("not-charging", false, toUnknown);
-}
-
-function toUnknown() {
-  changeStatus("unknown", false, cleanUp);
-}
-
-function cleanUp() {
-  battery.onchargingchange = function () {
-    battery.onchargingchange = null;
-    finish();
-  };
-  battery.onlevelchange = null;
-  log("Resetting power status to charging");
-  runEmulatorCmd("power status charging");
-}
-
-verifyInitialState();
deleted file mode 100644
--- a/dom/battery/test/marionette/test_deprecated_battery_status_full.js
+++ /dev/null
@@ -1,87 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-MARIONETTE_TIMEOUT = 10000;
-
-var battery = window.navigator.battery;
-var fromStatus = "full";
-var fromCharging = true;
-
-function verifyInitialState() {
-  ok(battery, "battery");
-  ok(battery.charging, "battery.charging");
-  runEmulatorCmd("power display", function (result) {
-    is(result.pop(), "OK", "power display successful");
-    ok(result.indexOf("status: Charging") !== -1, "power status charging");
-    setUp();
-  });
-}
-
-function unexpectedEvent(event) {
-  ok(false, "Unexpected " + event.type + " event");
-}
-
-function setUp() {
-  battery.onchargingchange = unexpectedEvent;
-  battery.onlevelchange = unexpectedEvent;
-  log("Changing power status to " + fromStatus);
-  runEmulatorCmd("power status " + fromStatus, toCharging);
-}
-
-function resetStatus(charging, nextFunction) {
-  log("Resetting power status to " + fromStatus);
-  if (charging !== fromCharging) {
-    battery.onchargingchange = function () {
-      battery.onchargingchange = unexpectedEvent;
-      nextFunction();
-    };
-    runEmulatorCmd("power status " + fromStatus);
-  }
-  else {
-    runEmulatorCmd("power status " + fromStatus, nextFunction);
-  }
-}
-
-function changeStatus(toStatus, toCharging, nextFunction) {
-  log("Changing power status to " + toStatus);
-  if (fromCharging !== toCharging) {
-    battery.onchargingchange = function (event) {
-      battery.onchargingchange = unexpectedEvent;
-      is(event.type, "chargingchange", "event type");
-      is(battery.charging, toCharging, "battery.charging");
-      resetStatus(toCharging, nextFunction);
-    };
-    runEmulatorCmd("power status " + toStatus);
-  }
-  else {
-    runEmulatorCmd("power status " + toStatus, function () {
-      is(battery.charging, toCharging, "battery.charging");
-      resetStatus(toCharging, nextFunction);
-    });
-  }
-}
-
-function toCharging() {
-  changeStatus("charging", true, toDischarging);
-}
-
-function toDischarging() {
-  changeStatus("discharging", false, toNotCharging);
-}
-
-function toNotCharging() {
-  changeStatus("not-charging", false, toUnknown);
-}
-
-function toUnknown() {
-  changeStatus("unknown", false, cleanUp);
-}
-
-function cleanUp() {
-  battery.onchargingchange = null;
-  battery.onlevelchange = null;
-  log("Resetting power status to charging");
-  runEmulatorCmd("power status charging", finish);
-}
-
-verifyInitialState();
deleted file mode 100644
--- a/dom/battery/test/marionette/test_deprecated_battery_status_not_charging.js
+++ /dev/null
@@ -1,93 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-MARIONETTE_TIMEOUT = 10000;
-
-var battery = window.navigator.battery;
-var fromStatus = "not-charging";
-var fromCharging = false;
-
-function verifyInitialState() {
-  ok(battery, "battery");
-  ok(battery.charging, "battery.charging");
-  runEmulatorCmd("power display", function (result) {
-    is(result.pop(), "OK", "power display successful");
-    ok(result.indexOf("status: Charging") !== -1, "power status charging");
-    setUp();
-  });
-}
-
-function unexpectedEvent(event) {
-  ok(false, "Unexpected " + event.type + " event");
-}
-
-function setUp() {
-  battery.onchargingchange = function () {
-    battery.onchargingchange = unexpectedEvent;
-    toCharging();
-  };
-  battery.onlevelchange = unexpectedEvent;
-  log("Changing power status to " + fromStatus);
-  runEmulatorCmd("power status " + fromStatus);
-}
-
-function resetStatus(charging, nextFunction) {
-  log("Resetting power status to " + fromStatus);
-  if (charging !== fromCharging) {
-    battery.onchargingchange = function () {
-      battery.onchargingchange = unexpectedEvent;
-      nextFunction();
-    };
-    runEmulatorCmd("power status " + fromStatus);
-  }
-  else {
-    runEmulatorCmd("power status " + fromStatus, nextFunction);
-  }
-}
-
-function changeStatus(toStatus, toCharging, nextFunction) {
-  log("Changing power status to " + toStatus);
-  if (fromCharging !== toCharging) {
-    battery.onchargingchange = function (event) {
-      battery.onchargingchange = unexpectedEvent;
-      is(event.type, "chargingchange", "event type");
-      is(battery.charging, toCharging, "battery.charging");
-      resetStatus(toCharging, nextFunction);
-    };
-    runEmulatorCmd("power status " + toStatus);
-  }
-  else {
-    runEmulatorCmd("power status " + toStatus, function () {
-      is(battery.charging, toCharging, "battery.charging");
-      resetStatus(toCharging, nextFunction);
-    });
-  }
-}
-
-function toCharging() {
-  changeStatus("charging", true, toDischarging);
-}
-
-function toDischarging() {
-  changeStatus("discharging", false, toFull);
-}
-
-function toFull() {
-  changeStatus("full", true, toUnknown);
-}
-
-function toUnknown() {
-  changeStatus("unknown", false, cleanUp);
-}
-
-function cleanUp() {
-  battery.onchargingchange = function () {
-    battery.onchargingchange = null;
-    finish();
-  };
-  battery.onlevelchange = null;
-  log("Resetting power status to charging");
-  runEmulatorCmd("power status charging");
-}
-
-verifyInitialState();
deleted file mode 100644
--- a/dom/battery/test/marionette/test_deprecated_battery_status_unknown.js
+++ /dev/null
@@ -1,93 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-MARIONETTE_TIMEOUT = 10000;
-
-var battery = window.navigator.battery;
-var fromStatus = "unknown";
-var fromCharging = false;
-
-function verifyInitialState() {
-  ok(battery, "battery");
-  ok(battery.charging, "battery.charging");
-  runEmulatorCmd("power display", function (result) {
-    is(result.pop(), "OK", "power display successful");
-    ok(result.indexOf("status: Charging") !== -1, "power status charging");
-    setUp();
-  });
-}
-
-function unexpectedEvent(event) {
-  ok(false, "Unexpected " + event.type + " event");
-}
-
-function setUp() {
-  battery.onchargingchange = function () {
-    battery.onchargingchange = unexpectedEvent;
-    toCharging();
-  };
-  battery.onlevelchange = unexpectedEvent;
-  log("Changing power status to " + fromStatus);
-  runEmulatorCmd("power status " + fromStatus);
-}
-
-function resetStatus(charging, nextFunction) {
-  log("Resetting power status to " + fromStatus);
-  if (charging !== fromCharging) {
-    battery.onchargingchange = function () {
-      battery.onchargingchange = unexpectedEvent;
-      nextFunction();
-    };
-    runEmulatorCmd("power status " + fromStatus);
-  }
-  else {
-    runEmulatorCmd("power status " + fromStatus, nextFunction);
-  }
-}
-
-function changeStatus(toStatus, toCharging, nextFunction) {
-  log("Changing power status to " + toStatus);
-  if (fromCharging !== toCharging) {
-    battery.onchargingchange = function (event) {
-      battery.onchargingchange = unexpectedEvent;
-      is(event.type, "chargingchange", "event type");
-      is(battery.charging, toCharging, "battery.charging");
-      resetStatus(toCharging, nextFunction);
-    };
-    runEmulatorCmd("power status " + toStatus);
-  }
-  else {
-    runEmulatorCmd("power status " + toStatus, function () {
-      is(battery.charging, toCharging, "battery.charging");
-      resetStatus(toCharging, nextFunction);
-    });
-  }
-}
-
-function toCharging() {
-  changeStatus("charging", true, toDischarging);
-}
-
-function toDischarging() {
-  changeStatus("discharging", false, toFull);
-}
-
-function toFull() {
-  changeStatus("full", true, toNotCharging);
-}
-
-function toNotCharging() {
-  changeStatus("not-charging", false, cleanUp);
-}
-
-function cleanUp() {
-  battery.onchargingchange = function () {
-    battery.onchargingchange = null;
-    finish();
-  };
-  battery.onlevelchange = null;
-  log("Resetting power status to charging");
-  runEmulatorCmd("power status charging");
-}
-
-verifyInitialState();
--- a/dom/battery/test/mochitest.ini
+++ b/dom/battery/test/mochitest.ini
@@ -1,8 +1,6 @@
 [test_battery_basics.html]
 skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug))
 [test_battery_charging.html]
 skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug))
 [test_battery_discharging.html]
 skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug))
-[test_deprecated_battery_basics.html]
-skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug))
--- a/dom/battery/test/test_battery_basics.html
+++ b/dom/battery/test/test_battery_basics.html
@@ -7,20 +7,23 @@
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 <script type="application/javascript">
 
+"use strict";
+
 SimpleTest.waitForExplicitFinish();
 
 /** Test for Battery API **/
 ok("getBattery" in navigator, "navigator.getBattery should exist");
+ok(!("battery" in navigator), "navigator.battery should not exist");
 
 navigator.getBattery().then(function (battery) {
   ok(battery.level >= 0.0 && battery.level <= 1.0, "Battery level " + battery.level + " should be in the range [0.0, 1.0]");
 
   SpecialPowers.pushPrefEnv({"set": [["dom.battery.test.default", true]]}, function () {
     ok(battery.charging, "Battery should be charging by default");
     is(battery.chargingTime, 0, "Battery chargingTime " + battery.chargingTime + " should be zero by default");
     is(battery.dischargingTime, Infinity, "Battery dischargingTime should be Infinity by default");
deleted file mode 100644
--- a/dom/battery/test/test_deprecated_battery_basics.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Test for Battery API</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-<p id="display"></p>
-<div id="content" style="display: none">
-</div>
-<pre id="test">
-<script type="application/javascript">
-
-/** Test for Battery API **/
-ok("battery" in navigator, "navigator.battery should exist");
-
-var battery = navigator.battery;
-ok(battery.level >= 0.0 && battery.level <= 1.0, "Battery level " + battery.level + " should be in the range [0.0, 1.0]");
-
-if (battery.charging) {
-  ok(battery.chargingTime >= 0, "Battery chargingTime " + battery.chargingTime + " should be nonnegative when charging");
-  is(battery.dischargingTime, Infinity, "Battery dischargingTime should be Infinity when charging");
-} else {
-  is(battery.chargingTime, Infinity, "Battery chargingTime should be Infinity when discharging");
-  ok(battery.dischargingTime > 0, "Battery dischargingTime " + battery.dischargingTime + " should be positive when discharging");
-}
-
-</script>
-</pre>
-</body>
-</html>
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -3353,49 +3353,36 @@ private:
 
     ReleaseWorker();
     return NS_OK;
   }
 
   void
   ReleaseWorker()
   {
-    class ReleaseRunnable final : public WorkerRunnable
+    class ReleaseRunnable final : public MainThreadWorkerRunnable
     {
       RefPtr<DeprecationWarningRunnable> mRunnable;
 
     public:
       ReleaseRunnable(WorkerPrivate* aWorkerPrivate,
                       DeprecationWarningRunnable* aRunnable)
-        : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+        : MainThreadWorkerRunnable(aWorkerPrivate)
         , mRunnable(aRunnable)
       {}
 
       virtual bool
       WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
       {
         MOZ_ASSERT(aWorkerPrivate);
         aWorkerPrivate->AssertIsOnWorkerThread();
 
         aWorkerPrivate->RemoveFeature(mRunnable);
         return true;
       }
-
-      virtual bool
-      PreDispatch(WorkerPrivate* aWorkerPrivate) override
-      {
-        AssertIsOnMainThread();
-        return true;
-      }
-
-      virtual void
-      PostDispatch(WorkerPrivate* aWorkerPrivate,
-                   bool aDispatchResult) override
-      {
-      }
     };
 
     RefPtr<ReleaseRunnable> runnable =
       new ReleaseRunnable(mWorkerPrivate, this);
     NS_WARN_IF(!runnable->Dispatch());
   }
 };
 
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -495,22 +495,20 @@ DOMInterfaces = {
 },
 
 'FileReaderSync': {
     'wrapperCache': False,
 },
 
 'FlyWebFetchEvent': {
     'headerFile': 'FlyWebServerEvents.h',
-    'nativeType': 'mozilla::dom::FlyWebFetchEvent',
 },
 
 'FlyWebWebSocketEvent': {
     'headerFile': 'FlyWebServerEvents.h',
-    'nativeType': 'mozilla::dom::FlyWebWebSocketEvent',
 },
 
 'FontFaceSet': {
     'implicitJSContext': [ 'load' ],
 },
 
 'FontFaceSetIterator': {
     'wrapperCache': False,
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -1128,48 +1128,48 @@ class CGHeaders(CGWrapper):
 
         map(addHeadersForType,
             getAllTypes(descriptors + callbackDescriptors, dictionaries,
                         callbacks))
 
         # Now make sure we're not trying to include the header from inside itself
         declareIncludes.discard(prefix + ".h")
 
+        def addHeaderForFunc(func, desc):
+            if func is None:
+                return
+            # Include the right class header, which we can only do
+            # if this is a class member function.
+            if desc is not None and not desc.headerIsDefault:
+                # An explicit header file was provided, assume that we know
+                # what we're doing.
+                return
+
+            if "::" in func:
+                # Strip out the function name and convert "::" to "/"
+                bindingHeaders.add("/".join(func.split("::")[:-1]) + ".h")
+
         # Now for non-callback descriptors make sure we include any
-        # headers needed by Func declarations.
+        # headers needed by Func declarations and other things like that.
         for desc in descriptors:
             # If this is an iterator interface generated for a seperate
             # iterable interface, skip generating type includes, as we have
             # what we need in IterableIterator.h
             if desc.interface.isExternal() or desc.interface.isIteratorInterface():
                 continue
 
-            def addHeaderForFunc(func):
-                if func is None:
-                    return
-                # Include the right class header, which we can only do
-                # if this is a class member function.
-                if not desc.headerIsDefault:
-                    # An explicit header file was provided, assume that we know
-                    # what we're doing.
-                    return
-
-                if "::" in func:
-                    # Strip out the function name and convert "::" to "/"
-                    bindingHeaders.add("/".join(func.split("::")[:-1]) + ".h")
-
             for m in desc.interface.members:
-                addHeaderForFunc(PropertyDefiner.getStringAttr(m, "Func"))
+                addHeaderForFunc(PropertyDefiner.getStringAttr(m, "Func"), desc)
                 staticTypeOverride = PropertyDefiner.getStringAttr(m, "StaticClassOverride")
                 if staticTypeOverride:
                     bindingHeaders.add("/".join(staticTypeOverride.split("::")) + ".h")
             # getExtendedAttribute() returns a list, extract the entry.
             funcList = desc.interface.getExtendedAttribute("Func")
             if funcList is not None:
-                addHeaderForFunc(funcList[0])
+                addHeaderForFunc(funcList[0], desc)
 
             if desc.interface.maplikeOrSetlikeOrIterable:
                 # We need ToJSValue.h for maplike/setlike type conversions
                 bindingHeaders.add("mozilla/dom/ToJSValue.h")
                 # Add headers for the key and value types of the
                 # maplike/setlike/iterable, since they'll be needed for
                 # convenience functions
                 if desc.interface.maplikeOrSetlikeOrIterable.hasKeyType():
@@ -1178,16 +1178,22 @@ class CGHeaders(CGWrapper):
                 if desc.interface.maplikeOrSetlikeOrIterable.hasValueType():
                     addHeadersForType((desc.interface.maplikeOrSetlikeOrIterable.valueType,
                                        desc, None))
 
         for d in dictionaries:
             if d.parent:
                 declareIncludes.add(self.getDeclarationFilename(d.parent))
             bindingHeaders.add(self.getDeclarationFilename(d))
+            for m in d.members:
+                addHeaderForFunc(PropertyDefiner.getStringAttr(m, "Func"),
+                                 None)
+            # No need to worry about Func on members of ancestors, because that
+            # will happen automatically in whatever files those ancestors live
+            # in.
 
         for c in callbacks:
             bindingHeaders.add(self.getDeclarationFilename(c))
 
         for c in callbackDescriptors:
             bindingHeaders.add(self.getDeclarationFilename(c.interface))
 
         if len(callbacks) != 0:
@@ -3318,32 +3324,57 @@ class CGDefineDOMInterfaceMethod(CGAbstr
                 }
                 return interfaceObject;
                 """)
         else:
             getConstructor = "return GetConstructorObjectHandle(aCx, aGlobal, aDefineOnGlobal);\n"
         return getConstructor
 
 
+def getConditionList(idlobj, cxName, objName):
+    """
+    Get the list of conditions for idlobj (to be used in "is this enabled"
+    checks).  This will be returned as a CGList with " &&\n" as the separator,
+    for readability.
+
+    objName is the name of the object that we're working with, because some of
+    our test functions want that.
+    """
+    conditions = []
+    pref = idlobj.getExtendedAttribute("Pref")
+    if pref:
+        assert isinstance(pref, list) and len(pref) == 1
+        conditions.append('Preferences::GetBool("%s")' % pref[0])
+    if idlobj.getExtendedAttribute("ChromeOnly"):
+        conditions.append("nsContentUtils::ThreadsafeIsCallerChrome()")
+    func = idlobj.getExtendedAttribute("Func")
+    if func:
+        assert isinstance(func, list) and len(func) == 1
+        conditions.append("%s(%s, %s)" % (func[0], cxName, objName))
+    if idlobj.getExtendedAttribute("SecureContext"):
+        conditions.append("mozilla::dom::IsSecureContextOrObjectIsFromSecureContext(%s, %s)" % (cxName, objName))
+
+    return CGList((CGGeneric(cond) for cond in conditions), " &&\n")
+
+
 class CGConstructorEnabled(CGAbstractMethod):
     """
     A method for testing whether we should be exposing this interface
     object or navigator property.  This can perform various tests
     depending on what conditions are specified on the interface.
     """
     def __init__(self, descriptor):
         CGAbstractMethod.__init__(self, descriptor,
                                   'ConstructorEnabled', 'bool',
                                   [Argument("JSContext*", "aCx"),
                                    Argument("JS::Handle<JSObject*>", "aObj")])
 
     def definition_body(self):
         body = CGList([], "\n")
 
-        conditions = []
         iface = self.descriptor.interface
 
         if not iface.isExposedOnMainThread():
             exposedInWindowCheck = dedent(
                 """
                 MOZ_ASSERT(!NS_IsMainThread(), "Why did we even get called?");
                 """)
             body.append(CGGeneric(exposedInWindowCheck))
@@ -3360,44 +3391,33 @@ class CGConstructorEnabled(CGAbstractMet
                 }
                 """, workerCondition=workerCondition.define())
             exposedInWorkerCheck = CGGeneric(exposedInWorkerCheck)
             if iface.isExposedOnMainThread():
                 exposedInWorkerCheck = CGIfWrapper(exposedInWorkerCheck,
                                                    "!NS_IsMainThread()")
             body.append(exposedInWorkerCheck)
 
-        pref = iface.getExtendedAttribute("Pref")
-        if pref:
-            assert isinstance(pref, list) and len(pref) == 1
-            conditions.append('Preferences::GetBool("%s")' % pref[0])
-        if iface.getExtendedAttribute("ChromeOnly"):
-            conditions.append("nsContentUtils::ThreadsafeIsCallerChrome()")
-        func = iface.getExtendedAttribute("Func")
-        if func:
-            assert isinstance(func, list) and len(func) == 1
-            conditions.append("%s(aCx, aObj)" % func[0])
-        if iface.getExtendedAttribute("SecureContext"):
-            conditions.append("mozilla::dom::IsSecureContextOrObjectIsFromSecureContext(aCx, aObj)")
+        conditions = getConditionList(iface, "aCx", "aObj")
         availableIn = getAvailableInTestFunc(iface)
         if availableIn:
-            conditions.append("%s(aCx, aObj)" % availableIn)
+            conditions.append(CGGeneric("%s(aCx, aObj)" % availableIn))
         checkAnyPermissions = self.descriptor.checkAnyPermissionsIndex
         if checkAnyPermissions is not None:
-            conditions.append("CheckAnyPermissions(aCx, aObj, anypermissions_%i)" % checkAnyPermissions)
+            conditions.append(CGGeneric("CheckAnyPermissions(aCx, aObj, anypermissions_%i)" % checkAnyPermissions))
         checkAllPermissions = self.descriptor.checkAllPermissionsIndex
         if checkAllPermissions is not None:
-            conditions.append("CheckAllPermissions(aCx, aObj, allpermissions_%i)" % checkAllPermissions)
+            conditions.append(CGGeneric("CheckAllPermissions(aCx, aObj, allpermissions_%i)" % checkAllPermissions))
+
         # We should really have some conditions
         assert len(body) or len(conditions)
 
         conditionsWrapper = ""
         if len(conditions):
-            conditionsWrapper = CGWrapper(CGList((CGGeneric(cond) for cond in conditions),
-                                                 " &&\n"),
+            conditionsWrapper = CGWrapper(conditions,
                                           pre="return ",
                                           post=";\n",
                                           reindent=True)
         else:
             conditionsWrapper = CGGeneric("return true;\n")
 
         body.append(conditionsWrapper)
         return body.define()
@@ -12757,28 +12777,29 @@ class CGDictionary(CGThing):
             "prop": self.makeMemberName(member.identifier.name),
             "convert": string.Template(conversionInfo.template).substitute(replacements),
             "propGet": propGet
         }
         # The conversion code will only run where a default value or a value passed
         # by the author needs to get converted, so we can remember if we have any
         # members present here.
         conversionReplacements["convert"] += "mIsAnyMemberPresent = true;\n"
-        if isChromeOnly(member):
-            conversion = ("if (!isNull) {\n"
-                          "  if (!nsContentUtils::ThreadsafeIsCallerChrome()) {\n"
-                          "    temp->setUndefined();\n"
-                          "  } else if (!${propGet}) {\n"
-                          "    return false;\n"
-                          "  }\n"
-                          "}\n")
-        else:
-            conversion = ("if (!isNull && !${propGet}) {\n"
-                          "  return false;\n"
-                          "}\n")
+        setTempValue = CGGeneric(dedent(
+            """
+            if (!${propGet}) {
+              return false;
+            }
+            """))
+        conditions = getConditionList(member, "cx", "*object")
+        if len(conditions) != 0:
+            setTempValue = CGIfElseWrapper(conditions.define(),
+                                           setTempValue,
+                                           CGGeneric("temp->setUndefined();\n"))
+        setTempValue = CGIfWrapper(setTempValue, "!isNull")
+        conversion = setTempValue.define()
         if member.defaultValue:
             if (member.type.isUnion() and
                 (not member.type.nullable() or
                  not isinstance(member.defaultValue, IDLNullValue))):
                 # Since this has a default value, it might have been initialized
                 # already.  Go ahead and uninit it before we try to init it
                 # again.
                 memberName = self.makeMemberName(member.identifier.name)
@@ -12878,18 +12899,19 @@ class CGDictionary(CGThing):
         conversion = CGWrapper(
             CGIndenter(conversion),
             pre=("do {\n"
                  "  // block for our 'break' successCode and scope for 'temp' and 'currentValue'\n"),
             post="} while(0);\n")
         if member.canHaveMissingValue():
             # Only do the conversion if we have a value
             conversion = CGIfWrapper(conversion, "%s.WasPassed()" % memberLoc)
-        if isChromeOnly(member):
-            conversion = CGIfWrapper(conversion, "nsContentUtils::ThreadsafeIsCallerChrome()")
+        conditions = getConditionList(member, "cx", "obj")
+        if len(conditions) != 0:
+            conversion = CGIfWrapper(conversion, conditions.define())
         return conversion
 
     def getMemberTrace(self, member):
         type = member.type
         assert typeNeedsRooting(type)
         memberLoc = self.makeMemberName(member.identifier.name)
         if not member.canHaveMissingValue():
             memberData = memberLoc
@@ -13492,28 +13514,37 @@ class CGBindingRoot(CGThing):
                     (desc.interface.hasInterfaceObject() and
                      (desc.interface.isJSImplemented() or
                       (ctor and isChromeOnly(ctor)))) or
                     # JS-implemented interfaces with clearable cached
                     # attrs have chromeonly _clearFoo methods.
                     (desc.interface.isJSImplemented() and
                      any(clearableCachedAttrs(desc))))
 
-        bindingHeaders["nsContentUtils.h"] = any(
-            descriptorHasChromeOnly(d) for d in descriptors)
         # XXXkhuey ugly hack but this is going away soon.
         bindingHeaders['xpcprivate.h'] = webIDLFile.endswith("EventTarget.webidl")
         hasWorkerStuff = len(config.getDescriptors(webIDLFile=webIDLFile,
                                                    workers=True)) != 0
         bindingHeaders["WorkerPrivate.h"] = hasWorkerStuff
 
         hasThreadChecks = hasWorkerStuff or any(d.hasThreadChecks() for d in descriptors)
         bindingHeaders["nsThreadUtils.h"] = hasThreadChecks
 
         dictionaries = config.getDictionaries(webIDLFile=webIDLFile)
+
+        def dictionaryHasChromeOnly(dictionary):
+            while dictionary:
+                if (any(isChromeOnly(m) for m in dictionary.members)):
+                    return True
+                dictionary = dictionary.parent
+            return False
+
+        bindingHeaders["nsContentUtils.h"] = (
+            any(descriptorHasChromeOnly(d) for d in descriptors) or
+            any(dictionaryHasChromeOnly(d) for d in dictionaries))
         hasNonEmptyDictionaries = any(
             len(dict.members) > 0 for dict in dictionaries)
         mainCallbacks = config.getCallbacks(webIDLFile=webIDLFile,
                                             workers=False)
         workerCallbacks = config.getCallbacks(webIDLFile=webIDLFile,
                                               workers=True)
         callbackDescriptors = config.getDescriptors(webIDLFile=webIDLFile,
                                                     isCallback=True)
--- a/dom/bindings/Exceptions.cpp
+++ b/dom/bindings/Exceptions.cpp
@@ -201,28 +201,16 @@ GetCurrentJSStack(int32_t aMaxDepth)
 
   if (!cx || !js::GetContextCompartment(cx)) {
     return nullptr;
   }
 
   return dom::exceptions::CreateStack(cx, aMaxDepth);
 }
 
-AutoForceSetExceptionOnContext::AutoForceSetExceptionOnContext(JSContext* aCx)
-  : mCx(aCx)
-{
-  mOldValue = JS::ContextOptionsRef(mCx).autoJSAPIOwnsErrorReporting();
-  JS::ContextOptionsRef(mCx).setAutoJSAPIOwnsErrorReporting(true);
-}
-
-AutoForceSetExceptionOnContext::~AutoForceSetExceptionOnContext()
-{
-  JS::ContextOptionsRef(mCx).setAutoJSAPIOwnsErrorReporting(mOldValue);
-}
-
 namespace exceptions {
 
 class JSStackFrame : public nsIStackFrame
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(JSStackFrame)
   NS_DECL_NSISTACKFRAME
--- a/dom/bindings/Exceptions.h
+++ b/dom/bindings/Exceptions.h
@@ -49,29 +49,16 @@ CreateException(JSContext* aCx, nsresult
                 const nsACString& aMessage = EmptyCString());
 
 // aMaxDepth can be used to define a maximal depth for the stack trace. If the
 // value is -1, a default maximal depth will be selected.  Will return null if
 // there is no JS stack right now.
 already_AddRefed<nsIStackFrame>
 GetCurrentJSStack(int32_t aMaxDepth = -1);
 
-// Throwing a TypeError on an ErrorResult may result in SpiderMonkey using its
-// own error reporting mechanism instead of just setting the exception on the
-// context.  This happens if no script is running. Bug 1107777 adds a flag that
-// forcibly turns this behaviour off. This is a stack helper to set the flag.
-class MOZ_STACK_CLASS AutoForceSetExceptionOnContext {
-private:
-  JSContext* mCx;
-  bool mOldValue;
-public:
-  explicit AutoForceSetExceptionOnContext(JSContext* aCx);
-  ~AutoForceSetExceptionOnContext();
-};
-
 // Internal stuff not intended to be widely used.
 namespace exceptions {
 
 // aMaxDepth can be used to define a maximal depth for the stack trace. If the
 // value is -1, a default maximal depth will be selected.  Will return null if
 // there is no JS stack right now.
 already_AddRefed<nsIStackFrame>
 CreateStack(JSContext* aCx, int32_t aMaxDepth = -1);
--- a/dom/bindings/ToJSValue.cpp
+++ b/dom/bindings/ToJSValue.cpp
@@ -53,17 +53,16 @@ ToJSValue(JSContext* aCx,
 bool
 ToJSValue(JSContext* aCx,
           ErrorResult& aArgument,
           JS::MutableHandle<JS::Value> aValue)
 {
   MOZ_ASSERT(aArgument.Failed());
   MOZ_ASSERT(!aArgument.IsUncatchableException(),
              "Doesn't make sense to convert uncatchable exception to a JS value!");
-  AutoForceSetExceptionOnContext forceExn(aCx);
   DebugOnly<bool> throwResult = aArgument.MaybeSetPendingException(aCx);
   MOZ_ASSERT(throwResult);
   DebugOnly<bool> getPendingResult = JS_GetPendingException(aCx, aValue);
   MOZ_ASSERT(getPendingResult);
   JS_ClearPendingException(aCx);
   return true;
 }
 
--- a/dom/bindings/mach_commands.py
+++ b/dom/bindings/mach_commands.py
@@ -37,16 +37,19 @@ class WebIDLProvider(MachCommandBase):
     def webidl_test(self, **kwargs):
         sys.path.insert(0, os.path.join(self.topsrcdir, 'other-licenses',
                         'ply'))
 
         # Make sure we drop our cached grammar bits in the objdir, not
         # wherever we happen to be running from.
         os.chdir(self.topobjdir)
 
+        if kwargs["verbose"] is None:
+            kwargs["verbose"] = False
+
         # Now we're going to create the cached grammar file in the
         # objdir.  But we're going to try loading it as a python
         # module, so we need to make sure the objdir is in our search
         # path.
         sys.path.insert(0, self.topobjdir)
 
         import runtests
         return runtests.run_tests(kwargs["tests"], verbose=kwargs["verbose"])
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -4397,20 +4397,21 @@ class IDLArgument(IDLObjectWithIdentifie
                     raise WebIDLError("[EnforceRange] must take no arguments",
                                       [attribute.location])
                 if self.clamp:
                     raise WebIDLError("[EnforceRange] and [Clamp] are mutually exclusive",
                                       [self.location])
                 self.enforceRange = True
             elif identifier == "TreatNonCallableAsNull":
                 self._allowTreatNonCallableAsNull = True
-            elif self.dictionaryMember and identifier == "ChromeOnly":
+            elif (self.dictionaryMember and
+                  (identifier == "ChromeOnly" or identifier == "Func")):
                 if not self.optional:
-                    raise WebIDLError("[ChromeOnly] must not be used on a required "
-                                      "dictionary member",
+                    raise WebIDLError("[%s] must not be used on a required "
+                                      "dictionary member" % identifier,
                                       [attribute.location])
             else:
                 raise WebIDLError("Unhandled extended attribute on %s" %
                                   ("a dictionary member" if self.dictionaryMember else
                                    "an argument"),
                                   [attribute.location])
             attrlist = attribute.listValue()
             self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True
--- a/dom/bindings/parser/runtests.py
+++ b/dom/bindings/parser/runtests.py
@@ -88,19 +88,21 @@ def run_tests(tests, verbose):
             print '%s:' % test
             for failure in failures:
                 print 'TEST-UNEXPECTED-FAIL | %s' % failure
 
 def get_parser():
     usage = """%(prog)s [OPTIONS] [TESTS]
                Where TESTS are relative to the tests directory."""
     parser = argparse.ArgumentParser(usage=usage)
-    parser.add_argument('-q', '--quiet', action='store_false', dest='verbose', default=True,
-                        help="Don't print passing tests.")
-    parser.add_argument('-v', '--verbose', action='store_true', dest='verbose', default=True,
+    parser.add_argument('-q', '--quiet', action='store_false', dest='verbose',
+                        help="Don't print passing tests.", default=None)
+    parser.add_argument('-v', '--verbose', action='store_true', dest='verbose',
                         help="Run tests in verbose mode.")
     parser.add_argument('tests', nargs="*", help="Tests to run")
     return parser
 
 if __name__ == '__main__':
     parser = get_parser()
     args = parser.parse_args()
+    if args.verbose is None:
+        args.verbose = True
     run_tests(args.tests, verbose=args.verbose)
--- a/dom/bindings/test/TestCodeGen.webidl
+++ b/dom/bindings/test/TestCodeGen.webidl
@@ -1124,16 +1124,25 @@ dictionary DictForConstructor {
   sequence<any> seq4;
   sequence<any> seq5;
   sequence<DictContainingSequence> seq6;
   object obj1;
   object? obj2;
   any any1 = null;
 };
 
+dictionary DictWithConditionalMembers {
+  [ChromeOnly]
+  long chromeOnlyMember;
+  [Func="TestFuncControlledMember"]
+  long funcControlledMember;
+  [ChromeOnly, Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  long chromeOnlyFuncControlledMember;
+};
+
 interface TestIndexedGetterInterface {
   getter long item(unsigned long idx);
   readonly attribute unsigned long length;
   legacycaller void();
 };
 
 interface TestNamedGetterInterface {
   getter DOMString (DOMString name);
--- a/dom/cache/CacheTypes.ipdlh
+++ b/dom/cache/CacheTypes.ipdlh
@@ -4,23 +4,23 @@
 
 include protocol PCache;
 include protocol PCacheStreamControl;
 include protocol PSendStream;
 include IPCStream;
 include ChannelInfo;
 include PBackgroundSharedTypes;
 
-using HeadersGuardEnum from "mozilla/dom/cache/IPCUtils.h";
-using ReferrerPolicy from "mozilla/dom/cache/IPCUtils.h";
-using RequestCredentials from "mozilla/dom/cache/IPCUtils.h";
-using RequestMode from "mozilla/dom/cache/IPCUtils.h";
-using RequestCache from "mozilla/dom/cache/IPCUtils.h";
-using RequestRedirect from "mozilla/dom/cache/IPCUtils.h";
-using ResponseType from "mozilla/dom/cache/IPCUtils.h";
+using HeadersGuardEnum from "mozilla/dom/FetchIPCTypes.h";
+using ReferrerPolicy from "mozilla/dom/FetchIPCTypes.h";
+using RequestCredentials from "mozilla/dom/FetchIPCTypes.h";
+using RequestMode from "mozilla/dom/FetchIPCTypes.h";
+using RequestCache from "mozilla/dom/FetchIPCTypes.h";
+using RequestRedirect from "mozilla/dom/FetchIPCTypes.h";
+using ResponseType from "mozilla/dom/FetchIPCTypes.h";
 using mozilla::void_t from "ipc/IPCMessageUtils.h";
 using struct nsID from "nsID.h";
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 struct CacheQueryParams
--- a/dom/cache/IPCUtils.h
+++ b/dom/cache/IPCUtils.h
@@ -4,61 +4,20 @@
  * 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_cache_IPCUtils_h
 #define mozilla_dom_cache_IPCUtils_h
 
 #include "ipc/IPCMessageUtils.h"
 
-// Fix X11 header brain damage that conflicts with HeadersGuardEnum::None
-#undef None
-
-#include "mozilla/dom/HeadersBinding.h"
-#include "mozilla/dom/RequestBinding.h"
-#include "mozilla/dom/ResponseBinding.h"
 #include "mozilla/dom/cache/Types.h"
 
 namespace IPC {
   template<>
-  struct ParamTraits<mozilla::dom::HeadersGuardEnum> :
-    public ContiguousEnumSerializer<mozilla::dom::HeadersGuardEnum,
-                                    mozilla::dom::HeadersGuardEnum::None,
-                                    mozilla::dom::HeadersGuardEnum::EndGuard_> {};
-  template<>
-  struct ParamTraits<mozilla::dom::ReferrerPolicy> :
-    public ContiguousEnumSerializer<mozilla::dom::ReferrerPolicy,
-                                    mozilla::dom::ReferrerPolicy::_empty,
-                                    mozilla::dom::ReferrerPolicy::EndGuard_> {};
-  template<>
-  struct ParamTraits<mozilla::dom::RequestMode> :
-    public ContiguousEnumSerializer<mozilla::dom::RequestMode,
-                                    mozilla::dom::RequestMode::Same_origin,
-                                    mozilla::dom::RequestMode::EndGuard_> {};
-  template<>
-  struct ParamTraits<mozilla::dom::RequestCredentials> :
-    public ContiguousEnumSerializer<mozilla::dom::RequestCredentials,
-                                    mozilla::dom::RequestCredentials::Omit,
-                                    mozilla::dom::RequestCredentials::EndGuard_> {};
-  template<>
-  struct ParamTraits<mozilla::dom::RequestCache> :
-    public ContiguousEnumSerializer<mozilla::dom::RequestCache,
-                                    mozilla::dom::RequestCache::Default,
-                                    mozilla::dom::RequestCache::EndGuard_> {};
-  template<>
-  struct ParamTraits<mozilla::dom::RequestRedirect> :
-    public ContiguousEnumSerializer<mozilla::dom::RequestRedirect,
-                                    mozilla::dom::RequestRedirect::Follow,
-                                    mozilla::dom::RequestRedirect::EndGuard_> {};
-  template<>
-  struct ParamTraits<mozilla::dom::ResponseType> :
-    public ContiguousEnumSerializer<mozilla::dom::ResponseType,
-                                    mozilla::dom::ResponseType::Basic,
-                                    mozilla::dom::ResponseType::EndGuard_> {};
-  template<>
   struct ParamTraits<mozilla::dom::cache::Namespace> :
     public ContiguousEnumSerializer<mozilla::dom::cache::Namespace,
                                     mozilla::dom::cache::DEFAULT_NAMESPACE,
                                     mozilla::dom::cache::NUMBER_OF_NAMESPACES>
   {};
 } // namespace IPC
 
 #endif // mozilla_dom_cache_IPCUtils_h
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -3599,18 +3599,20 @@ struct MOZ_STACK_CLASS CanvasBidiProcess
 
     mCtx->EnsureTarget();
 
     // If the operation is 'fill' with a simple color, we defer to gfxTextRun
     // which will handle color/svg-in-ot fonts appropriately. Such fonts will
     // not render well via the code below.
     if (mOp == CanvasRenderingContext2D::TextDrawOperation::FILL &&
         mState->StyleIsColor(CanvasRenderingContext2D::Style::FILL)) {
+      // TODO: determine if mCtx->mTarget is guaranteed to be non-null and valid
+      // here. If it's not, thebes will be null and we'll crash.
       RefPtr<gfxContext> thebes =
-        gfxContext::ForDrawTargetWithTransform(mCtx->mTarget);
+        gfxContext::CreatePreservingTransformOrNull(mCtx->mTarget);
       nscolor fill = mState->colorStyles[CanvasRenderingContext2D::Style::FILL];
       thebes->SetColor(Color::FromABGR(fill));
       gfxTextRun::DrawParams params(thebes);
       mTextRun->Draw(gfxTextRun::Range(mTextRun.get()), point, params);
       return;
     }
 
     uint32_t numRuns;
@@ -4783,17 +4785,17 @@ CanvasRenderingContext2D::DrawDirectlyTo
   nsIntSize scaledImageSize(std::ceil(aImgSize.width * scale.width),
                             std::ceil(aImgSize.height * scale.height));
   aSrc.Scale(scale.width, scale.height);
 
   // We're wrapping tempTarget's (our) DrawTarget here, so we need to restore
   // the matrix even though this is a temp gfxContext.
   AutoRestoreTransform autoRestoreTransform(mTarget);
 
-  RefPtr<gfxContext> context = gfxContext::ForDrawTarget(tempTarget);
+  RefPtr<gfxContext> context = gfxContext::CreateOrNull(tempTarget);
   if (!context) {
     gfxDevCrash(LogReason::InvalidContext) << "Canvas context problem";
     return;
   }
   context->SetMatrix(contextMatrix.
                        Scale(1.0 / contextScale.width,
                              1.0 / contextScale.height).
                        Translate(aDest.x - aSrc.x, aDest.y - aSrc.y));
@@ -4986,30 +4988,31 @@ CanvasRenderingContext2D::DrawWindow(nsG
   RefPtr<gfxContext> thebes;
   RefPtr<DrawTarget> drawDT;
   // Rendering directly is faster and can be done if mTarget supports Azure
   // and does not need alpha blending.
   if (gfxPlatform::GetPlatform()->SupportsAzureContentForDrawTarget(mTarget) &&
       GlobalAlpha() == 1.0f &&
       UsedOperation() == CompositionOp::OP_OVER)
   {
-    thebes = gfxContext::ForDrawTarget(mTarget);
-    MOZ_ASSERT(thebes); // alrady checked the draw target above
+    thebes = gfxContext::CreateOrNull(mTarget);
+    MOZ_ASSERT(thebes); // already checked the draw target above
+                        // (in SupportsAzureContentForDrawTarget)
     thebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21,
                                 matrix._22, matrix._31, matrix._32));
   } else {
     drawDT =
       gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(IntSize(ceil(sw), ceil(sh)),
                                                                    SurfaceFormat::B8G8R8A8);
     if (!drawDT || !drawDT->IsValid()) {
       aError.Throw(NS_ERROR_FAILURE);
       return;
     }
 
-    thebes = gfxContext::ForDrawTarget(drawDT);
+    thebes = gfxContext::CreateOrNull(drawDT);
     MOZ_ASSERT(thebes); // alrady checked the draw target above
     thebes->SetMatrix(gfxMatrix::Scaling(matrix._11, matrix._22));
   }
 
   nsCOMPtr<nsIPresShell> shell = presContext->PresShell();
   Unused << shell->RenderDocument(r, renderDocFlags, backgroundColor, thebes);
   // If this canvas was contained in the drawn window, the pre-transaction callback
   // may have returned its DT. If so, we must reacquire it here.
--- a/dom/canvas/DocumentRendererChild.cpp
+++ b/dom/canvas/DocumentRendererChild.cpp
@@ -78,17 +78,17 @@ DocumentRendererChild::RenderDocument(ns
                                          reinterpret_cast<uint8_t*>(data.BeginWriting()),
                                          IntSize(renderSize.width, renderSize.height),
                                          4 * renderSize.width,
                                          SurfaceFormat::B8G8R8A8);
     if (!dt || !dt->IsValid()) {
         gfxWarning() << "DocumentRendererChild::RenderDocument failed to Factory::CreateDrawTargetForData";
         return false;
     }
-    RefPtr<gfxContext> ctx = gfxContext::ForDrawTarget(dt);
+    RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(dt);
     MOZ_ASSERT(ctx); // already checked the draw target above
     ctx->SetMatrix(mozilla::gfx::ThebesMatrix(transform));
 
     nsCOMPtr<nsIPresShell> shell = presContext->PresShell();
     shell->RenderDocument(documentRect, renderFlags, bgColor, ctx);
 
     return true;
 }
--- 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,82 @@ 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();
+  MOZ_ASSERT(data);
+
+  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 +701,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 +759,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 +852,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 +901,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 +939,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 +1009,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 +1057,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 +1115,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 +1175,17 @@ DataTransfer::ConvertFromVariant(nsIVari
   *aLength = 0;
 
   uint16_t type;
   aVariant->GetDataType(&type);
   if (type == nsIDataType::VTYPE_INTERFACE ||
       type == nsIDataType::VTYPE_INTERFACE_IS) {
     nsCOMPtr<nsISupports> data;
     if (NS_FAILED(aVariant->GetAsISupports(getter_AddRefs(data)))) {
-     return false;
+      return false;
     }
 
     nsCOMPtr<nsIFlavorDataProvider> fdp = do_QueryInterface(data);
     if (fdp) {
       // for flavour data providers, use kFlavorHasDataProvider (which has the
       // value 0) as the length.
       fdp.forget(aSupports);
       *aLength = nsITransferable::kFlavorHasDataProvider;
@@ -1286,71 +1231,41 @@ 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)
 {
@@ -1377,36 +1292,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 +1362,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 +1408,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;
@@ -1624,21 +1488,22 @@ DataTransfer::FillInExternalCustomTypes(
   nsAutoCString str;
   str.Adopt(chrs, len);
 
   nsCOMPtr<nsIInputStream> stringStream;
   NS_NewCStringInputStream(getter_AddRefs(stringStream), str);
 
   nsCOMPtr<nsIBinaryInputStream> stream =
     do_CreateInstance("@mozilla.org/binaryinputstream;1");
-  stream->SetInputStream(stringStream);
   if (!stream) {
     return;
   }
 
+  stream->SetInputStream(stringStream);
+
   uint32_t type;
   do {
     stream->Read32(&type);
     if (type == eCustomClipboardTypeId_String) {
       uint32_t formatLength;
       stream->Read32(&formatLength);
       char* formatBytes;
       stream->ReadBytes(formatLength, &formatBytes);
--- a/dom/events/DataTransfer.h
+++ b/dom/events/DataTransfer.h
@@ -27,38 +27,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
@@ -83,27 +72,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
@@ -144,36 +132,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");
     }
@@ -205,18 +190,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
@@ -255,42 +246,35 @@ public:
     *aY = mDragImageY;
     return mDragImage;
   }
 
   nsresult Clone(nsISupports* aParent, EventMessage aEventMessage,
                  bool aUserCancelled, bool aIsCrossDomainSubFrameDrop,
                  DataTransfer** aResult);
 
-protected:
-
   // converts some formats used for compatibility in aInFormat into aOutFormat.
   // Text and text/unicode become text/plain, and URL becomes text/uri-list
   void GetRealFormat(const nsAString& aInFormat, nsAString& aOutFormat) const;
 
+protected:
+
   // caches text and uri-list data formats that exist in the drag service or
   // clipboard for retrieval later.
-  void CacheExternalData(const char* aFormat, uint32_t aIndex,
-                         nsIPrincipal* aPrincipal);
+  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;
@@ -332,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,368 @@
+/* -*- 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;
+
+  return it.forget();
+}
+
+void
+DataTransferItem::SetType(const nsAString& aType)
+{
+  mType = aType;
+}
+
+void
+DataTransferItem::SetData(nsIVariant* aData)
+{
+  if (!aData) {
+    // We are holding a temporary null which will later be filled.
+    // These are provided by the system, and have guaranteed properties about
+    // their kind based on their type.
+    MOZ_ASSERT(!mType.IsEmpty());
+
+    mKind = KIND_STRING;
+    for (uint32_t i = 0; i < ArrayLength(kFileMimeNameMap); ++i) {
+      if (mType.EqualsASCII(kFileMimeNameMap[i].mMimeName)) {
+        mKind = KIND_FILE;
+        break;
+      }
+    }
+
+    mData = nullptr;
+    return;
+  }
+
+  mKind = KIND_OTHER;
+  mData = aData;
+
+  nsCOMPtr<nsISupports> supports;
+  nsresult rv = aData->GetAsISupports(getter_AddRefs(supports));
+  if (NS_SUCCEEDED(rv) && supports) {
+    RefPtr<File> file = FileFromISupports(supports);
+    if (file) {
+      mKind = KIND_FILE;
+      return;
+    }
+  }
+
+  nsAutoString string;
+  // If we can't get the data type as a string, that means that the object
+  // should be considered to be of the "other" type. This is impossible
+  // through the APIs defined by the spec, but we provide extra Moz* APIs,
+  // which allow setting of non-string data. We determine whether we can
+  // consider it a string, by calling GetAsAString, and checking for success.
+  rv = aData->GetAsAString(string);
+  if (NS_SUCCEEDED(rv)) {
+    mKind = KIND_STRING;
+  }
+}
+
+void
+DataTransferItem::FillInExternalData()
+{
+  if (mData) {
+    return;
+  }
+
+  NS_ConvertUTF16toUTF8 utf8format(mType);
+  const char* format = utf8format.get();
+  if (strcmp(format, "text/plain") == 0) {
+    format = kUnicodeMime;
+  } else if (strcmp(format, "text/uri-list") == 0) {
+    format = kURLDataMime;
+  }
+
+  nsCOMPtr<nsITransferable> trans =
+    do_CreateInstance("@mozilla.org/widget/transferable;1");
+  if (NS_WARN_IF(!trans)) {
+    return;
+  }
+
+  trans->Init(nullptr);
+  trans->AddDataFlavor(format);
+
+  if (mParent->GetEventMessage() == ePaste) {
+    MOZ_ASSERT(mIndex == 0, "index in clipboard must be 0");
+
+    nsCOMPtr<nsIClipboard> clipboard =
+      do_GetService("@mozilla.org/widget/clipboard;1");
+    if (!clipboard || mParent->ClipboardType() < 0) {
+      return;
+    }
+
+    nsresult rv = clipboard->GetData(trans, mParent->ClipboardType());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return;
+    }
+  } else {
+    nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
+    if (!dragSession) {
+      return;
+    }
+
+    nsresult rv = dragSession->GetData(trans, mIndex);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return;
+    }
+  }
+
+  uint32_t length = 0;
+  nsCOMPtr<nsISupports> data;
+  nsresult rv = trans->GetTransferData(format, getter_AddRefs(data), &length);
+  if (NS_WARN_IF(NS_FAILED(rv) || !data)) {
+    return;
+  }
+
+  if (Kind() == KIND_FILE) {
+    // Because this is an external piece of data, mType is one of kFileMime,
+    // kPNGImageMime, kJPEGImageMime, or kGIFImageMime. We want to convert
+    // whatever type happens to actually be stored into a dom::File.
+
+    RefPtr<File> file = FileFromISupports(data);
+    MOZ_ASSERT(file, "Invalid format for Kind() == KIND_FILE");
+
+    data = do_QueryObject(file);
+  }
+
+  RefPtr<nsVariantCC> variant = new nsVariantCC();
+
+  nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data);
+  if (supportsstr) {
+    MOZ_ASSERT(Kind() == KIND_STRING);
+    nsAutoString str;
+    supportsstr->GetData(str);
+    variant->SetAsAString(str);
+  } else {
+    nsCOMPtr<nsISupportsCString> supportscstr = do_QueryInterface(data);
+    if (supportscstr) {
+      MOZ_ASSERT(Kind() == KIND_STRING);
+      nsAutoCString str;
+      supportscstr->GetData(str);
+      variant->SetAsACString(str);
+    } else {
+      MOZ_ASSERT(Kind() == KIND_FILE);
+      variant->SetAsISupports(data);
+    }
+  }
+
+  SetData(variant);
+}
+
+already_AddRefed<File>
+DataTransferItem::GetAsFile(ErrorResult& aRv)
+{
+  if (mKind != KIND_FILE) {
+    return nullptr;
+  }
+
+  nsIVariant* data = Data();
+  if (NS_WARN_IF(!data)) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsISupports> supports;
+  aRv = data->GetAsISupports(getter_AddRefs(supports));
+  MOZ_ASSERT(!aRv.Failed() && supports,
+             "Files should be stored with type dom::File!");
+  if (aRv.Failed() || !supports) {
+    return nullptr;
+  }
+
+  RefPtr<File> file = FileFromISupports(supports);
+  MOZ_ASSERT(file, "Files should be stored with type dom::File!");
+  if (!file) {
+    return nullptr;
+  }
+
+  // The File object should have been stored as a File in the nsIVariant. If it
+  // was stored as a Blob, with a file BlobImpl, we could still get to this
+  // point, except that file is a new File object, with a different identity
+  // then the original blob ibject. This should never happen so we assert
+  // against it.
+  MOZ_ASSERT(SameCOMIdentity(supports, NS_ISUPPORTS_CAST(nsIDOMBlob*, file)),
+             "Got back a new File object from FileFromISupports in GetAsFile!");
+
+  return file.forget();
+}
+
+already_AddRefed<File>
+DataTransferItem::FileFromISupports(nsISupports* aSupports)
+{
+  MOZ_ASSERT(aSupports);
+
+  RefPtr<File> file;
+
+  nsCOMPtr<nsIDOMBlob> domBlob = do_QueryInterface(aSupports);
+  if (domBlob) {
+    // Get out the blob - this is OK, because nsIDOMBlob is a builtinclass
+    // and the only implementer is Blob.
+    Blob* blob = static_cast<Blob*>(domBlob.get());
+    file = blob->ToFile();
+  } else if (nsCOMPtr<nsIFile> ifile = do_QueryInterface(aSupports)) {
+    printf("Creating a File from a nsIFile!\n");
+    file = File::CreateFromFile(GetParentObject(), ifile);
+  } else if (nsCOMPtr<nsIInputStream> stream = do_QueryInterface(aSupports)) {
+    // This consumes the stream object
+    ErrorResult rv;
+    file = CreateFileFromInputStream(GetParentObject(), stream, rv);
+    if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
+    }
+  } else if (nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(aSupports)) {
+    MOZ_ASSERT(blobImpl->IsFile());
+    file = File::Create(GetParentObject(), blobImpl);
+  }
+
+  return file.forget();
+}
+
+already_AddRefed<File>
+DataTransferItem::CreateFileFromInputStream(nsISupports* aParent,
+                                            nsIInputStream* aStream,
+                                            ErrorResult& aRv)
+{
+  const char* key = nullptr;
+  for (uint32_t i = 0; i < ArrayLength(kFileMimeNameMap); ++i) {
+    if (mType.EqualsASCII(kFileMimeNameMap[i].mMimeName)) {
+      key = kFileMimeNameMap[i].mFileName;
+      break;
+    }
+  }
+  if (!key) {
+    MOZ_ASSERT_UNREACHABLE("Unsupported mime type");
+    key = "GenericFileName";
+  }
+
+  nsXPIDLString fileName;
+  aRv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+                                           key, fileName);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  uint64_t available;
+  aRv = aStream->Available(&available);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  void* data = nullptr;
+  aRv = NS_ReadInputStreamToBuffer(aStream, &data, available);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  return File::CreateMemoryFile(aParent, data, available, fileName,
+                                mType, PR_Now());
+}
+
+void
+DataTransferItem::GetAsString(FunctionStringCallback* aCallback,
+                              ErrorResult& aRv)
+{
+  if (!aCallback || mKind != KIND_STRING) {
+    return;
+  }
+
+  nsIVariant* data = Data();
+  if (NS_WARN_IF(!data)) {
+    return;
+  }
+
+  nsAutoString stringData;
+  data->GetAsAString(stringData);
+
+  // Dispatch the callback to the main thread
+  class GASRunnable final : public Runnable
+  {
+  public:
+    GASRunnable(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> FileFromISupports(nsISupports* aParent);
+  already_AddRefed<File> CreateFileFromInputStream(nsISupports* aParent,
+                                                   nsIInputStream* aStream,
+                                                   ErrorResult& aRv);
+
+  // 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;
+};
+
+} // 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
@@ -1807,17 +1807,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/IMEStateManager.cpp
+++ b/dom/events/IMEStateManager.cpp
@@ -1024,19 +1024,20 @@ IMEStateManager::SetIMEState(const IMESt
     nsIContent* inputContent = aContent->FindFirstNonChromeOnlyAccessContent();
 
     // If we don't have an action hint and
     // return won't submit the form, use "next".
     if (context.mActionHint.IsEmpty() &&
         inputContent->IsHTMLElement(nsGkAtoms::input)) {
       bool willSubmit = false;
       nsCOMPtr<nsIFormControl> control(do_QueryInterface(inputContent));
-      mozilla::dom::Element* formElement = control->GetFormElement();
+      mozilla::dom::Element* formElement = nullptr;
       nsCOMPtr<nsIForm> form;
       if (control) {
+        formElement = control->GetFormElement();
         // is this a form and does it have a default submit element?
         if ((form = do_QueryInterface(formElement)) &&
             form->GetDefaultSubmitElement()) {
           willSubmit = true;
         // is this an html form and does it only have a single text input element?
         } else if (formElement && formElement->IsHTMLElement(nsGkAtoms::form) &&
                    !static_cast<dom::HTMLFormElement*>(formElement)->
                      ImplicitSubmissionIsDisabled()) {
--- a/dom/events/moz.build
+++ b/dom/events/moz.build
@@ -39,16 +39,18 @@ EXPORTS.mozilla.dom += [
     'BeforeAfterKeyboardEvent.h',
     'BeforeUnloadEvent.h',
     'ClipboardEvent.h',
     'CommandEvent.h',
     'CompositionEvent.h',
     'CustomEvent.h',
     'DataContainerEvent.h',
     'DataTransfer.h',
+    'DataTransferItem.h',
+    'DataTransferItemList.h',
     'DeviceMotionEvent.h',
     'DragEvent.h',
     'Event.h',
     'EventTarget.h',
     'FocusEvent.h',
     'ImageCaptureError.h',
     'InputEvent.h',
     'KeyboardEvent.h',
@@ -117,16 +119,18 @@ UNIFIED_SOURCES += [
     'UIEvent.cpp',
     'WheelEvent.cpp',
     'WheelHandlingHelper.cpp',
     'XULCommandEvent.cpp',
 ]
 
 # nsEventStateManager.cpp should be built separately because of Mac OS X headers.
 SOURCES += [
+    'DataTransferItem.cpp',
+    'DataTransferItemList.cpp',
     'EventStateManager.cpp',
 ]
 
 if CONFIG['MOZ_WEBSPEECH']:
     UNIFIED_SOURCES += ['SpeechRecognitionError.cpp']
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
--- a/dom/events/test/chrome.ini
+++ b/dom/events/test/chrome.ini
@@ -20,8 +20,9 @@ support-files =
 [test_bug602962.xul]
 [test_bug617528.xul]
 [test_bug679494.xul]
 [test_bug930374-chrome.html]
 [test_bug1128787-1.html]
 [test_bug1128787-2.html]
 [test_bug1128787-3.html]
 [test_eventctors.xul]
+[test_DataTransferItemList.html]
\ No newline at end of file
--- 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_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/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -257,26 +257,26 @@ MainThreadFetchResolver::OnResponseAvail
   }
 }
 
 MainThreadFetchResolver::~MainThreadFetchResolver()
 {
   NS_ASSERT_OWNINGTHREAD(MainThreadFetchResolver);
 }
 
-class WorkerFetchResponseRunnable final : public WorkerRunnable
+class WorkerFetchResponseRunnable final : public MainThreadWorkerRunnable
 {
   RefPtr<WorkerFetchResolver> mResolver;
   // Passed from main thread to worker thread after being initialized.
   RefPtr<InternalResponse> mInternalResponse;
 public:
   WorkerFetchResponseRunnable(WorkerPrivate* aWorkerPrivate,
                               WorkerFetchResolver* aResolver,
                               InternalResponse* aResponse)
-    : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+    : MainThreadWorkerRunnable(aWorkerPrivate)
     , mResolver(aResolver)
     , mInternalResponse(aResponse)
   {
   }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
@@ -291,35 +291,16 @@ public:
       promise->MaybeResolve(response);
     } else {
       ErrorResult result;
       result.ThrowTypeError<MSG_FETCH_FAILED>();
       promise->MaybeReject(result);
     }
     return true;
   }
-
-  bool
-  PreDispatch(WorkerPrivate* aWorkerPrivate) override
-  {
-    // Don't call default PreDispatch() since it incorrectly asserts we are
-    // dispatching from the parent worker thread.  We always dispatch from
-    // the main thread.
-    AssertIsOnMainThread();
-    return true;
-  }
-
-  void
-  PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
-  {
-    // Don't call default PostDispatch() since it incorrectly asserts we are
-    // dispatching from the parent worker thread.  We always dispatch from
-    // the main thread.
-    AssertIsOnMainThread();
-  }
 };
 
 class WorkerFetchResponseEndBase
 {
   RefPtr<PromiseWorkerProxy> mPromiseProxy;
 public:
   explicit WorkerFetchResponseEndBase(PromiseWorkerProxy* aPromiseProxy)
     : mPromiseProxy(aPromiseProxy)
@@ -331,23 +312,22 @@ public:
   WorkerRunInternal(WorkerPrivate* aWorkerPrivate)
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
     mPromiseProxy->CleanUp();
   }
 };
 
-class WorkerFetchResponseEndRunnable final : public WorkerRunnable
+class WorkerFetchResponseEndRunnable final : public MainThreadWorkerRunnable
                                            , public WorkerFetchResponseEndBase
 {
 public:
   explicit WorkerFetchResponseEndRunnable(PromiseWorkerProxy* aPromiseProxy)
-    : WorkerRunnable(aPromiseProxy->GetWorkerPrivate(),
-                     WorkerThreadUnchangedBusyCount)
+    : MainThreadWorkerRunnable(aPromiseProxy->GetWorkerPrivate())
     , WorkerFetchResponseEndBase(aPromiseProxy)
   {
   }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     WorkerRunInternal(aWorkerPrivate);
@@ -357,35 +337,16 @@ public:
   nsresult
   Cancel() override
   {
     // Execute Run anyway to make sure we cleanup our promise proxy to avoid
     // leaking the worker thread
     Run();
     return WorkerRunnable::Cancel();
   }
-
-  bool
-  PreDispatch(WorkerPrivate* aWorkerPrivate) override
-  {
-    // Don't call default PreDispatch() since it incorrectly asserts we are
-    // dispatching from the parent worker thread.  We always dispatch from
-    // the main thread.
-    AssertIsOnMainThread();
-    return true;
-  }
-
-  void
-  PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
-  {
-    // Don't call default PostDispatch() since it incorrectly asserts we are
-    // dispatching from the parent worker thread.  We always dispatch from
-    // the main thread.
-    AssertIsOnMainThread();
-  }
 };
 
 class WorkerFetchResponseEndControlRunnable final : public MainThreadWorkerControlRunnable
                                                   , public WorkerFetchResponseEndBase
 {
 public:
   explicit WorkerFetchResponseEndControlRunnable(PromiseWorkerProxy* aPromiseProxy)
     : MainThreadWorkerControlRunnable(aPromiseProxy->GetWorkerPrivate())
@@ -638,62 +599,43 @@ ExtractByteStreamFromBody(const ArrayBuf
   return NS_ERROR_FAILURE;
 }
 
 namespace {
 /*
  * Called on successfully reading the complete stream.
  */
 template <class Derived>
-class ContinueConsumeBodyRunnable final : public WorkerRunnable
+class ContinueConsumeBodyRunnable final : public MainThreadWorkerRunnable
 {
   // This has been addrefed before this runnable is dispatched,
   // released in WorkerRun().
   FetchBody<Derived>* mFetchBody;
   nsresult mStatus;
   uint32_t mLength;
   uint8_t* mResult;
 
 public:
   ContinueConsumeBodyRunnable(FetchBody<Derived>* aFetchBody, nsresult aStatus,
                               uint32_t aLength, uint8_t* aResult)
-    : WorkerRunnable(aFetchBody->mWorkerPrivate, WorkerThreadUnchangedBusyCount)
+    : MainThreadWorkerRunnable(aFetchBody->mWorkerPrivate)
     , mFetchBody(aFetchBody)
     , mStatus(aStatus)
     , mLength(aLength)
     , mResult(aResult)
   {
     MOZ_ASSERT(NS_IsMainThread());
   }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     mFetchBody->ContinueConsumeBody(mStatus, mLength, mResult);
     return true;
   }
-
-  bool
-  PreDispatch(WorkerPrivate* aWorkerPrivate) override
-  {
-    // Don't call default PreDispatch() since it incorrectly asserts we are
-    // dispatching from the parent worker thread.  We always dispatch from
-    // the main thread.
-    AssertIsOnMainThread();
-    return true;
-  }
-
-  void
-  PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
-  {
-    // Don't call default PostDispatch() since it incorrectly asserts we are
-    // dispatching from the parent worker thread.  We always dispatch from
-    // the main thread.
-    AssertIsOnMainThread();
-  }
 };
 
 // OnStreamComplete always adopts the buffer, utility class to release it in
 // a couple of places.
 class MOZ_STACK_CLASS AutoFreeBuffer final {
   uint8_t* mBuffer;
 
 public:
new file mode 100644
--- /dev/null
+++ b/dom/fetch/FetchIPCTypes.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_fetch_IPCUtils_h
+#define mozilla_dom_fetch_IPCUtils_h
+
+#include "ipc/IPCMessageUtils.h"
+
+// Fix X11 header brain damage that conflicts with HeadersGuardEnum::None
+#undef None
+
+#include "mozilla/dom/HeadersBinding.h"
+#include "mozilla/dom/RequestBinding.h"
+#include "mozilla/dom/ResponseBinding.h"
+
+namespace IPC {
+  template<>
+  struct ParamTraits<mozilla::dom::HeadersGuardEnum> :
+    public ContiguousEnumSerializer<mozilla::dom::HeadersGuardEnum,
+                                    mozilla::dom::HeadersGuardEnum::None,
+                                    mozilla::dom::HeadersGuardEnum::EndGuard_> {};
+  template<>
+  struct ParamTraits<mozilla::dom::ReferrerPolicy> :
+    public ContiguousEnumSerializer<mozilla::dom::ReferrerPolicy,
+                                    mozilla::dom::ReferrerPolicy::_empty,
+                                    mozilla::dom::ReferrerPolicy::EndGuard_> {};
+  template<>
+  struct ParamTraits<mozilla::dom::RequestMode> :
+    public ContiguousEnumSerializer<mozilla::dom::RequestMode,
+                                    mozilla::dom::RequestMode::Same_origin,
+                                    mozilla::dom::RequestMode::EndGuard_> {};
+  template<>
+  struct ParamTraits<mozilla::dom::RequestCredentials> :
+    public ContiguousEnumSerializer<mozilla::dom::RequestCredentials,
+                                    mozilla::dom::RequestCredentials::Omit,
+                                    mozilla::dom::RequestCredentials::EndGuard_> {};
+  template<>
+  struct ParamTraits<mozilla::dom::RequestCache> :
+    public ContiguousEnumSerializer<mozilla::dom::RequestCache,
+                                    mozilla::dom::RequestCache::Default,
+                                    mozilla::dom::RequestCache::EndGuard_> {};
+  template<>
+  struct ParamTraits<mozilla::dom::RequestRedirect> :
+    public ContiguousEnumSerializer<mozilla::dom::RequestRedirect,
+                                    mozilla::dom::RequestRedirect::Follow,
+                                    mozilla::dom::RequestRedirect::EndGuard_> {};
+  template<>
+  struct ParamTraits<mozilla::dom::ResponseType> :
+    public ContiguousEnumSerializer<mozilla::dom::ResponseType,
+                                    mozilla::dom::ResponseType::Basic,
+                                    mozilla::dom::ResponseType::EndGuard_> {};
+} // namespace IPC
+
+#endif // mozilla_dom_fetch_IPCUtils_h
new file mode 100644
--- /dev/null
+++ b/dom/fetch/FetchTypes.ipdlh
@@ -0,0 +1,61 @@
+/* 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 IPCStream;
+include ChannelInfo;
+include PBackgroundSharedTypes;
+
+using HeadersGuardEnum from "mozilla/dom/FetchIPCTypes.h";
+using ReferrerPolicy from "mozilla/dom/FetchIPCTypes.h";
+using RequestCredentials from "mozilla/dom/FetchIPCTypes.h";
+using RequestMode from "mozilla/dom/FetchIPCTypes.h";
+using ResponseType from "mozilla/dom/FetchIPCTypes.h";
+using RequestRedirect from "mozilla/dom/FetchIPCTypes.h";
+using RequestCache from "mozilla/dom/FetchIPCTypes.h";
+
+namespace mozilla {
+namespace dom {
+
+struct HeadersEntry
+{
+  nsCString name;
+  nsCString value;
+};
+
+// Note, this does not yet serialize *all* of InternalRequest
+// Make sure that it contains the fields that you care about
+struct IPCInternalRequest
+{
+  nsCString[] urls;
+  nsCString method;
+  HeadersEntry[] headers;
+  HeadersGuardEnum headersGuard;
+  nsString referrer;
+  ReferrerPolicy referrerPolicy;
+  RequestMode mode;
+  RequestCredentials credentials;
+  uint32_t contentPolicyType;
+  RequestCache requestCache;
+  RequestRedirect requestRedirect;
+};
+
+// Note, this does not yet serialize *all* of InternalResponse
+// Make sure that it contains the fields that you care about
+struct IPCInternalResponse
+{
+  ResponseType type;
+  nsCString[] urlList;
+  uint32_t status;
+  nsCString statusText;
+  HeadersEntry[] headers;
+  HeadersGuardEnum headersGuard;
+  IPCChannelInfo channelInfo;
+  OptionalPrincipalInfo principalInfo;
+  OptionalIPCStream body;
+  int64_t bodySize;
+};
+
+
+} // namespace ipc
+} // namespace mozilla
--- a/dom/fetch/InternalHeaders.cpp
+++ b/dom/fetch/InternalHeaders.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "mozilla/dom/InternalHeaders.h"
 
+#include "mozilla/dom/FetchTypes.h"
 #include "mozilla/ErrorResult.h"
 
 #include "nsCharSeparatedTokenizer.h"
 #include "nsContentUtils.h"
 #include "nsNetUtil.h"
 #include "nsReadableUtils.h"
 
 namespace mozilla {
@@ -18,16 +19,37 @@ namespace dom {
 
 InternalHeaders::InternalHeaders(const nsTArray<Entry>&& aHeaders,
                                  HeadersGuardEnum aGuard)
   : mGuard(aGuard)
   , mList(aHeaders)
 {
 }
 
+InternalHeaders::InternalHeaders(const nsTArray<HeadersEntry>& aHeadersEntryList,
+                                 HeadersGuardEnum aGuard)
+  : mGuard(aGuard)
+{
+  for (const HeadersEntry& headersEntry : aHeadersEntryList) {
+    mList.AppendElement(Entry(headersEntry.name(), headersEntry.value()));
+  }
+}
+
+void
+InternalHeaders::ToIPC(nsTArray<HeadersEntry>& aIPCHeaders,
+                       HeadersGuardEnum& aGuard)
+{
+  aGuard = mGuard;
+
+  aIPCHeaders.Clear();
+  for (Entry& entry : mList) {
+    aIPCHeaders.AppendElement(HeadersEntry(entry.mName, entry.mValue));
+  }
+}
+
 void
 InternalHeaders::Append(const nsACString& aName, const nsACString& aValue,
                         ErrorResult& aRv)
 {
   nsAutoCString lowerName;
   ToLowerCase(aName, lowerName);
 
   if (IsInvalidMutableHeader(lowerName, aValue, aRv)) {
--- a/dom/fetch/InternalHeaders.h
+++ b/dom/fetch/InternalHeaders.h
@@ -16,16 +16,17 @@
 
 namespace mozilla {
 
 class ErrorResult;
 
 namespace dom {
 
 template<typename T> class MozMap;
+class HeadersEntry;
 
 class InternalHeaders final
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalHeaders)
 
 public:
   struct Entry
   {
@@ -59,16 +60,22 @@ public:
     // Note that it's important to set the guard after Fill(), to make sure
     // that Fill() doesn't fail if aOther is immutable.
     mGuard = aOther.mGuard;
   }
 
   explicit InternalHeaders(const nsTArray<Entry>&& aHeaders,
                            HeadersGuardEnum aGuard = HeadersGuardEnum::None);
 
+  InternalHeaders(const nsTArray<HeadersEntry>& aHeadersEntryList,
+                  HeadersGuardEnum aGuard);
+
+  void ToIPC(nsTArray<HeadersEntry>& aIPCHeaders,
+             HeadersGuardEnum& aGuard);
+
   void Append(const nsACString& aName, const nsACString& aValue,
               ErrorResult& aRv);
   void Delete(const nsACString& aName, ErrorResult& aRv);
   void Get(const nsACString& aName, nsCString& aValue, ErrorResult& aRv) const;
   void GetAll(const nsACString& aName, nsTArray<nsCString>& aResults,
               ErrorResult& aRv) const;
   bool Has(const nsACString& aName, ErrorResult& aRv) const;
   void Set(const nsACString& aName, const nsACString& aValue, ErrorResult& aRv);
--- a/dom/fetch/InternalRequest.cpp
+++ b/dom/fetch/InternalRequest.cpp
@@ -6,16 +6,17 @@
 
 #include "InternalRequest.h"
 
 #include "nsIContentPolicy.h"
 #include "nsIDocument.h"
 #include "nsStreamUtils.h"
 
 #include "mozilla/ErrorResult.h"
+#include "mozilla/dom/FetchTypes.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/workers/Workers.h"
 
 #include "WorkerPrivate.h"
 
 namespace mozilla {
 namespace dom {
 
@@ -97,21 +98,56 @@ InternalRequest::InternalRequest(const I
   , mUnsafeRequest(aOther.mUnsafeRequest)
   , mUseURLCredentials(aOther.mUseURLCredentials)
   , mCreatedByFetchEvent(aOther.mCreatedByFetchEvent)
   , mContentPolicyTypeOverridden(aOther.mContentPolicyTypeOverridden)
 {
   // NOTE: does not copy body stream... use the fallible Clone() for that
 }
 
+InternalRequest::InternalRequest(const IPCInternalRequest& aIPCRequest)
+  : mMethod(aIPCRequest.method())
+  , mURLList(aIPCRequest.urls())
+  , mHeaders(new InternalHeaders(aIPCRequest.headers(),
+                                 aIPCRequest.headersGuard()))
+  , mContentPolicyType(aIPCRequest.contentPolicyType())
+  , mReferrer(aIPCRequest.referrer())
+  , mReferrerPolicy(aIPCRequest.referrerPolicy())
+  , mMode(aIPCRequest.mode())
+  , mCredentialsMode(aIPCRequest.credentials())
+  , mCacheMode(aIPCRequest.requestCache())
+  , mRedirectMode(aIPCRequest.requestRedirect())
+{
+  MOZ_ASSERT(!mURLList.IsEmpty());
+}
+
 InternalRequest::~InternalRequest()
 {
 }
 
 void
+InternalRequest::ToIPC(IPCInternalRequest* aIPCRequest)
+{
+  MOZ_ASSERT(aIPCRequest);
+  MOZ_ASSERT(!mURLList.IsEmpty());
+  aIPCRequest->urls() = mURLList;
+  aIPCRequest->method() = mMethod;
+
+  mHeaders->ToIPC(aIPCRequest->headers(), aIPCRequest->headersGuard());
+
+  aIPCRequest->referrer() = mReferrer;
+  aIPCRequest->referrerPolicy() = mReferrerPolicy;
+  aIPCRequest->mode() = mMode;
+  aIPCRequest->credentials() = mCredentialsMode;
+  aIPCRequest->contentPolicyType() = mContentPolicyType;
+  aIPCRequest->requestCache() = mCacheMode;
+  aIPCRequest->requestRedirect() = mRedirectMode;
+}
+
+void
 InternalRequest::SetContentPolicyType(nsContentPolicyType aContentPolicyType)
 {
   mContentPolicyType = aContentPolicyType;
 }
 
 void
 InternalRequest::OverrideContentPolicyType(nsContentPolicyType aContentPolicyType)
 {
--- a/dom/fetch/InternalRequest.h
+++ b/dom/fetch/InternalRequest.h
@@ -73,16 +73,17 @@ namespace dom {
  * TODO: Add a content type for location
  * TODO: Add a content type for hyperlink
  * TODO: Add a content type for form
  * TODO: Add a content type for favicon
  * TODO: Add a content type for download
  */
 
 class Request;
+class IPCInternalRequest;
 
 #define kFETCH_CLIENT_REFERRER_STR "about:client"
 
 class InternalRequest final
 {
   friend class Request;
 
 public:
@@ -145,16 +146,20 @@ public:
     , mSynchronous(false)
     , mUnsafeRequest(false)
     , mUseURLCredentials(false)
   {
     MOZ_ASSERT(!aURL.IsEmpty());
     AddURL(aURL);
   }
 
+  explicit InternalRequest(const IPCInternalRequest& aIPCRequest);
+
+  void ToIPC(IPCInternalRequest* aIPCRequest);
+
   already_AddRefed<InternalRequest> Clone();
 
   void
   GetMethod(nsCString& aMethod) const
   {
     aMethod.Assign(mMethod);
   }
 
--- a/dom/fetch/InternalResponse.cpp
+++ b/dom/fetch/InternalResponse.cpp
@@ -5,35 +5,144 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "InternalResponse.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/dom/InternalHeaders.h"
 #include "mozilla/dom/cache/CacheTypes.h"
 #include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
 #include "nsIURI.h"
 #include "nsStreamUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 InternalResponse::InternalResponse(uint16_t aStatus, const nsACString& aStatusText)
   : mType(ResponseType::Default)
   , mStatus(aStatus)
   , mStatusText(aStatusText)
   , mHeaders(new InternalHeaders(HeadersGuardEnum::Response))
   , mBodySize(UNKNOWN_BODY_SIZE)
 {
 }
 
+already_AddRefed<InternalResponse>
+InternalResponse::FromIPC(const IPCInternalResponse& aIPCResponse)
+{
+  MOZ_ASSERT(!aIPCResponse.urlList().IsEmpty());
+
+  if (aIPCResponse.type() == ResponseType::Error) {
+    return InternalResponse::NetworkError();
+  }
+
+  RefPtr<InternalResponse> response =
+    new InternalResponse(aIPCResponse.status(),
+                         aIPCResponse.statusText());
+
+  response->SetURLList(aIPCResponse.urlList());
+
+  response->mHeaders = new InternalHeaders(aIPCResponse.headers(),
+                                           aIPCResponse.headersGuard());
+
+  response->InitChannelInfo(aIPCResponse.channelInfo());
+  if (aIPCResponse.principalInfo().type() == mozilla::ipc::OptionalPrincipalInfo::TPrincipalInfo) {
+    UniquePtr<mozilla::ipc::PrincipalInfo> info(new mozilla::ipc::PrincipalInfo(aIPCResponse.principalInfo().get_PrincipalInfo()));
+    response->SetPrincipalInfo(Move(info));
+  }
+
+  nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(aIPCResponse.body());
+  response->SetBody(stream, aIPCResponse.bodySize());
+
+  switch (aIPCResponse.type())
+  {
+    case ResponseType::Basic:
+      response = response->BasicResponse();
+      break;
+    case ResponseType::Cors:
+      response = response->CORSResponse();
+      break;
+    case ResponseType::Default:
+      break;
+    case ResponseType::Opaque:
+      response = response->OpaqueResponse();
+      break;
+    case ResponseType::Opaqueredirect:
+      response = response->OpaqueRedirectResponse();
+      break;
+    default:
+      MOZ_CRASH("Unexpected ResponseType!");
+  }
+  MOZ_ASSERT(response);
+
+  return response.forget();
+}
+
 InternalResponse::~InternalResponse()
 {
 }
 
+template void
+InternalResponse::ToIPC<PContentParent>
+  (IPCInternalResponse* aIPCResponse,
+   PContentParent* aManager,
+   UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream);
+template void
+InternalResponse::ToIPC<PContentChild>
+  (IPCInternalResponse* aIPCResponse,
+   PContentChild* aManager,
+   UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream);
+template void
+InternalResponse::ToIPC<mozilla::ipc::PBackgroundParent>
+  (IPCInternalResponse* aIPCResponse,
+   mozilla::ipc::PBackgroundParent* aManager,
+   UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream);
+template void
+InternalResponse::ToIPC<mozilla::ipc::PBackgroundChild>
+  (IPCInternalResponse* aIPCResponse,
+   mozilla::ipc::PBackgroundChild* aManager,
+   UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream);
+
+template<typename M>
+void
+InternalResponse::ToIPC(IPCInternalResponse* aIPCResponse,
+                        M* aManager,
+                        UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream)
+{
+  MOZ_ASSERT(aIPCResponse);
+  MOZ_ASSERT(!mURLList.IsEmpty());
+  aIPCResponse->type() = mType;
+  aIPCResponse->urlList() = mURLList;
+  aIPCResponse->status() = GetUnfilteredStatus();
+  aIPCResponse->statusText() = GetUnfilteredStatusText();
+
+  mHeaders->ToIPC(aIPCResponse->headers(), aIPCResponse->headersGuard());
+
+  aIPCResponse->channelInfo() = mChannelInfo.AsIPCChannelInfo();
+  if (mPrincipalInfo) {
+    aIPCResponse->principalInfo() = *mPrincipalInfo;
+  } else {
+    aIPCResponse->principalInfo() = void_t();
+  }
+
+  nsCOMPtr<nsIInputStream> body;
+  int64_t bodySize;
+  GetUnfilteredBody(getter_AddRefs(body), &bodySize);
+
+  if (body) {
+    aAutoStream.reset(new mozilla::ipc::AutoIPCStream(aIPCResponse->body()));
+    aAutoStream->Serialize(body, aManager);
+  } else {
+    aIPCResponse->body() = void_t();
+  }
+
+  aIPCResponse->bodySize() = bodySize;
+}
+
 already_AddRefed<InternalResponse>
 InternalResponse::Clone()
 {
   RefPtr<InternalResponse> clone = CreateIncompleteCopy();
 
   clone->mHeaders = new InternalHeaders(*mHeaders);
   if (mWrappedResponse) {
     clone->mWrappedResponse = mWrappedResponse->Clone();
--- a/dom/fetch/InternalResponse.h
+++ b/dom/fetch/InternalResponse.h
@@ -12,31 +12,42 @@
 
 #include "mozilla/dom/ResponseBinding.h"
 #include "mozilla/dom/ChannelInfo.h"
 #include "mozilla/UniquePtr.h"
 
 namespace mozilla {
 namespace ipc {
 class PrincipalInfo;
+class AutoIPCStream;
 } // namespace ipc
 
 namespace dom {
 
 class InternalHeaders;
+class IPCInternalResponse;
 
 class InternalResponse final
 {
   friend class FetchDriver;
 
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalResponse)
 
   InternalResponse(uint16_t aStatus, const nsACString& aStatusText);
 
+  static already_AddRefed<InternalResponse>
+  FromIPC(const IPCInternalResponse& aIPCResponse);
+
+  template<typename M>
+  void
+  ToIPC(IPCInternalResponse* aIPCResponse,
+        M* aManager,
+        UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream);
+
   already_AddRefed<InternalResponse> Clone();
 
   static already_AddRefed<InternalResponse>
   NetworkError()
   {
     RefPtr<InternalResponse> response = new InternalResponse(0, EmptyCString());
     ErrorResult result;
     response->Headers()->SetGuard(HeadersGuardEnum::Immutable, result);
--- a/dom/fetch/moz.build
+++ b/dom/fetch/moz.build
@@ -3,16 +3,17 @@
 # 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/.
 
 EXPORTS.mozilla.dom += [
     'ChannelInfo.h',
     'Fetch.h',
     'FetchDriver.h',
+    'FetchIPCTypes.h',
     'FetchUtil.h',
     'Headers.h',
     'InternalHeaders.h',
     'InternalRequest.h',
     'InternalResponse.h',
     'Request.h',
     'Response.h',
 ]
@@ -27,16 +28,17 @@ UNIFIED_SOURCES += [
     'InternalRequest.cpp',
     'InternalResponse.cpp',
     'Request.cpp',
     'Response.cpp',
 ]
 
 IPDL_SOURCES += [
     'ChannelInfo.ipdlh',
+    'FetchTypes.ipdlh',
 ]
 
 LOCAL_INCLUDES += [
     '../workers',
     # For HttpBaseChannel.h dependencies
     '/netwerk/base',
     # For nsDataHandler.h
     '/netwerk/protocol/data',
--- a/dom/filesystem/Directory.cpp
+++ b/dom/filesystem/Directory.cpp
@@ -32,64 +32,16 @@
 // by Directory#CreateFileW.
 #ifdef CreateFile
 #undef CreateFile
 #endif
 
 namespace mozilla {
 namespace dom {
 
-namespace {
-
-bool
-TokenizerIgnoreNothing(char16_t /* aChar */)
-{
-  return false;
-}
-
-bool
-IsValidRelativeDOMPath(const nsString& aPath, nsTArray<nsString>& aParts)
-{
-  // We don't allow empty relative path to access the root.
-  if (aPath.IsEmpty()) {
-    return false;
-  }
-
-  // Leading and trailing "/" are not allowed.
-  if (aPath.First() == FILESYSTEM_DOM_PATH_SEPARATOR_CHAR ||
-      aPath.Last() == FILESYSTEM_DOM_PATH_SEPARATOR_CHAR) {
-    return false;
-  }
-
-  NS_NAMED_LITERAL_STRING(kCurrentDir, ".");
-  NS_NAMED_LITERAL_STRING(kParentDir, "..");
-
-  // Split path and check each path component.
-  nsCharSeparatedTokenizerTemplate<TokenizerIgnoreNothing>
-    tokenizer(aPath, FILESYSTEM_DOM_PATH_SEPARATOR_CHAR);
-
-  while (tokenizer.hasMoreTokens()) {
-    nsDependentSubstring pathComponent = tokenizer.nextToken();
-    // The path containing empty components, such as "foo//bar", is invalid.
-    // We don't allow paths, such as "../foo", "foo/./bar" and "foo/../bar",
-    // to walk up the directory.
-    if (pathComponent.IsEmpty() ||
-        pathComponent.Equals(kCurrentDir) ||
-        pathComponent.Equals(kParentDir)) {
-      return false;
-    }
-
-    aParts.AppendElement(pathComponent);
-  }
-
-  return true;
-}
-
-} // anonymous namespace
-
 NS_IMPL_CYCLE_COLLECTION_CLASS(Directory)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Directory)
   if (tmp->mFileSystem) {
     tmp->mFileSystem->Unlink();
     tmp->mFileSystem = nullptr;
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
@@ -515,17 +467,17 @@ Directory::DOMPathToRealPath(const nsASt
   nsString relativePath;
   relativePath = aPath;
 
   // Trim white spaces.
   static const char kWhitespace[] = "\b\t\r\n ";
   relativePath.Trim(kWhitespace);
 
   nsTArray<nsString> parts;
-  if (!IsValidRelativeDOMPath(relativePath, parts)) {
+  if (!FileSystemUtils::IsValidRelativeDOMPath(relativePath, parts)) {
     return NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR;
   }
 
   nsCOMPtr<nsIFile> file;
   nsresult rv = mFile->Clone(getter_AddRefs(file));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
--- a/dom/filesystem/FileSystemUtils.cpp
+++ b/dom/filesystem/FileSystemUtils.cpp
@@ -4,16 +4,26 @@
  * 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 "mozilla/dom/FileSystemUtils.h"
 
 namespace mozilla {
 namespace dom {
 
+namespace {
+
+bool
+TokenizerIgnoreNothing(char16_t /* aChar */)
+{
+  return false;
+}
+
+} // anonymous namespace
+
 /* static */ bool
 FileSystemUtils::IsDescendantPath(nsIFile* aFile,
                                   nsIFile* aDescendantFile)
 {
   nsAutoString path;
   nsresult rv = aFile->GetPath(path);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
@@ -29,10 +39,49 @@ FileSystemUtils::IsDescendantPath(nsIFil
   if (descendantPath.Length() <= path.Length() ||
       !StringBeginsWith(descendantPath, path)) {
     return false;
   }
 
   return true;
 }
 
+/* static */ bool
+FileSystemUtils::IsValidRelativeDOMPath(const nsAString& aPath,
+                                        nsTArray<nsString>& aParts)
+{
+  // We don't allow empty relative path to access the root.
+  if (aPath.IsEmpty()) {
+    return false;
+  }
+
+  // Leading and trailing "/" are not allowed.
+  if (aPath.First() == FILESYSTEM_DOM_PATH_SEPARATOR_CHAR ||
+      aPath.Last() == FILESYSTEM_DOM_PATH_SEPARATOR_CHAR) {
+    return false;
+  }
+
+  NS_NAMED_LITERAL_STRING(kCurrentDir, ".");
+  NS_NAMED_LITERAL_STRING(kParentDir, "..");
+
+  // Split path and check each path component.
+  nsCharSeparatedTokenizerTemplate<TokenizerIgnoreNothing>
+    tokenizer(aPath, FILESYSTEM_DOM_PATH_SEPARATOR_CHAR);
+
+  while (tokenizer.hasMoreTokens()) {
+    nsDependentSubstring pathComponent = tokenizer.nextToken();
+    // The path containing empty components, such as "foo//bar", is invalid.
+    // We don't allow paths, such as "../foo", "foo/./bar" and "foo/../bar",
+    // to walk up the directory.
+    if (pathComponent.IsEmpty() ||
+        pathComponent.Equals(kCurrentDir) ||
+        pathComponent.Equals(kParentDir)) {
+      return false;
+    }
+
+    aParts.AppendElement(pathComponent);
+  }
+
+  return true;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/filesystem/FileSystemUtils.h
+++ b/dom/filesystem/FileSystemUtils.h
@@ -22,14 +22,22 @@ namespace dom {
 class FileSystemUtils
 {
 public:
   /*
    * Return true if aDescendantPath is a descendant of aPath.
    */
   static bool
   IsDescendantPath(nsIFile* aPath, nsIFile* aDescendantPath);
+
+  /**
+   * Return true if this is valid DOMPath. It also splits the path in
+   * subdirectories and stores them in aParts.
+   */
+  static bool
+  IsValidRelativeDOMPath(const nsAString& aPath,
+                         nsTArray<nsString>& aParts);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_FileSystemUtils_h
--- a/dom/filesystem/OSFileSystem.h
+++ b/dom/filesystem/OSFileSystem.h
@@ -88,17 +88,16 @@ public:
                    ErrorResult& aRv) const override
   {
     MOZ_CRASH("This should not be called on the PBackground thread.");
   }
 
   virtual bool
   IsSafeFile(nsIFile* aFile) const override
   {
-    MOZ_CRASH("This should not be called on the PBackground thread.");
     return true;
   }
 
   virtual bool
   IsSafeDirectory(Directory* aDir) const override
   {
     MOZ_CRASH("This should not be called on the PBackground thread.");
     return true;
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/CallbackRunnables.cpp
@@ -0,0 +1,167 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "CallbackRunnables.h"
+#include "mozilla/dom/DirectoryBinding.h"
+#include "mozilla/dom/DOMError.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FileBinding.h"
+#include "mozilla/dom/FileEntry.h"
+#include "mozilla/dom/Promise.h"
+#include "nsIGlobalObject.h"
+#include "nsPIDOMWindow.h"
+
+namespace mozilla {
+namespace dom {
+
+EntryCallbackRunnable::EntryCallbackRunnable(EntryCallback* aCallback,
+                                             Entry* aEntry)
+  : mCallback(aCallback)
+  , mEntry(aEntry)
+{
+  MOZ_ASSERT(aCallback);
+  MOZ_ASSERT(aEntry);
+}
+
+NS_IMETHODIMP
+EntryCallbackRunnable::Run()
+{
+  mCallback->HandleEvent(*mEntry);
+  return NS_OK;
+}
+
+ErrorCallbackRunnable::ErrorCallbackRunnable(nsIGlobalObject* aGlobalObject,
+                                             ErrorCallback* aCallback,
+                                             nsresult aError)
+  : mGlobal(aGlobalObject)
+  , mCallback(aCallback)
+  , mError(aError)
+{
+  MOZ_ASSERT(aGlobalObject);
+  MOZ_ASSERT(aCallback);
+  MOZ_ASSERT(NS_FAILED(aError));
+}
+
+NS_IMETHODIMP
+ErrorCallbackRunnable::Run()
+{
+  nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
+  if (NS_WARN_IF(!window)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  RefPtr<DOMError> error = new DOMError(window, mError);
+  mCallback->HandleEvent(*error);
+  return NS_OK;
+}
+
+EmptyEntriesCallbackRunnable::EmptyEntriesCallbackRunnable(EntriesCallback* aCallback)
+  : mCallback(aCallback)
+{
+  MOZ_ASSERT(aCallback);
+}
+
+NS_IMETHODIMP
+EmptyEntriesCallbackRunnable::Run()
+{
+  Sequence<OwningNonNull<Entry>> sequence;
+  mCallback->HandleEvent(sequence);
+  return NS_OK;
+}
+
+GetEntryHelper::GetEntryHelper(nsIGlobalObject* aGlobalObject,
+                               DOMFileSystem* aFileSystem,
+                               EntryCallback* aSuccessCallback,
+                               ErrorCallback* aErrorCallback,
+                               DirectoryEntry::GetInternalType aType)
+  : mGlobal(aGlobalObject)
+  , mFileSystem(aFileSystem)
+  , mSuccessCallback(aSuccessCallback)
+  , mErrorCallback(aErrorCallback)
+  , mType(aType)
+{
+  MOZ_ASSERT(aGlobalObject);
+  MOZ_ASSERT(aFileSystem);
+  MOZ_ASSERT(aSuccessCallback || aErrorCallback);
+}
+
+GetEntryHelper::~GetEntryHelper()
+{}
+
+void
+GetEntryHelper::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+  if(NS_WARN_IF(!aValue.isObject())) {
+    return;
+  }
+
+  JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+
+  if (mType == DirectoryEntry::eGetFile) {
+    RefPtr<File> file;
+    if (NS_FAILED(UNWRAP_OBJECT(File, obj, file))) {
+      Error(NS_ERROR_DOM_TYPE_MISMATCH_ERR);
+      return;
+    }
+
+    RefPtr<FileEntry> entry = new FileEntry(mGlobal, file, mFileSystem);
+    mSuccessCallback->HandleEvent(*entry);
+    return;
+  }
+
+  MOZ_ASSERT(mType == DirectoryEntry::eGetDirectory);
+
+  RefPtr<Directory> directory;
+  if (NS_FAILED(UNWRAP_OBJECT(Directory, obj, directory))) {
+    Error(NS_ERROR_DOM_TYPE_MISMATCH_ERR);
+    return;
+  }
+
+  RefPtr<DirectoryEntry> entry =
+    new DirectoryEntry(mGlobal, directory, mFileSystem);
+  mSuccessCallback->HandleEvent(*entry);
+}
+
+void
+GetEntryHelper::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+  Error(NS_ERROR_DOM_NOT_FOUND_ERR);
+}
+
+void
+GetEntryHelper::Error(nsresult aError)
+{
+  MOZ_ASSERT(NS_FAILED(aError));
+
+  if (mErrorCallback) {
+    RefPtr<ErrorCallbackRunnable> runnable =
+      new ErrorCallbackRunnable(mGlobal, mErrorCallback, aError);
+    nsresult rv = NS_DispatchToMainThread(runnable);
+    NS_WARN_IF(NS_FAILED(rv));
+  }
+}
+
+NS_IMPL_ISUPPORTS0(GetEntryHelper);
+
+/* static */ void
+ErrorCallbackHelper::Call(nsIGlobalObject* aGlobal,
+                          const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+                          nsresult aError)
+{
+  MOZ_ASSERT(aGlobal);
+  MOZ_ASSERT(NS_FAILED(aError));
+
+  if (aErrorCallback.WasPassed()) {
+    RefPtr<ErrorCallbackRunnable> runnable =
+      new ErrorCallbackRunnable(aGlobal, &aErrorCallback.Value(), aError);
+    nsresult rv = NS_DispatchToMainThread(runnable);
+    NS_WARN_IF(NS_FAILED(rv));
+  }
+}
+
+} // dom namespace
+} // mozilla namespace
+
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/CallbackRunnables.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_ErrorCallbackRunnable_h
+#define mozilla_dom_ErrorCallbackRunnable_h
+
+#include "DirectoryEntry.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+namespace dom {
+
+class EntryCallbackRunnable final : public Runnable
+{
+public:
+  EntryCallbackRunnable(EntryCallback* aCallback,
+                        Entry* aEntry);
+
+  NS_IMETHOD
+  Run() override;
+
+private:
+  RefPtr<EntryCallback> mCallback;
+  RefPtr<Entry> mEntry;
+};
+
+class ErrorCallbackRunnable final : public Runnable
+{
+public:
+  ErrorCallbackRunnable(nsIGlobalObject* aGlobalObject,
+                        ErrorCallback* aCallback,
+                        nsresult aError);
+
+  NS_IMETHOD
+  Run() override;
+
+private:
+  nsCOMPtr<nsIGlobalObject> mGlobal;
+  RefPtr<ErrorCallback> mCallback;
+  nsresult mError;
+};
+
+class EmptyEntriesCallbackRunnable final : public Runnable
+{
+public:
+  explicit EmptyEntriesCallbackRunnable(EntriesCallback* aCallback);
+
+  NS_IMETHOD
+  Run() override;
+
+private:
+  RefPtr<EntriesCallback> mCallback;
+};
+
+class GetEntryHelper final : public PromiseNativeHandler
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  GetEntryHelper(nsIGlobalObject* aGlobalObject,
+                 DOMFileSystem* aFileSystem,
+                 EntryCallback* aSuccessCallback,
+                 ErrorCallback* aErrorCallback,
+                 DirectoryEntry::GetInternalType aType);
+
+  virtual void
+  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+  virtual void
+  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+private:
+  ~GetEntryHelper();
+
+  void
+  Error(nsresult aError);
+
+  nsCOMPtr<nsIGlobalObject> mGlobal;
+  RefPtr<DOMFileSystem> mFileSystem;
+  RefPtr<EntryCallback> mSuccessCallback;
+  RefPtr<ErrorCallback> mErrorCallback;
+  DirectoryEntry::GetInternalType mType;
+};
+
+class ErrorCallbackHelper
+{
+public:
+  static void
+  Call(nsIGlobalObject* aGlobal,
+       const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+       nsresult aError);
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_CallbackRunnables_h
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/DOMFileSystem.cpp
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "DOMFileSystem.h"
+#include "RootDirectoryEntry.h"
+#include "mozilla/dom/DOMFileSystemBinding.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMFileSystem, mParent, mRoot)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMFileSystem)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMFileSystem)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMFileSystem)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+/* static */ already_AddRefed<DOMFileSystem>
+DOMFileSystem::Create(nsIGlobalObject* aGlobalObject)
+
+{
+  MOZ_ASSERT(aGlobalObject);
+
+
+  nsID id;
+  nsresult rv = nsContentUtils::GenerateUUIDInPlace(id);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+
+  char chars[NSID_LENGTH];
+  id.ToProvidedString(chars);
+
+  // Any fileSystem has an unique ID. We use UUID, but our generator produces
+  // UUID in this format '{' + UUID + '}'. We remove them with these +1 and -2.
+  nsAutoCString name(Substring(chars + 1, chars + NSID_LENGTH - 2));
+
+  RefPtr<DOMFileSystem> fs =
+    new DOMFileSystem(aGlobalObject, NS_ConvertUTF8toUTF16(name));
+
+  return fs.forget();
+}
+
+DOMFileSystem::DOMFileSystem(nsIGlobalObject* aGlobal,
+                             const nsAString& aName)
+  : mParent(aGlobal)
+  , mName(aName)
+{
+  MOZ_ASSERT(aGlobal);
+}
+
+DOMFileSystem::~DOMFileSystem()
+{}
+
+JSObject*
+DOMFileSystem::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return DOMFileSystemBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+DOMFileSystem::CreateRoot(const Sequence<RefPtr<Entry>>& aEntries)
+{
+  MOZ_ASSERT(!mRoot);
+  mRoot = new RootDirectoryEntry(mParent, aEntries, this);
+}
+
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/DOMFileSystem.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_DOMFileSystem_h
+#define mozilla_dom_DOMFileSystem_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+namespace dom {
+
+class DirectoryEntry;
+class Entry;
+class OwningFileOrDirectory;
+
+class DOMFileSystem final
+  : public nsISupports
+  , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMFileSystem)
+
+  static already_AddRefed<DOMFileSystem>
+  Create(nsIGlobalObject* aGlobalObject);
+
+  nsIGlobalObject*
+  GetParentObject() const
+  {
+    return mParent;
+  }
+
+  virtual JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  void
+  GetName(nsAString& aName) const
+  {
+    aName = mName;
+  }
+
+  DirectoryEntry*
+  Root() const
+  {
+    return mRoot;
+  }
+
+  void
+  CreateRoot(const Sequence<RefPtr<Entry>>& aEntries);
+
+private:
+  explicit DOMFileSystem(nsIGlobalObject* aGlobalObject,
+                         const nsAString& aName);
+  ~DOMFileSystem();
+
+  nsCOMPtr<nsIGlobalObject> mParent;
+  RefPtr<DirectoryEntry> mRoot;
+  nsString mName;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_DOMFileSystem_h
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/DirectoryEntry.cpp
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "DirectoryEntry.h"
+#include "CallbackRunnables.h"
+#include "DirectoryReader.h"
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/FileSystemUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(DirectoryEntry, Entry, mDirectory)
+
+NS_IMPL_ADDREF_INHERITED(DirectoryEntry, Entry)
+NS_IMPL_RELEASE_INHERITED(DirectoryEntry, Entry)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DirectoryEntry)
+NS_INTERFACE_MAP_END_INHERITING(Entry)
+
+DirectoryEntry::DirectoryEntry(nsIGlobalObject* aGlobal,
+                               Directory* aDirectory,
+                               DOMFileSystem* aFileSystem)
+  : Entry(aGlobal, aFileSystem)
+  , mDirectory(aDirectory)
+{
+  MOZ_ASSERT(aGlobal);
+}
+
+DirectoryEntry::~DirectoryEntry()
+{}
+
+JSObject*
+DirectoryEntry::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return DirectoryEntryBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+DirectoryEntry::GetName(nsAString& aName, ErrorResult& aRv) const
+{
+  MOZ_ASSERT(mDirectory);
+  mDirectory->GetName(aName, aRv);
+}
+
+void
+DirectoryEntry::GetFullPath(nsAString& aPath, ErrorResult& aRv) const
+{
+  MOZ_ASSERT(mDirectory);
+  mDirectory->GetPath(aPath, aRv);
+}
+
+already_AddRefed<DirectoryReader>
+DirectoryEntry::CreateReader() const
+{
+  MOZ_ASSERT(mDirectory);
+
+  RefPtr<DirectoryReader> reader =
+    new DirectoryReader(GetParentObject(), Filesystem(), mDirectory);
+  return reader.forget();
+}
+
+void
+DirectoryEntry::GetInternal(const nsAString& aPath, const FileSystemFlags& aFlag,
+                            const Optional<OwningNonNull<EntryCallback>>& aSuccessCallback,
+                            const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+                            GetInternalType aType) const
+{
+  MOZ_ASSERT(mDirectory);
+
+  if (!aSuccessCallback.WasPassed() && !aErrorCallback.WasPassed()) {
+    return;
+  }
+
+  if (aFlag.mCreate) {
+    ErrorCallbackHelper::Call(GetParentObject(), aErrorCallback,
+                              NS_ERROR_DOM_SECURITY_ERR);
+    return;
+  }
+
+  nsTArray<nsString> parts;
+  if (!FileSystemUtils::IsValidRelativeDOMPath(aPath, parts)) {
+    ErrorCallbackHelper::Call(GetParentObject(), aErrorCallback,
+                              NS_ERROR_DOM_NOT_FOUND_ERR);
+    return;
+  }
+
+  ErrorResult error;
+  RefPtr<Promise> promise = mDirectory->Get(aPath, error);
+  if (NS_WARN_IF(error.Failed())) {
+    ErrorCallbackHelper::Call(GetParentObject(), aErrorCallback,
+                              error.StealNSResult());
+    return;
+  }
+
+  RefPtr<GetEntryHelper> handler =
+    new GetEntryHelper(GetParentObject(), Filesystem(),
+                       aSuccessCallback.WasPassed()
+                         ? &aSuccessCallback.Value() : nullptr,
+                       aErrorCallback.WasPassed()
+                         ? &aErrorCallback.Value() : nullptr,
+                       aType);
+  promise->AppendNativeHandler(handler);
+}
+
+void
+DirectoryEntry::RemoveRecursively(VoidCallback& aSuccessCallback,
+                                  const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback) const
+{
+  ErrorCallbackHelper::Call(GetParentObject(), aErrorCallback,
+                            NS_ERROR_DOM_SECURITY_ERR);
+}
+
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/DirectoryEntry.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_DirectoryEntry_h
+#define mozilla_dom_DirectoryEntry_h
+
+#include "mozilla/dom/Entry.h"
+#include "mozilla/dom/DOMFileSystemBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+class Directory;
+
+class DirectoryEntry : public Entry
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DirectoryEntry, Entry)
+
+  DirectoryEntry(nsIGlobalObject* aGlobalObject, Directory* aDirectory,
+                 DOMFileSystem* aFileSystem);
+
+  virtual JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  virtual bool
+  IsDirectory() const override
+  {
+    return true;
+  }
+
+  virtual void
+  GetName(nsAString& aName, ErrorResult& aRv) const override;
+
+  virtual void
+  GetFullPath(nsAString& aFullPath, ErrorResult& aRv) const override;
+
+  virtual already_AddRefed<DirectoryReader>
+  CreateReader() const;
+
+  void
+  GetFile(const nsAString& aPath, const FileSystemFlags& aFlag,
+          const Optional<OwningNonNull<EntryCallback>>& aSuccessCallback,
+          const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback) const
+  {
+    GetInternal(aPath, aFlag, aSuccessCallback, aErrorCallback, eGetFile);
+  }
+
+  void
+  GetDirectory(const nsAString& aPath, const FileSystemFlags& aFlag,
+               const Optional<OwningNonNull<EntryCallback>>& aSuccessCallback,
+               const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback) const
+  {
+    GetInternal(aPath, aFlag, aSuccessCallback, aErrorCallback, eGetDirectory);
+  }
+
+  void
+  RemoveRecursively(VoidCallback& aSuccessCallback,
+                    const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback) const;
+
+  enum GetInternalType { eGetFile, eGetDirectory };
+
+  virtual void
+  GetInternal(const nsAString& aPath, const FileSystemFlags& aFlag,
+              const Optional<OwningNonNull<EntryCallback>>& aSuccessCallback,
+              const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+              GetInternalType aType) const;
+
+protected:
+  virtual ~DirectoryEntry();
+
+private:
+  RefPtr<Directory> mDirectory;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_DirectoryEntry_h
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/DirectoryReader.cpp
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "DirectoryReader.h"
+#include "CallbackRunnables.h"
+#include "FileEntry.h"
+#include "mozilla/dom/FileBinding.h"
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/DirectoryBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "nsIGlobalObject.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+class PromiseHandler final : public PromiseNativeHandler
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  PromiseHandler(nsIGlobalObject* aGlobalObject,
+                 DOMFileSystem* aFileSystem,
+                 EntriesCallback* aSuccessCallback,
+                 ErrorCallback* aErrorCallback)
+    : mGlobal(aGlobalObject)
+    , mFileSystem(aFileSystem)
+    , mSuccessCallback(aSuccessCallback)
+    , mErrorCallback(aErrorCallback)
+  {
+    MOZ_ASSERT(aGlobalObject);
+    MOZ_ASSERT(aFileSystem);
+    MOZ_ASSERT(aSuccessCallback);
+  }
+
+  virtual void
+  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+    if(NS_WARN_IF(!aValue.isObject())) {
+      return;
+    }
+
+    JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+
+    uint32_t length;
+    if (NS_WARN_IF(!JS_GetArrayLength(aCx, obj, &length))) {
+      return;
+    }
+
+    Sequence<OwningNonNull<Entry>> sequence;
+    if (NS_WARN_IF(!sequence.SetLength(length, fallible))) {
+      return;
+    }
+
+    for (uint32_t i = 0; i < length; ++i) {
+      JS::Rooted<JS::Value> value(aCx);
+      if (NS_WARN_IF(!JS_GetElement(aCx, obj, i, &value))) {
+        return;
+      }
+
+      if(NS_WARN_IF(!value.isObject())) {
+        return;
+      }
+
+      JS::Rooted<JSObject*> valueObj(aCx, &value.toObject());
+
+      RefPtr<File> file;
+      if (NS_SUCCEEDED(UNWRAP_OBJECT(File, valueObj, file))) {
+        RefPtr<FileEntry> entry = new FileEntry(mGlobal, file, mFileSystem);
+        sequence[i] = entry;
+        continue;
+      }
+
+      RefPtr<Directory> directory;
+      if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Directory, valueObj,
+                                             directory)))) {
+        return;
+      }
+
+      RefPtr<DirectoryEntry> entry =
+        new DirectoryEntry(mGlobal, directory, mFileSystem);
+      sequence[i] = entry;
+    }
+
+    mSuccessCallback->HandleEvent(sequence);
+  }
+
+  virtual void
+  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+    if (mErrorCallback) {
+      RefPtr<ErrorCallbackRunnable> runnable =
+        new ErrorCallbackRunnable(mGlobal, mErrorCallback,
+                                  NS_ERROR_DOM_INVALID_STATE_ERR);
+      nsresult rv = NS_DispatchToMainThread(runnable);
+      NS_WARN_IF(NS_FAILED(rv));
+    }
+  }
+
+private:
+  ~PromiseHandler() {}
+
+  nsCOMPtr<nsIGlobalObject> mGlobal;
+  RefPtr<DOMFileSystem> mFileSystem;
+  RefPtr<EntriesCallback> mSuccessCallback;
+  RefPtr<ErrorCallback> mErrorCallback;
+};
+
+NS_IMPL_ISUPPORTS0(PromiseHandler);
+
+} // anonymous namespace
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DirectoryReader, mParent, mDirectory,
+                                      mFileSystem)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DirectoryReader)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DirectoryReader)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DirectoryReader)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+DirectoryReader::DirectoryReader(nsIGlobalObject* aGlobal,
+                                 DOMFileSystem* aFileSystem,
+                                 Directory* aDirectory)
+  : mParent(aGlobal)
+  , mFileSystem(aFileSystem)
+  , mDirectory(aDirectory)
+  , mAlreadyRead(false)
+{
+  MOZ_ASSERT(aGlobal);
+  MOZ_ASSERT(aFileSystem);
+}
+
+DirectoryReader::~DirectoryReader()
+{}
+
+JSObject*
+DirectoryReader::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return DirectoryReaderBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+DirectoryReader::ReadEntries(EntriesCallback& aSuccessCallback,
+                             const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+                             ErrorResult& aRv)
+{
+  MOZ_ASSERT(mDirectory);
+
+  if (mAlreadyRead) {
+    RefPtr<EmptyEntriesCallbackRunnable> runnable =
+      new EmptyEntriesCallbackRunnable(&aSuccessCallback);
+    aRv = NS_DispatchToMainThread(runnable);
+    NS_WARN_IF(aRv.Failed());
+    return;
+  }
+
+  // This object can be used only once.
+  mAlreadyRead = true;
+
+  ErrorResult rv;
+  RefPtr<Promise> promise = mDirectory->GetFilesAndDirectories(rv);
+  if (NS_WARN_IF(rv.Failed())) {
+    ErrorCallbackHelper::Call(GetParentObject(), aErrorCallback,
+                              rv.StealNSResult());
+    return;
+  }
+
+  RefPtr<PromiseHandler> handler =
+    new PromiseHandler(GetParentObject(), mFileSystem, &aSuccessCallback,
+                       aErrorCallback.WasPassed()
+                         ? &aErrorCallback.Value() : nullptr);
+  promise->AppendNativeHandler(handler);
+}
+
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/DirectoryReader.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_DirectoryReader_h
+#define mozilla_dom_DirectoryReader_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+namespace dom {
+
+class Directory;
+class DOMFileSystem;
+
+class DirectoryReader
+  : public nsISupports
+  , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DirectoryReader)
+
+  explicit DirectoryReader(nsIGlobalObject* aGlobalObject,
+                           DOMFileSystem* aFileSystem,
+                           Directory* aDirectory);
+
+  nsIGlobalObject*
+  GetParentObject() const
+  {
+    return mParent;
+  }
+
+  virtual JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  virtual void
+  ReadEntries(EntriesCallback& aSuccessCallback,
+              const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+              ErrorResult& aRv);
+
+protected:
+  virtual ~DirectoryReader();
+
+private:
+  nsCOMPtr<nsIGlobalObject> mParent;
+  RefPtr<DOMFileSystem> mFileSystem;
+  RefPtr<Directory> mDirectory;
+
+  bool mAlreadyRead;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_DirectoryReader_h
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/Entry.cpp
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "Entry.h"
+#include "DirectoryEntry.h"
+#include "FileEntry.h"
+#include "mozilla/dom/UnionTypes.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Entry, mParent, mFileSystem)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Entry)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Entry)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Entry)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+/* static */ already_AddRefed<Entry>
+Entry::Create(nsIGlobalObject* aGlobalObject,
+              const OwningFileOrDirectory& aFileOrDirectory,
+              DOMFileSystem* aFileSystem)
+{
+  MOZ_ASSERT(aGlobalObject);
+  MOZ_ASSERT(aFileSystem);
+
+  RefPtr<Entry> entry;
+  if (aFileOrDirectory.IsFile()) {
+    entry = new FileEntry(aGlobalObject,
+                          aFileOrDirectory.GetAsFile(),
+                          aFileSystem);
+  } else {
+    MOZ_ASSERT(aFileOrDirectory.IsDirectory());
+    entry = new DirectoryEntry(aGlobalObject,
+                               aFileOrDirectory.GetAsDirectory(),
+                               aFileSystem);
+  }
+
+  return entry.forget();
+}
+
+Entry::Entry(nsIGlobalObject* aGlobal, DOMFileSystem* aFileSystem)
+  : mParent(aGlobal)
+  , mFileSystem(aFileSystem)
+{
+  MOZ_ASSERT(aGlobal);
+  MOZ_ASSERT(aFileSystem);
+}
+
+Entry::~Entry()
+{}
+
+JSObject*
+Entry::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return EntryBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/Entry.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_Entry_h
+#define mozilla_dom_Entry_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIGlobalObject.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class DOMFileSystem;
+class OwningFileOrDirectory;
+
+class Entry
+  : public nsISupports
+  , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Entry)
+
+  static already_AddRefed<Entry>
+  Create(nsIGlobalObject* aGlobalObject,
+         const OwningFileOrDirectory& aFileOrDirectory,
+         DOMFileSystem* aFileSystem);
+
+  nsIGlobalObject*
+  GetParentObject() const
+  {
+    return mParent;
+  }
+
+  virtual JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  virtual bool
+  IsFile() const
+  {
+    return false;
+  }
+
+  virtual bool
+  IsDirectory() const
+  {
+    return false;
+  }
+
+  virtual void
+  GetName(nsAString& aName, ErrorResult& aRv) const = 0;
+
+  virtual void
+  GetFullPath(nsAString& aFullPath, ErrorResult& aRv) const = 0;
+
+  DOMFileSystem*
+  Filesystem() const
+  {
+    return mFileSystem;
+  }
+
+protected:
+  Entry(nsIGlobalObject* aGlobalObject,
+        DOMFileSystem* aFileSystem);
+  virtual ~Entry();
+
+private:
+  nsCOMPtr<nsIGlobalObject> mParent;
+  RefPtr<DOMFileSystem> mFileSystem;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Entry_h
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/FileEntry.cpp
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "FileEntry.h"
+#include "CallbackRunnables.h"
+#include "mozilla/dom/File.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+class BlobCallbackRunnable final : public Runnable
+{
+public:
+  BlobCallbackRunnable(BlobCallback* aCallback, File* aFile)
+    : mCallback(aCallback)
+    , mFile(aFile)
+  {
+    MOZ_ASSERT(aCallback);
+    MOZ_ASSERT(aFile);
+  }
+
+  NS_IMETHOD
+  Run() override
+  {
+    mCallback->HandleEvent(mFile);
+    return NS_OK;
+  }
+
+private:
+  RefPtr<BlobCallback> mCallback;
+  RefPtr<File> mFile;
+};
+
+} // anonymous namespace
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(FileEntry, Entry, mFile)
+
+NS_IMPL_ADDREF_INHERITED(FileEntry, Entry)
+NS_IMPL_RELEASE_INHERITED(FileEntry, Entry)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FileEntry)
+NS_INTERFACE_MAP_END_INHERITING(Entry)
+
+FileEntry::FileEntry(nsIGlobalObject* aGlobal,
+                     File* aFile,
+                     DOMFileSystem* aFileSystem)
+  : Entry(aGlobal, aFileSystem)
+  , mFile(aFile)
+{
+  MOZ_ASSERT(aGlobal);
+  MOZ_ASSERT(mFile);
+}
+
+FileEntry::~FileEntry()
+{}
+
+JSObject*
+FileEntry::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return FileEntryBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+FileEntry::GetName(nsAString& aName, ErrorResult& aRv) const
+{
+  mFile->GetName(aName);
+}
+
+void
+FileEntry::GetFullPath(nsAString& aPath, ErrorResult& aRv) const
+{
+  mFile->GetPath(aPath);
+}
+
+void
+FileEntry::CreateWriter(VoidCallback& aSuccessCallback,
+                        const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback) const
+{
+  ErrorCallbackHelper::Call(GetParentObject(), aErrorCallback,
+                            NS_ERROR_DOM_SECURITY_ERR);
+}
+
+void
+FileEntry::GetFile(BlobCallback& aSuccessCallback,
+                   const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback) const
+{
+  RefPtr<BlobCallbackRunnable> runnable =
+    new BlobCallbackRunnable(&aSuccessCallback, mFile);
+  nsresult rv = NS_DispatchToMainThread(runnable);
+  NS_WARN_IF(NS_FAILED(rv));
+}
+
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/FileEntry.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_FileEntry_h
+#define mozilla_dom_FileEntry_h
+
+#include "mozilla/dom/Entry.h"
+
+namespace mozilla {
+namespace dom {
+
+class File;
+
+class FileEntry final : public Entry
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FileEntry, Entry)
+
+  FileEntry(nsIGlobalObject* aGlobalObject, File* aFile,
+            DOMFileSystem* aFileSystem);
+
+  virtual JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  virtual bool
+  IsFile() const override
+  {
+    return true;
+  }
+
+  virtual void
+  GetName(nsAString& aName, ErrorResult& aRv) const override;
+
+  virtual void
+  GetFullPath(nsAString& aFullPath, ErrorResult& aRv) const override;
+
+  void
+  CreateWriter(VoidCallback& aSuccessCallback,
+               const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback) const;
+
+  void
+  GetFile(BlobCallback& aSuccessCallback,
+          const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback) const;
+
+private:
+  ~FileEntry();
+
+  RefPtr<File> mFile;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FileEntry_h
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/RootDirectoryEntry.cpp
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "RootDirectoryEntry.h"
+#include "RootDirectoryReader.h"
+#include "mozilla/dom/FileSystemUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(RootDirectoryEntry, DirectoryEntry, mEntries)
+
+NS_IMPL_ADDREF_INHERITED(RootDirectoryEntry, DirectoryEntry)
+NS_IMPL_RELEASE_INHERITED(RootDirectoryEntry, DirectoryEntry)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(RootDirectoryEntry)
+NS_INTERFACE_MAP_END_INHERITING(DirectoryEntry)
+
+RootDirectoryEntry::RootDirectoryEntry(nsIGlobalObject* aGlobal,
+                                       const Sequence<RefPtr<Entry>>& aEntries,
+                                       DOMFileSystem* aFileSystem)
+  : DirectoryEntry(aGlobal, nullptr, aFileSystem)
+  , mEntries(aEntries)
+{
+  MOZ_ASSERT(aGlobal);
+}
+
+RootDirectoryEntry::~RootDirectoryEntry()
+{}
+
+void
+RootDirectoryEntry::GetName(nsAString& aName, ErrorResult& aRv) const
+{
+  aName.Truncate();
+}
+
+void
+RootDirectoryEntry::GetFullPath(nsAString& aPath, ErrorResult& aRv) const
+{
+  aPath.AssignLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
+}
+
+already_AddRefed<DirectoryReader>
+RootDirectoryEntry::CreateReader() const
+{
+  RefPtr<DirectoryReader> reader =
+    new RootDirectoryReader(GetParentObject(), Filesystem(), mEntries);
+  return reader.forget();
+}
+
+void
+RootDirectoryEntry::GetInternal(const nsAString& aPath, const FileSystemFlags& aFlag,
+                                const Optional<OwningNonNull<EntryCallback>>& aSuccessCallback,
+                                const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+                                GetInternalType aType) const
+{
+  if (!aSuccessCallback.WasPassed() && !aErrorCallback.WasPassed()) {
+    return;
+  }
+
+  if (aFlag.mCreate) {
+    ErrorCallbackHelper::Call(GetParentObject(), aErrorCallback,
+                              NS_ERROR_DOM_SECURITY_ERR);
+    return;
+  }
+
+  nsTArray<nsString> parts;
+  if (!FileSystemUtils::IsValidRelativeDOMPath(aPath, parts)) {
+    ErrorCallbackHelper::Call(GetParentObject(), aErrorCallback,
+                              NS_ERROR_DOM_NOT_FOUND_ERR);
+    return;
+  }
+
+  MOZ_ASSERT(!parts.IsEmpty());
+
+  RefPtr<Entry> entry;
+  for (uint32_t i = 0; i < mEntries.Length(); ++i) {
+    ErrorResult rv;
+    nsAutoString name;
+    mEntries[i]->GetName(name, rv);
+
+    if (NS_WARN_IF(rv.Failed())) {
+      ErrorCallbackHelper::Call(GetParentObject(), aErrorCallback,
+                                rv.StealNSResult());
+      return;
+    }
+
+    if (name == parts[0]) {
+      entry = mEntries[i];
+      break;
+    }
+  }
+
+  // Not found.
+  if (!entry) {
+    ErrorCallbackHelper::Call(GetParentObject(), aErrorCallback,
+                              NS_ERROR_DOM_NOT_FOUND_ERR);
+    return;
+  }
+
+  // No subdirectory in the path.
+  if (parts.Length() == 1) {
+    if ((entry->IsFile() && aType == eGetDirectory) ||
+        (entry->IsDirectory() && aType == eGetFile)) {
+      ErrorCallbackHelper::Call(GetParentObject(), aErrorCallback,
+                                NS_ERROR_DOM_TYPE_MISMATCH_ERR);
+      return;
+    }
+
+    if (aSuccessCallback.WasPassed()) {
+      RefPtr<EntryCallbackRunnable> runnable =
+        new EntryCallbackRunnable(&aSuccessCallback.Value(), entry);
+      nsresult rv = NS_DispatchToMainThread(runnable);
+      NS_WARN_IF(NS_FAILED(rv));
+    }
+    return;
+  }
+
+  // Subdirectories, but this is a file.
+  if (entry->IsFile()) {
+    ErrorCallbackHelper::Call(GetParentObject(), aErrorCallback,
+                              NS_ERROR_DOM_NOT_FOUND_ERR);
+    return;
+  }
+
+  // Let's recreate a path without the first directory.
+  nsAutoString path;
+  for (uint32_t i = 1, len = parts.Length(); i < len; ++i) {
+    path.Append(parts[i]);
+    if (i < len - 1) {
+      path.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
+    }
+  }
+
+  auto* directoryEntry = static_cast<DirectoryEntry*>(entry.get());
+  directoryEntry->GetInternal(path, aFlag, aSuccessCallback, aErrorCallback,
+                              aType);
+}
+
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/RootDirectoryEntry.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_RootDirectoryEntry_h
+#define mozilla_dom_RootDirectoryEntry_h
+
+#include "mozilla/dom/DirectoryEntry.h"
+
+namespace mozilla {
+namespace dom {
+
+class RootDirectoryEntry final : public DirectoryEntry
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RootDirectoryEntry, DirectoryEntry)
+
+  RootDirectoryEntry(nsIGlobalObject* aGlobalObject,
+                     const Sequence<RefPtr<Entry>>& aEntries,
+                     DOMFileSystem* aFileSystem);
+
+  virtual void
+  GetName(nsAString& aName, ErrorResult& aRv) const override;
+
+  virtual void
+  GetFullPath(nsAString& aFullPath, ErrorResult& aRv) const override;
+
+  virtual already_AddRefed<DirectoryReader>
+  CreateReader() const override;
+
+private:
+  ~RootDirectoryEntry();
+
+  virtual void
+  GetInternal(const nsAString& aPath, const FileSystemFlags& aFlag,
+              const Optional<OwningNonNull<EntryCallback>>& aSuccessCallback,
+              const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+              GetInternalType aType) const override;
+
+  void
+  Error(const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+        nsresult aError) const;
+
+  Sequence<RefPtr<Entry>> mEntries;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_RootDirectoryEntry_h
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/RootDirectoryReader.cpp
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "RootDirectoryReader.h"
+#include "CallbackRunnables.h"
+#include "nsIGlobalObject.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+class EntriesCallbackRunnable final : public Runnable
+{
+public:
+  EntriesCallbackRunnable(EntriesCallback* aCallback,
+                          const Sequence<RefPtr<Entry>>& aEntries)
+    : mCallback(aCallback)
+    , mEntries(aEntries)
+  {
+    MOZ_ASSERT(aCallback);
+  }
+
+  NS_IMETHOD
+  Run() override
+  {
+    Sequence<OwningNonNull<Entry>> entries;
+    for (uint32_t i = 0; i < mEntries.Length(); ++i) {
+      if (!entries.AppendElement(mEntries[i].forget(), fallible)) {
+        return NS_ERROR_OUT_OF_MEMORY;
+      }
+    }
+
+    mCallback->HandleEvent(entries);
+    return NS_OK;
+  }
+
+private:
+  RefPtr<EntriesCallback> mCallback;
+  Sequence<RefPtr<Entry>> mEntries;
+};
+
+} // anonymous namespace
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(RootDirectoryReader, DirectoryReader, mEntries)
+
+NS_IMPL_ADDREF_INHERITED(RootDirectoryReader, DirectoryReader)
+NS_IMPL_RELEASE_INHERITED(RootDirectoryReader, DirectoryReader)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(RootDirectoryReader)
+NS_INTERFACE_MAP_END_INHERITING(DirectoryReader)
+
+RootDirectoryReader::RootDirectoryReader(nsIGlobalObject* aGlobal,
+                                         DOMFileSystem* aFileSystem,
+                                         const Sequence<RefPtr<Entry>>& aEntries)
+  : DirectoryReader(aGlobal, aFileSystem, nullptr)
+  , mEntries(aEntries)
+  , mAlreadyRead(false)
+{
+  MOZ_ASSERT(aGlobal);
+  MOZ_ASSERT(aFileSystem);
+}
+
+RootDirectoryReader::~RootDirectoryReader()
+{}
+
+void
+RootDirectoryReader::ReadEntries(EntriesCallback& aSuccessCallback,
+                                 const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+                                 ErrorResult& aRv)
+{
+  if (mAlreadyRead) {
+    RefPtr<EmptyEntriesCallbackRunnable> runnable =
+      new EmptyEntriesCallbackRunnable(&aSuccessCallback);
+    aRv = NS_DispatchToMainThread(runnable);
+    NS_WARN_IF(aRv.Failed());
+    return;
+  }
+
+  // This object can be used only once.
+  mAlreadyRead = true;
+
+  RefPtr<EntriesCallbackRunnable> runnable =
+    new EntriesCallbackRunnable(&aSuccessCallback, mEntries);
+  aRv = NS_DispatchToMainThread(runnable);
+  NS_WARN_IF(aRv.Failed());
+}
+
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/RootDirectoryReader.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_RootDirectoryReader_h
+#define mozilla_dom_RootDirectoryReader_h
+
+#include "DirectoryReader.h"
+
+namespace mozilla {
+namespace dom {
+
+class RootDirectoryReader final : public DirectoryReader
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RootDirectoryReader, DirectoryReader)
+
+  explicit RootDirectoryReader(nsIGlobalObject* aGlobalObject,
+                               DOMFileSystem* aFileSystem,
+                               const Sequence<RefPtr<Entry>>& aEntries);
+
+  virtual void
+  ReadEntries(EntriesCallback& aSuccessCallback,
+              const Optional<OwningNonNull<ErrorCallback>>& aErrorCallback,
+              ErrorResult& aRv) override;
+
+private:
+  ~RootDirectoryReader();
+
+  Sequence<RefPtr<Entry>> mEntries;
+  bool mAlreadyRead;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_RootDirectoryReader_h
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/moz.build
@@ -0,0 +1,30 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+TEST_DIRS += ['tests']
+
+EXPORTS.mozilla.dom += [
+    'DirectoryEntry.h',
+    'DirectoryReader.h',
+    'DOMFileSystem.h',
+    'Entry.h',
+    'FileEntry.h',
+]
+
+UNIFIED_SOURCES += [
+    'CallbackRunnables.cpp',
+    'DirectoryEntry.cpp',
+    'DirectoryReader.cpp',
+    'DOMFileSystem.cpp',
+    'Entry.cpp',
+    'FileEntry.cpp',
+    'RootDirectoryEntry.cpp',
+    'RootDirectoryReader.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+include('/ipc/chromium/chromium-config.mozbuild')
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/tests/mochitest.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+  script_entries.js
+
+[test_basic.html]
+[test_no_dnd.html]
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/tests/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+MOCHITEST_MANIFESTS += ['mochitest.ini']
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/tests/script_entries.js
@@ -0,0 +1,39 @@
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+Cu.importGlobalProperties(["File", "Directory"]);
+
+addMessageListener("entries.open", function (e) {
+  var tmpFile = Cc["@mozilla.org/file/directory_service;1"]
+                  .getService(Ci.nsIDirectoryService)
+                  .QueryInterface(Ci.nsIProperties)
+                  .get('TmpD', Ci.nsIFile)
+  tmpFile.append('file.txt');
+  tmpFile.createUnique(Components.interfaces.nsIFile.FILE_TYPE, 0o600);
+
+  var tmpDir = Cc["@mozilla.org/file/directory_service;1"]
+                  .getService(Ci.nsIDirectoryService)
+                  .QueryInterface(Ci.nsIProperties)
+                  .get('TmpD', Ci.nsIFile)
+
+  tmpDir.append('dir-test');
+  tmpDir.createUnique(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0o700);
+
+  var file1 = tmpDir.clone();
+  file1.append('foo.txt');
+  file1.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0o600);
+
+  var dir1 = tmpDir.clone();
+  dir1.append('subdir');
+  dir1.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0o700);
+
+  var file2 = dir1.clone();
+  file2.append('bar.txt');
+  file2.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0o600);
+
+  var dir2 = dir1.clone();
+  dir2.append('subsubdir');
+  dir2.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0o700);
+
+  sendAsyncMessage("entries.opened", {
+    data: [ new Directory(tmpDir.path), new File(tmpFile) ]
+  });
+});
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/tests/test_basic.html
@@ -0,0 +1,437 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for Blink FileSystem API - subset</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<input id="entries" type="file"></input>
+<script type="application/javascript;version=1.7">
+
+var fileEntry;
+var directoryEntry;
+
+function setup_tests() {
+  SpecialPowers.pushPrefEnv({"set": [["dom.webkitBlink.dirPicker.enabled", true],
+                                     ["dom.webkitBlink.filesystem.enabled", true]]}, next);
+}
+
+function populate_entries() {
+  var url = SimpleTest.getTestFileURL("script_entries.js");
+  var script = SpecialPowers.loadChromeScript(url);
+
+  function onOpened(message) {
+    var entries = document.getElementById('entries');
+    SpecialPowers.wrap(entries).mozSetDndFilesAndDirectories(message.data);
+
+    script.destroy();
+    next();
+  }
+
+  script.addMessageListener("entries.opened", onOpened);
+  script.sendAsyncMessage("entries.open");
+}
+
+function test_entries() {
+  var entries = document.getElementById('entries');
+  ok("webkitEntries" in entries, "HTMLInputElement.webkitEntries");
+  is(entries.webkitEntries.length, 2, "HTMLInputElement.webkitEntries.length == 2");
+  is(entries.files.length, 1, "HTMLInputElement.files is still populated");
+
+  for (var i = 0; i < entries.webkitEntries.length; ++i) {
+    if (entries.webkitEntries[i].isFile) {
+      ok(!fileEntry, "We just want 1 fileEntry");
+      fileEntry = entries.webkitEntries[i];
+    } else {
+      ok(entries.webkitEntries[i].isDirectory, "If not a file, we have a directory.");
+      ok(!directoryEntry, "We just want 1 directoryEntry");
+      directoryEntry = entries.webkitEntries[i];
+    }
+  }
+
+  next();
+}
+
+function test_fileEntry() {
+  ok("name" in fileEntry, "We have a name.");
+  ok("fullPath" in fileEntry, "We have a fullPath.");
+  ok("filesystem" in fileEntry, "We have a filesystem.");
+
+  next();
+}
+
+function test_fileEntry_file() {
+  fileEntry.file(function(file) {
+    ok(file, "We have a file here!");
+    is(file.name, fileEntry.name, "Same file name.");
+    next();
+  }, function() {
+    ok(false, "Something when wrong!");
+  });
+}
+
+function test_fileEntry_createWriter() {
+  fileEntry.createWriter(function(file) {
+    ok(false, "Something when wrong!");
+  }, function() {
+    ok(true, "We don't support createWrite");
+    next();
+  });
+}
+
+function test_directoryEntry() {
+  ok("name" in directoryEntry, "We have a name.");
+  ok("fullPath" in directoryEntry, "We have a fullPath.");
+  ok("filesystem" in directoryEntry, "We have a filesystem.");
+
+  next();
+}
+
+function test_directoryEntry_createReader() {
+  var reader = directoryEntry.createReader();
+  ok(reader, "We have a DirectoryReader");
+
+  reader.readEntries(function(a) {
+    ok(Array.isArray(a), "We want an array.");
+    is(a.length, 2, "reader.readyEntries returns 2 elements.");
+
+    for (var i = 0; i < 2; ++i) {
+      ok(a[i].name == "subdir" || a[i].name == "foo.txt", "Correct names");
+      is(a[i].fullPath, directoryEntry.fullPath + "/" + a[i].name, "FullPath is correct");
+    }
+
+    // Called twice:
+    reader.readEntries(function(a) {
+      ok(Array.isArray(a), "We want an array.");
+      is(a.length, 0, "reader.readyEntries returns 0 elements.");
+      next();
+    }, function() {
+      ok(false, "Something when wrong!");
+    });
+
+  }, function() {
+    ok(false, "Something when wrong!");
+  });
+}
+
+function test_directoryEntry_getFile_securityError() {
+  directoryEntry.getFile("foo", { create: true },
+  function() {
+    ok(false, "This should not happen.");
+  }, function(e) {
+    is(e.name, "SecurityError", "This must generate a SecurityError.");
+    next();
+  });
+}
+
+function test_directoryEntry_getFile_typeMismatchError() {
+  directoryEntry.getFile("subdir", {},
+  function() {
+    ok(false, "This should not happen.");
+  }, function(e) {
+    is(e.name, "TypeMismatchError", "This must generate a TypeMismatchError.");
+    next();
+  });
+}
+
+function test_directoryEntry_getFile_nonValidPath() {
+  directoryEntry.getFile("../../", {},
+  function() {
+    ok(false, "This should not happen.");
+  }, function(e) {
+    is(e.name, "NotFoundError", "This must generate a NotFoundError.");
+    next();
+  });
+}
+
+function test_directoryEntry_getFile_nonExistingPath() {
+  directoryEntry.getFile("foo_bar.txt", {},
+  function() {
+    ok(false, "This should not happen.");
+  }, function(e) {
+    is(e.name, "NotFoundError", "This must generate a NotFoundError.");
+    next();
+  });
+}
+
+function test_directoryEntry_getFile_simple() {
+  directoryEntry.getFile("foo.txt", {},
+  function(e) {
+    is(e.name, "foo.txt", "We have the right FileEntry.");
+    next();
+  }, function(e) {
+    ok(false, "This should not happen.");
+  });
+}
+
+function test_directoryEntry_getFile_deep() {
+  directoryEntry.getFile("subdir/bar.txt", {},
+  function(e) {
+    is(e.name, "bar.txt", "We have the right FileEntry.");
+    next();
+  }, function(e) {
+    ok(false, "This should not happen.");
+  });
+}
+
+function test_directoryEntry_getDirectory_securityError() {
+  directoryEntry.getDirectory("foo", { create: true },
+  function() {
+    ok(false, "This should not happen.");
+  }, function(e) {
+    is(e.name, "SecurityError", "This must generate a SecurityError.");
+    next();
+  });
+}
+
+function test_directoryEntry_getDirectory_typeMismatchError() {
+  directoryEntry.getDirectory("foo.txt", {},
+  function() {
+    ok(false, "This should not happen.");
+  }, function(e) {
+    is(e.name, "TypeMismatchError", "This must generate a TypeMismatchError.");
+    next();
+  });
+}
+
+function test_directoryEntry_getDirectory_nonValidPath() {
+  directoryEntry.getDirectory("../../", {},
+  function() {
+    ok(false, "This should not happen.");
+  }, function(e) {
+    is(e.name, "NotFoundError", "This must generate a NotFoundError.");
+    next();
+  });
+}
+
+function test_directoryEntry_getDirectory_nonExistingPath() {
+  directoryEntry.getDirectory("non_existing_dir", {},
+  function() {
+    ok(false, "This should not happen.");
+  }, function(e) {
+    is(e.name, "NotFoundError", "This must generate a NotFoundError.");
+    next();
+  });
+}
+
+function test_directoryEntry_getDirectory_simple() {
+  directoryEntry.getDirectory("subdir", {},
+  function(e) {
+    is(e.name, "subdir", "We have the right DirectoryEntry.");
+    next();
+  }, function(e) {
+    ok(false, "This should not happen.");
+  });
+}
+
+function test_directoryEntry_getDirectory_deep() {
+  directoryEntry.getDirectory("subdir/subsubdir", {},
+  function(e) {
+    is(e.name, "subsubdir", "We have the right DirectoryEntry.");
+    next();
+  }, function(e) {
+    ok(false, "This should not happen.");
+  });
+}
+
+function test_filesystem() {
+  is(fileEntry.filesystem, directoryEntry.filesystem, "FileSystem object is shared.");
+
+  var fs = fileEntry.filesystem;
+  ok(fs.name, "FileSystem.name exists.");
+  ok(fs.root, "FileSystem has a root.");
+
+  is(fs.root.name, "", "FileSystem.root.name must be an empty string.");
+  is(fs.root.fullPath, "/", "FileSystem.root.fullPath must be '/'");
+
+  reader = fs.root.createReader();
+  reader.readEntries(function(a) {
+    ok(Array.isArray(a), "We want an array.");
+    is(a.length, 2, "reader.readyEntries returns 2 elements.");
+    next();
+  }, function() {
+    ok(false, "Something when wrong!");
+  });
+}
+
+function test_root_getFile_securityError() {
+  fileEntry.filesystem.root.getFile("foo", { create: true },
+  function() {
+    ok(false, "This should not happen.");
+  }, function(e) {
+    is(e.name, "SecurityError", "This must generate a SecurityError.");
+    next();
+  });
+}
+
+function test_root_getFile_typeMismatchError() {
+  fileEntry.filesystem.root.getFile(directoryEntry.name, {},
+  function() {
+    ok(false, "This should not happen.");
+  }, function(e) {
+    is(e.name, "TypeMismatchError", "This must generate a TypeMismatchError.");
+    next();
+  });
+}
+
+function test_root_getFile_nonValidPath() {
+  fileEntry.filesystem.root.getFile("../../", {},
+  function() {
+    ok(false, "This should not happen.");
+  }, function(e) {
+    is(e.name, "NotFoundError", "This must generate a NotFoundError.");
+    next();
+  });
+}
+
+function test_root_getFile_nonExistingPath() {
+  fileEntry.filesystem.root.getFile("existing.txt", {},
+  function() {
+    ok(false, "This should not happen.");
+  }, function(e) {
+    is(e.name, "NotFoundError", "This must generate a NotFoundError.");
+    next();
+  });
+}
+
+function test_root_getFile_simple() {
+  fileEntry.filesystem.root.getFile(fileEntry.name, {},
+  function(e) {
+    is(e.name, fileEntry.name, "We have the right FileEntry.");
+    next();
+  }, function(e) {
+    ok(false, "This should not happen.");
+  });
+}
+
+function test_root_getFile_deep() {
+  fileEntry.filesystem.root.getFile(directoryEntry.name + "/subdir/bar.txt", {},
+  function(e) {
+    is(e.name, "bar.txt", "We have the right FileEntry.");
+    next();
+  }, function(e) {
+    ok(false, "This should not happen.");
+  });
+}
+
+function test_root_getDirectory_securityError() {
+  fileEntry.filesystem.root.getDirectory("foo", { create: true },
+  function() {
+    ok(false, "This should not happen.");
+  }, function(e) {
+    is(e.name, "SecurityError", "This must generate a SecurityError.");
+    next();
+  });
+}
+
+function test_root_getDirectory_typeMismatchError() {
+  fileEntry.filesystem.root.getDirectory(fileEntry.name, {},
+  function() {
+    ok(false, "This should not happen.");
+  }, function(e) {
+    is(e.name, "TypeMismatchError", "This must generate a TypeMismatchError.");
+    next();
+  });
+}
+
+function test_root_getDirectory_nonValidPath() {
+  fileEntry.filesystem.root.getDirectory("../../", {},
+  function() {
+    ok(false, "This should not happen.");
+  }, function(e) {
+    is(e.name, "NotFoundError", "This must generate a NotFoundError.");
+    next();
+  });
+}
+
+function test_root_getDirectory_nonExistingPath() {
+  fileEntry.filesystem.root.getDirectory("404", {},
+  function() {
+    ok(false, "This should not happen.");
+  }, function(e) {
+    is(e.name, "NotFoundError", "This must generate a NotFoundError.");
+    next();
+  });
+}
+
+function test_root_getDirectory_simple() {
+  fileEntry.filesystem.root.getDirectory(directoryEntry.name, {},
+  function(e) {
+    is(e.name, directoryEntry.name, "We have the right DirectoryEntry.");
+    next();
+  }, function(e) {
+    ok(false, "This should not happen.");
+  });
+}
+
+function test_root_getDirectory_deep() {
+  fileEntry.filesystem.root.getDirectory(directoryEntry.name + "/subdir/subsubdir", {},
+  function(e) {
+    is(e.name, "subsubdir", "We have the right DirectoryEntry.");
+    next();
+  }, function(e) {
+    ok(false, "This should not happen.");
+  });
+}
+
+var tests = [
+  setup_tests,
+  populate_entries,
+
+  test_entries,
+
+  test_fileEntry,
+  test_fileEntry_file,
+  test_fileEntry_createWriter,
+
+  test_directoryEntry,
+  test_directoryEntry_createReader,
+
+  test_directoryEntry_getFile_securityError,
+  test_directoryEntry_getFile_typeMismatchError,
+  test_directoryEntry_getFile_nonValidPath,
+  test_directoryEntry_getFile_nonExistingPath,
+  test_directoryEntry_getFile_simple,
+  test_directoryEntry_getFile_deep,
+
+  test_directoryEntry_getDirectory_securityError,
+  test_directoryEntry_getDirectory_typeMismatchError,
+  test_directoryEntry_getDirectory_nonValidPath,
+  test_directoryEntry_getDirectory_nonExistingPath,
+  test_directoryEntry_getDirectory_simple,
+  test_directoryEntry_getDirectory_deep,
+
+  test_filesystem,
+
+  test_root_getFile_securityError,
+  test_root_getFile_typeMismatchError,
+  test_root_getFile_nonValidPath,
+  test_root_getFile_nonExistingPath,
+  test_root_getFile_simple,
+  test_root_getFile_deep,
+
+  test_root_getDirectory_securityError,
+  test_root_getDirectory_typeMismatchError,
+  test_root_getDirectory_nonValidPath,
+  test_root_getDirectory_nonExistingPath,
+  test_root_getDirectory_simple,
+  test_root_getDirectory_deep,
+];
+
+function next() {
+  if (!tests.length) {
+    SimpleTest.finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
+}
+
+SimpleTest.waitForExplicitFinish();
+next();
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/tests/test_no_dnd.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for Blink FileSystem API - no DND == no webkitEntries</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<input id="entries" type="file"></input>
+<script type="application/javascript;version=1.7">
+
+var fileEntry;
+var directoryEntry;
+
+function setup_tests() {
+  SpecialPowers.pushPrefEnv({"set": [["dom.webkitBlink.dirPicker.enabled", true],
+                                     ["dom.webkitBlink.filesystem.enabled", true]]}, next);
+}
+
+function populate_entries() {
+  var url = SimpleTest.getTestFileURL("script_entries.js");
+  var script = SpecialPowers.loadChromeScript(url);
+
+  function onOpened(message) {
+    var entries = document.getElementById('entries');
+
+    for (var i = 0 ; i < message.data.length; ++i) {
+      if (message.data[i] instanceof File) {
+        SpecialPowers.wrap(entries).mozSetFileArray([message.data[i]]);
+        next();
+      }
+    }
+
+    script.destroy();
+  }
+
+  script.addMessageListener("entries.opened", onOpened);
+  script.sendAsyncMessage("entries.open");
+}
+
+function test_entries() {
+  var entries = document.getElementById('entries');
+  ok("webkitEntries" in entries, "HTMLInputElement.webkitEntries");
+  is(entries.webkitEntries.length, 0, "HTMLInputElement.webkitEntries.length == 0");
+  is(entries.files.length, 1, "HTMLInputElement.files is still populated");
+
+  next();
+}
+
+var tests = [
+  setup_tests,
+  populate_entries,
+
+  test_entries,