Merge mozilla-central to fx-team
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 08 Jun 2016 12:15:05 +0200
changeset 301151 81f4cc3f6f4c21758b63605d69b47fa6cb3c142a
parent 301150 1401fcd673699f8d6e431ccb643ab46e166da2a9 (current diff)
parent 301056 f8ad071a6e14331d73fa44c8d3108bc2b66b2174 (diff)
child 301152 54354d5ade58598ba75e4fcbe29f2cae3d3cbfa1
push id78241
push userkwierso@gmail.com
push dateThu, 09 Jun 2016 00:09:10 +0000
treeherdermozilla-inbound@2c66b75bbb7f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-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 @@