Merge mozilla-central to fx-team
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 01 Sep 2015 15:06:05 +0200
changeset 260371 4aa12ff974247beecc4b131ddd6299f774163025
parent 260370 6ddd2771e1643911cedf92e8714da1fa26c1e74a (current diff)
parent 260266 dd509db16a138beb777599662ad88e3207bc2d2c (diff)
child 260372 8613a4ad3e3ba283fa7cbf3a31ee91178dd37b6e
push id29308
push userryanvm@gmail.com
push dateWed, 02 Sep 2015 01:15:13 +0000
treeherdermozilla-central@fb720c90eb49 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone43.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/workers/test/serviceworkers/fetch/origin/https/realindex.html^headers^
dom/workers/test/serviceworkers/fetch/origin/realindex.html^headers^
js/src/jit-test/tests/basic/bug1195298.js
--- a/accessible/jsat/TraversalRules.jsm
+++ b/accessible/jsat/TraversalRules.jsm
@@ -24,43 +24,46 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, 'Prefilters',  // jshint ignore:line
   'resource://gre/modules/accessibility/Constants.jsm');
 
 let gSkipEmptyImages = new PrefCache('accessibility.accessfu.skip_empty_images');
 
 function BaseTraversalRule(aRoles, aMatchFunc, aPreFilter) {
   this._explicitMatchRoles = new Set(aRoles);
   this._matchRoles = aRoles;
-  if (aRoles.indexOf(Roles.LABEL) < 0) {
-    this._matchRoles.push(Roles.LABEL);
-  }
-  if (aRoles.indexOf(Roles.INTERNAL_FRAME) < 0) {
-    // Used for traversing in to child OOP frames.
-    this._matchRoles.push(Roles.INTERNAL_FRAME);
+  if (aRoles.length) {
+    if (aRoles.indexOf(Roles.LABEL) < 0) {
+      this._matchRoles.push(Roles.LABEL);
+    }
+    if (aRoles.indexOf(Roles.INTERNAL_FRAME) < 0) {
+      // Used for traversing in to child OOP frames.
+      this._matchRoles.push(Roles.INTERNAL_FRAME);
+    }
   }
   this._matchFunc = aMatchFunc || function() { return Filters.MATCH; };
   this.preFilter = aPreFilter || gSimplePreFilter;
 }
 
 BaseTraversalRule.prototype = {
-    getMatchRoles: function BaseTraversalRule_getmatchRoles(aRules) {
-      aRules.value = this._matchRoles;
-      return aRules.value.length;
+    getMatchRoles: function BaseTraversalRule_getmatchRoles(aRoles) {
+      aRoles.value = this._matchRoles;
+      return aRoles.value.length;
     },
 
     match: function BaseTraversalRule_match(aAccessible)
     {
       let role = aAccessible.role;
       if (role == Roles.INTERNAL_FRAME) {
         return (Utils.getMessageManager(aAccessible.DOMNode)) ?
           Filters.MATCH  | Filters.IGNORE_SUBTREE : Filters.IGNORE;
       }
 
-      let matchResult = this._explicitMatchRoles.has(role) ?
-          this._matchFunc(aAccessible) : Filters.IGNORE;
+      let matchResult =
+        (this._explicitMatchRoles.has(role) || !this._explicitMatchRoles.size) ?
+        this._matchFunc(aAccessible) : Filters.IGNORE;
 
       // If we are on a label that nests a checkbox/radio we should land on it.
       // It is a bigger touch target, and it reduces clutter.
       if (role == Roles.LABEL && !(matchResult & Filters.IGNORE_SUBTREE)) {
         let control = Utils.getEmbeddedControl(aAccessible);
         if (control && this._explicitMatchRoles.has(control.role)) {
           matchResult = this._matchFunc(control) | Filters.IGNORE_SUBTREE;
         }
--- a/accessible/tests/mochitest/jsat/doc_traversal.html
+++ b/accessible/tests/mochitest/jsat/doc_traversal.html
@@ -5,65 +5,68 @@
   <meta charset="utf-8" />
   <style>
     .content:before {
       content: "Content";
     }
   </style>
 </head>
 <body>
-  <h3 id="heading-1">A small first heading</h3>
-  <form>
-    <label for="input-1-1">Name:</label>
-    <input id="input-1-1">
-    <label id="label-1-2">Favourite Ice Cream Flavour:<input id="input-1-2"></label>
-    <button id="button-1-1">Magic Button</button>
-    <label for="radio-1-1">Radios are old: </label>
-    <input id="radio-1-1" type="radio">
-    <label for="radio-1-2">Radios are new: </label>
-    <input id="radio-1-2" type="radio">
-    <label for="input-1-3">Password:</label>
-    <input id="input-1-3" type="password">
-    <label for="input-1-4">Unlucky number:</label>
-    <input id="input-1-4" type="tel">
-    <input id="button-1-2" type="button" value="Fun">
-    <label for="checkbox-1-1">Check me: </label>
-    <input id="checkbox-1-1" type="checkbox">
-    <select id="select-1-1">
-      <option>Value 1</option>
-      <option>Value 2</option>
-      <option>Value 3</option>
-    </select>
-    <select id="select-1-2" multiple="true">
-      <option>Value 1</option>
-      <option>Value 2</option>
-      <option>Value 3</option>
-    </select>
-    <label for="checkbox-1-2">Check me too: </label>
-    <input id="checkbox-1-2" type="checkbox">
-    <label for="checkbox-1-3">But not me: </label>
-    <input id="checkbox-1-3" type="checkbox" aria-hidden="true">
-    <label for="checkbox-1-4">Or me! </label>
-    <input id="checkbox-1-4" type="checkbox" style="visibility:hidden">
-    <select id="select-1-3" size="3">
-      <option>Value 1</option>
-      <option>Value 2</option>
-      <option>Value 3</option>
-    </select>
-    <label for="input-1-5">Electronic mailing address:</label>
-    <input id="input-1-5" type="email">
-    <input id="button-1-3" type="submit" value="Submit">
-
-  </form>
-  <h2 id="heading-2">A larger second</h2>
-  <div id="heading-3" role="heading">ARIA is fun</div>
-  <input id="button-2-1" type="button" value="More Fun">
-  <div id="button-2-2" tabindex="0" role="button">ARIA fun</div>
-  <div id="button-2-3" tabindex="0" role="button" aria-pressed="false">My little togglebutton</div>
-  <div id="button-2-4" tabindex="0" role="spinbutton">ARIA fun</div>
+  <header id="header-1">
+    <h3 id="heading-1">A small first heading</h3>
+    <form>
+      <label for="input-1-1">Name:</label>
+      <input id="input-1-1">
+      <label id="label-1-2">Favourite Ice Cream Flavour:<input id="input-1-2"></label>
+      <button id="button-1-1">Magic Button</button>
+      <label for="radio-1-1">Radios are old: </label>
+      <input id="radio-1-1" type="radio">
+      <label for="radio-1-2">Radios are new: </label>
+      <input id="radio-1-2" type="radio">
+      <label for="input-1-3">Password:</label>
+      <input id="input-1-3" type="password">
+      <label for="input-1-4">Unlucky number:</label>
+      <input id="input-1-4" type="tel">
+      <input id="button-1-2" type="button" value="Fun">
+      <label for="checkbox-1-1">Check me: </label>
+      <input id="checkbox-1-1" type="checkbox">
+      <select id="select-1-1">
+        <option>Value 1</option>
+        <option>Value 2</option>
+        <option>Value 3</option>
+      </select>
+      <select id="select-1-2" multiple="true">
+        <option>Value 1</option>
+        <option>Value 2</option>
+        <option>Value 3</option>
+      </select>
+      <label for="checkbox-1-2">Check me too: </label>
+      <input id="checkbox-1-2" type="checkbox">
+      <label for="checkbox-1-3">But not me: </label>
+      <input id="checkbox-1-3" type="checkbox" aria-hidden="true">
+      <label for="checkbox-1-4">Or me! </label>
+      <input id="checkbox-1-4" type="checkbox" style="visibility:hidden">
+      <select id="select-1-3" size="3">
+        <option>Value 1</option>
+        <option>Value 2</option>
+        <option>Value 3</option>
+      </select>
+      <label for="input-1-5">Electronic mailing address:</label>
+      <input id="input-1-5" type="email">
+      <input id="button-1-3" type="submit" value="Submit">
+    </form>
+  </header>
+  <main id="main-1">
+    <h2 id="heading-2">A larger second</h2>
+    <div id="heading-3" role="heading">ARIA is fun</div>
+    <input id="button-2-1" type="button" value="More Fun">
+    <div id="button-2-2" tabindex="0" role="button">ARIA fun</div>
+    <div id="button-2-3" tabindex="0" role="button" aria-pressed="false">My little togglebutton</div>
+    <div id="button-2-4" tabindex="0" role="spinbutton">ARIA fun</div>
+  </main>
   <h1 id="heading-4" style="display:none">Invisible header</h1>
   <dl id="list-1">
     <dt id="listitem-1-1">Programming Language</dt>
     <dd>A esoteric weapon wielded by only the most formidable warriors,
     for its unrelenting strict power is unfathomable.</dd>
   </dl>
   <ul id="list-2" onclick="alert('hi');">
     <li id="listitem-2-1">Lists of Programming Languages</li>
@@ -136,18 +139,20 @@
     </tfoot>
     <tbody>
       <tr>
         <td>Dirt</td>
         <td>Messy Stuff</td>
       </tr>
     </tbody>
   </table>
-  <div id="statusbar-1" role="status">Last sync:<span>2 days ago</span></div>
-  <div aria-label="Last sync: 30min ago" id="statusbar-2" role="status"></div>
+  <footer id="footer-1">
+    <div id="statusbar-1" role="status">Last sync:<span>2 days ago</span></div>
+    <div aria-label="Last sync: 30min ago" id="statusbar-2" role="status"></div>
+  </footer>
 
   <span id="switch-1" role="switch" aria-checked="false" aria-label="Light switch"></span>
   <p>This is a MathML formula <math id="math-1" display="block">
     <mfrac>
       <mrow><mi>x</mi><mo>+</mo><mn>1</mn></mrow>
       <msqrt><mn>3</mn><mo>/</mo><mn>4</mn></msqrt>
     </mfrac>
   </math> with some text after.</p>
--- a/accessible/tests/mochitest/jsat/test_traversal.html
+++ b/accessible/tests/mochitest/jsat/test_traversal.html
@@ -122,16 +122,19 @@
                               'link-2', 'anchor-2', 'link-3', '3', '1', '4',
                               '1', 'Sunday', 'M', 'Week 1', '3', '4', '7', '2',
                               '5 8', 'gridcell4', 'Just an innocuous separator',
                               'Dirty Words', 'Meaning', 'Mud', 'Wet Dirt',
                               'Dirt', 'Messy Stuff', 'statusbar-1', 'statusbar-2',
                               'switch-1', 'This is a MathML formula ', 'math-1',
                               'with some text after.']);
 
+      queueTraversalSequence(gQueue, docAcc, TraversalRules.Landmark, null,
+                             ['header-1', 'main-1', 'footer-1']);
+
       gQueue.invoke();
     }
 
     SimpleTest.waitForExplicitFinish();
     addLoadEvent(function () {
       /* We open a new browser because we need to test with a top-level content
          document. */
       openBrowserWindow(
--- a/b2g/chrome/content/devtools/hud.js
+++ b/b2g/chrome/content/devtools/hud.js
@@ -557,19 +557,19 @@ let consoleWatcher = {
             metric.name = 'warnings';
             output += 'Warning (console)';
             break;
 
           case 'info':
             this.handleTelemetryMessage(target, packet);
 
             // Currently, informational log entries are tracked only by
-            // advanced telemetry. Nonetheless, for consistency, we
-            // continue here and let the function return normally, when it
-            // concludes 'info' entries are not being watched.
+            // telemetry. Nonetheless, for consistency, we continue here
+            // and let the function return normally, when it concludes 'info'
+            // entries are not being watched.
             metric.name = 'info';
             break;
 
           default:
             return;
         }
         break;
 
@@ -590,16 +590,35 @@ let consoleWatcher = {
           value: Math.round(duration)
         });
         break;
 
       default:
         return;
     }
 
+    if (developerHUD._telemetry) {
+      // Always record telemetry for these metrics.
+      if (metric.name === 'errors' || metric.name === 'warnings' || metric.name === 'reflows') {
+        let value = target.metrics.get(metric.name);
+        metric.value = (value || 0) + 1;
+        target._logHistogram(metric);
+
+        // Telemetry has already been recorded.
+        metric.skipTelemetry = true;
+
+        // If the metric is not being watched, persist the incremented value.
+        // If the metric is being watched, `target.bump` will increment the value
+        // of the metric and will persist the incremented value.
+        if (!this._watching[metric.name]) {
+          target.metrics.set(metric.name, metric.value);
+        }
+      }
+    }
+
     if (!this._watching[metric.name]) {
       return;
     }
 
     target.bump(metric, output);
   },
 
   formatSourceURL(packet) {
--- a/b2g/config/aries/sources.xml
+++ b/b2g/config/aries/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e935894ef5f27e2f04b9e929a45a958e6288a223">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c80e8ff25425b007181fd6e3de0500a0358fab37"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="805025801a68f9ddbba6ffd2ed3926c97fa7fcc8"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b4f6fd4afd03161f53c7d2a663750f94762bd238"/>
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e935894ef5f27e2f04b9e929a45a958e6288a223">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c80e8ff25425b007181fd6e3de0500a0358fab37"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="805025801a68f9ddbba6ffd2ed3926c97fa7fcc8"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b4f6fd4afd03161f53c7d2a663750f94762bd238"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c80e8ff25425b007181fd6e3de0500a0358fab37"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="805025801a68f9ddbba6ffd2ed3926c97fa7fcc8"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="2d58f4b9206b50b8fda0d5036da6f0c62608db7c"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="d70e4bfdcb65e7514de0f9315b74aea1c811678d"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c80e8ff25425b007181fd6e3de0500a0358fab37"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="805025801a68f9ddbba6ffd2ed3926c97fa7fcc8"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b4f6fd4afd03161f53c7d2a663750f94762bd238"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e935894ef5f27e2f04b9e929a45a958e6288a223">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c80e8ff25425b007181fd6e3de0500a0358fab37"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="805025801a68f9ddbba6ffd2ed3926c97fa7fcc8"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b4f6fd4afd03161f53c7d2a663750f94762bd238"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="05a36844c1046a1eb07d5b1325f85ed741f961ea">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c80e8ff25425b007181fd6e3de0500a0358fab37"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="805025801a68f9ddbba6ffd2ed3926c97fa7fcc8"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b4f6fd4afd03161f53c7d2a663750f94762bd238"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c80e8ff25425b007181fd6e3de0500a0358fab37"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="805025801a68f9ddbba6ffd2ed3926c97fa7fcc8"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="2d58f4b9206b50b8fda0d5036da6f0c62608db7c"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="d70e4bfdcb65e7514de0f9315b74aea1c811678d"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e935894ef5f27e2f04b9e929a45a958e6288a223">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c80e8ff25425b007181fd6e3de0500a0358fab37"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="805025801a68f9ddbba6ffd2ed3926c97fa7fcc8"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b4f6fd4afd03161f53c7d2a663750f94762bd238"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "c80e8ff25425b007181fd6e3de0500a0358fab37", 
+        "git_revision": "805025801a68f9ddbba6ffd2ed3926c97fa7fcc8", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "617c2b44e26a8bd8c79d96a12fb9fd373c873e44", 
+    "revision": "fb284fae468b639c8dbfd33ad2395c8397c090ec", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c80e8ff25425b007181fd6e3de0500a0358fab37"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="805025801a68f9ddbba6ffd2ed3926c97fa7fcc8"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b4f6fd4afd03161f53c7d2a663750f94762bd238"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="05a36844c1046a1eb07d5b1325f85ed741f961ea">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c80e8ff25425b007181fd6e3de0500a0358fab37"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="805025801a68f9ddbba6ffd2ed3926c97fa7fcc8"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b4f6fd4afd03161f53c7d2a663750f94762bd238"/>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -7572,16 +7572,27 @@ let RestoreLastSessionObserver = {
 };
 
 function restoreLastSession() {
   SessionStore.restoreLastSession();
 }
 
 var TabContextMenu = {
   contextTab: null,
+  _updateToggleMuteMenuItem(aTab, aConditionFn) {
+    ["muted", "soundplaying"].forEach(attr => {
+      if (!aConditionFn || aConditionFn(attr)) {
+        if (aTab.hasAttribute(attr)) {
+          aTab.toggleMuteMenuItem.setAttribute(attr, "true");
+        } else {
+          aTab.toggleMuteMenuItem.removeAttribute(attr);
+        }
+      }
+    });
+  },
   updateContextMenu: function updateContextMenu(aPopupMenu) {
     this.contextTab = aPopupMenu.triggerNode.localName == "tab" ?
                       aPopupMenu.triggerNode : gBrowser.selectedTab;
     let disabled = gBrowser.tabs.length == 1;
 
     var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple");
     for (let menuItem of menuItems)
       menuItem.disabled = disabled;
@@ -7632,16 +7643,35 @@ var TabContextMenu = {
     let toggleMute = document.getElementById("context_toggleMuteTab");
     if (this.contextTab.hasAttribute("muted")) {
       toggleMute.label = gNavigatorBundle.getString("unmuteTab.label");
       toggleMute.accessKey = gNavigatorBundle.getString("unmuteTab.accesskey");
     } else {
       toggleMute.label = gNavigatorBundle.getString("muteTab.label");
       toggleMute.accessKey = gNavigatorBundle.getString("muteTab.accesskey");
     }
+
+    this.contextTab.toggleMuteMenuItem = toggleMute;
+    this._updateToggleMuteMenuItem(this.contextTab);
+
+    this.contextTab.addEventListener("TabAttrModified", this, false);
+    aPopupMenu.addEventListener("popuphiding", this, false);
+  },
+  handleEvent(aEvent) {
+    switch (aEvent.type) {
+      case "popuphiding":
+        gBrowser.removeEventListener("TabAttrModified", this);
+        aEvent.target.removeEventListener("popuphiding", this);
+        break;
+      case "TabAttrModified":
+        let tab = aEvent.target;
+        this._updateToggleMuteMenuItem(tab,
+          attr => aEvent.detail.changed.indexOf(attr) >= 0);
+        break;
+    }
   }
 };
 
 XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
                                   "resource:///modules/devtools/gDevTools.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "gDevToolsBrowser",
                                   "resource:///modules/devtools/gDevTools.jsm");
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -3953,20 +3953,28 @@
           };
 
           var label;
           if (tab.mOverCloseButton) {
             label = tab.selected ?
                     stringWithShortcut("tabs.closeSelectedTab.tooltip", "key_close") :
                     this.mStringBundle.getString("tabs.closeTab.tooltip");
           } else if (tab._overPlayingIcon) {
-            let stringID = tab.linkedBrowser.audioMuted ?
-              "tabs.unmuteAudio.tooltip" :
-              "tabs.muteAudio.tooltip";
-            label = stringWithShortcut(stringID, "key_toggleMute");
+            let stringID;
+            if (tab.selected) {
+              stringID = tab.linkedBrowser.audioMuted ?
+                "tabs.unmuteAudio.tooltip" :
+                "tabs.muteAudio.tooltip";
+              label = stringWithShortcut(stringID, "key_toggleMute");
+            } else {
+              stringID = tab.linkedBrowser.audioMuted ?
+                "tabs.unmuteAudio.background.tooltip" :
+                "tabs.muteAudio.background.tooltip";
+              label = this.mStringBundle.getString(stringID);
+            }
           } else {
             label = tab.getAttribute("label") +
                       (this.AppConstants.E10S_TESTING_ONLY && tab.linkedBrowser && tab.linkedBrowser.isRemoteBrowser ? " - e10s" : "");
           }
           event.target.setAttribute("label", label);
         ]]></body>
       </method>
 
--- a/browser/base/content/test/general/browser_audioTabIcon.js
+++ b/browser/base/content/test/general/browser_audioTabIcon.js
@@ -5,16 +5,36 @@ function* wait_for_tab_playing_event(tab
     if (event.detail.changed.indexOf("soundplaying") >= 0) {
       is(tab.hasAttribute("soundplaying"), expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
       return true;
     }
     return false;
   });
 }
 
+function* play(tab) {
+  let browser = tab.linkedBrowser;
+  yield ContentTask.spawn(browser, {}, function* () {
+    let audio = content.document.querySelector("audio");
+    audio.play();
+  });
+
+  yield wait_for_tab_playing_event(tab, true);
+}
+
+function* pause(tab) {
+  let browser = tab.linkedBrowser;
+  yield ContentTask.spawn(browser, {}, function* () {
+    let audio = content.document.querySelector("audio");
+    audio.pause();
+  });
+
+  yield wait_for_tab_playing_event(tab, false);
+}
+
 function disable_non_test_mouse(disable) {
   let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
                     .getInterface(Ci.nsIDOMWindowUtils);
   utils.disableNonTestMouseEvents(disable);
 }
 
 function* hover_icon(icon, tooltip) {
   disable_non_test_mouse(true);
@@ -31,21 +51,29 @@ function leave_icon(icon) {
   EventUtils.synthesizeMouse(icon, 0, 0, {type: "mouseout"});
   EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
   EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
   EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
 
   disable_non_test_mouse(false);
 }
 
-function* test_tooltip(icon, expectedTooltip) {
+function* test_tooltip(icon, expectedTooltip, isActiveTab) {
   let tooltip = document.getElementById("tabbrowser-tab-tooltip");
 
   yield hover_icon(icon, tooltip);
-  is(tooltip.getAttribute("label").indexOf(expectedTooltip), 0, "Correct tooltip expected");
+  if (isActiveTab) {
+    // The active tab should have the keybinding shortcut in the tooltip.
+    // We check this by ensuring that the strings are not equal but the expected
+    // message appears in the beginning.
+    isnot(tooltip.getAttribute("label"), expectedTooltip, "Tooltips should not be equal");
+    is(tooltip.getAttribute("label").indexOf(expectedTooltip), 0, "Correct tooltip expected");
+  } else {
+    is(tooltip.getAttribute("label"), expectedTooltip, "Tooltips should not be equal");
+  }
   leave_icon(icon);
 }
 
 function get_wait_for_mute_promise(tab, expectMuted) {
   return BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, event => {
     if (event.detail.changed.indexOf("muted") >= 0) {
       is(tab.hasAttribute("muted"), expectMuted, "The tab should " + (expectMuted ? "" : "not ") + "be muted");
       return true;
@@ -84,63 +112,68 @@ function* test_muting_using_menu(tab, ex
   yield popupShownPromise;
 
   // Check the menu
   let expectedLabel = expectMuted ? "Unmute Tab" : "Mute Tab";
   let toggleMute = document.getElementById("context_toggleMuteTab");
   is(toggleMute.label, expectedLabel, "Correct label expected");
   is(toggleMute.accessKey, "M", "Correct accessKey expected");
 
+  is(toggleMute.hasAttribute("muted"), expectMuted, "Should have the correct state for the muted attribute");
+  ok(!toggleMute.hasAttribute("soundplaying"), "Should not have the soundplaying attribute");
+
+  yield play(tab);
+
+  is(toggleMute.hasAttribute("muted"), expectMuted, "Should have the correct state for the muted attribute");
+  ok(toggleMute.hasAttribute("soundplaying"), "Should have the soundplaying attribute");
+
+  yield pause(tab);
+
+  is(toggleMute.hasAttribute("muted"), expectMuted, "Should have the correct state for the muted attribute");
+  ok(!toggleMute.hasAttribute("soundplaying"), "Should not have the soundplaying attribute");
+
   // Click on the menu and wait for the tab to be muted.
   let mutedPromise = get_wait_for_mute_promise(tab, !expectMuted);
   let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
   EventUtils.synthesizeMouseAtCenter(toggleMute, {});
   yield popupHiddenPromise;
   yield mutedPromise;
 }
 
 function* test_playing_icon_on_tab(tab, browser, isPinned) {
   let icon = document.getAnonymousElementByAttribute(tab, "anonid",
                                                      isPinned ? "overlay-icon" : "soundplaying-icon");
+  let isActiveTab = tab === gBrowser.selectedTab;
 
-  yield ContentTask.spawn(browser, {}, function* () {
-    let audio = content.document.querySelector("audio");
-    audio.play();
-  });
+  yield play(tab);
 
-  yield wait_for_tab_playing_event(tab, true);
-
-  yield test_tooltip(icon, "Mute tab");
+  yield test_tooltip(icon, "Mute tab", isActiveTab);
 
   ok(!("muted" in get_tab_attributes(tab)), "No muted attribute should be persisted");
 
   yield test_mute_tab(tab, icon, true);
 
   ok("muted" in get_tab_attributes(tab), "Muted attribute should be persisted");
 
-  yield test_tooltip(icon, "Unmute tab");
+  yield test_tooltip(icon, "Unmute tab", isActiveTab);
 
   yield test_mute_tab(tab, icon, false);
 
   ok(!("muted" in get_tab_attributes(tab)), "No muted attribute should be persisted");
 
-  yield test_tooltip(icon, "Mute tab");
+  yield test_tooltip(icon, "Mute tab", isActiveTab);
 
   yield test_mute_tab(tab, icon, true);
 
-  yield ContentTask.spawn(browser, {}, function* () {
-    let audio = content.document.querySelector("audio");
-    audio.pause();
-  });
-  yield wait_for_tab_playing_event(tab, false);
+  yield pause(tab);
 
   ok(tab.hasAttribute("muted") &&
      !tab.hasAttribute("soundplaying"), "Tab should still be muted but not playing");
 
-  yield test_tooltip(icon, "Unmute tab");
+  yield test_tooltip(icon, "Unmute tab", isActiveTab);
 
   yield test_mute_tab(tab, icon, false);
 
   ok(!tab.hasAttribute("muted") &&
      !tab.hasAttribute("soundplaying"), "Tab should not be be muted or playing");
 
   // Make sure it's possible to mute using the context menu.
   yield test_muting_using_menu(tab, false);
@@ -179,39 +212,32 @@ function* test_swapped_browser(oldTab, n
   yield AudioPlaybackPromise;
 
   ok(newTab.hasAttribute("muted"), "Expected the correct muted attribute on the new tab");
   is(newTab.hasAttribute("soundplaying"), isPlaying, "Expected the correct soundplaying attribute on the new tab");
 }
 
 function* test_browser_swapping(tab, browser) {
   // First, test swapping with a playing but muted tab.
-  yield ContentTask.spawn(browser, {}, function* () {
-    let audio = content.document.querySelector("audio");
-    audio.play();
-  });
-  yield wait_for_tab_playing_event(tab, true);
+  yield play(tab);
 
   let icon = document.getAnonymousElementByAttribute(tab, "anonid",
                                                      "soundplaying-icon");
   yield test_mute_tab(tab, icon, true);
 
   yield BrowserTestUtils.withNewTab({
     gBrowser,
     url: "about:blank",
   }, function*(newBrowser) {
     yield test_swapped_browser(tab, newBrowser, true)
 
     // Now, test swapping with a muted but not playing tab.
     // Note that the tab remains muted, so we only need to pause playback.
     tab = gBrowser.getTabForBrowser(newBrowser);
-    yield ContentTask.spawn(newBrowser, {}, function* () {
-      let audio = content.document.querySelector("audio");
-      audio.pause();
-    });
+    yield pause(tab);
 
     yield BrowserTestUtils.withNewTab({
       gBrowser,
       url: "about:blank",
     }, newBrowser => test_swapped_browser(tab, newBrowser, false));
   });
 }
 
@@ -221,34 +247,25 @@ function* test_click_on_pinned_tab_after
 
     gBrowser.selectedTab = originallySelectedTab;
     isnot(tab, gBrowser.selectedTab, "Sanity check, the tab should not be selected!");
 
     // Steps to reproduce the bug:
     //   Pin the tab.
     gBrowser.pinTab(tab);
 
-    //   Start playbak.
-    yield ContentTask.spawn(browser, {}, function* () {
-      let audio = content.document.querySelector("audio");
-      audio.play();
-    });
-
-    //   Wait for playback to start.
-    yield wait_for_tab_playing_event(tab, true);
+    //   Start playback and wait for it to finish.
+    yield play(tab);
 
     //   Mute the tab.
     let icon = document.getAnonymousElementByAttribute(tab, "anonid", "overlay-icon");
     yield test_mute_tab(tab, icon, true);
 
-    //   Stop playback
-    yield ContentTask.spawn(browser, {}, function* () {
-      let audio = content.document.querySelector("audio");
-      audio.pause();
-    });
+    // Pause playback and wait for it to finish.
+    yield pause(tab);
 
     // Unmute tab.
     yield test_mute_tab(tab, icon, false);
 
     // Now click on the tab.
     let image = document.getAnonymousElementByAttribute(tab, "anonid", "tab-icon-image");
     EventUtils.synthesizeMouseAtCenter(image, {button: 0});
 
@@ -267,24 +284,18 @@ function* test_click_on_pinned_tab_after
   }, test_on_browser);
 }
 
 // This test only does something useful in e10s!
 function* test_cross_process_load() {
   function* test_on_browser(browser) {
     let tab = gBrowser.getTabForBrowser(browser);
 
-    // Start playback.
-    yield ContentTask.spawn(browser, {}, function* () {
-      let audio = content.document.querySelector("audio");
-      audio.play();
-    });
-
-    // Wait for playback to start.
-    yield wait_for_tab_playing_event(tab, true);
+    //   Start playback and wait for it to finish.
+    yield play(tab);
 
     let soundPlayingStoppedPromise = BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false,
       event => event.detail.changed.indexOf("soundplaying") >= 0
     );
 
     // Go to a different process.
     browser.loadURI("about:");
     yield BrowserTestUtils.browserLoaded(browser);
@@ -310,48 +321,33 @@ function* test_mute_keybinding() {
     yield mutedPromise;
   }
   function* test_on_browser(browser) {
     let tab = gBrowser.getTabForBrowser(browser);
 
     // Make sure it's possible to mute before the tab is playing.
     yield test_muting_using_keyboard(tab);
 
-    // Start playback.
-    yield ContentTask.spawn(browser, {}, function* () {
-      let audio = content.document.querySelector("audio");
-      audio.play();
-    });
-
-    // Wait for playback to start.
-    yield wait_for_tab_playing_event(tab, true);
+    //   Start playback and wait for it to finish.
+    yield play(tab);
 
     // Make sure it's possible to mute after the tab is playing.
     yield test_muting_using_keyboard(tab);
 
-    // Start playback.
-    yield ContentTask.spawn(browser, {}, function* () {
-      let audio = content.document.querySelector("audio");
-      audio.pause();
-    });
+    // Pause playback and wait for it to finish.
+    yield pause(tab);
 
     // Make sure things work if the tab is pinned.
     gBrowser.pinTab(tab);
 
     // Make sure it's possible to mute before the tab is playing.
     yield test_muting_using_keyboard(tab);
 
-    // Start playback.
-    yield ContentTask.spawn(browser, {}, function* () {
-      let audio = content.document.querySelector("audio");
-      audio.play();
-    });
-
-    // Wait for playback to start.
-    yield wait_for_tab_playing_event(tab, true);
+    //   Start playback and wait for it to finish.
+    yield play(tab);
 
     // Make sure it's possible to mute after the tab is playing.
     yield test_muting_using_keyboard(tab);
 
     gBrowser.unpinTab(tab);
   }
 
   yield BrowserTestUtils.withNewTab({
--- a/browser/locales/en-US/chrome/browser/tabbrowser.properties
+++ b/browser/locales/en-US/chrome/browser/tabbrowser.properties
@@ -35,8 +35,10 @@ tabs.closeTab.tooltip=Close tab
 # %S is the keyboard shortcut for closing the current tab
 tabs.closeSelectedTab.tooltip=Close tab (%S)
 # LOCALIZATION NOTE (tabs.muteAudio.tooltip):
 # %S is the keyboard shortcut for "Mute tab"
 tabs.muteAudio.tooltip=Mute tab (%S)
 # LOCALIZATION NOTE (tabs.unmuteAudio.tooltip):
 # %S is the keyboard shortcut for "Unmute tab"
 tabs.unmuteAudio.tooltip=Unmute tab (%S)
+tabs.muteAudio.background.tooltip=Mute tab
+tabs.unmuteAudio.background.tooltip=Unmute tab
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -4965,16 +4965,17 @@ nsDocShell::DisplayLoadError(nsresult aE
         error.AssignLiteral("corruptedContentError");
         break;
       case NS_ERROR_INTERCEPTION_FAILED:
       case NS_ERROR_OPAQUE_INTERCEPTION_DISABLED:
       case NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE:
       case NS_ERROR_INTERCEPTED_ERROR_RESPONSE:
       case NS_ERROR_INTERCEPTED_USED_RESPONSE:
       case NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION:
+      case NS_ERROR_BAD_OPAQUE_REDIRECT_INTERCEPTION:
         // ServiceWorker intercepted request, but something went wrong.
         nsContentUtils::MaybeReportInterceptionErrorToConsole(GetDocument(),
                                                               aError);
         error.AssignLiteral("corruptedContentError");
         break;
       default:
         break;
     }
@@ -10558,16 +10559,18 @@ nsDocShell::DoURILoad(nsIURI* aURI,
       httpChannelInternal->SetThirdPartyFlags(
         nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
     }
     if (aFirstParty) {
       httpChannelInternal->SetDocumentURI(aURI);
     } else {
       httpChannelInternal->SetDocumentURI(aReferrerURI);
     }
+    httpChannelInternal->SetRedirectMode(
+      nsIHttpChannelInternal::REDIRECT_MODE_MANUAL);
   }
 
   nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(channel));
   if (props) {
     // save true referrer for those who need it (e.g. xpinstall whitelisting)
     // Currently only http and ftp channels support this.
     props->SetPropertyAsInterface(NS_LITERAL_STRING("docshell.internalReferrer"),
                                   aReferrerURI);
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -674,18 +674,18 @@ nsIContent::PreHandleEvent(EventChainPre
   aVisitor.mCanHandle = true;
   aVisitor.mMayHaveListenerManager = HasListenerManager();
 
   // Don't propagate mouseover and mouseout events when mouse is moving
   // inside chrome access only content.
   bool isAnonForEvents = IsRootOfChromeAccessOnlySubtree();
   if ((aVisitor.mEvent->mMessage == eMouseOver ||
        aVisitor.mEvent->mMessage == eMouseOut ||
-       aVisitor.mEvent->mMessage == NS_POINTER_OVER ||
-       aVisitor.mEvent->mMessage == NS_POINTER_OUT) &&
+       aVisitor.mEvent->mMessage == ePointerOver ||
+       aVisitor.mEvent->mMessage == ePointerOut) &&
       // Check if we should stop event propagation when event has just been
       // dispatched or when we're about to propagate from
       // chrome access only subtree or if we are about to propagate out of
       // a shadow root to a shadow root host.
       ((this == aVisitor.mEvent->originalTarget &&
         !ChromeOnlyAccess()) || isAnonForEvents || GetShadowRoot())) {
      nsCOMPtr<nsIContent> relatedTarget =
        do_QueryInterface(aVisitor.mEvent->AsMouseEvent()->relatedTarget);
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -3453,16 +3453,18 @@ nsContentUtils::MaybeReportInterceptionE
   } else if (aError == NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE) {
     messageName = "BadOpaqueInterceptionRequestMode";
   } else if (aError == NS_ERROR_INTERCEPTED_ERROR_RESPONSE) {
     messageName = "InterceptedErrorResponse";
   } else if (aError == NS_ERROR_INTERCEPTED_USED_RESPONSE) {
     messageName = "InterceptedUsedResponse";
   } else if (aError == NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION) {
     messageName = "ClientRequestOpaqueInterception";
+  } else if (aError == NS_ERROR_BAD_OPAQUE_REDIRECT_INTERCEPTION) {
+    messageName = "BadOpaqueRedirectInterception";
   }
 
   if (messageName) {
     return ReportToConsole(nsIScriptError::warningFlag,
                            NS_LITERAL_CSTRING("Service Worker Interception"),
                            aDocument,
                            nsContentUtils::eDOM_PROPERTIES,
                            messageName);
@@ -7843,17 +7845,17 @@ nsContentUtils::SendMouseEvent(nsCOMPtr<
     msg = eMouseUp;
   else if (aType.EqualsLiteral("mousemove"))
     msg = eMouseMove;
   else if (aType.EqualsLiteral("mouseover"))
     msg = eMouseEnterIntoWidget;
   else if (aType.EqualsLiteral("mouseout"))
     msg = eMouseExitFromWidget;
   else if (aType.EqualsLiteral("contextmenu")) {
-    msg = NS_CONTEXTMENU;
+    msg = eContextMenu;
     contextMenuKey = (aButton == 0);
   } else if (aType.EqualsLiteral("MozMouseHittest"))
     msg = eMouseHitTest;
   else
     return NS_ERROR_FAILURE;
 
   if (aInputSourceArg == nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN) {
     aInputSourceArg = nsIDOMMouseEvent::MOZ_SOURCE_MOUSE;
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -1307,16 +1307,18 @@ public:
    * Get the textual contents of a node. This is a concatenation of all
    * textnodes that are direct or (depending on aDeep) indirect children
    * of the node.
    *
    * NOTE! No serialization takes place and <br> elements
    * are not converted into newlines. Only textnodes and cdata nodes are
    * added to the result.
    *
+   * @see nsLayoutUtils::GetFrameTextContent
+   *
    * @param aNode Node to get textual contents of.
    * @param aDeep If true child elements of aNode are recursivly descended
    *              into to find text children.
    * @param aResult the result. Out param.
    * @return false on out of memory errors, true otherwise.
    */
   MOZ_WARN_UNUSED_RESULT
   static bool GetNodeTextContent(nsINode* aNode, bool aDeep,
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -697,25 +697,25 @@ nsDOMWindowUtils::SendPointerEventCommon
   nsPoint offset;
   nsCOMPtr<nsIWidget> widget = GetWidget(&offset);
   if (!widget) {
     return NS_ERROR_FAILURE;
   }
 
   EventMessage msg;
   if (aType.EqualsLiteral("pointerdown")) {
-    msg = NS_POINTER_DOWN;
+    msg = ePointerDown;
   } else if (aType.EqualsLiteral("pointerup")) {
-    msg = NS_POINTER_UP;
+    msg = ePointerUp;
   } else if (aType.EqualsLiteral("pointermove")) {
-    msg = NS_POINTER_MOVE;
+    msg = ePointerMove;
   } else if (aType.EqualsLiteral("pointerover")) {
-    msg = NS_POINTER_OVER;
+    msg = ePointerOver;
   } else if (aType.EqualsLiteral("pointerout")) {
-    msg = NS_POINTER_OUT;
+    msg = ePointerOut;
   } else {
     return NS_ERROR_FAILURE;
   }
 
   if (aInputSourceArg == nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN) {
     aInputSourceArg = nsIDOMMouseEvent::MOZ_SOURCE_MOUSE;
   }
 
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -1731,24 +1731,25 @@ public:
   }
 
   /**
    * Set the display document for this document.  aDisplayDocument must not be
    * null.
    */
   void SetDisplayDocument(nsIDocument* aDisplayDocument)
   {
-    NS_PRECONDITION(!GetShell() &&
-                    !GetContainer() &&
-                    !GetWindow(),
-                    "Shouldn't set mDisplayDocument on documents that already "
-                    "have a presentation or a docshell or a window");
-    NS_PRECONDITION(aDisplayDocument != this, "Should be different document");
-    NS_PRECONDITION(!aDisplayDocument->GetDisplayDocument(),
-                    "Display documents should not nest");
+    MOZ_ASSERT(!GetShell() &&
+               !GetContainer() &&
+               !GetWindow(),
+               "Shouldn't set mDisplayDocument on documents that already "
+               "have a presentation or a docshell or a window");
+    MOZ_ASSERT(aDisplayDocument, "Must not be null");
+    MOZ_ASSERT(aDisplayDocument != this, "Should be different document");
+    MOZ_ASSERT(!aDisplayDocument->GetDisplayDocument(),
+               "Display documents should not nest");
     mDisplayDocument = aDisplayDocument;
     mHasDisplayDocument = !!aDisplayDocument;
   }
 
   /**
    * A class that represents an external resource load that has begun but
    * doesn't have a document yet.  Observers can be registered on this object,
    * and will be notified after the document is created.  Observers registered
@@ -1759,17 +1760,17 @@ public:
    * notified, with a null document pointer.
    */
   class ExternalResourceLoad : public nsISupports
   {
   public:
     virtual ~ExternalResourceLoad() {}
 
     void AddObserver(nsIObserver* aObserver) {
-      NS_PRECONDITION(aObserver, "Must have observer");
+      MOZ_ASSERT(aObserver, "Must have observer");
       mObservers.AppendElement(aObserver);
     }
 
     const nsTArray< nsCOMPtr<nsIObserver> > & Observers() {
       return mObservers;
     }
   protected:
     nsAutoTArray< nsCOMPtr<nsIObserver>, 8 > mObservers;
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -262,16 +262,18 @@ support-files =
 [test_appname_override.html]
 [test_async_setTimeout_stack.html]
 [test_async_setTimeout_stack_across_globals.html]
 [test_audioWindowUtils.html]
 [test_audioNotification.html]
 skip-if = buildapp == 'mulet'
 [test_audioNotificationStopOnNavigation.html]
 skip-if = buildapp == 'mulet'
+[test_audioNotificationWithEarlyPlay.html]
+skip-if = buildapp == 'mulet'
 [test_bug1091883.html]
 [test_bug116083.html]
 [test_bug793311.html]
 [test_bug913761.html]
 [test_bug976673.html]
 [test_bug978522.html]
 [test_bug979109.html]
 [test_bug989665.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_audioNotificationWithEarlyPlay.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for audio controller in windows</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+</pre>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var expectedNotification = null;
+
+var observer = {
+  observe: function(subject, topic, data) {
+    is(topic, "audio-playback", "audio-playback received");
+    is(data, expectedNotification, "This is the right notification");
+    runTest();
+  }
+};
+
+var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+                                   .getService(SpecialPowers.Ci.nsIObserverService);
+
+var audio = new Audio();
+audio.loop = true;
+audio.preload = "metadata";
+
+var tests = [
+  function() {
+    observerService.addObserver(observer, "audio-playback", false);
+    ok(true, "Observer set");
+    runTest();
+  },
+
+  function() {
+    expectedNotification = 'active';
+    audio.src = "audio.ogg";
+    audio.play();
+  },
+
+  function() {
+    expectedNotification = 'inactive';
+    audio.pause();
+  },
+
+  function() {
+    observerService.removeObserver(observer, "audio-playback");
+    ok(true, "Observer removed");
+    runTest();
+  }
+];
+
+function runTest() {
+  if (!tests.length) {
+    SimpleTest.finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
+}
+
+runTest();
+
+</script>
+</body>
+</html>
+
--- a/dom/cache/CacheStorage.cpp
+++ b/dom/cache/CacheStorage.cpp
@@ -234,16 +234,51 @@ CacheStorage::CreateOnWorker(Namespace a
     return ref.forget();
   }
 
   nsRefPtr<CacheStorage> ref = new CacheStorage(aNamespace, aGlobal,
                                                 principalInfo, feature);
   return ref.forget();
 }
 
+// static
+bool
+CacheStorage::DefineCaches(JSContext* aCx, JS::Handle<JSObject*> aGlobal)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(js::GetObjectClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL,
+             "Passed object is not a global object!");
+
+  if (NS_WARN_IF(!CacheStorageBinding::GetConstructorObject(aCx, aGlobal) ||
+                 !CacheBinding::GetConstructorObject(aCx, aGlobal))) {
+    return false;
+  }
+
+  nsIPrincipal* principal = nsContentUtils::ObjectPrincipal(aGlobal);
+  MOZ_ASSERT(principal);
+
+  ErrorResult rv;
+  nsRefPtr<CacheStorage> storage =
+    CreateOnMainThread(DEFAULT_NAMESPACE, xpc::NativeGlobal(aGlobal), principal,
+                       false, /* private browsing */
+                       true,  /* force trusted */
+                       rv);
+  if (NS_WARN_IF(rv.Failed())) {
+    return ThrowMethodFailed(aCx, rv);
+  }
+
+  JS::Rooted<JS::Value> caches(aCx);
+  js::AssertSameCompartment(aCx, aGlobal);
+  if (NS_WARN_IF(!ToJSValue(aCx, storage, &caches))) {
+    return false;
+  }
+
+  return JS_DefineProperty(aCx, aGlobal, "caches", caches, JSPROP_ENUMERATE);
+}
+
 CacheStorage::CacheStorage(Namespace aNamespace, nsIGlobalObject* aGlobal,
                            const PrincipalInfo& aPrincipalInfo, Feature* aFeature)
   : mNamespace(aNamespace)
   , mGlobal(aGlobal)
   , mPrincipalInfo(MakeUnique<PrincipalInfo>(aPrincipalInfo))
   , mFeature(aFeature)
   , mActor(nullptr)
   , mStatus(NS_OK)
--- a/dom/cache/CacheStorage.h
+++ b/dom/cache/CacheStorage.h
@@ -51,16 +51,19 @@ public:
   CreateOnMainThread(Namespace aNamespace, nsIGlobalObject* aGlobal,
                      nsIPrincipal* aPrincipal, bool aStorageDisabled,
                      bool aForceTrustedOrigin, ErrorResult& aRv);
 
   static already_AddRefed<CacheStorage>
   CreateOnWorker(Namespace aNamespace, nsIGlobalObject* aGlobal,
                  workers::WorkerPrivate* aWorkerPrivate, ErrorResult& aRv);
 
+  static bool
+  DefineCaches(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
+
   // webidl interface methods
   already_AddRefed<Promise> Match(const RequestOrUSVString& aRequest,
                                   const CacheQueryOptions& aOptions,
                                   ErrorResult& aRv);
   already_AddRefed<Promise> Has(const nsAString& aKey, ErrorResult& aRv);
   already_AddRefed<Promise> Open(const nsAString& aKey, ErrorResult& aRv);
   already_AddRefed<Promise> Delete(const nsAString& aKey, ErrorResult& aRv);
   already_AddRefed<Promise> Keys(ErrorResult& aRv);
--- a/dom/cache/CacheTypes.ipdlh
+++ b/dom/cache/CacheTypes.ipdlh
@@ -8,16 +8,17 @@ include protocol PCacheStreamControl;
 include InputStreamParams;
 include ChannelInfo;
 include PBackgroundSharedTypes;
 
 using HeadersGuardEnum 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 mozilla::void_t from "ipc/IPCMessageUtils.h";
 using struct nsID from "nsID.h";
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
@@ -59,16 +60,17 @@ struct CacheRequest
   HeadersEntry[] headers;
   HeadersGuardEnum headersGuard;
   nsString referrer;
   RequestMode mode;
   RequestCredentials credentials;
   CacheReadStreamOrVoid body;
   uint32_t contentPolicyType;
   RequestCache requestCache;
+  RequestRedirect requestRedirect;
 };
 
 union CacheRequestOrVoid
 {
   void_t;
   CacheRequest;
 };
 
--- a/dom/cache/DBAction.cpp
+++ b/dom/cache/DBAction.cpp
@@ -165,17 +165,17 @@ DBAction::OpenConnection(const QuotaInfo
     rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(conn));
   }
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   // Check the schema to make sure it is not too old.
   int32_t schemaVersion = 0;
   rv = conn->GetSchemaVersion(&schemaVersion);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
-  if (schemaVersion > 0 && schemaVersion < db::kMaxWipeSchemaVersion) {
+  if (schemaVersion > 0 && schemaVersion < db::kFirstShippedSchemaVersion) {
     conn = nullptr;
     rv = WipeDatabase(dbFile, aDBDir);
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
     rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(conn));
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   }
 
--- a/dom/cache/DBSchema.cpp
+++ b/dom/cache/DBSchema.cpp
@@ -9,16 +9,17 @@
 #include "ipc/IPCMessageUtils.h"
 #include "mozilla/dom/InternalHeaders.h"
 #include "mozilla/dom/cache/CacheTypes.h"
 #include "mozilla/dom/cache/SavedTypes.h"
 #include "mozilla/dom/cache/Types.h"
 #include "mozilla/dom/cache/TypeUtils.h"
 #include "mozIStorageConnection.h"
 #include "mozIStorageStatement.h"
+#include "mozStorageHelper.h"
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
 #include "nsCRT.h"
 #include "nsHttp.h"
 #include "nsICryptoHash.h"
 #include "nsNetCID.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/dom/HeadersBinding.h"
@@ -26,21 +27,145 @@
 #include "mozilla/dom/ResponseBinding.h"
 #include "nsIContentPolicy.h"
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 namespace db {
 
-const int32_t kMaxWipeSchemaVersion = 15;
+const int32_t kFirstShippedSchemaVersion = 15;
 
 namespace {
 
-const int32_t kLatestSchemaVersion = 15;
+// Update this whenever the DB schema is changed.
+const int32_t kLatestSchemaVersion = 16;
+
+// ---------
+// The following constants define the SQL schema.  These are defined in the
+// same order the SQL should be executed in CreateOrMigrateSchema().  They are
+// broken out as constants for convenient use in validation and migration.
+// ---------
+
+// The caches table is the single source of truth about what Cache
+// objects exist for the origin.  The contents of the Cache are stored
+// in the entries table that references back to caches.
+//
+// The caches table is also referenced from storage.  Rows in storage
+// represent named Cache objects.  There are cases, however, where
+// a Cache can still exist, but not be in a named Storage.  For example,
+// when content is still using the Cache after CacheStorage::Delete()
+// has been run.
+//
+// For now, the caches table mainly exists for data integrity with
+// foreign keys, but could be expanded to contain additional cache object
+// information.
+//
+// AUTOINCREMENT is necessary to prevent CacheId values from being reused.
+const char* const kTableCaches =
+  "CREATE TABLE caches ("
+    "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT "
+  ")";
+
+// Security blobs are quite large and duplicated for every Response from
+// the same https origin.  This table is used to de-duplicate this data.
+const char* const kTableSecurityInfo =
+  "CREATE TABLE security_info ("
+    "id INTEGER NOT NULL PRIMARY KEY, "
+    "hash BLOB NOT NULL, "  // first 8-bytes of the sha1 hash of data column
+    "data BLOB NOT NULL, "  // full security info data, usually a few KB
+    "refcount INTEGER NOT NULL"
+  ")";
+
+// Index the smaller hash value instead of the large security data blob.
+const char* const kIndexSecurityInfoHash =
+  "CREATE INDEX security_info_hash_index ON security_info (hash)";
+
+const char* const kTableEntries =
+  "CREATE TABLE entries ("
+    "id INTEGER NOT NULL PRIMARY KEY, "
+    "request_method TEXT NOT NULL, "
+    "request_url_no_query TEXT NOT NULL, "
+    "request_url_no_query_hash BLOB NOT NULL, " // first 8-bytes of sha1 hash
+    "request_url_query TEXT NOT NULL, "
+    "request_url_query_hash BLOB NOT NULL, "    // first 8-bytes of sha1 hash
+    "request_referrer TEXT NOT NULL, "
+    "request_headers_guard INTEGER NOT NULL, "
+    "request_mode INTEGER NOT NULL, "
+    "request_credentials INTEGER NOT NULL, "
+    "request_contentpolicytype INTEGER NOT NULL, "
+    "request_cache INTEGER NOT NULL, "
+    "request_body_id TEXT NULL, "
+    "response_type INTEGER NOT NULL, "
+    "response_url TEXT NOT NULL, "
+    "response_status INTEGER NOT NULL, "
+    "response_status_text TEXT NOT NULL, "
+    "response_headers_guard INTEGER NOT NULL, "
+    "response_body_id TEXT NULL, "
+    "response_security_info_id INTEGER NULL REFERENCES security_info(id), "
+    "response_principal_info TEXT NOT NULL, "
+    "response_redirected INTEGER NOT NULL, "
+    // Note that response_redirected_url is either going to be empty, or
+    // it's going to be a URL different than response_url.
+    "response_redirected_url TEXT NOT NULL, "
+    "cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE, "
+
+    // New columns must be added at the end of table to migrate and
+    // validate properly.
+    "request_redirect INTEGER NOT NULL"
+  ")";
+
+// Create an index to support the QueryCache() matching algorithm.  This
+// needs to quickly find entries in a given Cache that match the request
+// URL.  The url query is separated in order to support the ignoreSearch
+// option.  Finally, we index hashes of the URL values instead of the
+// actual strings to avoid excessive disk bloat.  The index will duplicate
+// the contents of the columsn in the index.  The hash index will prune
+// the vast majority of values from the query result so that normal
+// scanning only has to be done on a few values to find an exact URL match.
+const char* const kIndexEntriesRequest =
+  "CREATE INDEX entries_request_match_index "
+            "ON entries (cache_id, request_url_no_query_hash, "
+                        "request_url_query_hash)";
+
+const char* const kTableRequestHeaders =
+  "CREATE TABLE request_headers ("
+    "name TEXT NOT NULL, "
+    "value TEXT NOT NULL, "
+    "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE"
+  ")";
+
+const char* const kTableResponseHeaders =
+  "CREATE TABLE response_headers ("
+    "name TEXT NOT NULL, "
+    "value TEXT NOT NULL, "
+    "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE"
+  ")";
+
+// We need an index on response_headers, but not on request_headers,
+// because we quickly need to determine if a VARY header is present.
+const char* const kIndexResponseHeadersName =
+  "CREATE INDEX response_headers_name_index "
+            "ON response_headers (name)";
+
+// NOTE: key allows NULL below since that is how "" is represented
+//       in a BLOB column.  We use BLOB to avoid encoding issues
+//       with storing DOMStrings.
+const char* const kTableStorage =
+  "CREATE TABLE storage ("
+    "namespace INTEGER NOT NULL, "
+    "key BLOB NULL, "
+    "cache_id INTEGER NOT NULL REFERENCES caches(id), "
+    "PRIMARY KEY(namespace, key) "
+  ")";
+
+// ---------
+// End schema definition
+// ---------
+
 const int32_t kMaxEntriesPerStatement = 255;
 
 const uint32_t kPageSize = 4 * 1024;
 
 // Grow the database in chunks to reduce fragmentation
 const uint32_t kGrowthSize = 32 * 1024;
 const uint32_t kGrowthPages = kGrowthSize / kPageSize;
 static_assert(kGrowthSize % kPageSize == 0,
@@ -82,22 +207,28 @@ static_assert(int(RequestCredentials::Om
 static_assert(int(RequestCache::Default) == 0 &&
               int(RequestCache::No_store) == 1 &&
               int(RequestCache::Reload) == 2 &&
               int(RequestCache::No_cache) == 3 &&
               int(RequestCache::Force_cache) == 4 &&
               int(RequestCache::Only_if_cached) == 5 &&
               int(RequestCache::EndGuard_) == 6,
               "RequestCache values are as expected");
+static_assert(int(RequestRedirect::Follow) == 0 &&
+              int(RequestRedirect::Error) == 1 &&
+              int(RequestRedirect::Manual) == 2 &&
+              int(RequestRedirect::EndGuard_) == 3,
+              "RequestRedirect values are as expected");
 static_assert(int(ResponseType::Basic) == 0 &&
               int(ResponseType::Cors) == 1 &&
               int(ResponseType::Default) == 2 &&
               int(ResponseType::Error) == 3 &&
               int(ResponseType::Opaque) == 4 &&
-              int(ResponseType::EndGuard_) == 5,
+              int(ResponseType::Opaqueredirect) == 5 &&
+              int(ResponseType::EndGuard_) == 6,
               "ResponseType values are as expected");
 
 // If the static_asserts below fails, it means that you have changed the
 // Namespace enum in a way that may be incompatible with the existing data
 // stored in the DOM Cache.  You would need to update the Cache database schema
 // accordingly and adjust the failing static_assert.
 static_assert(DEFAULT_NAMESPACE == 0 &&
               CHROME_ONLY_NAMESPACE == 1 &&
@@ -201,169 +332,100 @@ static nsresult BindId(mozIStorageStatem
 static nsresult ExtractId(mozIStorageStatement* aState, uint32_t aPos,
                           nsID* aIdOut);
 static nsresult CreateAndBindKeyStatement(mozIStorageConnection* aConn,
                                           const char* aQueryFormat,
                                           const nsAString& aKey,
                                           mozIStorageStatement** aStateOut);
 static nsresult HashCString(nsICryptoHash* aCrypto, const nsACString& aIn,
                             nsACString& aOut);
+nsresult Validate(mozIStorageConnection* aConn);
+nsresult Migrate(mozIStorageConnection* aConn);
 } // namespace
 
 nsresult
-CreateSchema(mozIStorageConnection* aConn)
+CreateOrMigrateSchema(mozIStorageConnection* aConn)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aConn);
 
   int32_t schemaVersion;
   nsresult rv = aConn->GetSchemaVersion(&schemaVersion);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   if (schemaVersion == kLatestSchemaVersion) {
-    // We already have the correct schema, so just get started.
+    // We already have the correct schema version.  Validate it matches
+    // our expected schema and then proceed.
+    rv = Validate(aConn);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
     return rv;
   }
 
-  if (!schemaVersion) {
-    // The caches table is the single source of truth about what Cache
-    // objects exist for the origin.  The contents of the Cache are stored
-    // in the entries table that references back to caches.
-    //
-    // The caches table is also referenced from storage.  Rows in storage
-    // represent named Cache objects.  There are cases, however, where
-    // a Cache can still exist, but not be in a named Storage.  For example,
-    // when content is still using the Cache after CacheStorage::Delete()
-    // has been run.
-    //
-    // For now, the caches table mainly exists for data integrity with
-    // foreign keys, but could be expanded to contain additional cache object
-    // information.
-    //
-    // AUTOINCREMENT is necessary to prevent CacheId values from being reused.
-    rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-      "CREATE TABLE caches ("
-        "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT "
-      ");"
-    ));
+  mozStorageTransaction trans(aConn, false,
+                              mozIStorageConnection::TRANSACTION_IMMEDIATE);
+  bool needVacuum = false;
+
+  if (schemaVersion) {
+    // A schema exists, but its not the current version.  Attempt to
+    // migrate it to our new schema.
+    rv = Migrate(aConn);
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-    // Security blobs are quite large and duplicated for every Response from
-    // the same https origin.  This table is used to de-duplicate this data.
-    rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-      "CREATE TABLE security_info ("
-        "id INTEGER NOT NULL PRIMARY KEY, "
-        "hash BLOB NOT NULL, "  // first 8-bytes of the sha1 hash of data column
-        "data BLOB NOT NULL, "  // full security info data, usually a few KB
-        "refcount INTEGER NOT NULL"
-      ");"
-    ));
-    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
-
-    // Index the smaller hash value instead of the large security data blob.
-    rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-      "CREATE INDEX security_info_hash_index ON security_info (hash);"
-    ));
+    // Migrations happen infrequently and reflect a chance in DB structure.
+    // This is a good time to rebuild the database.  It also helps catch
+    // if a new migration is incorrect by fast failing on the corruption.
+    needVacuum = true;
+
+  } else {
+    // There is no schema installed.  Create the database from scratch.
+    rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableCaches));
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-    rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-      "CREATE TABLE entries ("
-        "id INTEGER NOT NULL PRIMARY KEY, "
-        "request_method TEXT NOT NULL, "
-        "request_url_no_query TEXT NOT NULL, "
-        "request_url_no_query_hash BLOB NOT NULL, " // first 8-bytes of sha1 hash
-        "request_url_query TEXT NOT NULL, "
-        "request_url_query_hash BLOB NOT NULL, "    // first 8-bytes of sha1 hash
-        "request_referrer TEXT NOT NULL, "
-        "request_headers_guard INTEGER NOT NULL, "
-        "request_mode INTEGER NOT NULL, "
-        "request_credentials INTEGER NOT NULL, "
-        "request_contentpolicytype INTEGER NOT NULL, "
-        "request_cache INTEGER NOT NULL, "
-        "request_body_id TEXT NULL, "
-        "response_type INTEGER NOT NULL, "
-        "response_url TEXT NOT NULL, "
-        "response_status INTEGER NOT NULL, "
-        "response_status_text TEXT NOT NULL, "
-        "response_headers_guard INTEGER NOT NULL, "
-        "response_body_id TEXT NULL, "
-        "response_security_info_id INTEGER NULL REFERENCES security_info(id), "
-        "response_principal_info TEXT NOT NULL, "
-        "response_redirected INTEGER NOT NULL, "
-        // Note that response_redirected_url is either going to be empty, or
-        // it's going to be a URL different than response_url.
-        "response_redirected_url TEXT NOT NULL, "
-        "cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE"
-      ");"
-    ));
+    rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableSecurityInfo));
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = aConn->ExecuteSimpleSQL(nsDependentCString(kIndexSecurityInfoHash));
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableEntries));
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = aConn->ExecuteSimpleSQL(nsDependentCString(kIndexEntriesRequest));
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-    // Create an index to support the QueryCache() matching algorithm.  This
-    // needs to quickly find entries in a given Cache that match the request
-    // URL.  The url query is separated in order to support the ignoreSearch
-    // option.  Finally, we index hashes of the URL values instead of the
-    // actual strings to avoid excessive disk bloat.  The index will duplicate
-    // the contents of the columsn in the index.  The hash index will prune
-    // the vast majority of values from the query result so that normal
-    // scanning only has to be done on a few values to find an exact URL match.
-    rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-      "CREATE INDEX entries_request_match_index "
-                "ON entries (cache_id, request_url_no_query_hash, "
-                            "request_url_query_hash);"
-    ));
+    rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableRequestHeaders));
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-    rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-      "CREATE TABLE request_headers ("
-        "name TEXT NOT NULL, "
-        "value TEXT NOT NULL, "
-        "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE"
-      ");"
-    ));
+    rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableResponseHeaders));
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-    rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-      "CREATE TABLE response_headers ("
-        "name TEXT NOT NULL, "
-        "value TEXT NOT NULL, "
-        "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE"
-      ");"
-    ));
+    rv = aConn->ExecuteSimpleSQL(nsDependentCString(kIndexResponseHeadersName));
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-    // We need an index on response_headers, but not on request_headers,
-    // because we quickly need to determine if a VARY header is present.
-    rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-      "CREATE INDEX response_headers_name_index "
-                "ON response_headers (name);"
-    ));
-    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
-
-    // NOTE: key allows NULL below since that is how "" is represented
-    //       in a BLOB column.  We use BLOB to avoid encoding issues
-    //       with storing DOMStrings.
-    rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-      "CREATE TABLE storage ("
-        "namespace INTEGER NOT NULL, "
-        "key BLOB NULL, "
-        "cache_id INTEGER NOT NULL REFERENCES caches(id), "
-        "PRIMARY KEY(namespace, key) "
-      ");"
-    ));
+    rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableStorage));
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
     rv = aConn->SetSchemaVersion(kLatestSchemaVersion);
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
     rv = aConn->GetSchemaVersion(&schemaVersion);
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   }
 
-  if (schemaVersion != kLatestSchemaVersion) {
-    return NS_ERROR_FAILURE;
+  rv = Validate(aConn);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = trans.Commit();
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  if (needVacuum) {
+    // Unfortunately, this must be performed outside of the transaction.
+    aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("VACUUM"));
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   }
 
   return rv;
 }
 
 nsresult
 InitializeConnection(mozIStorageConnection* aConn)
 {
@@ -1525,16 +1587,17 @@ InsertEntry(mozIStorageConnection* aConn
       "request_url_query, "
       "request_url_query_hash, "
       "request_referrer, "
       "request_headers_guard, "
       "request_mode, "
       "request_credentials, "
       "request_contentpolicytype, "
       "request_cache, "
+      "request_redirect, "
       "request_body_id, "
       "response_type, "
       "response_url, "
       "response_status, "
       "response_status_text, "
       "response_headers_guard, "
       "response_body_id, "
       "response_security_info_id, "
@@ -1549,16 +1612,17 @@ InsertEntry(mozIStorageConnection* aConn
       ":request_url_query, "
       ":request_url_query_hash, "
       ":request_referrer, "
       ":request_headers_guard, "
       ":request_mode, "
       ":request_credentials, "
       ":request_contentpolicytype, "
       ":request_cache, "
+      ":request_redirect, "
       ":request_body_id, "
       ":response_type, "
       ":response_url, "
       ":response_status, "
       ":response_status_text, "
       ":response_headers_guard, "
       ":response_body_id, "
       ":response_security_info_id, "
@@ -1617,16 +1681,19 @@ InsertEntry(mozIStorageConnection* aConn
   rv = state->BindInt32ByName(NS_LITERAL_CSTRING("request_contentpolicytype"),
     static_cast<int32_t>(aRequest.contentPolicyType()));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = state->BindInt32ByName(NS_LITERAL_CSTRING("request_cache"),
     static_cast<int32_t>(aRequest.requestCache()));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
+  rv = state->BindInt32ByName(NS_LITERAL_CSTRING("request_redirect"),
+    static_cast<int32_t>(aRequest.requestRedirect()));
+
   rv = BindId(state, NS_LITERAL_CSTRING("request_body_id"), aRequestBodyId);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = state->BindInt32ByName(NS_LITERAL_CSTRING("response_type"),
                               static_cast<int32_t>(aResponse.type()));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = state->BindUTF8StringByName(NS_LITERAL_CSTRING("response_url"),
@@ -1896,16 +1963,17 @@ ReadRequest(mozIStorageConnection* aConn
       "request_url_no_query, "
       "request_url_query, "
       "request_referrer, "
       "request_headers_guard, "
       "request_mode, "
       "request_credentials, "
       "request_contentpolicytype, "
       "request_cache, "
+      "request_redirect, "
       "request_body_id "
     "FROM entries "
     "WHERE id=:id;"
   ), getter_AddRefs(state));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = state->BindInt32ByName(NS_LITERAL_CSTRING("id"), aEntryId);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
@@ -1950,23 +2018,29 @@ ReadRequest(mozIStorageConnection* aConn
     static_cast<nsContentPolicyType>(requestContentPolicyType);
 
   int32_t requestCache;
   rv = state->GetInt32(8, &requestCache);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   aSavedRequestOut->mValue.requestCache() =
     static_cast<RequestCache>(requestCache);
 
+  int32_t requestRedirect;
+  rv = state->GetInt32(9, &requestRedirect);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  aSavedRequestOut->mValue.requestRedirect() =
+    static_cast<RequestRedirect>(requestRedirect);
+
   bool nullBody = false;
-  rv = state->GetIsNull(9, &nullBody);
+  rv = state->GetIsNull(10, &nullBody);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   aSavedRequestOut->mHasBodyId = !nullBody;
 
   if (aSavedRequestOut->mHasBodyId) {
-    rv = ExtractId(state, 9, &aSavedRequestOut->mBodyId);
+    rv = ExtractId(state, 10, &aSavedRequestOut->mBodyId);
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   }
 
   rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
     "SELECT "
       "name, "
       "value "
     "FROM request_headers "
@@ -2180,12 +2254,231 @@ IncrementalVacuum(mozIStorageConnection*
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   MOZ_ASSERT(freePages <= kMaxFreePages);
 #endif
 
   return NS_OK;
 }
 
+namespace {
+
+#ifdef DEBUG
+struct Expect
+{
+  // Expect exact SQL
+  Expect(const char* aName, const char* aType, const char* aSql)
+    : mName(aName)
+    , mType(aType)
+    , mSql(aSql)
+    , mIgnoreSql(false)
+  { }
+
+  // Ignore SQL
+  Expect(const char* aName, const char* aType)
+    : mName(aName)
+    , mType(aType)
+    , mIgnoreSql(true)
+  { }
+
+  const nsCString mName;
+  const nsCString mType;
+  const nsCString mSql;
+  const bool mIgnoreSql;
+};
+#endif
+
+nsresult
+Validate(mozIStorageConnection* aConn)
+{
+  int32_t schemaVersion;
+  nsresult rv = aConn->GetSchemaVersion(&schemaVersion);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  if (NS_WARN_IF(schemaVersion != kLatestSchemaVersion)) {
+    return NS_ERROR_FAILURE;
+  }
+
+#ifdef DEBUG
+  // This is the schema we expect the database at the latest version to
+  // contain.  Update this list if you add a new table or index.
+  Expect expect[] = {
+    Expect("caches", "table", kTableCaches),
+    Expect("sqlite_sequence", "table"), // auto-gen by sqlite
+    Expect("security_info", "table", kTableSecurityInfo),
+    Expect("security_info_hash_index", "index", kIndexSecurityInfoHash),
+    Expect("entries", "table", kTableEntries),
+    Expect("entries_request_match_index", "index", kIndexEntriesRequest),
+    Expect("request_headers", "table", kTableRequestHeaders),
+    Expect("response_headers", "table", kTableResponseHeaders),
+    Expect("response_headers_name_index", "index", kIndexResponseHeadersName),
+    Expect("storage", "table", kTableStorage),
+    Expect("sqlite_autoindex_storage_1", "index"), // auto-gen by sqlite
+  };
+  const uint32_t expectLength = sizeof(expect) / sizeof(Expect);
+
+  // Read the schema from the sqlite_master table and compare.
+  nsCOMPtr<mozIStorageStatement> state;
+  rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT name, type, sql FROM sqlite_master;"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  bool hasMoreData = false;
+  while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) {
+    nsAutoCString name;
+    rv = state->GetUTF8String(0, name);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    nsAutoCString type;
+    rv = state->GetUTF8String(1, type);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    nsAutoCString sql;
+    rv = state->GetUTF8String(2, sql);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    bool foundMatch = false;
+    for (uint32_t i = 0; i < expectLength; ++i) {
+      if (name == expect[i].mName) {
+        if (type != expect[i].mType) {
+          NS_WARNING(nsPrintfCString("Unexpected type for Cache schema entry %s",
+                     name.get()).get());
+          return NS_ERROR_FAILURE;
+        }
+
+        if (!expect[i].mIgnoreSql && sql != expect[i].mSql) {
+          NS_WARNING(nsPrintfCString("Unexpected SQL for Cache schema entry %s",
+                     name.get()).get());
+          return NS_ERROR_FAILURE;
+        }
+
+        foundMatch = true;
+        break;
+      }
+    }
+
+    if (NS_WARN_IF(!foundMatch)) {
+      NS_WARNING(nsPrintfCString("Unexpected schema entry %s in Cache database",
+                 name.get()).get());
+      return NS_ERROR_FAILURE;
+    }
+  }
+#endif
+
+  return rv;
+}
+
+// -----
+// Schema migration code
+// -----
+
+typedef nsresult (*MigrationFunc)(mozIStorageConnection*);
+struct Migration
+{
+  Migration(int32_t aFromVersion, MigrationFunc aFunc)
+    : mFromVersion(aFromVersion)
+    , mFunc(aFunc)
+  { }
+  int32_t mFromVersion;
+  MigrationFunc mFunc;
+};
+
+// Declare migration functions here.  Each function should upgrade
+// the version by a single increment.  Don't skip versions.
+nsresult MigrateFrom15To16(mozIStorageConnection* aConn);
+
+// Configure migration functions to run for the given starting version.
+Migration sMigrationList[] = {
+  Migration(15, MigrateFrom15To16),
+};
+
+uint32_t sMigrationListLength = sizeof(sMigrationList) / sizeof(Migration);
+
+nsresult
+Migrate(mozIStorageConnection* aConn)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+
+  int32_t currentVersion = 0;
+  nsresult rv = aConn->GetSchemaVersion(&currentVersion);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  while (currentVersion < kLatestSchemaVersion) {
+    // Wiping old databases is handled in DBAction because it requires
+    // making a whole new mozIStorageConnection.  Make sure we don't
+    // accidentally get here for one of those old databases.
+    MOZ_ASSERT(currentVersion >= kFirstShippedSchemaVersion);
+
+    for (uint32_t i = 0; i < sMigrationListLength; ++i) {
+      if (sMigrationList[i].mFromVersion == currentVersion) {
+        rv = sMigrationList[i].mFunc(aConn);
+        if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+        break;
+      }
+    }
+
+    DebugOnly<int32_t> lastVersion = currentVersion;
+    rv = aConn->GetSchemaVersion(&currentVersion);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+    MOZ_ASSERT(currentVersion > lastVersion);
+  }
+
+  MOZ_ASSERT(currentVersion == kLatestSchemaVersion);
+
+  return rv;
+}
+
+nsresult MigrateFrom15To16(mozIStorageConnection* aConn)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+
+  // Add the request_redirect column with a default value of "follow".  Note,
+  // we only use a default value here because its required by ALTER TABLE and
+  // we need to apply the default "follow" to existing records in the table.
+  // We don't actually want to keep the default in the schema for future
+  // INSERTs.
+  nsresult rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "ALTER TABLE entries "
+    "ADD COLUMN request_redirect INTEGER NOT NULL DEFAULT 0"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  // Now overwrite the master SQL for the entries table to remove the column
+  // default value.  This is also necessary for our Validate() method to
+  // pass on this database.
+  rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "PRAGMA writable_schema = ON"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  nsCOMPtr<mozIStorageStatement> state;
+  rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "UPDATE sqlite_master SET sql=:sql WHERE name='entries'"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindUTF8StringByName(NS_LITERAL_CSTRING("sql"),
+                                   nsDependentCString(kTableEntries));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = aConn->SetSchemaVersion(16);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "PRAGMA writable_schema = OFF"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  return rv;
+}
+
+} // anonymous namespace
+
 } // namespace db
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
--- a/dom/cache/DBSchema.h
+++ b/dom/cache/DBSchema.h
@@ -24,18 +24,19 @@ class CacheQueryParams;
 class CacheRequest;
 class CacheRequestOrVoid;
 class CacheResponse;
 struct SavedRequest;
 struct SavedResponse;
 
 namespace db {
 
+// Note, this cannot be executed within a transaction.
 nsresult
-CreateSchema(mozIStorageConnection* aConn);
+CreateOrMigrateSchema(mozIStorageConnection* aConn);
 
 // Note, this cannot be executed within a transaction.
 nsresult
 InitializeConnection(mozIStorageConnection* aConn);
 
 nsresult
 CreateCacheId(mozIStorageConnection* aConn, CacheId* aCacheIdOut);
 
@@ -111,17 +112,18 @@ StorageForgetCache(mozIStorageConnection
 nsresult
 StorageGetKeys(mozIStorageConnection* aConn, Namespace aNamespace,
                nsTArray<nsString>& aKeysOut);
 
 // Note, this works best when its NOT executed within a transaction.
 nsresult
 IncrementalVacuum(mozIStorageConnection* aConn);
 
-// We will wipe out databases with a schema versions less than this.
-extern const int32_t kMaxWipeSchemaVersion;
+// We will wipe out databases with a schema versions less than this.  Newer
+// versions will be migrated on open to the latest schema version.
+extern const int32_t kFirstShippedSchemaVersion;
 
 } // namespace db
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_cache_DBSchema_h
--- a/dom/cache/IPCUtils.h
+++ b/dom/cache/IPCUtils.h
@@ -34,16 +34,21 @@ namespace IPC {
                                     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,
--- a/dom/cache/Manager.cpp
+++ b/dom/cache/Manager.cpp
@@ -49,26 +49,19 @@ public:
 
   virtual nsresult
   RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
                         mozIStorageConnection* aConn) override
   {
     nsresult rv = BodyCreateDir(aDBDir);
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-    {
-      mozStorageTransaction trans(aConn, false,
-                                  mozIStorageConnection::TRANSACTION_IMMEDIATE);
-
-      rv = db::CreateSchema(aConn);
-      if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
-
-      rv = trans.Commit();
-      if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
-    }
+    // executes in its own transaction
+    rv = db::CreateOrMigrateSchema(aConn);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
     // If the Context marker file exists, then the last session was
     // not cleanly shutdown.  In these cases sqlite will ensure that
     // the database is valid, but we might still orphan data.  Both
     // Cache objects and body files can be referenced by DOM objects
     // after they are "removed" from their parent.  So we need to
     // look and see if any of these late access objects have been
     // orphaned.
@@ -395,17 +388,17 @@ private:
 
     MOZ_ASSERT(!sFactory->mManagerList.IsEmpty());
 
     {
       ManagerList::ForwardIterator iter(sFactory->mManagerList);
       while (iter.HasMore()) {
         nsRefPtr<Manager> manager = iter.GetNext();
         if (aOrigin.IsVoid() ||
-            manager->mManagerId->ExtendedOrigin() == aOrigin) {
+            manager->mManagerId->QuotaOrigin() == aOrigin) {
           manager->Abort();
         }
       }
     }
   }
 
   static void
   ShutdownAllOnBackgroundThread()
--- a/dom/cache/ManagerId.cpp
+++ b/dom/cache/ManagerId.cpp
@@ -16,53 +16,42 @@ namespace cache {
 
 // static
 nsresult
 ManagerId::Create(nsIPrincipal* aPrincipal, ManagerId** aManagerIdOut)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // The QuotaManager::GetInfoFromPrincipal() has special logic for system
-  // and about: principals.  We currently don't need the system principal logic
-  // because ManagerId only uses the origin for in memory comparisons.  We
-  // also don't do any special logic to host the same Cache for different about:
-  // pages, so we don't need those checks either.
-  //
-  // But, if we get the same QuotaManager directory for different about:
-  // origins, we probably only want one Manager instance.  So, we might
-  // want to start using the QM's concept of origin uniqueness here.
-  //
-  // TODO: consider using QuotaManager's modified origin here (bug 1112071)
-
-  nsCString origin;
-  nsresult rv = aPrincipal->GetOriginNoSuffix(origin);
+  // and about: principals.  We need to use the same modified origin in
+  // order to interpret calls from QM correctly.
+  nsCString quotaOrigin;
+  nsresult rv = QuotaManager::GetInfoFromPrincipal(aPrincipal,
+                                                   nullptr,   //group
+                                                   &quotaOrigin,
+                                                   nullptr);  // is app
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  nsCString jarPrefix;
-  rv = aPrincipal->GetJarPrefix(jarPrefix);
-  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
-
-  nsRefPtr<ManagerId> ref = new ManagerId(aPrincipal, origin, jarPrefix);
+  nsRefPtr<ManagerId> ref = new ManagerId(aPrincipal, quotaOrigin);
   ref.forget(aManagerIdOut);
 
   return NS_OK;
 }
 
 already_AddRefed<nsIPrincipal>
 ManagerId::Principal() const
 {
   MOZ_ASSERT(NS_IsMainThread());
   nsCOMPtr<nsIPrincipal> ref = mPrincipal;
   return ref.forget();
 }
 
-ManagerId::ManagerId(nsIPrincipal* aPrincipal, const nsACString& aOrigin,
-                     const nsACString& aJarPrefix)
+ManagerId::ManagerId(nsIPrincipal* aPrincipal, const nsACString& aQuotaOrigin)
     : mPrincipal(aPrincipal)
-    , mExtendedOrigin(aJarPrefix + aOrigin)
+    , mQuotaOrigin(aQuotaOrigin)
 {
   MOZ_ASSERT(mPrincipal);
 }
 
 ManagerId::~ManagerId()
 {
   // If we're already on the main thread, then default destruction is fine
   if (NS_IsMainThread()) {
--- a/dom/cache/ManagerId.h
+++ b/dom/cache/ManagerId.h
@@ -24,36 +24,35 @@ class ManagerId final
 {
 public:
   // Main thread only
   static nsresult Create(nsIPrincipal* aPrincipal, ManagerId** aManagerIdOut);
 
   // Main thread only
   already_AddRefed<nsIPrincipal> Principal() const;
 
-  const nsACString& ExtendedOrigin() const { return mExtendedOrigin; }
+  const nsACString& QuotaOrigin() const { return mQuotaOrigin; }
 
   bool operator==(const ManagerId& aOther) const
   {
-    return mExtendedOrigin == aOther.mExtendedOrigin;
+    return mQuotaOrigin == aOther.mQuotaOrigin;
   }
 
 private:
-  ManagerId(nsIPrincipal* aPrincipal, const nsACString& aOrigin,
-            const nsACString& aJarPrefix);
+  ManagerId(nsIPrincipal* aPrincipal, const nsACString& aOrigin);
   ~ManagerId();
 
   ManagerId(const ManagerId&) = delete;
   ManagerId& operator=(const ManagerId&) = delete;
 
   // only accessible on main thread
   nsCOMPtr<nsIPrincipal> mPrincipal;
 
   // immutable to allow threadsfe access
-  const nsCString mExtendedOrigin;
+  const nsCString mQuotaOrigin;
 
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::cache::ManagerId)
 };
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
--- a/dom/cache/TypeUtils.cpp
+++ b/dom/cache/TypeUtils.cpp
@@ -174,16 +174,17 @@ TypeUtils::ToCacheRequest(CacheRequest& 
   nsRefPtr<InternalHeaders> headers = aIn->Headers();
   MOZ_ASSERT(headers);
   ToHeadersEntryList(aOut.headers(), headers);
   aOut.headersGuard() = headers->Guard();
   aOut.mode() = aIn->Mode();
   aOut.credentials() = aIn->GetCredentialsMode();
   aOut.contentPolicyType() = aIn->ContentPolicyType();
   aOut.requestCache() = aIn->GetCacheMode();
+  aOut.requestRedirect() = aIn->GetRedirectMode();
 
   if (aBodyAction == IgnoreBody) {
     aOut.body() = void_t();
     return;
   }
 
   // BodyUsed flag is checked and set previously in ToInternalRequest()
 
@@ -207,18 +208,18 @@ TypeUtils::ToCacheResponseWithoutBody(Ca
     // Pass all Response URL schemes through... The spec only requires we take
     // action on invalid schemes for Request objects.
     ProcessURL(aOut.url(), nullptr, nullptr, nullptr, aRv);
     if (aRv.Failed()) {
       return;
     }
   }
 
-  aOut.status() = aIn.GetStatus();
-  aOut.statusText() = aIn.GetStatusText();
+  aOut.status() = aIn.GetUnfilteredStatus();
+  aOut.statusText() = aIn.GetUnfilteredStatusText();
   nsRefPtr<InternalHeaders> headers = aIn.UnfilteredHeaders();
   MOZ_ASSERT(headers);
   if (HasVaryStar(headers)) {
     aRv.ThrowTypeError(MSG_RESPONSE_HAS_VARY_STAR);
     return;
   }
   ToHeadersEntryList(aOut.headers(), headers);
   aOut.headersGuard() = headers->Guard();
@@ -240,17 +241,17 @@ TypeUtils::ToCacheResponse(CacheResponse
 
   nsRefPtr<InternalResponse> ir = aIn.GetInternalResponse();
   ToCacheResponseWithoutBody(aOut, *ir, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   nsCOMPtr<nsIInputStream> stream;
-  ir->GetInternalBody(getter_AddRefs(stream));
+  ir->GetUnfilteredBody(getter_AddRefs(stream));
   if (stream) {
     aIn.SetBodyUsed();
   }
 
   SerializeCacheStream(stream, &aOut.body(), aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
@@ -299,26 +300,29 @@ TypeUtils::ToResponse(const CacheRespons
     ir->SetPrincipalInfo(Move(info));
   }
 
   nsCOMPtr<nsIInputStream> stream = ReadStream::Create(aIn.body());
   ir->SetBody(stream);
 
   switch (aIn.type())
   {
+    case ResponseType::Basic:
+      ir = ir->BasicResponse();
+      break;
+    case ResponseType::Cors:
+      ir = ir->CORSResponse();
+      break;
     case ResponseType::Default:
       break;
     case ResponseType::Opaque:
       ir = ir->OpaqueResponse();
       break;
-    case ResponseType::Basic:
-      ir = ir->BasicResponse();
-      break;
-    case ResponseType::Cors:
-      ir = ir->CORSResponse();
+    case ResponseType::Opaqueredirect:
+      ir = ir->OpaqueRedirectResponse();
       break;
     default:
       MOZ_CRASH("Unexpected ResponseType!");
   }
   MOZ_ASSERT(ir);
 
   nsRefPtr<Response> ref = new Response(GetGlobalObject(), ir);
   return ref.forget();
@@ -335,16 +339,17 @@ TypeUtils::ToInternalRequest(const Cache
   url.Append(aIn.urlQuery());
   internalRequest->SetURL(url);
 
   internalRequest->SetReferrer(aIn.referrer());
   internalRequest->SetMode(aIn.mode());
   internalRequest->SetCredentialsMode(aIn.credentials());
   internalRequest->SetContentPolicyType(aIn.contentPolicyType());
   internalRequest->SetCacheMode(aIn.requestCache());
+  internalRequest->SetRedirectMode(aIn.requestRedirect());
 
   nsRefPtr<InternalHeaders> internalHeaders =
     ToInternalHeaders(aIn.headers(), aIn.headersGuard());
   ErrorResult result;
   internalRequest->Headers()->SetGuard(aIn.headersGuard(), result);
   MOZ_ASSERT(!result.Failed());
   internalRequest->Headers()->Fill(*internalHeaders, result);
   MOZ_ASSERT(!result.Failed());
--- a/dom/cache/moz.build
+++ b/dom/cache/moz.build
@@ -95,8 +95,12 @@ MOCHITEST_MANIFESTS += [
 
 MOCHITEST_CHROME_MANIFESTS += [
     'test/mochitest/chrome.ini',
 ]
 
 BROWSER_CHROME_MANIFESTS += [
     'test/mochitest/browser.ini',
 ]
+
+XPCSHELL_TESTS_MANIFESTS += [
+    'test/xpcshell/xpcshell.ini',
+]
new file mode 100644
--- /dev/null
+++ b/dom/cache/test/xpcshell/head.js
@@ -0,0 +1,77 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * All images in schema_15_profile.zip are from https://github.com/mdn/sw-test/
+ * and are CC licensed by https://www.flickr.com/photos/legofenris/.
+ */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+// services required be initialized in order to run CacheStorage
+var ss = Cc['@mozilla.org/storage/service;1']
+         .createInstance(Ci.mozIStorageService);
+var sts = Cc['@mozilla.org/network/stream-transport-service;1']
+          .getService(Ci.nsIStreamTransportService);
+var hash = Cc['@mozilla.org/security/hash;1']
+           .createInstance(Ci.nsICryptoHash);
+
+// Expose Cache and Fetch symbols on the global
+Cu.importGlobalProperties(['caches', 'fetch']);
+
+// Extract a zip file into the profile
+function create_test_profile(zipFileName) {
+  do_get_profile();
+
+  var directoryService = Cc['@mozilla.org/file/directory_service;1']
+                         .getService(Ci.nsIProperties);
+  var profileDir = directoryService.get('ProfD', Ci.nsIFile);
+  var currentDir = directoryService.get('CurWorkD', Ci.nsIFile);
+
+  var packageFile = currentDir.clone();
+  packageFile.append(zipFileName);
+
+  var zipReader = Cc['@mozilla.org/libjar/zip-reader;1']
+                  .createInstance(Ci.nsIZipReader);
+  zipReader.open(packageFile);
+
+  var entryNames = [];
+  var entries = zipReader.findEntries(null);
+  while (entries.hasMore()) {
+    var entry = entries.getNext();
+    entryNames.push(entry);
+  }
+  entryNames.sort();
+
+  for (var entryName of entryNames) {
+    var zipentry = zipReader.getEntry(entryName);
+
+    var file = profileDir.clone();
+    entryName.split('/').forEach(function(part) {
+      file.append(part);
+    });
+
+    if (zipentry.isDirectory) {
+      file.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0755', 8));
+    } else {
+      var istream = zipReader.getInputStream(entryName);
+
+      var ostream = Cc['@mozilla.org/network/file-output-stream;1']
+                    .createInstance(Ci.nsIFileOutputStream);
+      ostream.init(file, -1, parseInt('0644', 8), 0);
+
+      var bostream = Cc['@mozilla.org/network/buffered-output-stream;1']
+                     .createInstance(Ci.nsIBufferedOutputStream);
+      bostream.init(ostream, 32 * 1024);
+
+      bostream.writeFrom(istream, istream.available());
+
+      istream.close();
+      bostream.close();
+    }
+  }
+
+  zipReader.close();
+}
new file mode 100644
--- /dev/null
+++ b/dom/cache/test/xpcshell/make_profile.js
@@ -0,0 +1,142 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * All images in schema_15_profile.zip are from https://github.com/mdn/sw-test/
+ * and are CC licensed by https://www.flickr.com/photos/legofenris/.
+ */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+// Enumerate the directory tree and store results in entryList as
+//
+//  { path: 'a/b/c', file: <nsIFile> }
+//
+// The algorithm starts with the first entry already in entryList.
+function enumerate_tree(entryList) {
+  for (var index = 0; index < entryList.length; ++index) {
+    var path = entryList[index].path;
+    var file = entryList[index].file;
+
+    if (file.isDirectory()) {
+      var dirList = file.directoryEntries;
+      while (dirList.hasMoreElements()) {
+        var dirFile = dirList.getNext().QueryInterface(Ci.nsIFile);
+        entryList.push({ path: path + '/' + dirFile.leafName, file: dirFile });
+      }
+    }
+  }
+}
+
+function zip_profile(zipFile, profileDir) {
+  var zipWriter = Cc['@mozilla.org/zipwriter;1']
+                  .createInstance(Ci.nsIZipWriter);
+  zipWriter.open(zipFile, 0x04 | 0x08 | 0x20);
+
+  var root = profileDir.clone();
+  root.append('storage');
+  root.append('default');
+  root.append('chrome');
+
+  var entryList = [{path: 'storage/default/chrome', file: root}];
+  enumerate_tree(entryList);
+
+  entryList.forEach(function(entry) {
+    if (entry.file.isDirectory()) {
+      zipWriter.addEntryDirectory(entry.path, entry.file.lastModifiedTime,
+                                  false);
+    } else {
+      var istream = Cc['@mozilla.org/network/file-input-stream;1']
+                    .createInstance(Ci.nsIFileInputStream);
+      istream.init(entry.file, -1, -1, 0);
+      zipWriter.addEntryStream(entry.path, entry.file.lastModifiedTime,
+                               Ci.nsIZipWriter.COMPRESSION_DEFAULT, istream,
+                               false);
+      istream.close();
+    }
+  });
+
+  zipWriter.close();
+}
+
+function exactGC() {
+  return new Promise(function(resolve) {
+    var count = 0;
+    function doPreciseGCandCC() {
+      function scheduleGCCallback() {
+        Cu.forceCC();
+
+        if (++count < 2) {
+          doPreciseGCandCC();
+        } else {
+          resolve();
+        }
+      }
+      Cu.schedulePreciseGC(scheduleGCCallback);
+    }
+    doPreciseGCandCC();
+  });
+}
+
+function resetQuotaManager() {
+  return new Promise(function(resolve) {
+    var qm = Cc['@mozilla.org/dom/quota/manager;1']
+             .getService(Ci.nsIQuotaManager);
+
+    var prefService = Cc['@mozilla.org/preferences-service;1']
+                      .getService(Ci.nsIPrefService);
+
+    // enable quota manager testing mode
+    var pref = 'dom.quotaManager.testing';
+    prefService.getBranch(null).setBoolPref(pref, true);
+
+    qm.reset();
+
+    // disable quota manager testing mode
+    //prefService.getBranch(null).setBoolPref(pref, false);
+
+    var uri = Cc['@mozilla.org/network/io-service;1']
+              .getService(Ci.nsIIOService)
+              .newURI('http://example.com', null, null);
+    var principal = Cc['@mozilla.org/scriptsecuritymanager;1']
+                    .getService(Ci.nsIScriptSecurityManager)
+                    .getSystemPrincipal();
+
+    // use getUsageForPrincipal() to get a callback when the reset() is done
+    qm.getUsageForPrincipal(principal, function(principal, usage, fileUsage) {
+      resolve(usage);
+    });
+  });
+}
+
+function run_test() {
+  do_test_pending();
+  do_get_profile();
+
+  var directoryService = Cc['@mozilla.org/file/directory_service;1']
+                         .getService(Ci.nsIProperties);
+  var profileDir = directoryService.get('ProfD', Ci.nsIFile);
+  var currentDir = directoryService.get('CurWorkD', Ci.nsIFile);
+
+  var zipFile = currentDir.clone();
+  zipFile.append('new_profile.zip');
+  if (zipFile.exists()) {
+    zipFile.remove(false);
+  }
+  ok(!zipFile.exists());
+
+  caches.open('xpcshell-test').then(function(c) {
+    var request = new Request('http://example.com/index.html');
+    var response = new Response('hello world');
+    return c.put(request, response);
+  }).then(exactGC).then(resetQuotaManager).then(function() {
+    zip_profile(zipFile, profileDir);
+    dump('### ### created zip at: ' + zipFile.path + '\n');
+    do_test_finished();
+  }).catch(function(e) {
+    do_test_finished();
+    ok(false, e);
+  });
+}
new file mode 100644
index 0000000000000000000000000000000000000000..6d742275b30921a721dddbcbbd12439d3081bdce
GIT binary patch
literal 2577
zc$}S;3p7-D9LKMfw0f{-YlSA9Xh=+AW<<2vXiGF|)Fwt_9*jq$d3ZD)Nh2-2ydstI
z7-W!))Yc5UEn;g#8Hq7!5*f@PrqGzx^jb8j{oQl#J^yp=@ALcK|NY(H7vZElXE6ZG
z1AwfJ=6%3_LKFBNAXA7Wv=0t~#o^J^015=-OCkp1AZ|z%K<S4!O8~_C?0NER`!RCn
zPm`IT$x}~pwpk#Kg2tjL=#Lo8z7(f1zDa39bab@ox@@Bj@FmAoKEh{811W}M&=_BZ
z0uS3iNR!NYOHcw<&Bl;l$!6pO0R#%}Be0(@AhvA+ss_nTJZGmD_tUa2^RY*2w0TS&
z|C_qLO@DAR7{M=k))FEP-JzN}u`Idg=KH}1PjU1HtD>ru?VqZ5mUQYKQZ+D;0P5@Y
zP@%%;`2J#Q5@Vb9&xPVVO>nnOrkHEz#W(1cs;92n>TcNJ6_QMKUH0R#BiHxuv5(7%
zOo#&+OMBu`6)ier=GwMmu<_N(1t-%Qs;{&{94nW3MoL93K1X3Y1i3DYiqkh_NT0)P
zugwKI-Tc@#SVnE_@uIT&J%#nLL*d<xk_Z0tO(fwR*M?rgQv*!g3C_H8)Yg2PV%EU3
zAxR*uy56+As68e(*H~2A&f34)R1lX{EKJ2++R=B1Q1qyxZ;4ICK7qOgbfro4-DE1U
zi_0HPar19P@msee0&429M^&3%3S;vh{!Yps%(<K=KKuZQ(tvClG4Y5)vB13?A7cyl
zDX4pG%zBPlLm?D<Y4!Q6VI8`}OwBVXHv5{Oj?dC{w}r7%$)Gy4F3$ao>OBg~KY$IQ
z40L$Vguj#y44@b(6}rzuMqbsrl(&}d;3ZdvAxbJN-2U26gg;@hv7!jbU4d6xCBetN
zjFE0f<2^cPa(OI?VP_KTw>+L9L9S1I4QXl9zONeI?n?(dB;UB=36^GD*lX73qMZQh
zUOw7p6Q5XA*Z=ZhuY-R^WI5ucnsrtNB3QdCw23=*H*fcNjQ*=p@B8Jh>5WNRqI%77
z5f6Mbww}x3<*uStvq5b|iBaI~h$p`5L+*v{+oGx|{3mfZ;)Qs3y(EtIkn)C$delrR
zdDMTHb-r)xYW~f#r$`+(DGZuRZf5&c^N|@N7Ywg8L@$5gk21GPza8wbr7^9eqQ}TH
z#M38g6JcRjKK_;C-=xLK?TGPIQc#7X77d!M&u4hKTC;Zzc`JuBTs)3jP%;crD;Q5X
zySKomS?_>AO(fhsm|KW#cs%f|mv<Dh4n)1d35~#yJ=V&`#gY+BX@A+R`!^!>g6XZq
zBniixz{3WW`?8ZPANX*W?!EnF4%^0Qb(3x;Q|JH+mG;2w<4=&RHwV^@oP{>KdOSyY
zU$jLtW1AO%yB%pp58K&|oKQF6u|Fy1xtMhIg%-9jJj-Q{L+nvQkChmeBceqCIZ&Bi
zWjHtw&i3yf+O_g}Sk$PB{C~IFbQnIgS+>|(zB;tR2yKuxF57Tqk$l~0v_-3~6#1GS
z-8p}vc0#14B!6l*A)nnjq5Cew^vxUD&P&Sj=U>;BC)T7{Mbf;nPbu|FE$7nB6GLzR
zwq!-jih{Oa4ihQu9cHx$@kDN6+!0=0Ow{S1VkWP!EYmh(Sg3h&`$SMzp}_9oEvu)@
zFlTCW(JrjBVaK*O;u+1Ph7Ph%3w~>^Q)^Ro6TL6t-04;ma(DODfxgl7)*H}}VhTZ$
zBpH|0^px1E%EbirobTE0ye&sE(RD~HlM4(Qrf14zsGfJTZc)mXZ1He;vZy(kS-^bE
zbTWiLq_BIDe&U<BTH>$N{*4p6B&64)G6DVlcEl}pzHmb7sTVGjf#1KfRgQ~RL`=(0
zoh&ntMh%?4gPROXZz|C~sKt@VJU&z^+{qTplz?ebB@=il9>oT;jnP0N$%p!xi98c!
z^pz-cbGV`b?;cP+pg)Ov`_+-(fTOL&K)o&Tc(^Iv0&i|=iL=C+u7j<CnPQ-8ETJ%O
z3=9v4MwsCV`_Ta(OOH3yR3ZA#EJbM4>D{TiV(0P7y28=X&&7pf9~SBAqBf~b={rqj
z#qRnpG=Tn%Z|}2v?FZWD-S$J|MB6E*&Gg<ci3Cj%nV}dv!*!=ho$kAzqzYzZe%^(@
zB-&t#=#_J4DKN7?6{C%&pbvZ{dRo6u-J0ng`)O;cz5)Ng`WNA(GWTsM)yX6CI{@&I
HPo#eV>zUe=
new file mode 100644
--- /dev/null
+++ b/dom/cache/test/xpcshell/test_migration.js
@@ -0,0 +1,38 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * All images in schema_15_profile.zip are from https://github.com/mdn/sw-test/
+ * and are CC licensed by https://www.flickr.com/photos/legofenris/.
+ */
+
+function run_test() {
+  do_test_pending();
+  create_test_profile('schema_15_profile.zip');
+
+  var cache;
+  caches.open('xpcshell-test').then(function(c) {
+    cache = c;
+    ok(cache, 'cache exists');
+    return cache.keys();
+  }).then(function(requestList) {
+    ok(requestList.length > 0, 'should have at least one request in cache');
+    requestList.forEach(function(request) {
+      ok(request, 'each request in list should be non-null');
+      ok(request.redirect === 'follow', 'request.redirect should default to "follow"');
+    });
+    return Promise.all(requestList.map(function(request) {
+      return cache.match(request);
+    }));
+  }).then(function(responseList) {
+    ok(responseList.length > 0, 'should have at least one response in cache');
+    responseList.forEach(function(response) {
+      ok(response, 'each request in list should be non-null');
+    });
+  }).then(function() {
+    do_test_finished();
+  }).catch(function(e) {
+    ok(false, 'caught exception ' + e);
+    do_test_finished();
+  });
+}
new file mode 100644
--- /dev/null
+++ b/dom/cache/test/xpcshell/xpcshell.ini
@@ -0,0 +1,16 @@
+# 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/.
+
+[DEFAULT]
+head = head.js
+tail =
+skip-if = toolkit == 'gonk'
+support-files =
+  schema_15_profile.zip
+
+# dummy test entry to generate profile zip files
+[make_profile.js]
+  skip-if = true
+
+[test_migration.js]
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -348,18 +348,18 @@ EventListenerManager::AddEventListenerIn
              aTypeAtom == nsGkAtoms::ontouchcancel) {
     mMayHaveTouchEventListener = true;
     nsPIDOMWindow* window = GetInnerWindowForTarget();
     // we don't want touchevent listeners added by scrollbars to flip this flag
     // so we ignore listeners created with system event flag
     if (window && !aFlags.mInSystemGroup) {
       window->SetHasTouchEventListeners();
     }
-  } else if (aEventMessage >= NS_POINTER_EVENT_START &&
-             aEventMessage <= NS_POINTER_LOST_CAPTURE) {
+  } else if (aEventMessage >= ePointerEventFirst &&
+             aEventMessage <= ePointerEventLast) {
     nsPIDOMWindow* window = GetInnerWindowForTarget();
     if (aTypeAtom == nsGkAtoms::onpointerenter ||
         aTypeAtom == nsGkAtoms::onpointerleave) {
       mMayHavePointerEnterLeaveEventListener = true;
       if (window) {
 #ifdef DEBUG
         nsCOMPtr<nsIDocument> d = window->GetExtantDoc();
         NS_WARN_IF_FALSE(!nsContentUtils::IsChromeDoc(d),
--- a/dom/events/EventNameList.h
+++ b/dom/events/EventNameList.h
@@ -164,17 +164,17 @@ EVENT(change,
       NS_FORM_CHANGE,
       EventNameType_HTMLXUL,
       eBasicEventClass)
 EVENT(click,
       eMouseClick,
       EventNameType_All,
       eMouseEventClass)
 EVENT(contextmenu,
-      NS_CONTEXTMENU,
+      eContextMenu,
       EventNameType_HTMLXUL,
       eMouseEventClass)
 // Not supported yet
 // EVENT(cuechange)
 EVENT(dblclick,
       eMouseDoubleClick,
       EventNameType_HTMLXUL,
       eMouseEventClass)
@@ -306,53 +306,53 @@ EVENT(mozpointerlockchange,
       NS_POINTERLOCKCHANGE,
       EventNameType_HTML,
       eBasicEventClass)
 EVENT(mozpointerlockerror,
       NS_POINTERLOCKERROR,
       EventNameType_HTML,
       eBasicEventClass)
 EVENT(pointerdown,
-      NS_POINTER_DOWN,
+      ePointerDown,
       EventNameType_All,
       ePointerEventClass)
 EVENT(pointermove,
-      NS_POINTER_MOVE,
+      ePointerMove,
       EventNameType_All,
       ePointerEventClass)
 EVENT(pointerup,
-      NS_POINTER_UP,
+      ePointerUp,
       EventNameType_All,
       ePointerEventClass)
 EVENT(pointercancel,
-      NS_POINTER_CANCEL,
+      ePointerCancel,
       EventNameType_All,
       ePointerEventClass)
 EVENT(pointerover,
-      NS_POINTER_OVER,
+      ePointerOver,
       EventNameType_All,
       ePointerEventClass)
 EVENT(pointerout,
-      NS_POINTER_OUT,
+      ePointerOut,
       EventNameType_All,
       ePointerEventClass)
 EVENT(pointerenter,
-      NS_POINTER_ENTER,
+      ePointerEnter,
       EventNameType_All,
       ePointerEventClass)
 EVENT(pointerleave,
-      NS_POINTER_LEAVE,
+      ePointerLeave,
       EventNameType_All,
       ePointerEventClass)
 EVENT(gotpointercapture,
-      NS_POINTER_GOT_CAPTURE,
+      ePointerGotCapture,
       EventNameType_All,
       ePointerEventClass)
 EVENT(lostpointercapture,
-      NS_POINTER_LOST_CAPTURE,
+      ePointerLostCapture,
       EventNameType_All,
       ePointerEventClass)
 
 // Not supported yet; probably never because "wheel" is a better idea.
 // EVENT(mousewheel)
 EVENT(pause,
       NS_PAUSE,
       EventNameType_HTML,
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -538,17 +538,17 @@ EventStateManager::PreHandleEvent(nsPres
       UIEvent::CalculateScreenPoint(aPresContext, aEvent);
     sLastClientPoint =
       UIEvent::CalculateClientPoint(aPresContext, aEvent, nullptr);
   }
 
   *aStatus = nsEventStatus_eIgnore;
 
   switch (aEvent->mMessage) {
-  case NS_CONTEXTMENU:
+  case eContextMenu:
     if (sIsPointerLocked) {
       return NS_ERROR_DOM_INVALID_STATE_ERR;
     }
     break;
   case eMouseDown: {
     switch (mouseEvent->button) {
     case WidgetMouseEvent::eLeftButton:
       BeginTrackingDragGesture(aPresContext, mouseEvent, aTargetFrame);
@@ -617,26 +617,26 @@ EventStateManager::PreHandleEvent(nsPres
       // "exit" or "move" events.  Any necessary "out" or "over" events
       // will be generated by GenerateMouseEnterExit
       mouseEvent->mMessage = eMouseMove;
       mouseEvent->reason = WidgetMouseEvent::eSynthesized;
       // then fall through...
     } else {
       if (sPointerEventEnabled) {
         // We should synthetize corresponding pointer events
-        GeneratePointerEnterExit(NS_POINTER_LEAVE, mouseEvent);
+        GeneratePointerEnterExit(ePointerLeave, mouseEvent);
       }
       GenerateMouseEnterExit(mouseEvent);
       //This is a window level mouse exit event and should stop here
       aEvent->mMessage = eVoidEvent;
       break;
     }
   case eMouseMove:
-  case NS_POINTER_DOWN:
-  case NS_POINTER_MOVE: {
+  case ePointerDown:
+  case ePointerMove: {
     // on the Mac, GenerateDragGesture() may not return until the drag
     // has completed and so |aTargetFrame| may have been deleted (moving
     // a bookmark, for example).  If this is the case, however, we know
     // that ClearFrameRefs() has been called and it cleared out
     // |mCurrentTarget|. As a result, we should pass |mCurrentTarget|
     // into UpdateCursor().
     GenerateDragGesture(aPresContext, mouseEvent);
     UpdateCursor(aPresContext, aEvent, mCurrentTarget, aStatus);
@@ -1169,17 +1169,17 @@ CrossProcessSafeEvent(const WidgetEvent&
   case eKeyboardEventClass:
   case eWheelEventClass:
     return true;
   case eMouseEventClass:
     switch (aEvent.mMessage) {
     case eMouseDown:
     case eMouseUp:
     case eMouseMove:
-    case NS_CONTEXTMENU:
+    case eContextMenu:
     case eMouseEnterIntoWidget:
     case eMouseExitFromWidget:
       return true;
     default:
       return false;
     }
   case eTouchEventClass:
     switch (aEvent.mMessage) {
@@ -1447,17 +1447,17 @@ EventStateManager::FireContextClick()
                                                         nsGkAtoms::embed,
                                                         nsGkAtoms::object)) {
         allowedToDispatch = false;
       }
     }
 
     if (allowedToDispatch) {
       // init the event while mCurrentTarget is still good
-      WidgetMouseEvent event(true, NS_CONTEXTMENU, targetWidget,
+      WidgetMouseEvent event(true, eContextMenu, targetWidget,
                              WidgetMouseEvent::eReal);
       event.clickCount = 1;
       FillInEventFromGestureDown(&event);
         
       // stop selection tracking, we're in control now
       if (mCurrentTarget)
       {
         nsRefPtr<nsFrameSelection> frameSel =
@@ -1473,17 +1473,17 @@ EventStateManager::FireContextClick()
       nsIDocument* doc = mGestureDownContent->GetCrossShadowCurrentDoc();
       AutoHandlingUserInputStatePusher userInpStatePusher(true, &event, doc);
 
       // dispatch to DOM
       EventDispatcher::Dispatch(mGestureDownContent, mPresContext, &event,
                                 nullptr, &status);
 
       // We don't need to dispatch to frame handling because no frames
-      // watch NS_CONTEXTMENU except for nsMenuFrame and that's only for
+      // watch eContextMenu except for nsMenuFrame and that's only for
       // dismissal. That's just as well since we don't really know
       // which frame to send it to.
     }
   }
 
   // now check if the event has been handled. If so, stop tracking a drag
   if (status == nsEventStatus_eConsumeNoDefault) {
     StopTrackingDragGesture();
@@ -2996,24 +2996,24 @@ EventStateManager::PostHandleEvent(nsPre
               fm->SetFocusedWindow(mDocument->GetWindow());
             }
           }
         }
       }
       SetActiveManager(this, activeContent);
     }
     break;
-  case NS_POINTER_CANCEL: {
+  case ePointerCancel: {
     if(WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent()) {
       GenerateMouseEnterExit(mouseEvent);
     }
     // This break was commented specially
     // break;
   }
-  case NS_POINTER_UP: {
+  case ePointerUp: {
     WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent();
     // After UP/Cancel Touch pointers become invalid so we can remove relevant helper from Table
     // Mouse/Pen pointers are valid all the time (not only between down/up)
     if (pointerEvent->inputSource == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) {
       mPointersEnterLeaveHelper.Remove(pointerEvent->pointerId);
       GenerateMouseEnterExit(pointerEvent);
     }
     break;
@@ -3867,18 +3867,17 @@ public:
         // mouseenter/leave is fired only on elements.
         current = current->GetParent();
       }
     }
   }
 
   ~EnterLeaveDispatcher()
   {
-    if (mEventMessage == eMouseEnter ||
-        mEventMessage == NS_POINTER_ENTER) {
+    if (mEventMessage == eMouseEnter || mEventMessage == ePointerEnter) {
       for (int32_t i = mTargets.Count() - 1; i >= 0; --i) {
         mESM->DispatchMouseOrPointerEvent(mMouseEvent, mEventMessage,
                                           mTargets[i], mRelatedTarget);
       }
     } else {
       for (int32_t i = 0; i < mTargets.Count(); ++i) {
         mESM->DispatchMouseOrPointerEvent(mMouseEvent, mEventMessage,
                                           mTargets[i], mRelatedTarget);
@@ -3939,27 +3938,27 @@ EventStateManager::NotifyMouseOut(Widget
   // switching the hover state to null here.
   bool isPointer = aMouseEvent->mClass == ePointerEventClass;
   if (!aMovingInto && !isPointer) {
     // Unset :hover
     SetContentState(nullptr, NS_EVENT_STATE_HOVER);
   }
 
   // In case we go out from capturing element (retargetedByPointerCapture is true)
-  // we should dispatch NS_POINTER_LEAVE event and only for capturing element.
+  // we should dispatch ePointerLeave event and only for capturing element.
   nsRefPtr<nsIContent> movingInto = aMouseEvent->retargetedByPointerCapture
                                     ? wrapper->mLastOverElement->GetParent()
                                     : aMovingInto;
 
   EnterLeaveDispatcher leaveDispatcher(this, wrapper->mLastOverElement,
                                        movingInto, aMouseEvent,
-                                       isPointer ? NS_POINTER_LEAVE : eMouseLeave);
+                                       isPointer ? ePointerLeave : eMouseLeave);
 
   // Fire mouseout
-  DispatchMouseOrPointerEvent(aMouseEvent, isPointer ? NS_POINTER_OUT : eMouseOut,
+  DispatchMouseOrPointerEvent(aMouseEvent, isPointer ? ePointerOut : eMouseOut,
                               wrapper->mLastOverElement, aMovingInto);
 
   wrapper->mLastOverFrame = nullptr;
   wrapper->mLastOverElement = nullptr;
 
   // Turn recursion protection back off
   wrapper->mFirstOutEventElement = nullptr;
 }
@@ -4005,34 +4004,34 @@ EventStateManager::NotifyMouseOver(Widge
   // DispatchMouseOrPointerEvent() call below, since NotifyMouseOut() resets it, bug 298477.
   nsCOMPtr<nsIContent> lastOverElement = wrapper->mLastOverElement;
 
   bool isPointer = aMouseEvent->mClass == ePointerEventClass;
   
   Maybe<EnterLeaveDispatcher> enterDispatcher;
   if (dispatch) {
     enterDispatcher.emplace(this, aContent, lastOverElement, aMouseEvent,
-                            isPointer ? NS_POINTER_ENTER : eMouseEnter);
+                            isPointer ? ePointerEnter : eMouseEnter);
   }
 
   NotifyMouseOut(aMouseEvent, aContent);
 
   // Store the first mouseOver event we fire and don't refire mouseOver
   // to that element while the first mouseOver is still ongoing.
   wrapper->mFirstOverEventElement = aContent;
 
   if (!isPointer) {
     SetContentState(aContent, NS_EVENT_STATE_HOVER);
   }
 
   if (dispatch) {
     // Fire mouseover
     wrapper->mLastOverFrame = 
       DispatchMouseOrPointerEvent(aMouseEvent,
-                                  isPointer ? NS_POINTER_OVER : eMouseOver,
+                                  isPointer ? ePointerOver : eMouseOver,
                                   aContent, lastOverElement);
     wrapper->mLastOverElement = aContent;
   } else {
     wrapper->mLastOverFrame = nullptr;
     wrapper->mLastOverElement = nullptr;
   }
 
   // Turn recursion protection back off
@@ -4144,33 +4143,33 @@ EventStateManager::GenerateMouseEnterExi
       } else {
         aMouseEvent->lastRefPoint = sLastRefPoint;
       }
 
       // Update the last known refPoint with the current refPoint.
       sLastRefPoint = aMouseEvent->refPoint;
 
     }
-  case NS_POINTER_MOVE:
-  case NS_POINTER_DOWN:
+  case ePointerMove:
+  case ePointerDown:
     {
       // Get the target content target (mousemove target == mouseover target)
       nsCOMPtr<nsIContent> targetElement = GetEventTargetContent(aMouseEvent);
       if (!targetElement) {
         // We're always over the document root, even if we're only
         // over dead space in a page (whose frame is not associated with
         // any content) or in print preview dead space
         targetElement = mDocument->GetRootElement();
       }
       if (targetElement) {
         NotifyMouseOver(aMouseEvent, targetElement);
       }
     }
     break;
-  case NS_POINTER_UP:
+  case ePointerUp:
     {
       // Get the target content target (mousemove target == mouseover target)
       nsCOMPtr<nsIContent> targetElement = GetEventTargetContent(aMouseEvent);
       if (!targetElement) {
         // We're always over the document root, even if we're only
         // over dead space in a page (whose frame is not associated with
         // any content) or in print preview dead space
         targetElement = mDocument->GetRootElement();
@@ -4179,18 +4178,18 @@ EventStateManager::GenerateMouseEnterExi
         OverOutElementsWrapper* helper = GetWrapperByEventID(aMouseEvent);
         if (helper) {
           helper->mLastOverElement = targetElement;
         }
         NotifyMouseOut(aMouseEvent, nullptr);
       }
     }
     break;
-  case NS_POINTER_LEAVE:
-  case NS_POINTER_CANCEL:
+  case ePointerLeave:
+  case ePointerCancel:
   case eMouseExitFromWidget:
     {
       // This is actually the window mouse exit or pointer leave event. We're not moving
       // into any new element.
 
       OverOutElementsWrapper* helper = GetWrapperByEventID(aMouseEvent);
       if (helper->mLastOverFrame &&
           nsContentUtils::GetTopLevelWidget(aMouseEvent->widget) !=
--- a/dom/events/WheelHandlingHelper.cpp
+++ b/dom/events/WheelHandlingHelper.cpp
@@ -209,17 +209,17 @@ WheelTransaction::OnEvent(WidgetEvent* a
     }
     case eKeyPress:
     case eKeyUp:
     case eKeyDown:
     case eMouseUp:
     case eMouseDown:
     case eMouseDoubleClick:
     case eMouseClick:
-    case NS_CONTEXTMENU:
+    case eContextMenu:
     case NS_DRAGDROP_DROP:
       EndTransaction();
       return;
     default:
       break;
   }
 }
 
--- a/dom/fetch/ChannelInfo.cpp
+++ b/dom/fetch/ChannelInfo.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/ChannelInfo.h"
 #include "nsCOMPtr.h"
+#include "nsContentUtils.h"
 #include "nsIChannel.h"
 #include "nsIDocument.h"
 #include "nsIHttpChannel.h"
 #include "nsSerializationHelper.h"
 #include "mozilla/net/HttpBaseChannel.h"
 #include "mozilla/ipc/ChannelInfo.h"
 #include "nsIJARChannel.h"
 #include "nsJARChannel.h"
@@ -64,16 +65,30 @@ ChannelInfo::InitFromChannel(nsIChannel*
       redirectedURI->GetSpec(mRedirectedURISpec);
     }
   }
 
   mInited = true;
 }
 
 void
+ChannelInfo::InitFromChromeGlobal(nsIGlobalObject* aGlobal)
+{
+  MOZ_ASSERT(!mInited, "Cannot initialize the object twice");
+  MOZ_ASSERT(aGlobal);
+
+  MOZ_RELEASE_ASSERT(
+    nsContentUtils::IsSystemPrincipal(aGlobal->PrincipalOrNull()));
+
+  mSecurityInfo.Truncate();
+  mRedirected = false;
+  mInited = true;
+}
+
+void
 ChannelInfo::InitFromIPCChannelInfo(const ipc::IPCChannelInfo& aChannelInfo)
 {
   MOZ_ASSERT(!mInited, "Cannot initialize the object twice");
 
   mSecurityInfo = aChannelInfo.securityInfo();
   mRedirectedURISpec = aChannelInfo.redirectedURI();
   mRedirected = aChannelInfo.redirected();
 
--- a/dom/fetch/ChannelInfo.h
+++ b/dom/fetch/ChannelInfo.h
@@ -7,16 +7,17 @@
 #ifndef mozilla_dom_ChannelInfo_h
 #define mozilla_dom_ChannelInfo_h
 
 #include "nsString.h"
 #include "nsCOMPtr.h"
 
 class nsIChannel;
 class nsIDocument;
+class nsIGlobalObject;
 class nsIURI;
 
 namespace mozilla {
 namespace ipc {
 class IPCChannelInfo;
 } // namespace ipc
 
 namespace dom {
@@ -64,16 +65,17 @@ public:
     mRedirectedURISpec = aRHS.mRedirectedURISpec;
     mInited = aRHS.mInited;
     mRedirected = aRHS.mRedirected;
     return *this;
   }
 
   void InitFromDocument(nsIDocument* aDoc);
   void InitFromChannel(nsIChannel* aChannel);
+  void InitFromChromeGlobal(nsIGlobalObject* aGlobal);
   void InitFromIPCChannelInfo(const IPCChannelInfo& aChannelInfo);
 
   // This restores every possible information stored from a previous channel
   // object on a new one.
   nsresult ResurrectInfoOnChannel(nsIChannel* aChannel);
 
   bool IsInitialized() const
   {
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -26,16 +26,17 @@
 #include "nsHostObjectProtocolHandler.h"
 #include "nsNetUtil.h"
 #include "nsPrintfCString.h"
 #include "nsStreamUtils.h"
 #include "nsStringStream.h"
 
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/workers/Workers.h"
+#include "mozilla/unused.h"
 
 #include "Fetch.h"
 #include "InternalRequest.h"
 #include "InternalResponse.h"
 
 namespace mozilla {
 namespace dom {
 
@@ -44,16 +45,17 @@ NS_IMPL_ISUPPORTS(FetchDriver,
                   nsIAsyncVerifyRedirectCallback, nsIThreadRetargetableStreamListener)
 
 FetchDriver::FetchDriver(InternalRequest* aRequest, nsIPrincipal* aPrincipal,
                          nsILoadGroup* aLoadGroup)
   : mPrincipal(aPrincipal)
   , mLoadGroup(aLoadGroup)
   , mRequest(aRequest)
   , mFetchRecursionCount(0)
+  , mCORSFlagEverSet(false)
   , mResponseAvailableCalled(false)
 {
 }
 
 FetchDriver::~FetchDriver()
 {
   // We assert this since even on failures, we should call
   // FailWithNetworkError().
@@ -89,28 +91,28 @@ FetchDriver::Fetch(bool aCORSFlag)
       FailWithNetworkError();
     }
     return rv;
   }
 
   MOZ_CRASH("Synchronous fetch not supported");
 }
 
-nsresult
-FetchDriver::ContinueFetch(bool aCORSFlag)
+FetchDriver::MainFetchOp
+FetchDriver::SetTaintingAndGetNextOp(bool aCORSFlag)
 {
   workers::AssertIsOnMainThread();
 
   nsAutoCString url;
   mRequest->GetURL(url);
   nsCOMPtr<nsIURI> requestURI;
   nsresult rv = NS_NewURI(getter_AddRefs(requestURI), url,
                           nullptr, nullptr);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    return FailWithNetworkError();
+    return MainFetchOp(NETWORK_ERROR);
   }
 
   // CSP/mixed content checks.
   int16_t shouldLoad;
   rv = NS_CheckContentLoadPolicy(mRequest->ContentPolicyType(),
                                  requestURI,
                                  mPrincipal,
                                  mDocument,
@@ -118,61 +120,101 @@ FetchDriver::ContinueFetch(bool aCORSFla
                                  // Content-Type header?
                                  EmptyCString(), /* mime guess */
                                  nullptr, /* extra */
                                  &shouldLoad,
                                  nsContentUtils::GetContentPolicy(),
                                  nsContentUtils::GetSecurityManager());
   if (NS_WARN_IF(NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad))) {
     // Disallowed by content policy.
-    return FailWithNetworkError();
+    return MainFetchOp(NETWORK_ERROR);
   }
 
-  // Begin Step 4 of the Fetch algorithm
+  // Begin Step 8 of the Main Fetch algorithm
   // https://fetch.spec.whatwg.org/#fetching
 
   nsAutoCString scheme;
   rv = requestURI->GetScheme(scheme);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    return FailWithNetworkError();
+    return MainFetchOp(NETWORK_ERROR);
   }
 
-  rv = mPrincipal->CheckMayLoad(requestURI, false /* report */, false /* allowIfInheritsPrincipal */);
+  // request's current url's origin is request's origin and the CORS flag is unset
+  // request's current url's scheme is "data" and request's same-origin data-URL flag is set
+  // request's current url's scheme is "about"
+  rv = mPrincipal->CheckMayLoad(requestURI, false /* report */,
+                                false /* allowIfInheritsPrincipal */);
   if ((!aCORSFlag && NS_SUCCEEDED(rv)) ||
       (scheme.EqualsLiteral("data") && mRequest->SameOriginDataURL()) ||
       scheme.EqualsLiteral("about")) {
-    return BasicFetch();
+    return MainFetchOp(BASIC_FETCH);
+  }
+
+  // request's mode is "same-origin"
+  if (mRequest->Mode() == RequestMode::Same_origin) {
+    return MainFetchOp(NETWORK_ERROR);
+  }
+
+  // request's mode is "no-cors"
+  if (mRequest->Mode() == RequestMode::No_cors) {
+    mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_OPAQUE);
+    return MainFetchOp(BASIC_FETCH);
+  }
+
+  // request's current url's scheme is not one of "http" and "https"
+  if (!scheme.EqualsLiteral("http") && !scheme.EqualsLiteral("https")) {
+    return MainFetchOp(NETWORK_ERROR);
   }
 
-  if (mRequest->Mode() == RequestMode::Same_origin) {
+  // request's mode is "cors-with-forced-preflight"
+  // request's unsafe-request flag is set and either request's method is not
+  // a simple method or a header in request's header list is not a simple header
+  if (mRequest->Mode() == RequestMode::Cors_with_forced_preflight ||
+      (mRequest->UnsafeRequest() && (!mRequest->HasSimpleMethod() ||
+                                     !mRequest->Headers()->HasOnlySimpleHeaders()))) {
+    mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_CORS);
+    mRequest->SetRedirectMode(RequestRedirect::Error);
+
+    // Note, the following text from Main Fetch step 8 is handled in
+    // nsCORSListenerProxy when CheckRequestApproved() fails:
+    //
+    //  The result of performing an HTTP fetch using request with the CORS
+    //  flag and CORS-preflight flag set. If the result is a network error,
+    //  clear cache entries using request.
+
+    return MainFetchOp(HTTP_FETCH, true /* cors */, true /* preflight */);
+  }
+
+  // Otherwise
+  mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_CORS);
+  return MainFetchOp(HTTP_FETCH, true /* cors */, false /* preflight */);
+}
+
+nsresult
+FetchDriver::ContinueFetch(bool aCORSFlag)
+{
+  workers::AssertIsOnMainThread();
+
+  MainFetchOp nextOp = SetTaintingAndGetNextOp(aCORSFlag);
+
+  if (nextOp.mType == NETWORK_ERROR) {
     return FailWithNetworkError();
   }
 
-  if (mRequest->Mode() == RequestMode::No_cors) {
-    mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_OPAQUE);
+  if (nextOp.mType == BASIC_FETCH) {
     return BasicFetch();
   }
 
-  if (!scheme.EqualsLiteral("http") && !scheme.EqualsLiteral("https")) {
-    return FailWithNetworkError();
+  if (nextOp.mType == HTTP_FETCH) {
+    return HttpFetch(nextOp.mCORSFlag, nextOp.mCORSPreflightFlag);
   }
 
-  bool corsPreflight = false;
-  if (mRequest->Mode() == RequestMode::Cors_with_forced_preflight ||
-      (mRequest->UnsafeRequest() && (!mRequest->HasSimpleMethod() || !mRequest->Headers()->HasOnlySimpleHeaders()))) {
-    corsPreflight = true;
-  }
-  // The Request constructor should ensure that no-cors requests have simple
-  // method and headers, so we should never attempt to preflight for such
-  // Requests.
-  MOZ_ASSERT_IF(mRequest->Mode() == RequestMode::No_cors, !corsPreflight);
-
-  mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_CORS);
-  return HttpFetch(true /* aCORSFlag */, corsPreflight);
-}
+  MOZ_ASSERT_UNREACHABLE("Unexpected main fetch operation!");
+  return FailWithNetworkError();
+ }
 
 nsresult
 FetchDriver::BasicFetch()
 {
   nsAutoCString url;
   mRequest->GetURL(url);
   nsCOMPtr<nsIURI> uri;
   nsresult rv = NS_NewURI(getter_AddRefs(uri),
@@ -326,16 +368,21 @@ FetchDriver::BasicFetch()
 // Necko HTTP implementation.
 nsresult
 FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthenticationFlag)
 {
   // Step 1. "Let response be null."
   mResponse = nullptr;
   nsresult rv;
 
+  // We need to track the CORS flag through redirects.  Since there is no way
+  // for us to go from CORS mode to non-CORS mode, we just need to remember
+  // if it has ever been set.
+  mCORSFlagEverSet = mCORSFlagEverSet || aCORSFlag;
+
   nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     FailWithNetworkError();
     return rv;
   }
 
   nsAutoCString url;
   mRequest->GetURL(url);
@@ -482,16 +529,23 @@ FetchDriver::HttpFetch(bool aCORSFlag, b
       httpChan->SetRequestHeader(NS_LITERAL_CSTRING("origin"),
                                  NS_ConvertUTF16toUTF8(origin),
                                  false /* merge */);
     }
     // Bug 1120722 - Authorization will be handled later.
     // Auth may require prompting, we don't support it yet.
     // The next patch in this same bug prevents this from aborting the request.
     // Credentials checks for CORS are handled by nsCORSListenerProxy,
+
+    nsCOMPtr<nsIHttpChannelInternal> internalChan = do_QueryInterface(httpChan);
+
+    // Conversion between enumerations is safe due to static asserts in
+    // dom/workers/ServiceWorkerManager.cpp
+    internalChan->SetCorsMode(static_cast<uint32_t>(mRequest->Mode()));
+    internalChan->SetRedirectMode(static_cast<uint32_t>(mRequest->GetRedirectMode()));
   }
 
   // Step 5. Proxy authentication will be handled by Necko.
   // FIXME(nsm): Bug 1120715.
   // Step 7-10. "If request's cache mode is neither no-store nor reload..."
 
   // Continue setting up 'HTTPRequest'. Content-Type and body data.
   nsCOMPtr<nsIUploadChannel2> uploadChan = do_QueryInterface(chan);
@@ -529,20 +583,20 @@ FetchDriver::HttpFetch(bool aCORSFlag, b
       // If it is not an http channel, it has to be a jar one.
       MOZ_ASSERT(jarChannel);
       jarChannel->ForceNoIntercept();
     }
   }
 
   nsCOMPtr<nsIStreamListener> listener = this;
 
-  // Unless the cors mode is explicitly no-cors, we set up a cors proxy even in
-  // the same-origin case, since the proxy does not enforce cors header checks
-  // in the same-origin case.
-  if (mRequest->Mode() != RequestMode::No_cors) {
+  // Only use nsCORSListenerProxy if we are in CORS mode.  Otherwise it
+  // will overwrite the CorsMode flag unconditionally to "cors" or
+  // "cors-with-forced-preflight".
+  if (mRequest->Mode() == RequestMode::Cors) {
     // Set up a CORS proxy that will handle the various requirements of the CORS
     // protocol. It handles the preflight cache and CORS response headers.
     // If the request is allowed, it will start our original request
     // and our observer will be notified. On failure, our observer is notified
     // directly.
     nsRefPtr<nsCORSListenerProxy> corsListener =
       new nsCORSListenerProxy(this, mPrincipal, useCredentials);
     rv = corsListener->Init(chan, DataURIHandling::Allow);
@@ -611,16 +665,19 @@ FetchDriver::BeginAndGetFilteredResponse
       filteredResponse = aResponse->BasicResponse();
       break;
     case InternalRequest::RESPONSETAINT_CORS:
       filteredResponse = aResponse->CORSResponse();
       break;
     case InternalRequest::RESPONSETAINT_OPAQUE:
       filteredResponse = aResponse->OpaqueResponse();
       break;
+    case InternalRequest::RESPONSETAINT_OPAQUEREDIRECT:
+      filteredResponse = aResponse->OpaqueRedirectResponse();
+      break;
     default:
       MOZ_CRASH("Unexpected case");
   }
 
   MOZ_ASSERT(filteredResponse);
   MOZ_ASSERT(mObserver);
   mObserver->OnResponseAvailable(filteredResponse);
   mResponseAvailableCalled = true;
@@ -691,25 +748,32 @@ public:
 NS_IMPL_ISUPPORTS(FillResponseHeaders, nsIHttpHeaderVisitor)
 } // namespace
 
 NS_IMETHODIMP
 FetchDriver::OnStartRequest(nsIRequest* aRequest,
                             nsISupports* aContext)
 {
   workers::AssertIsOnMainThread();
-  MOZ_ASSERT(!mPipeOutputStream);
-  MOZ_ASSERT(mObserver);
+
+  // Note, this can be called multiple times if we are doing an opaqueredirect.
+  // In that case we will get a simulated OnStartRequest() and then the real
+  // channel will call in with an errored OnStartRequest().
+
   nsresult rv;
   aRequest->GetStatus(&rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     FailWithNetworkError();
     return rv;
   }
 
+  // We should only get to the following code once.
+  MOZ_ASSERT(!mPipeOutputStream);
+  MOZ_ASSERT(mObserver);
+
   nsRefPtr<InternalResponse> response;
   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
   if (httpChannel) {
     uint32_t responseStatus;
     httpChannel->GetResponseStatus(&responseStatus);
 
     nsAutoCString statusText;
     httpChannel->GetResponseStatusText(statusText);
@@ -834,43 +898,69 @@ FetchDriver::AsyncOnChannelRedirect(nsIC
                                     nsIChannel* aNewChannel,
                                     uint32_t aFlags,
                                     nsIAsyncVerifyRedirectCallback *aCallback)
 {
   NS_PRECONDITION(aNewChannel, "Redirect without a channel?");
 
   nsresult rv;
 
-  // Section 4.2, Step 4.6-4.7, enforcing a redirect count is done by Necko.
-  // The pref used is "network.http.redirection-limit" which is set to 20 by
-  // default.
-  //
-  // Step 4.8. We only unset this for spec compatibility. Any actions we take
-  // on mRequest here do not affect what the channel does.
+  // HTTP Fetch step 5, "redirect status", step 1
+  if (NS_WARN_IF(mRequest->GetRedirectMode() == RequestRedirect::Error)) {
+    aOldChannel->Cancel(NS_BINDING_FAILED);
+    return NS_BINDING_FAILED;
+  }
+
+  // HTTP Fetch step 5, "redirect status", steps 2 through 6 are automatically
+  // handled by necko before calling AsyncOnChannelRedirect() with the new
+  // nsIChannel.
+
+  // HTTP Fetch step 5, "redirect status", steps 7 and 8 enforcing a redirect
+  // count are done by Necko.  The pref used is "network.http.redirection-limit"
+  // which is set to 20 by default.
+
+  // HTTP Fetch Step 9, "redirect status". We only unset this for spec
+  // compatibility. Any actions we take on mRequest here do not affect what the
+  //channel does.
   mRequest->UnsetSameOriginDataURL();
 
-  //
-  // Requests that require preflight are not permitted to redirect.
-  // Fetch spec section 4.2 "HTTP Fetch", step 4.9 just uses the manual
-  // redirect flag to decide whether to execute step 4.10 or not. We do not
-  // represent it in our implementation.
-  // The only thing we do is to check if the request requires a preflight (part
-  // of step 4.9), in which case we abort. This part cannot be done by
-  // nsCORSListenerProxy since it does not have access to mRequest.
-  // which case. Step 4.10.3 is handled by OnRedirectVerifyCallback(), and all
-  // the other steps are handled by nsCORSListenerProxy.
-  if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags)) {
-    rv = DoesNotRequirePreflight(aNewChannel);
-    if (NS_FAILED(rv)) {
-      NS_WARNING("FetchDriver::OnChannelRedirect: "
-                 "DoesNotRequirePreflight returned failure");
-      return rv;
-    }
+  // HTTP Fetch step 5, "redirect status", step 10 requires us to halt the
+  // redirect, but successfully return an opaqueredirect Response to the
+  // initiating Fetch.
+  if (mRequest->GetRedirectMode() == RequestRedirect::Manual) {
+    // Ideally we would simply not cancel the old channel and allow it to
+    // be processed as normal.  Unfortunately this is quite fragile and
+    // other redirect handlers can easily break it for certain use cases.
+    //
+    // For example, nsCORSListenerProxy cancels vetoed redirect channels.
+    // The HTTP cache will also error on vetoed redirects when the
+    // redirect has been previously cached.
+    //
+    // Therefore simulate the completion of the channel to produce the
+    // opaqueredirect Response and then cancel the original channel.  This
+    // will result in OnStartRequest() getting called twice, but the second
+    // time will be with an error response (from the Cancel) which will
+    // be ignored.
+    mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_OPAQUEREDIRECT);
+    unused << OnStartRequest(aOldChannel, nullptr);
+    unused << OnStopRequest(aOldChannel, nullptr, NS_OK);
+
+    aOldChannel->Cancel(NS_BINDING_FAILED);
+
+    return NS_BINDING_FAILED;
   }
 
+  // The following steps are from HTTP Fetch step 5, "redirect status", step 11
+  // which requires the RequestRedirect to be "follow".
+  MOZ_ASSERT(mRequest->GetRedirectMode() == RequestRedirect::Follow);
+
+  // HTTP Fetch step 5, "redirect status", steps 11.1 and 11.2 block redirecting
+  // to a URL with credentials in CORS mode.  This is implemented in
+  // nsCORSListenerProxy.
+
   mRedirectCallback = aCallback;
   mOldRedirectChannel = aOldChannel;
   mNewRedirectChannel = aNewChannel;
 
   nsCOMPtr<nsIChannelEventSink> outer =
     do_GetInterface(mNotificationCallbacks);
   if (outer) {
     // The callee is supposed to call OnRedirectVerifyCallback() on success,
@@ -953,38 +1043,54 @@ FetchDriver::GetInterface(const nsIID& a
   }
 
   return QueryInterface(aIID, aResult);
 }
 
 NS_IMETHODIMP
 FetchDriver::OnRedirectVerifyCallback(nsresult aResult)
 {
-  // On a successful redirect we perform the following substeps of Section 4.2,
-  // step 4.10.
+  // On a successful redirect we perform the following substeps of HTTP Fetch,
+  // step 5, "redirect status", step 11.
   if (NS_SUCCEEDED(aResult)) {
-    // Step 4.10.3 "Set request's url to locationURL." so that when we set the
-    // Response's URL from the Request's URL in Section 4, step 6, we get the
-    // final value.
+    // Step 11.5 "Append locationURL to request's url list." so that when we set the
+    // Response's URL from the Request's URL in Main Fetch, step 15, we get the
+    // final value. Note, we still use a single URL value instead of a list.
     nsCOMPtr<nsIURI> newURI;
     nsresult rv = NS_GetFinalChannelURI(mNewRedirectChannel, getter_AddRefs(newURI));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       aResult = rv;
     } else {
       // We need to update our request's URL.
       nsAutoCString newUrl;
       newURI->GetSpec(newUrl);
       mRequest->SetURL(newUrl);
     }
   }
 
   if (NS_FAILED(aResult)) {
     mOldRedirectChannel->Cancel(aResult);
   }
 
+  // Implement Main Fetch step 8 again on redirect.
+  MainFetchOp nextOp = SetTaintingAndGetNextOp(mCORSFlagEverSet);
+
+  if (nextOp.mType == NETWORK_ERROR) {
+    // Cancel the channel if Main Fetch blocks the redirect from continuing.
+    aResult = NS_ERROR_DOM_BAD_URI;
+    mOldRedirectChannel->Cancel(aResult);
+  } else {
+    // Otherwise, we rely on necko and the CORS proxy to do the right thing
+    // as the redirect is followed.  In general this means basic or http
+    // fetch.  If we've ever been CORS, we need to stay CORS.
+    MOZ_ASSERT(nextOp.mType == BASIC_FETCH || nextOp.mType == HTTP_FETCH);
+    MOZ_ASSERT_IF(mCORSFlagEverSet, nextOp.mType == HTTP_FETCH);
+    MOZ_ASSERT_IF(mCORSFlagEverSet, nextOp.mCORSFlag);
+  }
+
   mOldRedirectChannel = nullptr;
   mNewRedirectChannel = nullptr;
   mRedirectCallback->OnRedirectVerifyCallback(aResult);
   mRedirectCallback = nullptr;
   return NS_OK;
 }
 
 void
--- a/dom/fetch/FetchDriver.h
+++ b/dom/fetch/FetchDriver.h
@@ -72,25 +72,48 @@ private:
   nsCOMPtr<nsIOutputStream> mPipeOutputStream;
   nsRefPtr<FetchDriverObserver> mObserver;
   nsCOMPtr<nsIInterfaceRequestor> mNotificationCallbacks;
   nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
   nsCOMPtr<nsIChannel> mOldRedirectChannel;
   nsCOMPtr<nsIChannel> mNewRedirectChannel;
   nsCOMPtr<nsIDocument> mDocument;
   uint32_t mFetchRecursionCount;
+  bool mCORSFlagEverSet;
 
   DebugOnly<bool> mResponseAvailableCalled;
 
   FetchDriver() = delete;
   FetchDriver(const FetchDriver&) = delete;
   FetchDriver& operator=(const FetchDriver&) = delete;
   ~FetchDriver();
 
+  enum MainFetchOpType
+  {
+    NETWORK_ERROR,
+    BASIC_FETCH,
+    HTTP_FETCH,
+    NUM_MAIN_FETCH_OPS
+  };
+
+  struct MainFetchOp
+  {
+    explicit MainFetchOp(MainFetchOpType aType, bool aCORSFlag = false,
+                         bool aCORSPreflightFlag = false)
+      : mType(aType), mCORSFlag(aCORSFlag),
+        mCORSPreflightFlag(aCORSPreflightFlag)
+    { }
+
+    MainFetchOpType mType;
+    bool mCORSFlag;
+    bool mCORSPreflightFlag;
+  };
+
   nsresult Fetch(bool aCORSFlag);
+  MainFetchOp SetTaintingAndGetNextOp(bool aCORSFlag);
   nsresult ContinueFetch(bool aCORSFlag);
   nsresult BasicFetch();
   nsresult HttpFetch(bool aCORSFlag = false, bool aCORSPreflightFlag = false, bool aAuthenticationFlag = false);
   nsresult ContinueHttpFetchAfterNetworkFetch();
   // Returns the filtered response sent to the observer.
   // Callers who don't have access to a channel can pass null for aFinalURI.
   already_AddRefed<InternalResponse>
   BeginAndGetFilteredResponse(InternalResponse* aResponse, nsIURI* aFinalURI);
--- a/dom/fetch/InternalRequest.cpp
+++ b/dom/fetch/InternalRequest.cpp
@@ -37,16 +37,17 @@ InternalRequest::GetRequestConstructorCo
   copy->mSameOriginDataURL = true;
   copy->mPreserveContentCodings = true;
   // The default referrer is already about:client.
 
   copy->mContentPolicyType = nsIContentPolicy::TYPE_FETCH;
   copy->mMode = mMode;
   copy->mCredentialsMode = mCredentialsMode;
   copy->mCacheMode = mCacheMode;
+  copy->mRedirectMode = mRedirectMode;
   copy->mCreatedByFetchEvent = mCreatedByFetchEvent;
   return copy.forget();
 }
 
 already_AddRefed<InternalRequest>
 InternalRequest::Clone()
 {
   nsRefPtr<InternalRequest> clone = new InternalRequest(*this);
@@ -75,16 +76,17 @@ InternalRequest::InternalRequest(const I
   , mURL(aOther.mURL)
   , mHeaders(new InternalHeaders(*aOther.mHeaders))
   , mContentPolicyType(aOther.mContentPolicyType)
   , mReferrer(aOther.mReferrer)
   , mMode(aOther.mMode)
   , mCredentialsMode(aOther.mCredentialsMode)
   , mResponseTainting(aOther.mResponseTainting)
   , mCacheMode(aOther.mCacheMode)
+  , mRedirectMode(aOther.mRedirectMode)
   , mAuthenticationFlag(aOther.mAuthenticationFlag)
   , mForceOriginHeader(aOther.mForceOriginHeader)
   , mPreserveContentCodings(aOther.mPreserveContentCodings)
   , mSameOriginDataURL(aOther.mSameOriginDataURL)
   , mSandboxedStorageAreaURLs(aOther.mSandboxedStorageAreaURLs)
   , mSkipServiceWorker(aOther.mSkipServiceWorker)
   , mSynchronous(aOther.mSynchronous)
   , mUnsafeRequest(aOther.mUnsafeRequest)
--- a/dom/fetch/InternalRequest.h
+++ b/dom/fetch/InternalRequest.h
@@ -87,26 +87,28 @@ class InternalRequest final
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalRequest)
 
   enum ResponseTainting
   {
     RESPONSETAINT_BASIC,
     RESPONSETAINT_CORS,
     RESPONSETAINT_OPAQUE,
+    RESPONSETAINT_OPAQUEREDIRECT,
   };
 
   explicit InternalRequest()
     : mMethod("GET")
     , mHeaders(new InternalHeaders(HeadersGuardEnum::None))
     , mReferrer(NS_LITERAL_STRING(kFETCH_CLIENT_REFERRER_STR))
     , mMode(RequestMode::No_cors)
     , mCredentialsMode(RequestCredentials::Omit)
     , mResponseTainting(RESPONSETAINT_BASIC)
     , mCacheMode(RequestCache::Default)
+    , mRedirectMode(RequestRedirect::Follow)
     , mAuthenticationFlag(false)
     , mForceOriginHeader(false)
     , mPreserveContentCodings(false)
       // FIXME(nsm): This should be false by default, but will lead to the
       // algorithm never loading data: URLs right now. See Bug 1018872 about
       // how certain contexts will override it to set it to true. Fetch
       // specification does not handle this yet.
     , mSameOriginDataURL(true)
@@ -259,16 +261,28 @@ public:
   }
 
   void
   SetCacheMode(RequestCache aCacheMode)
   {
     mCacheMode = aCacheMode;
   }
 
+  RequestRedirect
+  GetRedirectMode() const
+  {
+    return mRedirectMode;
+  }
+
+  void
+  SetRedirectMode(RequestRedirect aRedirectMode)
+  {
+    mRedirectMode = aRedirectMode;
+  }
+
   nsContentPolicyType
   ContentPolicyType() const
   {
     return mContentPolicyType;
   }
 
   void
   SetContentPolicyType(nsContentPolicyType aContentPolicyType);
@@ -385,16 +399,17 @@ private:
   // "about:client": client (default)
   // URL: an URL
   nsString mReferrer;
 
   RequestMode mMode;
   RequestCredentials mCredentialsMode;
   ResponseTainting mResponseTainting;
   RequestCache mCacheMode;
+  RequestRedirect mRedirectMode;
 
   bool mAuthenticationFlag;
   bool mForceOriginHeader;
   bool mPreserveContentCodings;
   bool mSameOriginDataURL;
   bool mSandboxedStorageAreaURLs;
   bool mSkipServiceWorker;
   bool mSynchronous;
--- a/dom/fetch/InternalResponse.cpp
+++ b/dom/fetch/InternalResponse.cpp
@@ -120,26 +120,35 @@ InternalResponse::StripFragmentAndSetUrl
 
 already_AddRefed<InternalResponse>
 InternalResponse::OpaqueResponse()
 {
   MOZ_ASSERT(!mWrappedResponse, "Can't OpaqueResponse a already wrapped response");
   nsRefPtr<InternalResponse> response = new InternalResponse(0, EmptyCString());
   response->mType = ResponseType::Opaque;
   response->mTerminationReason = mTerminationReason;
-  response->mURL = mURL;
   response->mChannelInfo = mChannelInfo;
   if (mPrincipalInfo) {
     response->mPrincipalInfo = MakeUnique<mozilla::ipc::PrincipalInfo>(*mPrincipalInfo);
   }
   response->mWrappedResponse = this;
   return response.forget();
 }
 
 already_AddRefed<InternalResponse>
+InternalResponse::OpaqueRedirectResponse()
+{
+  MOZ_ASSERT(!mWrappedResponse, "Can't OpaqueRedirectResponse a already wrapped response");
+  nsRefPtr<InternalResponse> response = OpaqueResponse();
+  response->mType = ResponseType::Opaqueredirect;
+  response->mURL = mURL;
+  return response.forget();
+}
+
+already_AddRefed<InternalResponse>
 InternalResponse::CreateIncompleteCopy()
 {
   nsRefPtr<InternalResponse> copy = new InternalResponse(mStatus, mStatusText);
   copy->mType = mType;
   copy->mTerminationReason = mTerminationReason;
   copy->mURL = mURL;
   copy->mChannelInfo = mChannelInfo;
   if (mPrincipalInfo) {
--- a/dom/fetch/InternalResponse.h
+++ b/dom/fetch/InternalResponse.h
@@ -44,29 +44,33 @@ public:
     response->mType = ResponseType::Error;
     return response.forget();
   }
 
   already_AddRefed<InternalResponse>
   OpaqueResponse();
 
   already_AddRefed<InternalResponse>
+  OpaqueRedirectResponse();
+
+  already_AddRefed<InternalResponse>
   BasicResponse();
 
   already_AddRefed<InternalResponse>
   CORSResponse();
 
   ResponseType
   Type() const
   {
     MOZ_ASSERT_IF(mType == ResponseType::Error, !mWrappedResponse);
     MOZ_ASSERT_IF(mType == ResponseType::Default, !mWrappedResponse);
     MOZ_ASSERT_IF(mType == ResponseType::Basic, mWrappedResponse);
     MOZ_ASSERT_IF(mType == ResponseType::Cors, mWrappedResponse);
     MOZ_ASSERT_IF(mType == ResponseType::Opaque, mWrappedResponse);
+    MOZ_ASSERT_IF(mType == ResponseType::Opaqueredirect, mWrappedResponse);
     return mType;
   }
 
   bool
   IsError() const
   {
     return Type() == ResponseType::Error;
   }
@@ -86,22 +90,42 @@ public:
   }
 
   uint16_t
   GetStatus() const
   {
     return mStatus;
   }
 
+  uint16_t
+  GetUnfilteredStatus() const
+  {
+    if (mWrappedResponse) {
+      return mWrappedResponse->GetStatus();
+    }
+
+    return GetStatus();
+  }
+
   const nsCString&
   GetStatusText() const
   {
     return mStatusText;
   }
 
+  const nsCString&
+  GetUnfilteredStatusText() const
+  {
+    if (mWrappedResponse) {
+      return mWrappedResponse->GetStatusText();
+    }
+
+    return GetStatusText();
+  }
+
   InternalHeaders*
   Headers()
   {
     return mHeaders;
   }
 
   InternalHeaders*
   UnfilteredHeaders()
@@ -109,35 +133,36 @@ public:
     if (mWrappedResponse) {
       return mWrappedResponse->Headers();
     };
 
     return Headers();
   }
 
   void
-  GetInternalBody(nsIInputStream** aStream)
+  GetUnfilteredBody(nsIInputStream** aStream)
   {
     if (mWrappedResponse) {
       MOZ_ASSERT(!mBody);
       return mWrappedResponse->GetBody(aStream);
     }
     nsCOMPtr<nsIInputStream> stream = mBody;
     stream.forget(aStream);
   }
 
   void
   GetBody(nsIInputStream** aStream)
   {
-    if (Type() == ResponseType::Opaque) {
+    if (Type() == ResponseType::Opaque ||
+        Type() == ResponseType::Opaqueredirect) {
       *aStream = nullptr;
       return;
     }
 
-    return GetInternalBody(aStream);
+    return GetUnfilteredBody(aStream);
   }
 
   void
   SetBody(nsIInputStream* aBody)
   {
     if (mWrappedResponse) {
       return mWrappedResponse->SetBody(aBody);
     }
--- a/dom/fetch/Request.cpp
+++ b/dom/fetch/Request.cpp
@@ -286,16 +286,20 @@ Request::Constructor(const GlobalObject&
 
   RequestCache cache = aInit.mCache.WasPassed() ?
                        aInit.mCache.Value() : fallbackCache;
   if (cache != RequestCache::EndGuard_) {
     request->ClearCreatedByFetchEvent();
     request->SetCacheMode(cache);
   }
 
+  if (aInit.mRedirect.WasPassed()) {
+    request->SetRedirectMode(aInit.mRedirect.Value());
+  }
+
   // Request constructor step 14.
   if (aInit.mMethod.WasPassed()) {
     nsAutoCString method(aInit.mMethod.Value());
     nsAutoCString upperCaseMethod = method;
     ToUpperCase(upperCaseMethod);
 
     // Step 14.1. Disallow forbidden methods, and anything that is not a HTTP
     // token, since HTTP states that Method may be any of the defined values or
--- a/dom/fetch/Request.h
+++ b/dom/fetch/Request.h
@@ -71,16 +71,22 @@ public:
   }
 
   RequestCache
   Cache() const
   {
     return mRequest->GetCacheMode();
   }
 
+  RequestRedirect
+  Redirect() const
+  {
+    return mRequest->GetRedirectMode();
+  }
+
   RequestContext
   Context() const
   {
     return mRequest->Context();
   }
 
   void
   SetContentPolicyType(nsContentPolicyType aContentPolicyType)
--- a/dom/fetch/Response.cpp
+++ b/dom/fetch/Response.cpp
@@ -157,22 +157,25 @@ Response::Constructor(const GlobalObject
   }
 
   nsRefPtr<InternalResponse> internalResponse =
     new InternalResponse(aInit.mStatus, statusText);
 
   // Grab a valid channel info from the global so this response is 'valid' for
   // interception.
   if (NS_IsMainThread()) {
+    ChannelInfo info;
     nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(global);
-    MOZ_ASSERT(window);
-    nsIDocument* doc = window->GetExtantDoc();
-    MOZ_ASSERT(doc);
-    ChannelInfo info;
-    info.InitFromDocument(doc);
+    if (window) {
+      nsIDocument* doc = window->GetExtantDoc();
+      MOZ_ASSERT(doc);
+      info.InitFromDocument(doc);
+    } else {
+      info.InitFromChromeGlobal(global);
+    }
     internalResponse->InitChannelInfo(info);
   } else {
     workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate();
     MOZ_ASSERT(worker);
     internalResponse->InitChannelInfo(worker->GetChannelInfo());
   }
 
   nsRefPtr<Response> r = new Response(global, internalResponse);
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -111,16 +111,44 @@ static PRLogModuleInfo* gMediaElementEve
 // This controls the b2g specific of pausing the media element when the
 // AudioChannel tells us to mute it.
 #define PAUSE_MEDIA_ELEMENT_FROM_AUDIOCHANNEL
 #endif
 
 using namespace mozilla::layers;
 using mozilla::net::nsMediaFragmentURIParser;
 
+class MOZ_STACK_CLASS AutoNotifyAudioChannelAgent
+{
+  nsRefPtr<mozilla::dom::HTMLMediaElement> mElement;
+  bool mShouldNotify;
+  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
+public:
+  AutoNotifyAudioChannelAgent(mozilla::dom::HTMLMediaElement* aElement,
+                              bool aNotify
+                              MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+    : mElement(aElement)
+    , mShouldNotify(aNotify)
+  {
+    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+    if (mShouldNotify) {
+      mElement->NotifyAudioChannelAgent(false);
+    }
+  }
+  ~AutoNotifyAudioChannelAgent()
+  {
+    if (mShouldNotify) {
+      // The audio channel agent is destroyed at this point.
+      if (mElement->MaybeCreateAudioChannelAgent()) {
+        mElement->NotifyAudioChannelAgent(true);
+      }
+    }
+  }
+};
+
 namespace mozilla {
 namespace dom {
 
 // Number of milliseconds between progress events as defined by spec
 static const uint32_t PROGRESS_MS = 350;
 
 // Number of milliseconds of no data before a stall event is fired as defined by spec
 static const uint32_t STALL_MS = 3000;
@@ -3184,16 +3212,24 @@ void HTMLMediaElement::ProcessMediaFragm
   }
 }
 
 void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo,
                                       nsAutoPtr<const MetadataTags> aTags)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  // If the element is gaining or losing an audio track, we need to notify
+  // the audio channel agent so that the correct audio-playback events will
+  // get dispatched.
+  bool audioTrackChanging = mMediaInfo.HasAudio() != aInfo->HasAudio();
+  AutoNotifyAudioChannelAgent autoNotify(this,
+                                         audioTrackChanging &&
+                                         mPlayingThroughTheAudioChannel);
+
   mMediaInfo = *aInfo;
   mIsEncrypted = aInfo->IsEncrypted()
 #ifdef MOZ_EME
                  || mPendingEncryptedInitData.IsEncrypted()
 #endif // MOZ_EME
                  ;
   mTags = aTags.forget();
   mLoadedDataFired = false;
@@ -4483,17 +4519,35 @@ nsresult HTMLMediaElement::UpdateChannel
   }
 
 #ifdef PAUSE_MEDIA_ELEMENT_FROM_AUDIOCHANNEL
   SuspendOrResumeElement(ComputedMuted(), false);
 #endif
   return NS_OK;
 }
 
-void HTMLMediaElement::UpdateAudioChannelPlayingState()
+bool
+HTMLMediaElement::MaybeCreateAudioChannelAgent()
+{
+  if (!mAudioChannelAgent) {
+    nsresult rv;
+    mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1", &rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return false;
+    }
+    MOZ_ASSERT(mAudioChannelAgent);
+    mAudioChannelAgent->InitWithWeakCallback(OwnerDoc()->GetInnerWindow(),
+                                             static_cast<int32_t>(mAudioChannel),
+                                             this);
+  }
+  return true;
+}
+
+void
+HTMLMediaElement::UpdateAudioChannelPlayingState()
 {
   bool playingThroughTheAudioChannel =
      (!mPaused &&
       !Muted() &&
       std::fabs(Volume()) > 1e-7 &&
       (HasAttr(kNameSpaceID_None, nsGkAtoms::loop) ||
        (mReadyState >= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
         !IsPlaybackEnded()) ||
@@ -4501,28 +4555,19 @@ void HTMLMediaElement::UpdateAudioChanne
   if (playingThroughTheAudioChannel != mPlayingThroughTheAudioChannel) {
     mPlayingThroughTheAudioChannel = playingThroughTheAudioChannel;
 
     // If we are not playing, we don't need to create a new audioChannelAgent.
     if (!mAudioChannelAgent && !mPlayingThroughTheAudioChannel) {
        return;
     }
 
-    if (!mAudioChannelAgent) {
-      nsresult rv;
-      mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1", &rv);
-      if (!mAudioChannelAgent) {
-        return;
-      }
-      mAudioChannelAgent->InitWithWeakCallback(OwnerDoc()->GetInnerWindow(),
-                                               static_cast<int32_t>(mAudioChannel),
-                                               this);
+    if (MaybeCreateAudioChannelAgent()) {
+      NotifyAudioChannelAgent(mPlayingThroughTheAudioChannel);
     }
-
-    NotifyAudioChannelAgent(mPlayingThroughTheAudioChannel);
   }
 }
 
 void
 HTMLMediaElement::NotifyAudioChannelAgent(bool aPlaying)
 {
   // Immediately check if this should go to the MSG instead of the normal
   // media playback route.
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -30,20 +30,16 @@
 #undef CurrentTime
 #endif
 
 #include "mozilla/dom/HTMLMediaElementBinding.h"
 
 // Define to output information on decoding and painting framerate
 /* #define DEBUG_FRAME_RATE 1 */
 
-class nsIChannel;
-class nsIHttpChannel;
-class nsILoadGroup;
-
 typedef uint16_t nsMediaNetworkState;
 typedef uint16_t nsMediaReadyState;
 
 namespace mozilla {
 class ErrorResult;
 class MediaResource;
 class MediaDecoder;
 class VideoFrameContainer;
@@ -51,19 +47,23 @@ namespace dom {
 class MediaKeys;
 class TextTrack;
 class TimeRanges;
 class WakeLock;
 class MediaTrack;
 } // namespace dom
 } // namespace mozilla
 
+class AutoNotifyAudioChannelAgent;
+class nsIChannel;
+class nsIHttpChannel;
+class nsILoadGroup;
+class nsIRunnable;
 class nsITimer;
 class nsRange;
-class nsIRunnable;
 
 namespace mozilla {
 namespace dom {
 
 // Number of milliseconds between timeupdate events as defined by spec
 #define TIMEUPDATE_MS 250
 
 class MediaError;
@@ -73,16 +73,18 @@ class AudioTrackList;
 class VideoTrackList;
 
 class HTMLMediaElement : public nsGenericHTMLElement,
                          public nsIDOMHTMLMediaElement,
                          public nsIObserver,
                          public MediaDecoderOwner,
                          public nsIAudioChannelAgentCallback
 {
+  friend AutoNotifyAudioChannelAgent;
+
 public:
   typedef mozilla::TimeStamp TimeStamp;
   typedef mozilla::layers::ImageContainer ImageContainer;
   typedef mozilla::VideoFrameContainer VideoFrameContainer;
   typedef mozilla::MediaStream MediaStream;
   typedef mozilla::MediaResource MediaResource;
   typedef mozilla::MediaDecoderOwner MediaDecoderOwner;
   typedef mozilla::MetadataTags MetadataTags;
@@ -1045,16 +1047,20 @@ protected:
   TextTrackManager* GetOrCreateTextTrackManager();
 
   // Recomputes ready state and fires events as necessary based on current state.
   void UpdateReadyStateInternal();
 
   // Notifies the audio channel agent when the element starts or stops playing.
   void NotifyAudioChannelAgent(bool aPlaying);
 
+  // Creates the audio channel agent if needed.  Returns true if the audio
+  // channel agent is ready to be used.
+  bool MaybeCreateAudioChannelAgent();
+
   class nsAsyncEventRunner;
   using nsGenericHTMLElement::DispatchEvent;
   // For nsAsyncEventRunner.
   nsresult DispatchEvent(const nsAString& aName);
 
   // The current decoder. Load() has been called on this decoder.
   // At most one of mDecoder and mSrcStream can be non-null.
   nsRefPtr<MediaDecoder> mDecoder;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -37,16 +37,19 @@
 #include "mozilla/dom/DataStoreService.h"
 #include "mozilla/dom/DataTransfer.h"
 #include "mozilla/dom/DOMStorageIPC.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/ExternalHelperAppParent.h"
 #include "mozilla/dom/FileSystemRequestParent.h"
 #include "mozilla/dom/GeolocationBinding.h"
+#ifdef MOZ_EME
+#include "mozilla/dom/MediaKeySystemAccess.h"
+#endif
 #include "mozilla/dom/NuwaParent.h"
 #include "mozilla/dom/PContentBridgeParent.h"
 #include "mozilla/dom/PContentPermissionRequestParent.h"
 #include "mozilla/dom/PCycleCollectWithLogsParent.h"
 #include "mozilla/dom/PFMRadioParent.h"
 #include "mozilla/dom/PMemoryReportRequestParent.h"
 #include "mozilla/dom/ServiceWorkerRegistrar.h"
 #include "mozilla/dom/bluetooth/PBluetoothParent.h"
@@ -1094,16 +1097,33 @@ ContentParent::RecvGetGMPPluginVersionFo
                                              nsCString* aVersion)
 {
     return GMPServiceParent::RecvGetGMPPluginVersionForAPI(aAPI, Move(aTags),
                                                            aHasVersion,
                                                            aVersion);
 }
 
 bool
+ContentParent::RecvIsGMPPresentOnDisk(const nsString& aKeySystem,
+                                      const nsCString& aVersion,
+                                      bool* aIsPresent,
+                                      nsCString* aMessage)
+{
+#ifdef MOZ_EME
+    *aIsPresent = MediaKeySystemAccess::IsGMPPresentOnDisk(aKeySystem,
+                                                           aVersion,
+                                                           *aMessage);
+#else
+    *aIsPresent = false;
+#endif
+
+    return true;
+}
+
+bool
 ContentParent::RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv, uint32_t* aRunID)
 {
     *aRv = NS_OK;
     return mozilla::plugins::SetupBridge(aPluginId, this, false, aRv, aRunID);
 }
 
 bool
 ContentParent::RecvConnectPluginBridge(const uint32_t& aPluginId, nsresult* aRv)
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -171,16 +171,20 @@ public:
                                         TabId* aTabId) override;
     virtual bool RecvBridgeToChildProcess(const ContentParentId& aCpId) override;
 
     virtual bool RecvCreateGMPService() override;
     virtual bool RecvGetGMPPluginVersionForAPI(const nsCString& aAPI,
                                                nsTArray<nsCString>&& aTags,
                                                bool* aHasPlugin,
                                                nsCString* aVersion) override;
+    virtual bool RecvIsGMPPresentOnDisk(const nsString& aKeySystem,
+                                        const nsCString& aVersion,
+                                        bool* aIsPresent,
+                                        nsCString* aMessage) override;
 
     virtual bool RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv, uint32_t* aRunID) override;
     virtual bool RecvConnectPluginBridge(const uint32_t& aPluginId, nsresult* aRv) override;
     virtual bool RecvGetBlocklistState(const uint32_t& aPluginId, uint32_t* aIsBlocklisted) override;
     virtual bool RecvFindPlugins(const uint32_t& aPluginEpoch,
                                  nsTArray<PluginTag>* aPlugins,
                                  uint32_t* aNewPluginEpoch) override;
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -691,16 +691,18 @@ parent:
                             ProcessPriority priority,
                             TabId openerTabId)
         returns (ContentParentId cpId, bool isForApp, bool isForBrowser, TabId tabId);
     sync BridgeToChildProcess(ContentParentId cpId);
 
     async CreateGMPService();
     sync GetGMPPluginVersionForAPI(nsCString api, nsCString[] tags)
         returns (bool hasPlugin, nsCString version);
+    sync IsGMPPresentOnDisk(nsString keySystem, nsCString version)
+        returns (bool isPresent, nsCString message);
 
     /**
      * This call connects the content process to a plugin process. While this
      * call runs, a new PluginModuleParent will be created in the ContentChild
      * via bridging. The corresponding PluginModuleChild will live in the plugin
      * process.
      */
     sync LoadPlugin(uint32_t aPluginId) returns (nsresult aResult, uint32_t aRunID);
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -168,10 +168,12 @@ InterceptionFailed=ServiceWorker network
 # LOCALIZATION NOTE: Do not translate "ServiceWorker", "FetchEvent.respondWith()", "opaque", or "Response".
 OpaqueInterceptionDisabled=A ServiceWorker passed an opaque Response to FetchEvent.respondWith() while opaque interception is disabled.
 # LOCALIZATION NOTE: Do not translate "ServiceWorker", "FetchEvent.respondWith()", "FetchEvent.request.type", "same-origin", "cors", "no-cors", "opaque", "Response", or "RequestMode".
 BadOpaqueInterceptionRequestMode=A ServiceWorker passed an opaque Response to FetchEvent.respondWith() while the FetchEvent.request.type was either "same-origin" or "cors". Opaque Response objects are only valid when the RequestMode is "no-cors".
 # LOCALIZATION NOTE: Do not translate "ServiceWorker", "Error", "Response", "FetchEvent.respondWith()", or "fetch()".
 InterceptedErrorResponse=A ServiceWorker passed an Error Response to FetchEvent.respondWith(). This typically means the ServiceWorker performed an invalid fetch() call.
 # LOCALIZATION NOTE: Do not translate "ServiceWorker", "Response", "FetchEvent.respondWith()", or "Response.clone()".
 InterceptedUsedResponse=A ServiceWorker passed a used Response to FetchEvent.respondWith(). The body of a Response may only be read once. Use Response.clone() to access the body multiple times.
-# LOCALIZATION NOTE: Do not translate "ServiceWorker", "Response", "FetchEvent.respondWith()", "FetchEvent.request", or "Worker".
+# LOCALIZATION NOTE: Do not translate "ServiceWorker", "opaque", "Response", "FetchEvent.respondWith()", "FetchEvent.request", or "Worker".
 ClientRequestOpaqueInterception=A ServiceWorker passed an opaque Response to FetchEvent.respondWith() while FetchEvent.request was a client request. A client request is generally a browser navigation or top-level Worker script.
+# LOCALIZATION NOTE: Do not translate "ServiceWorker", "opaqueredirect", "Response", "FetchEvent.respondWith()", or "FetchEvent.request".
+BadOpaqueRedirectInterception=A ServiceWorker passed an opaqueredirect Response to FetchEvent.respondWith() while FetchEvent.request was not a navigation request.
--- a/dom/media/eme/GMPVideoDecoderTrialCreator.cpp
+++ b/dom/media/eme/GMPVideoDecoderTrialCreator.cpp
@@ -72,34 +72,62 @@ GMPVideoDecoderTrialCreator::GetCreateTr
   switch (Preferences::GetInt(pref, (int)Pending)) {
     case 0: return Pending;
     case 1: return Succeeded;
     case 2: return Failed;
     default: return Pending;
   }
 }
 
+/* static */ void
+GMPVideoDecoderTrialCreator::UpdateTrialCreateState(const nsAString& aKeySystem,
+                                                    uint32_t aState)
+{
+  UpdateTrialCreateState(aKeySystem, (TrialCreateState)aState);
+}
+
+/* static */ void
+GMPVideoDecoderTrialCreator::UpdateTrialCreateState(const nsAString& aKeySystem,
+                                                    TrialCreateState aState)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (XRE_GetProcessType() == GeckoProcessType_Content) {
+    // Pref has to be set from the chrome process. Dispatch to chrome via
+    // GMPService.
+    nsCOMPtr<mozIGeckoMediaPluginService> service =
+      do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+    NS_ENSURE_TRUE_VOID(service);
+
+    service->UpdateTrialCreateState(aKeySystem, (uint32_t)aState);
+    return;
+  }
+
+  const char* pref = TrialCreatePrefName(aKeySystem);
+  if (pref) {
+    Preferences::SetInt(pref, (int)aState);
+  }
+}
+
 void
 GMPVideoDecoderTrialCreator::TrialCreateGMPVideoDecoderFailed(const nsAString& aKeySystem,
                                                               const nsACString& aReason)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   EME_LOG("GMPVideoDecoderTrialCreator::TrialCreateGMPVideoDecoderFailed(%s)",
           NS_ConvertUTF16toUTF8(aKeySystem).get());
 
   TrialCreateData* data = mTestCreate.Get(aKeySystem);
   if (!data) {
     return;
   }
   data->mStatus = Failed;
-  const char* pref = TrialCreatePrefName(aKeySystem);
-  if (pref) {
-    Preferences::SetInt(pref, (int)Failed);
-  }
+  UpdateTrialCreateState(aKeySystem, Failed);
+
   for (nsRefPtr<AbstractPromiseLike>& promise: data->mPending) {
     promise->Reject(NS_ERROR_DOM_NOT_SUPPORTED_ERR, aReason);
   }
   data->mPending.Clear();
   data->mTest = nullptr;
 }
 
 void
@@ -110,20 +138,18 @@ GMPVideoDecoderTrialCreator::TrialCreate
   EME_LOG("GMPVideoDecoderTrialCreator::TrialCreateGMPVideoDecoderSucceeded(%s)",
     NS_ConvertUTF16toUTF8(aKeySystem).get());
 
   TrialCreateData* data = mTestCreate.Get(aKeySystem);
   if (!data) {
     return;
   }
   data->mStatus = Succeeded;
-  const char* pref = TrialCreatePrefName(aKeySystem);
-  if (pref) {
-    Preferences::SetInt(pref, (int)Succeeded);
-  }
+  UpdateTrialCreateState(aKeySystem, Succeeded);
+
   for (nsRefPtr<AbstractPromiseLike>& promise : data->mPending) {
     promise->Resolve();
   }
   data->mPending.Clear();
   data->mTest = nullptr;
 }
 
 nsresult
@@ -479,36 +505,30 @@ TestGMPVideoDecoder::CreateGMPVideoDecod
 {
   MOZ_ASSERT(OnGMPThread());
 
   nsTArray<nsCString> tags;
   tags.AppendElement(NS_LITERAL_CSTRING("h264"));
   tags.AppendElement(NS_ConvertUTF16toUTF8(mKeySystem));
 
   UniquePtr<GetGMPVideoDecoderCallback> callback(new Callback(this));
-  nsCString fakeNodeId;
-  if (NS_FAILED(GenerateRandomName(fakeNodeId, 32)) ||
-      NS_FAILED(mGMPService->GetGMPVideoDecoder(&tags, fakeNodeId, Move(callback)))) {
+  if (NS_FAILED(mGMPService->GetGMPVideoDecoder(&tags,
+                                                NS_LITERAL_CSTRING("fakeNodeId1234567890fakeNodeId12"),
+                                                Move(callback)))) {
     ReportFailure(NS_LITERAL_CSTRING("TestGMPVideoDecoder GMPService GetGMPVideoDecoder returned failure"));
   }
 }
 
 void
 GMPVideoDecoderTrialCreator::MaybeAwaitTrialCreate(const nsAString& aKeySystem,
                                                    AbstractPromiseLike* aPromisey,
                                                    nsPIDOMWindow* aParent)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (XRE_GetProcessType() == GeckoProcessType_Content) {
-    // Currently broken with e10s...
-    aPromisey->Resolve();
-    return;
-  }
-
   if (!mTestCreate.Contains(aKeySystem)) {
     mTestCreate.Put(aKeySystem, new TrialCreateData(aKeySystem));
   }
   TrialCreateData* data = mTestCreate.Get(aKeySystem);
   MOZ_ASSERT(data);
 
   switch (data->mStatus) {
     case TrialCreateState::Succeeded: {
--- a/dom/media/eme/GMPVideoDecoderTrialCreator.h
+++ b/dom/media/eme/GMPVideoDecoderTrialCreator.h
@@ -34,16 +34,18 @@ public:
                              MediaKeySystemAccess* aAccess,
                              T* aPromiseLike,
                              nsPIDOMWindow* aParent)
   {
     nsRefPtr<PromiseLike<T>> p(new PromiseLike<T>(aPromiseLike, aAccess));
     MaybeAwaitTrialCreate(aKeySystem, p, aParent);
   }
 
+  static void UpdateTrialCreateState(const nsAString& aKeySystem, uint32_t aState);
+
 private:
 
   class AbstractPromiseLike {
   public:
     NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractPromiseLike);
 
     virtual void Resolve() = 0;
     virtual void Reject(nsresult aResult, const nsACString& aMessage) = 0;
@@ -83,16 +85,18 @@ private:
   // Note: Keep this in sync with GetCreateTrialState.
   enum TrialCreateState {
     Pending = 0,
     Succeeded = 1,
     Failed = 2,
   };
 
   static TrialCreateState GetCreateTrialState(const nsAString& aKeySystem);
+  static void UpdateTrialCreateState(const nsAString& aKeySystem,
+                                     TrialCreateState aState);
 
   struct TrialCreateData {
     explicit TrialCreateData(const nsAString& aKeySystem)
       : mKeySystem(aKeySystem)
       , mStatus(GetCreateTrialState(aKeySystem))
     {}
     ~TrialCreateData() {}
     const nsString mKeySystem;
@@ -167,9 +171,9 @@ private:
   GMPVideoDecoderProxy* mGMP;
   GMPVideoHost* mHost;
   bool mReceivedDecoded;
 };
 
 } // namespace dom
 } // namespace mozilla
 
-#endif
\ No newline at end of file
+#endif
--- a/dom/media/eme/MediaKeySystemAccess.cpp
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -1,14 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/ContentChild.h"
 #include "mozilla/dom/MediaKeySystemAccess.h"
 #include "mozilla/dom/MediaKeySystemAccessBinding.h"
 #include "mozilla/Preferences.h"
 #include "nsContentTypeParser.h"
 #ifdef MOZ_FMP4
 #include "MP4Decoder.h"
 #endif
 #ifdef XP_WIN
@@ -105,20 +106,17 @@ HaveGMPFor(mozIGeckoMediaPluginService* 
   return hasPlugin;
 }
 
 #ifdef XP_WIN
 static bool
 AdobePluginFileExists(const nsACString& aVersionStr,
                       const nsAString& aFilename)
 {
-  if (XRE_GetProcessType() != GeckoProcessType_Default) {
-    NS_WARNING("AdobePluginFileExists() lying because it doesn't work with e10s");
-    return true;
-  }
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
 
   nsCOMPtr<nsIFile> path;
   nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(path));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
   }
 
   rv = path->Append(NS_LITERAL_STRING("gmp-eme-adobe"));
@@ -148,16 +146,71 @@ AdobePluginDLLExists(const nsACString& a
 
 static bool
 AdobePluginVoucherExists(const nsACString& aVersionStr)
 {
   return AdobePluginFileExists(aVersionStr, NS_LITERAL_STRING("eme-adobe.voucher"));
 }
 #endif
 
+/* static */ bool
+MediaKeySystemAccess::IsGMPPresentOnDisk(const nsAString& aKeySystem,
+                                         const nsACString& aVersion,
+                                         nsACString& aOutMessage)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (XRE_GetProcessType() != GeckoProcessType_Default) {
+    // We need to be able to access the filesystem, so call this in the
+    // main process via ContentChild.
+    ContentChild* contentChild = ContentChild::GetSingleton();
+    if (NS_WARN_IF(!contentChild)) {
+      return false;
+    }
+
+    nsCString message;
+    bool result = false;
+    bool ok = contentChild->SendIsGMPPresentOnDisk(nsString(aKeySystem), nsCString(aVersion),
+                                                   &result, &message);
+    aOutMessage = message;
+    return ok && result;
+  }
+
+  bool isPresent = true;
+
+#if XP_WIN
+  if (aKeySystem.EqualsLiteral("com.adobe.primetime")) {
+    if (!AdobePluginDLLExists(aVersion)) {
+      NS_WARNING("Adobe EME plugin disappeared from disk!");
+      aOutMessage = NS_LITERAL_CSTRING("Adobe DLL was expected to be on disk but was not");
+      isPresent = false;
+    }
+    if (!AdobePluginVoucherExists(aVersion)) {
+      NS_WARNING("Adobe EME voucher disappeared from disk!");
+      aOutMessage = NS_LITERAL_CSTRING("Adobe plugin voucher was expected to be on disk but was not");
+      isPresent = false;
+    }
+
+    if (!isPresent) {
+      // Reset the prefs that Firefox's GMP downloader sets, so that
+      // Firefox will try to download the plugin next time the updater runs.
+      Preferences::ClearUser("media.gmp-eme-adobe.lastUpdate");
+      Preferences::ClearUser("media.gmp-eme-adobe.version");
+    } else if (!EMEVoucherFileExists()) {
+      // Gecko doesn't have a voucher file for the plugin-container.
+      // Adobe EME isn't going to work, so don't advertise that it will.
+      aOutMessage = NS_LITERAL_CSTRING("Plugin-container voucher not present");
+      isPresent = false;
+    }
+  }
+#endif
+
+  return isPresent;
+}
+
 static MediaKeySystemStatus
 EnsureMinCDMVersion(mozIGeckoMediaPluginService* aGMPService,
                     const nsAString& aKeySystem,
                     int32_t aMinCdmVersion,
                     nsACString& aOutMessage,
                     nsACString& aOutCdmVersion)
 {
   nsTArray<nsCString> tags;
@@ -174,39 +227,19 @@ EnsureMinCDMVersion(mozIGeckoMediaPlugin
 
   aOutCdmVersion = versionStr;
 
   if (!hasPlugin) {
     aOutMessage = NS_LITERAL_CSTRING("CDM is not installed");
     return MediaKeySystemStatus::Cdm_not_installed;
   }
 
-#ifdef XP_WIN
-  if (aKeySystem.EqualsLiteral("com.adobe.primetime")) {
-    // Verify that anti-virus hasn't "helpfully" deleted the Adobe GMP DLL,
-    // as we suspect may happen (Bug 1160382).
-    bool somethingMissing = false;
-    if (!AdobePluginDLLExists(versionStr)) {
-      aOutMessage = NS_LITERAL_CSTRING("Adobe DLL was expected to be on disk but was not");
-      somethingMissing = true;
-    }
-    if (!AdobePluginVoucherExists(versionStr)) {
-      aOutMessage = NS_LITERAL_CSTRING("Adobe plugin voucher was expected to be on disk but was not");
-      somethingMissing = true;
-    }
-    if (somethingMissing) {
-      NS_WARNING("Adobe EME plugin or voucher disappeared from disk!");
-      // Reset the prefs that Firefox's GMP downloader sets, so that
-      // Firefox will try to download the plugin next time the updater runs.
-      Preferences::ClearUser("media.gmp-eme-adobe.lastUpdate");
-      Preferences::ClearUser("media.gmp-eme-adobe.version");
-      return MediaKeySystemStatus::Cdm_not_installed;
-    }
+  if (!MediaKeySystemAccess::IsGMPPresentOnDisk(aKeySystem, versionStr, aOutMessage)) {
+    return MediaKeySystemStatus::Cdm_not_installed;
   }
-#endif
 
   nsresult rv;
   int32_t version = versionStr.ToInteger(&rv);
   if (aMinCdmVersion != NO_CDM_VERSION &&
       (NS_FAILED(rv) || version < 0 || aMinCdmVersion > version)) {
     aOutMessage = NS_LITERAL_CSTRING("Installed CDM version insufficient");
     return MediaKeySystemStatus::Cdm_insufficient_version;
   }
@@ -251,22 +284,16 @@ MediaKeySystemAccess::GetKeySystemStatus
     }
 #endif
 #ifdef XP_MACOSX
     if (!nsCocoaFeatures::OnLionOrLater()) {
       aOutMessage = NS_LITERAL_CSTRING("Minimum MacOSX version not met for Adobe EME");
       return MediaKeySystemStatus::Cdm_not_supported;
     }
 #endif
-    if (!EMEVoucherFileExists()) {
-      // Gecko doesn't have a voucher file for the plugin-container.
-      // Adobe EME isn't going to work, so don't advertise that it will.
-      aOutMessage = NS_LITERAL_CSTRING("Plugin-container voucher not present");
-      return MediaKeySystemStatus::Cdm_not_supported;
-    }
     return EnsureMinCDMVersion(mps, aKeySystem, aMinCdmVersion, aOutMessage, aOutCdmVersion);
   }
 #endif
 
   return MediaKeySystemStatus::Cdm_not_supported;
 }
 
 static bool
--- a/dom/media/eme/MediaKeySystemAccess.h
+++ b/dom/media/eme/MediaKeySystemAccess.h
@@ -54,16 +54,20 @@ public:
 
   static bool IsSupported(const nsAString& aKeySystem,
                           const Sequence<MediaKeySystemOptions>& aOptions);
 
   static void NotifyObservers(nsIDOMWindow* aWindow,
                               const nsAString& aKeySystem,
                               MediaKeySystemStatus aStatus);
 
+  static bool IsGMPPresentOnDisk(const nsAString& aKeySystem,
+                                 const nsACString& aVersion,
+                                 nsACString& aOutMessage);
+
 private:
   nsCOMPtr<nsPIDOMWindow> mParent;
   const nsString mKeySystem;
   const nsString mCDMVersion;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/eme/moz.build
+++ b/dom/media/eme/moz.build
@@ -36,9 +36,11 @@ UNIFIED_SOURCES += [
     'MediaKeyMessageEvent.cpp',
     'MediaKeys.cpp',
     'MediaKeySession.cpp',
     'MediaKeyStatusMap.cpp',
     'MediaKeySystemAccess.cpp',
     'MediaKeySystemAccessManager.cpp',
 ]
 
+include('/ipc/chromium/chromium-config.mozbuild')
+
 FINAL_LIBRARY = 'xul'
--- a/dom/media/gmp/GMPChild.cpp
+++ b/dom/media/gmp/GMPChild.cpp
@@ -428,17 +428,17 @@ GMPChild::GetUTF8LibPath(nsACString& aOu
   libFile->GetPath(path);
   aOutLibPath = NS_ConvertUTF16toUTF8(path);
 
   return true;
 #endif
 }
 
 bool
-GMPChild::RecvStartPlugin()
+GMPChild::AnswerStartPlugin()
 {
   LOGD("%s", __FUNCTION__);
 
 #if defined(XP_WIN)
   PreLoadLibraries(mPluginPath);
 #endif
   if (!PreLoadPluginVoucher()) {
     NS_WARNING("Plugin voucher failed to load!");
--- a/dom/media/gmp/GMPChild.h
+++ b/dom/media/gmp/GMPChild.h
@@ -51,17 +51,17 @@ private:
   friend class GMPContentChild;
 
   bool PreLoadPluginVoucher();
   void PreLoadSandboxVoucher();
 
   bool GetUTF8LibPath(nsACString& aOutLibPath);
 
   virtual bool RecvSetNodeId(const nsCString& aNodeId) override;
-  virtual bool RecvStartPlugin() override;
+  virtual bool AnswerStartPlugin() override;
 
   virtual PCrashReporterChild* AllocPCrashReporterChild(const NativeThreadId& aThread) override;
   virtual bool DeallocPCrashReporterChild(PCrashReporterChild*) override;
 
   virtual PGMPTimerChild* AllocPGMPTimerChild() override;
   virtual bool DeallocPGMPTimerChild(PGMPTimerChild* aActor) override;
 
   virtual PGMPStorageChild* AllocPGMPStorageChild() override;
--- a/dom/media/gmp/GMPParent.cpp
+++ b/dom/media/gmp/GMPParent.cpp
@@ -159,27 +159,24 @@ GMPParent::LoadProcess()
       mProcess = nullptr;
       return NS_ERROR_FAILURE;
     }
     LOGD("%s: Opened channel to new child process", __FUNCTION__);
 
     bool ok = SendSetNodeId(mNodeId);
     if (!ok) {
       LOGD("%s: Failed to send node id to child process", __FUNCTION__);
-      mProcess->Delete();
-      mProcess = nullptr;
       return NS_ERROR_FAILURE;
     }
     LOGD("%s: Sent node id to child process", __FUNCTION__);
 
-    ok = SendStartPlugin();
+    // Intr call to block initialization on plugin load.
+    ok = CallStartPlugin();
     if (!ok) {
       LOGD("%s: Failed to send start to child process", __FUNCTION__);
-      mProcess->Delete();
-      mProcess = nullptr;
       return NS_ERROR_FAILURE;
     }
     LOGD("%s: Sent StartPlugin to child process", __FUNCTION__);
   }
 
   mState = GMPStateLoaded;
 
   // Hold a self ref while the child process is alive. This ensures that
--- a/dom/media/gmp/GMPServiceChild.cpp
+++ b/dom/media/gmp/GMPServiceChild.cpp
@@ -177,16 +177,50 @@ GeckoMediaPluginServiceChild::GetNodeId(
 {
   UniquePtr<GetServiceChildCallback> callback(
     new GetNodeIdDone(aOrigin, aTopLevelOrigin, aInPrivateBrowsing, Move(aCallback)));
   GetServiceChild(Move(callback));
   return NS_OK;
 }
 
 NS_IMETHODIMP
+GeckoMediaPluginServiceChild::UpdateTrialCreateState(const nsAString& aKeySystem,
+                                                     uint32_t aState)
+{
+  if (NS_GetCurrentThread() != mGMPThread) {
+    mGMPThread->Dispatch(NS_NewRunnableMethodWithArgs<nsString, uint32_t>(
+      this, &GeckoMediaPluginServiceChild::UpdateTrialCreateState,
+      aKeySystem, aState), NS_DISPATCH_NORMAL);
+    return NS_OK;
+  }
+
+  class Callback : public GetServiceChildCallback
+  {
+  public:
+    Callback(const nsAString& aKeySystem, uint32_t aState)
+      : mKeySystem(aKeySystem)
+      , mState(aState)
+    { }
+
+    virtual void Done(GMPServiceChild* aService) override
+    {
+      aService->SendUpdateGMPTrialCreateState(mKeySystem, mState);
+    }
+
+  private:
+    nsString mKeySystem;
+    uint32_t mState;
+  };
+
+  UniquePtr<GetServiceChildCallback> callback(new Callback(aKeySystem, aState));
+  GetServiceChild(Move(callback));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 GeckoMediaPluginServiceChild::Observe(nsISupports* aSubject,
                                       const char* aTopic,
                                       const char16_t* aSomeData)
 {
   LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, aTopic));
   if (!strcmp(NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, aTopic)) {
     if (mServiceChild) {
       mozilla::SyncRunnable::DispatchToThread(mGMPThread,
--- a/dom/media/gmp/GMPServiceChild.h
+++ b/dom/media/gmp/GMPServiceChild.h
@@ -44,16 +44,18 @@ public:
   NS_IMETHOD GetPluginVersionForAPI(const nsACString& aAPI,
                                     nsTArray<nsCString>* aTags,
                                     bool* aHasPlugin,
                                     nsACString& aOutVersion) override;
   NS_IMETHOD GetNodeId(const nsAString& aOrigin,
                        const nsAString& aTopLevelOrigin,
                        bool aInPrivateBrowsingMode,
                        UniquePtr<GetNodeIdCallback>&& aCallback) override;
+  NS_IMETHOD UpdateTrialCreateState(const nsAString& aKeySystem,
+                                    uint32_t aState) override;
 
   NS_DECL_NSIOBSERVER
 
   void SetServiceChild(UniquePtr<GMPServiceChild>&& aServiceChild);
 
   void RemoveGMPContentParent(GMPContentParent* aGMPContentParent);
 
 protected:
--- a/dom/media/gmp/GMPServiceParent.cpp
+++ b/dom/media/gmp/GMPServiceParent.cpp
@@ -4,16 +4,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "GMPServiceParent.h"
 #include "GMPService.h"
 #include "prio.h"
 #include "mozilla/Logging.h"
 #include "GMPParent.h"
 #include "GMPVideoDecoderParent.h"
+#ifdef MOZ_EME
+#include "mozilla/dom/GMPVideoDecoderTrialCreator.h"
+#endif
 #include "nsIObserverService.h"
 #include "GeckoChildProcessHost.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/SyncRunnable.h"
 #include "nsXPCOMPrivate.h"
 #include "mozilla/Services.h"
 #include "nsNativeCharsetUtils.h"
@@ -1197,16 +1200,32 @@ GeckoMediaPluginServiceParent::GetNodeId
                                          UniquePtr<GetNodeIdCallback>&& aCallback)
 {
   nsCString nodeId;
   nsresult rv = GetNodeId(aOrigin, aTopLevelOrigin, aInPrivateBrowsing, nodeId);
   aCallback->Done(rv, nodeId);
   return rv;
 }
 
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::UpdateTrialCreateState(const nsAString& aKeySystem,
+                                                      uint32_t aState)
+{
+#ifdef MOZ_EME
+  nsString keySystem(aKeySystem);
+  NS_DispatchToMainThread(NS_NewRunnableFunction([keySystem, aState] {
+    mozilla::dom::GMPVideoDecoderTrialCreator::UpdateTrialCreateState(keySystem, aState);
+  }));
+
+  return NS_OK;
+#else
+  return NS_ERROR_FAILURE;
+#endif
+}
+
 static bool
 ExtractHostName(const nsACString& aOrigin, nsACString& aOutData)
 {
   nsCString str;
   str.Assign(aOrigin);
   int begin = str.Find("://");
   // The scheme is missing!
   if (begin == -1) {
@@ -1566,16 +1585,24 @@ GMPServiceParent::RecvGetGMPNodeId(const
                                    const bool& aInPrivateBrowsing,
                                    nsCString* aID)
 {
   nsresult rv = mService->GetNodeId(aOrigin, aTopLevelOrigin,
                                     aInPrivateBrowsing, *aID);
   return NS_SUCCEEDED(rv);
 }
 
+bool
+GMPServiceParent::RecvUpdateGMPTrialCreateState(const nsString& aKeySystem,
+                                                const uint32_t& aState)
+{
+  mService->UpdateTrialCreateState(aKeySystem, aState);
+  return true;
+}
+
 /* static */
 bool
 GMPServiceParent::RecvGetGMPPluginVersionForAPI(const nsCString& aAPI,
                                                 nsTArray<nsCString>&& aTags,
                                                 bool* aHasPlugin,
                                                 nsCString* aVersion)
 {
   nsRefPtr<GeckoMediaPluginServiceParent> service =
--- a/dom/media/gmp/GMPServiceParent.h
+++ b/dom/media/gmp/GMPServiceParent.h
@@ -38,16 +38,18 @@ public:
   NS_IMETHOD GetPluginVersionForAPI(const nsACString& aAPI,
                                     nsTArray<nsCString>* aTags,
                                     bool* aHasPlugin,
                                     nsACString& aOutVersion) override;
   NS_IMETHOD GetNodeId(const nsAString& aOrigin,
                        const nsAString& aTopLevelOrigin,
                        bool aInPrivateBrowsingMode,
                        UniquePtr<GetNodeIdCallback>&& aCallback) override;
+  NS_IMETHOD UpdateTrialCreateState(const nsAString& aKeySystem,
+                                    uint32_t aState) override;
 
   NS_DECL_MOZIGECKOMEDIAPLUGINCHROMESERVICE
   NS_DECL_NSIOBSERVER
 
   void AsyncShutdownNeeded(GMPParent* aParent);
   void AsyncShutdownComplete(GMPParent* aParent);
 
   int32_t AsyncShutdownTimeoutMs();
@@ -214,16 +216,18 @@ public:
   virtual bool RecvGetGMPNodeId(const nsString& aOrigin,
                                 const nsString& aTopLevelOrigin,
                                 const bool& aInPrivateBrowsing,
                                 nsCString* aID) override;
   static bool RecvGetGMPPluginVersionForAPI(const nsCString& aAPI,
                                             nsTArray<nsCString>&& aTags,
                                             bool* aHasPlugin,
                                             nsCString* aVersion);
+  virtual bool RecvUpdateGMPTrialCreateState(const nsString& aKeySystem,
+                                             const uint32_t& aState) override;
 
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
   static PGMPServiceParent* Create(Transport* aTransport, ProcessId aOtherPid);
 
 private:
   nsRefPtr<GeckoMediaPluginServiceParent> mService;
 };
--- a/dom/media/gmp/PGMP.ipdl
+++ b/dom/media/gmp/PGMP.ipdl
@@ -29,15 +29,15 @@ parent:
   async PGMPContentChildDestroyed();
 
   async AsyncShutdownComplete();
   async AsyncShutdownRequired();
 
 child:
   async BeginAsyncShutdown();
   async CrashPluginNow();
-  async StartPlugin();
+  intr StartPlugin();
   async SetNodeId(nsCString nodeId);
   async CloseActive();
 };
 
 } // namespace gmp
 } // namespace mozilla
--- a/dom/media/gmp/PGMPService.ipdl
+++ b/dom/media/gmp/PGMPService.ipdl
@@ -16,12 +16,14 @@ sync protocol PGMPService
 
 parent:
   sync LoadGMP(nsCString nodeId, nsCString api, nsCString[] tags,
                ProcessId[] alreadyBridgedTo)
     returns (ProcessId id, nsCString displayName, uint32_t pluginId);
   sync GetGMPNodeId(nsString origin, nsString topLevelOrigin,
                     bool inPrivateBrowsing)
     returns (nsCString id);
+
+  async UpdateGMPTrialCreateState(nsString keySystem, uint32_t status);
 };
 
 } // namespace gmp
 } // namespace mozilla
--- a/dom/media/gmp/mozIGeckoMediaPluginService.idl
+++ b/dom/media/gmp/mozIGeckoMediaPluginService.idl
@@ -47,17 +47,17 @@ public:
 
 [ptr] native TagArray(nsTArray<nsCString>);
 native GetGMPDecryptorCallback(mozilla::UniquePtr<GetGMPDecryptorCallback>&&);
 native GetGMPAudioDecoderCallback(mozilla::UniquePtr<GetGMPAudioDecoderCallback>&&);
 native GetGMPVideoDecoderCallback(mozilla::UniquePtr<GetGMPVideoDecoderCallback>&&);
 native GetGMPVideoEncoderCallback(mozilla::UniquePtr<GetGMPVideoEncoderCallback>&&);
 native GetNodeIdCallback(mozilla::UniquePtr<GetNodeIdCallback>&&);
 
-[scriptable, uuid(787cf744-eea8-48c8-b692-376e0485ef97)]
+[scriptable, uuid(661492d6-726b-4ba0-8e6e-14bfaf2b62e4)]
 interface mozIGeckoMediaPluginService : nsISupports
 {
 
   /**
    * The GMP thread. Callable from any thread.
    */
   readonly attribute nsIThread thread;
 
@@ -142,9 +142,15 @@ interface mozIGeckoMediaPluginService : 
   /**
    * Gets the NodeId for a (origin, urlbarOrigin, isInprivateBrowsing) tuple.
    */
   [noscript]
   void getNodeId(in AString origin,
                  in AString topLevelOrigin,
                  in bool inPrivateBrowsingMode,
                  in GetNodeIdCallback callback);
+
+  /**
+   * Stores the result of trying to create a decoder for the given keysystem.
+   */
+  [noscript]
+  void updateTrialCreateState(in AString keySystem, in uint32_t status);
 };
--- a/dom/media/mediasource/ContainerParser.cpp
+++ b/dom/media/mediasource/ContainerParser.cpp
@@ -266,39 +266,50 @@ public:
       mLastMapping.reset();
       return false;
     }
 
     if (mCompleteMediaHeaderRange.IsNull()) {
       mCompleteMediaHeaderRange = MediaByteRange(mapping[0].mSyncOffset,
                                                  mapping[0].mEndOffset);
     }
-    mLastMapping = Some(mapping[completeIdx]);
 
     if (foundNewCluster && mOffset >= mapping[endIdx].mEndOffset) {
       // We now have all information required to delimit a complete cluster.
       int64_t endOffset = mapping[endIdx+1].mSyncOffset;
       if (mapping[endIdx+1].mInitOffset > mapping[endIdx].mInitOffset) {
         // We have a new init segment before this cluster.
         endOffset = mapping[endIdx+1].mInitOffset;
       }
       mCompleteMediaSegmentRange = MediaByteRange(mapping[endIdx].mSyncOffset,
                                                   endOffset);
     } else if (mapping[endIdx].mClusterEndOffset >= 0 &&
                mOffset >= mapping[endIdx].mClusterEndOffset) {
       mCompleteMediaSegmentRange = MediaByteRange(mapping[endIdx].mSyncOffset,
                                                   mParser.EndSegmentOffset(mapping[endIdx].mClusterEndOffset));
     }
 
-    if (!completeIdx) {
+    Maybe<WebMTimeDataOffset> previousMapping;
+    if (completeIdx) {
+      previousMapping = Some(mapping[completeIdx - 1]);
+    } else {
+      previousMapping = mLastMapping;
+    }
+
+    mLastMapping = Some(mapping[completeIdx]);
+
+    if (!previousMapping && completeIdx + 1u >= mapping.Length()) {
+      // We have no previous nor next block available,
+      // so we can't estimate this block's duration.
       return false;
     }
 
-    uint64_t frameDuration =
-      mapping[completeIdx].mTimecode - mapping[completeIdx - 1].mTimecode;
+    uint64_t frameDuration = (completeIdx + 1u < mapping.Length())
+      ? mapping[completeIdx + 1].mTimecode - mapping[completeIdx].mTimecode
+      : mapping[completeIdx].mTimecode - previousMapping.ref().mTimecode;
     aStart = mapping[0].mTimecode / NS_PER_USEC;
     aEnd = (mapping[completeIdx].mTimecode + frameDuration) / NS_PER_USEC;
 
     MSE_DEBUG(WebMContainerParser, "[%lld, %lld] [fso=%lld, leo=%lld, l=%u processedIdx=%u fs=%lld]",
               aStart, aEnd, mapping[0].mSyncOffset,
               mapping[completeIdx].mEndOffset, mapping.Length(), completeIdx,
               mCompleteMediaSegmentRange.mEnd);
 
--- a/dom/media/test/test_fragment_play.html
+++ b/dom/media/test/test_fragment_play.html
@@ -6,17 +6,17 @@
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <script type="text/javascript" src="manifest.js"></script>
   <script type="text/javascript" src="fragment_play.js"></script>
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
-
+PARALLEL_TESTS = 1;
 var manager = new MediaTestManager;
 
 // Fragment parameters to try. These tests
 // try playing the video. Tests for other fragment
 // formats are in test_fragment_noplay.html.
 var gFragmentParams = [
   { fragment: "", start: null, end: null },
   { fragment: "#t=,", start: null, end: null },
@@ -73,19 +73,17 @@ function startTest(test, token) {
   var name = test.name + " fragment test";
   var localIs = function(name) { return function(a, b, msg) {
     is(a, b, name + ": " + msg);
   }}(name);
   var localOk = function(name) { return function(a, msg) {
     ok(a, name + ": " + msg);
   }}(name);
   var localFinish = function(v, manager) { return function() {
-    if (v.parentNode) {
-      v.parentNode.removeChild(v);
-    }
+    removeNodeAndSource(v);
     manager.finished(v.token);
   }}(v, manager);
   window['test_fragment_play'](v, test.start, test.end, localIs, localOk, localFinish);
 }
 
 manager.runTests(createTestArray(), startTest);
 
 </script>
--- a/dom/media/webm/WebMDemuxer.cpp
+++ b/dom/media/webm/WebMDemuxer.cpp
@@ -126,18 +126,16 @@ WebMDemuxer::WebMDemuxer(MediaResource* 
 WebMDemuxer::WebMDemuxer(MediaResource* aResource, bool aIsMediaSource)
   : mResource(aResource)
   , mBufferedState(nullptr)
   , mInitData(nullptr)
   , mContext(nullptr)
   , mVideoTrack(0)
   , mAudioTrack(0)
   , mSeekPreroll(0)
-  , mLastAudioFrameTime(0)
-  , mLastVideoFrameTime(0)
   , mAudioCodec(-1)
   , mVideoCodec(-1)
   , mHasVideo(false)
   , mHasAudio(false)
   , mNeedReIndex(true)
   , mLastWebMBlockOffset(-1)
   , mIsMediaSource(aIsMediaSource)
 {
@@ -502,37 +500,47 @@ WebMDemuxer::GetNextPacket(TrackInfo::Tr
     return false;
   }
   int64_t tstamp = holder->Timestamp();
 
   // The end time of this frame is the start time of the next frame.  Fetch
   // the timestamp of the next packet for this track.  If we've reached the
   // end of the resource, use the file's duration as the end time of this
   // video frame.
-  int64_t next_tstamp = 0;
+  int64_t next_tstamp = INT64_MIN;
   if (aType == TrackInfo::kAudioTrack) {
     nsRefPtr<NesteggPacketHolder> next_holder(NextPacket(aType));
     if (next_holder) {
       next_tstamp = next_holder->Timestamp();
       PushAudioPacket(next_holder);
-    } else {
+    } else if (!mIsMediaSource ||
+               (mIsMediaSource && mLastAudioFrameTime.isSome())) {
       next_tstamp = tstamp;
-      next_tstamp += tstamp - mLastAudioFrameTime;
+      next_tstamp += tstamp - mLastAudioFrameTime.refOr(0);
+    } else {
+      PushAudioPacket(holder);
     }
-    mLastAudioFrameTime = tstamp;
+    mLastAudioFrameTime = Some(tstamp);
   } else if (aType == TrackInfo::kVideoTrack) {
     nsRefPtr<NesteggPacketHolder> next_holder(NextPacket(aType));
     if (next_holder) {
       next_tstamp = next_holder->Timestamp();
       PushVideoPacket(next_holder);
-    } else {
+    } else if (!mIsMediaSource ||
+               (mIsMediaSource && mLastVideoFrameTime.isSome())) {
       next_tstamp = tstamp;
-      next_tstamp += tstamp - mLastVideoFrameTime;
+      next_tstamp += tstamp - mLastVideoFrameTime.refOr(0);
+    } else {
+      PushVideoPacket(holder);
     }
-    mLastVideoFrameTime = tstamp;
+    mLastVideoFrameTime = Some(tstamp);
+  }
+
+  if (mIsMediaSource && next_tstamp == INT64_MIN) {
+    return false;
   }
 
   int64_t discardPadding = 0;
   (void) nestegg_packet_discard_padding(holder->Packet(), &discardPadding);
 
   for (uint32_t i = 0; i < count; ++i) {
     unsigned char* data;
     size_t length;
@@ -652,17 +660,17 @@ WebMDemuxer::DemuxPacket()
 }
 
 int64_t
 WebMDemuxer::GetNextKeyframeTime()
 {
   EnsureUpToDateIndex();
   uint64_t keyframeTime;
   uint64_t lastFrame =
-    media::TimeUnit::FromMicroseconds(mLastVideoFrameTime).ToNanoseconds();
+    media::TimeUnit::FromMicroseconds(mLastVideoFrameTime.refOr(0)).ToNanoseconds();
   if (!mBufferedState->GetNextKeyframeTime(lastFrame, &keyframeTime) ||
       keyframeTime <= lastFrame) {
     return -1;
   }
   return media::TimeUnit::FromNanoseconds(keyframeTime).ToMicroseconds();
 }
 
 void
@@ -720,18 +728,18 @@ WebMDemuxer::SeekInternal(const media::T
     r = nestegg_offset_seek(mContext, offset);
     if (r == -1) {
       WEBM_DEBUG("and nestegg_offset_seek to %" PRIu64 " failed", offset);
       return NS_ERROR_FAILURE;
     }
     WEBM_DEBUG("got offset from buffered state: %" PRIu64 "", offset);
   }
 
-  mLastAudioFrameTime = 0;
-  mLastVideoFrameTime = 0;
+  mLastAudioFrameTime.reset();
+  mLastVideoFrameTime.reset();
 
   return NS_OK;
 }
 
 media::TimeIntervals
 WebMDemuxer::GetBuffered()
 {
   EnsureUpToDateIndex();
--- a/dom/media/webm/WebMDemuxer.h
+++ b/dom/media/webm/WebMDemuxer.h
@@ -151,21 +151,20 @@ private:
   uint32_t mAudioTrack;
 
   // Number of microseconds that must be discarded from the start of the Stream.
   uint64_t mCodecDelay;
 
   // Nanoseconds to discard after seeking.
   uint64_t mSeekPreroll;
 
-  int64_t mLastAudioFrameTime;
-
   // Calculate the frame duration from the last decodeable frame using the
   // previous frame's timestamp.  In NS.
-  int64_t mLastVideoFrameTime;
+  Maybe<int64_t> mLastAudioFrameTime;
+  Maybe<int64_t> mLastVideoFrameTime;
 
   // Codec ID of audio track
   int mAudioCodec;
   // Codec ID of video track
   int mVideoCodec;
 
   // Booleans to indicate if we have audio and/or video data
   bool mHasVideo;
--- a/dom/notification/Notification.cpp
+++ b/dom/notification/Notification.cpp
@@ -404,16 +404,32 @@ public:
 
   void
   WorkerRunInternal(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     mNotification->DispatchTrustedEvent(mEventName);
   }
 };
 
+class ReleaseNotificationRunnable final : public NotificationWorkerRunnable
+{
+  Notification* mNotification;
+public:
+  explicit ReleaseNotificationRunnable(Notification* aNotification)
+    : NotificationWorkerRunnable(aNotification->mWorkerPrivate)
+    , mNotification(aNotification)
+  {}
+
+  void
+  WorkerRunInternal(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+  {
+    mNotification->ReleaseObject();
+  }
+};
+
 // Create one whenever you require ownership of the notification. Use with
 // UniquePtr<>. See Notification.h for details.
 class NotificationRef final {
   friend class WorkerNotificationObserver;
 
 private:
   Notification* mNotification;
   bool mInited;
@@ -447,28 +463,41 @@ public:
   Initialized()
   {
     return mInited;
   }
 
   ~NotificationRef()
   {
     if (Initialized() && mNotification) {
-      if (mNotification->mWorkerPrivate && NS_IsMainThread()) {
-        nsRefPtr<ReleaseNotificationControlRunnable> r =
-          new ReleaseNotificationControlRunnable(mNotification);
-        AutoSafeJSContext cx;
-        if (!r->Dispatch(cx)) {
-          MOZ_CRASH("Will leak worker thread Notification!");
+      Notification* notification = mNotification;
+      mNotification = nullptr;
+      if (notification->mWorkerPrivate && NS_IsMainThread()) {
+        // Try to pass ownership back to the worker. If the dispatch succeeds we
+        // are guaranteed this runnable will run, and that it will run after queued
+        // event runnables, so event runnables will have a safe pointer to the
+        // Notification.
+        //
+        // If the dispatch fails, the worker isn't running anymore and the event
+        // runnables have already run or been canceled. We can use a control
+        // runnable to release the reference.
+        nsRefPtr<ReleaseNotificationRunnable> r =
+          new ReleaseNotificationRunnable(notification);
+
+        AutoJSAPI jsapi;
+        jsapi.Init();
+        if (!r->Dispatch(jsapi.cx())) {
+          nsRefPtr<ReleaseNotificationControlRunnable> r =
+            new ReleaseNotificationControlRunnable(notification);
+          MOZ_ALWAYS_TRUE(r->Dispatch(jsapi.cx()));
         }
       } else {
-        mNotification->AssertIsOnTargetThread();
-        mNotification->ReleaseObject();
+        notification->AssertIsOnTargetThread();
+        notification->ReleaseObject();
       }
-      mNotification = nullptr;
     }
   }
 
   // XXXnsm, is it worth having some sort of WeakPtr like wrapper instead of
   // a rawptr that the NotificationRef can invalidate?
   Notification*
   GetNotification()
   {
@@ -968,54 +997,16 @@ public:
   }
 
 protected:
   virtual ~WorkerNotificationObserver()
   {
     AssertIsOnMainThread();
 
     MOZ_ASSERT(mNotificationRef);
-    Notification* notification = mNotificationRef->GetNotification();
-    if (!notification) {
-      return;
-    }
-
-    // Try to pass ownership back to the worker. If the dispatch succeeds we
-    // are guaranteed this runnable will run, and that it will run after queued
-    // event runnables, so event runnables will have a safe pointer to the
-    // Notification.
-    //
-    // If the dispatch fails, the worker isn't running anymore and the event
-    // runnables have already run. We can just let the standard NotificationRef
-    // release routine take over when ReleaseNotificationRunnable gets deleted.
-    class ReleaseNotificationRunnable final : public NotificationWorkerRunnable
-    {
-      UniquePtr<NotificationRef> mNotificationRef;
-    public:
-      explicit ReleaseNotificationRunnable(UniquePtr<NotificationRef> aRef)
-        : NotificationWorkerRunnable(aRef->GetNotification()->mWorkerPrivate)
-        , mNotificationRef(Move(aRef))
-      {}
-
-      void
-      WorkerRunInternal(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
-      {
-        UniquePtr<NotificationRef> ref;
-        mozilla::Swap(ref, mNotificationRef);
-        // Gets released at the end of the function.
-      }
-    };
-
-    nsRefPtr<ReleaseNotificationRunnable> r =
-      new ReleaseNotificationRunnable(Move(mNotificationRef));
-    notification = nullptr;
-
-    AutoJSAPI jsapi;
-    jsapi.Init();
-    r->Dispatch(jsapi.cx());
   }
 };
 
 NS_IMPL_ISUPPORTS_INHERITED0(WorkerNotificationObserver, NotificationObserver)
 
 class ServiceWorkerNotificationObserver final : public nsIObserver
 {
 public:
--- a/dom/notification/Notification.h
+++ b/dom/notification/Notification.h
@@ -84,32 +84,22 @@ public:
  *   the underlying Notification is null.
  *
  * To unify code-paths, we use the same NotificationRef in the main
  * thread implementation too.
  *
  * Note that the Notification's JS wrapper does it's standard
  * AddRef()/Release() and is not affected by any of this.
  *
- * There is one case related to the WorkerNotificationObserver having to
- * dispatch WorkerRunnables to the worker thread which will use the
- * Notification object. We can end up in a situation where an event runnable is
- * dispatched to the worker, gets queued in the worker's event queue, but then,
- * the worker yields to the main thread. Here the main thread observer is
- * destroyed, which frees its NotificationRef. The NotificationRef dispatches
- * a ControlRunnable to the worker, which runs before the event runnable,
- * leading to the event runnable possibly not having a valid Notification
- * reference.
- * We solve this problem by having WorkerNotificationObserver's dtor
- * dispatching a standard WorkerRunnable to do the release (this guarantees the
- * ordering of the release is after the event runnables). All WorkerRunnables
- * that get dispatched successfully are guaranteed to run on the worker before
- * it shuts down. If that dispatch fails, the standard ControlRunnable based
- * shutdown is acceptable since the already dispatched event runnables have
- * already run or canceled (the worker is already past Running).
+ * Since the worker event queue can have runnables that will dispatch events on
+ * the Notification, the NotificationRef destructor will first try to release
+ * the Notification by dispatching a normal runnable to the worker so that it is
+ * queued after any event runnables. If that dispatch fails, it means the worker
+ * is no longer running and queued WorkerRunnables will be canceled, so we
+ * dispatch a control runnable instead.
  *
  */
 class Notification : public DOMEventTargetHelper
 {
   friend class CloseNotificationRunnable;
   friend class NotificationTask;
   friend class NotificationPermissionRequest;
   friend class NotificationObserver;
--- a/dom/push/test/mochitest.ini
+++ b/dom/push/test/mochitest.ini
@@ -14,10 +14,11 @@ skip-if = os == "android" || toolkit == 
 [test_multiple_register.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_multiple_register_during_service_activation.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_unregister.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_multiple_register_different_scope.html]
 skip-if = os == "android" || toolkit == "gonk"
-[test_try_registering_offline_disabled.html]
-skip-if = os == "android" || toolkit == "gonk"
+# Disabled for too many intermittent failures (bug 1164432)
+#  [test_try_registering_offline_disabled.html]
+#  skip-if = os == "android" || toolkit == "gonk"
--- a/dom/tests/mochitest/fetch/test_fetch_cors.js
+++ b/dom/tests/mochitest/fetch/test_fetch_cors.js
@@ -1120,40 +1120,70 @@ function testRedirects() {
              headers: { "Content-Type": "text/plain" },
              hops: [{ server: "http://mochi.test:8888",
                     },
                     { server: "http://example.com",
                       allowOrigin: origin,
                     },
                     ],
            },
-           { pass: 0,
+           { pass: 1,
              method: "POST",
              body: "hi there",
              headers: { "Content-Type": "text/plain",
                         "my-header": "myValue",
                       },
              hops: [{ server: "http://mochi.test:8888",
                     },
                     { server: "http://example.com",
                       allowOrigin: origin,
                       allowHeaders: "my-header",
                     },
                     ],
            },
            { pass: 0,
+             method: "POST",
+             body: "hi there",
+             headers: { "Content-Type": "text/plain",
+                        "my-header": "myValue",
+                      },
+             hops: [{ server: "http://mochi.test:8888",
+                    },
+                    { server: "http://test1.example.com",
+                      allowOrigin: origin,
+                      allowHeaders: "my-header",
+                    },
+                    { server: "http://test2.example.com",
+                      allowOrigin: origin,
+                      allowHeaders: "my-header",
+                    }
+                    ],
+           },
+           { pass: 1,
              method: "DELETE",
              hops: [{ server: "http://mochi.test:8888",
                     },
                     { server: "http://example.com",
                       allowOrigin: origin,
                     },
                     ],
            },
            { pass: 0,
+             method: "DELETE",
+             hops: [{ server: "http://mochi.test:8888",
+                    },
+                    { server: "http://test1.example.com",
+                      allowOrigin: origin,
+                    },
+                    { server: "http://test2.example.com",
+                      allowOrigin: origin,
+                    },
+                    ],
+           },
+           { pass: 0,
              method: "POST",
              body: "hi there",
              headers: { "Content-Type": "text/plain",
                         "my-header": "myValue",
                       },
              hops: [{ server: "http://example.com",
                       allowOrigin: origin,
                     },
--- a/dom/webidl/Request.webidl
+++ b/dom/webidl/Request.webidl
@@ -18,16 +18,17 @@ interface Request {
   [SameObject] readonly attribute Headers headers;
 
   [Func="mozilla::dom::Request::RequestContextEnabled"]
   readonly attribute RequestContext context;
   readonly attribute DOMString referrer;
   readonly attribute RequestMode mode;
   readonly attribute RequestCredentials credentials;
   readonly attribute RequestCache cache;
+  readonly attribute RequestRedirect redirect;
 
   [Throws,
    NewObject] Request clone();
 
   // Bug 1124638 - Allow chrome callers to set the context.
   [ChromeOnly]
   void setContentPolicyType(nsContentPolicyType context);
 };
@@ -35,16 +36,17 @@ Request implements Body;
 
 dictionary RequestInit {
   ByteString method;
   HeadersInit headers;
   BodyInit body;
   RequestMode mode;
   RequestCredentials credentials;
   RequestCache cache;
+  RequestRedirect redirect;
 };
 
 // Gecko currently does not ship RequestContext, so please don't use it in IDL
 // that is exposed to script.
 enum RequestContext {
   "audio", "beacon", "cspreport", "download", "embed", "eventsource", "favicon", "fetch",
   "font", "form", "frame", "hyperlink", "iframe", "image", "imageset", "import",
   "internal", "location", "manifest", "object", "ping", "plugin", "prefetch", "script",
@@ -56,8 +58,9 @@ enum RequestContext {
 // allows us to use the various conversion conveniences offered by the WebIDL
 // codegen. The Request constructor has explicit checks to prevent it being
 // passed as a valid value, while Request.mode never returns it. Since enums
 // are only exposed as strings to client JS, this has the same effect as not
 // exposing it at all.
 enum RequestMode { "same-origin", "no-cors", "cors", "cors-with-forced-preflight" };
 enum RequestCredentials { "omit", "same-origin", "include" };
 enum RequestCache { "default", "no-store", "reload", "no-cache", "force-cache", "only-if-cached" };
+enum RequestRedirect { "follow", "error", "manual" };
--- a/dom/webidl/Response.webidl
+++ b/dom/webidl/Response.webidl
@@ -29,9 +29,9 @@ Response implements Body;
 
 dictionary ResponseInit {
   unsigned short status = 200;
   // WebIDL spec doesn't allow default values for ByteString.
   ByteString statusText;
   HeadersInit headers;
 };
 
-enum ResponseType { "basic", "cors", "default", "error", "opaque" };
+enum ResponseType { "basic", "cors", "default", "error", "opaque", "opaqueredirect" };
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -125,17 +125,18 @@ public:
       // channel info for the worker script.
       channelInfo = mWorkerChannelInfo;
     }
     nsresult rv = mChannel->SetChannelInfo(&channelInfo);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    mChannel->SynthesizeStatus(mInternalResponse->GetStatus(), mInternalResponse->GetStatusText());
+    mChannel->SynthesizeStatus(mInternalResponse->GetUnfilteredStatus(),
+                               mInternalResponse->GetUnfilteredStatusText());
 
     nsAutoTArray<InternalHeaders::Entry, 5> entries;
     mInternalResponse->UnfilteredHeaders()->GetEntries(entries);
     for (uint32_t i = 0; i < entries.Length(); ++i) {
        mChannel->SynthesizeHeader(entries[i].mName, entries[i].mValue);
     }
 
     rv = mChannel->FinishSynthesizedResponse();
@@ -143,28 +144,31 @@ public:
     return rv;
   }
 };
 
 class RespondWithHandler final : public PromiseNativeHandler
 {
   nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
   nsMainThreadPtrHandle<ServiceWorker> mServiceWorker;
-  RequestMode mRequestMode;
-  bool mIsClientRequest;
+  const RequestMode mRequestMode;
+  const bool mIsClientRequest;
+  const bool mIsNavigationRequest;
 public:
   NS_DECL_ISUPPORTS
 
   RespondWithHandler(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
                      nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker,
-                     RequestMode aRequestMode, bool aIsClientRequest)
+                     RequestMode aRequestMode, bool aIsClientRequest,
+                     bool aIsNavigationRequest)
     : mInterceptedChannel(aChannel)
     , mServiceWorker(aServiceWorker)
     , mRequestMode(aRequestMode)
     , mIsClientRequest(aIsClientRequest)
+    , mIsNavigationRequest(aIsNavigationRequest)
   {
   }
 
   void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
   void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
   void CancelRequest(nsresult aStatus);
@@ -259,53 +263,62 @@ RespondWithHandler::ResolvedCallback(JSC
   // Allow opaque response interception to be disabled until we can ensure the
   // security implications are not a complete disaster.
   if (response->Type() == ResponseType::Opaque &&
       !worker->OpaqueInterceptionEnabled()) {
     autoCancel.SetCancelStatus(NS_ERROR_OPAQUE_INTERCEPTION_DISABLED);
     return;
   }
 
-  // Section 4.2, step 2.2:
+  // Section "HTTP Fetch", step 2.2:
   //  If one of the following conditions is true, return a network error:
   //    * response's type is "error".
   //    * request's mode is not "no-cors" and response's type is "opaque".
   //    * request is a client request and response's type is neither "basic"
   //      nor "default".
+  //    * request is not a navigation request and response's type is
+  //      "opaqueredirect".
 
   if (response->Type() == ResponseType::Error) {
     autoCancel.SetCancelStatus(NS_ERROR_INTERCEPTED_ERROR_RESPONSE);
     return;
   }
 
   if (response->Type() == ResponseType::Opaque && mRequestMode != RequestMode::No_cors) {
     autoCancel.SetCancelStatus(NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE);
     return;
   }
 
+  // TODO: remove this case as its no longer in the spec (bug 1184967)
   if (mIsClientRequest && response->Type() != ResponseType::Basic &&
-      response->Type() != ResponseType::Default) {
+      response->Type() != ResponseType::Default &&
+      response->Type() != ResponseType::Opaqueredirect) {
     autoCancel.SetCancelStatus(NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION);
     return;
   }
 
+  if (!mIsNavigationRequest && response->Type() == ResponseType::Opaqueredirect) {
+    autoCancel.SetCancelStatus(NS_ERROR_BAD_OPAQUE_REDIRECT_INTERCEPTION);
+    return;
+  }
+
   if (NS_WARN_IF(response->BodyUsed())) {
     autoCancel.SetCancelStatus(NS_ERROR_INTERCEPTED_USED_RESPONSE);
     return;
   }
 
   nsRefPtr<InternalResponse> ir = response->GetInternalResponse();
   if (NS_WARN_IF(!ir)) {
     return;
   }
 
   nsAutoPtr<RespondWithClosure> closure(
       new RespondWithClosure(mInterceptedChannel, ir, worker->GetChannelInfo()));
   nsCOMPtr<nsIInputStream> body;
-  ir->GetInternalBody(getter_AddRefs(body));
+  ir->GetUnfilteredBody(getter_AddRefs(body));
   // Errors and redirects may not have a body.
   if (body) {
     response->SetBodyUsed();
 
     nsCOMPtr<nsIOutputStream> responseBody;
     rv = mInterceptedChannel->GetResponseBody(getter_AddRefs(responseBody));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return;
@@ -354,17 +367,17 @@ FetchEvent::RespondWith(Promise& aArg, E
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   nsRefPtr<InternalRequest> ir = mRequest->GetInternalRequest();
   mWaitToRespond = true;
   nsRefPtr<RespondWithHandler> handler =
     new RespondWithHandler(mChannel, mServiceWorker, mRequest->Mode(),
-                           ir->IsClientRequest());
+                           ir->IsClientRequest(), ir->IsNavigationRequest());
   aArg.AppendNativeHandler(handler);
 }
 
 already_AddRefed<ServiceWorkerClient>
 FetchEvent::GetClient()
 {
   if (!mClient) {
     if (!mClientInfo) {
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -89,16 +89,25 @@ static_assert(nsIHttpChannelInternal::CO
               "RequestMode enumeration value should match Necko CORS mode value.");
 static_assert(nsIHttpChannelInternal::CORS_MODE_NO_CORS == static_cast<uint32_t>(RequestMode::No_cors),
               "RequestMode enumeration value should match Necko CORS mode value.");
 static_assert(nsIHttpChannelInternal::CORS_MODE_CORS == static_cast<uint32_t>(RequestMode::Cors),
               "RequestMode enumeration value should match Necko CORS mode value.");
 static_assert(nsIHttpChannelInternal::CORS_MODE_CORS_WITH_FORCED_PREFLIGHT == static_cast<uint32_t>(RequestMode::Cors_with_forced_preflight),
               "RequestMode enumeration value should match Necko CORS mode value.");
 
+static_assert(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW == static_cast<uint32_t>(RequestRedirect::Follow),
+              "RequestRedirect enumeration value should make Necko Redirect mode value.");
+static_assert(nsIHttpChannelInternal::REDIRECT_MODE_ERROR == static_cast<uint32_t>(RequestRedirect::Error),
+              "RequestRedirect enumeration value should make Necko Redirect mode value.");
+static_assert(nsIHttpChannelInternal::REDIRECT_MODE_MANUAL == static_cast<uint32_t>(RequestRedirect::Manual),
+              "RequestRedirect enumeration value should make Necko Redirect mode value.");
+static_assert(3 == static_cast<uint32_t>(RequestRedirect::EndGuard_),
+              "RequestRedirect enumeration value should make Necko Redirect mode value.");
+
 static StaticRefPtr<ServiceWorkerManager> gInstance;
 
 // Tracks the "dom.disable_open_click_delay" preference.  Modified on main
 // thread, read on worker threads. This is set once in the ServiceWorkerManager
 // constructor before any service workers are spawned.
 // It is updated every time a "notificationclick" event is dispatched. While
 // this is done without synchronization, at the worst, the thread will just get
 // an older value within which a popup is allowed to be displayed, which will
@@ -3611,33 +3620,37 @@ class FetchEventRunnable : public Worker
   nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
   nsMainThreadPtrHandle<ServiceWorker> mServiceWorker;
   nsTArray<nsCString> mHeaderNames;
   nsTArray<nsCString> mHeaderValues;
   nsAutoPtr<ServiceWorkerClientInfo> mClientInfo;
   nsCString mSpec;
   nsCString mMethod;
   bool mIsReload;
+  DebugOnly<bool> mIsHttpChannel;
   RequestMode mRequestMode;
+  RequestRedirect mRequestRedirect;
   RequestCredentials mRequestCredentials;
   nsContentPolicyType mContentPolicyType;
   nsCOMPtr<nsIInputStream> mUploadStream;
   nsCString mReferrer;
 public:
   FetchEventRunnable(WorkerPrivate* aWorkerPrivate,
                      nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
                      nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker,
                      nsAutoPtr<ServiceWorkerClientInfo>& aClientInfo,
                      bool aIsReload)
     : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
     , mInterceptedChannel(aChannel)
     , mServiceWorker(aServiceWorker)
     , mClientInfo(aClientInfo)
     , mIsReload(aIsReload)
+    , mIsHttpChannel(false)
     , mRequestMode(RequestMode::No_cors)
+    , mRequestRedirect(RequestRedirect::Follow)
     // By default we set it to same-origin since normal HTTP fetches always
     // send credentials to same-origin websites unless explicitly forbidden.
     , mRequestCredentials(RequestCredentials::Same_origin)
     , mContentPolicyType(nsIContentPolicy::TYPE_INVALID)
     , mReferrer(kFETCH_CLIENT_REFERRER_STR)
   {
     MOZ_ASSERT(aWorkerPrivate);
   }
@@ -3683,39 +3696,46 @@ public:
     // channels are intercepted but don't have referrers.
     if (NS_SUCCEEDED(rv) && referrerURI) {
       rv = referrerURI->GetSpec(mReferrer);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
     if (httpChannel) {
+      mIsHttpChannel = true;
+
       rv = httpChannel->GetRequestMethod(mMethod);
       NS_ENSURE_SUCCESS(rv, rv);
 
       nsCOMPtr<nsIHttpChannelInternal> internalChannel = do_QueryInterface(httpChannel);
       NS_ENSURE_TRUE(internalChannel, NS_ERROR_NOT_AVAILABLE);
 
-      uint32_t mode;
-      internalChannel->GetCorsMode(&mode);
-      switch (mode) {
+      uint32_t corsMode;
+      internalChannel->GetCorsMode(&corsMode);
+      switch (corsMode) {
         case nsIHttpChannelInternal::CORS_MODE_SAME_ORIGIN:
           mRequestMode = RequestMode::Same_origin;
           break;
         case nsIHttpChannelInternal::CORS_MODE_NO_CORS:
           mRequestMode = RequestMode::No_cors;
           break;
         case nsIHttpChannelInternal::CORS_MODE_CORS:
         case nsIHttpChannelInternal::CORS_MODE_CORS_WITH_FORCED_PREFLIGHT:
           mRequestMode = RequestMode::Cors;
           break;
         default:
           MOZ_CRASH("Unexpected CORS mode");
       }
 
+      // This is safe due to static_asserts at top of file.
+      uint32_t redirectMode;
+      internalChannel->GetRedirectMode(&redirectMode);
+      mRequestRedirect = static_cast<RequestRedirect>(redirectMode);
+
       if (loadFlags & nsIRequest::LOAD_ANONYMOUS) {
         mRequestCredentials = RequestCredentials::Omit;
       } else {
         bool includeCrossOrigin;
         internalChannel->GetCorsIncludeCredentials(&includeCrossOrigin);
         if (includeCrossOrigin) {
           mRequestCredentials = RequestCredentials::Include;
         }
@@ -3798,16 +3818,17 @@ private:
       }
     }
 
     nsRefPtr<Headers> headers = new Headers(globalObj.GetAsSupports(), internalHeaders);
     reqInit.mHeaders.Construct();
     reqInit.mHeaders.Value().SetAsHeaders() = headers;
 
     reqInit.mMode.Construct(mRequestMode);
+    reqInit.mRedirect.Construct(mRequestRedirect);
     reqInit.mCredentials.Construct(mRequestCredentials);
 
     ErrorResult result;
     nsRefPtr<Request> request = Request::Constructor(globalObj, requestInfo, reqInit, result);
     if (NS_WARN_IF(result.Failed())) {
       result.SuppressException();
       return false;
     }
@@ -3816,16 +3837,21 @@ private:
     MOZ_ASSERT(internalReq);
     internalReq->SetCreatedByFetchEvent();
 
     internalReq->SetBody(mUploadStream);
     internalReq->SetReferrer(NS_ConvertUTF8toUTF16(mReferrer));
 
     request->SetContentPolicyType(mContentPolicyType);
 
+    // TODO: remove conditional on http here once app protocol support is
+    //       removed from service worker interception
+    MOZ_ASSERT_IF(mIsHttpChannel && internalReq->IsNavigationRequest(),
+                  request->Redirect() == RequestRedirect::Manual);
+
     RootedDictionary<FetchEventInit> init(aCx);
     init.mRequest.Construct();
     init.mRequest.Value() = request;
     init.mBubbles = false;
     init.mCancelable = false;
     init.mIsReload.Construct(mIsReload);
     nsRefPtr<FetchEvent> event =
       FetchEvent::Constructor(globalObj, NS_LITERAL_STRING("fetch"), init, result);
--- a/dom/workers/test/serviceworkers/fetch/origin/https/origin_test.js
+++ b/dom/workers/test/serviceworkers/fetch/origin/https/origin_test.js
@@ -1,15 +1,16 @@
 var prefix = "/tests/dom/workers/test/serviceworkers/fetch/origin/https/";
 
 self.addEventListener("install", function(event) {
   event.waitUntil(
     self.caches.open("origin-cache")
       .then(c => {
-        return c.add(prefix + 'index-https.sjs');
+        return c.add(new Request(prefix + 'index-https.sjs',
+                                 { redirect: 'manual' }));
       })
   );
 });
 
 self.addEventListener("fetch", function(event) {
   if (event.request.url.indexOf("index-cached-https.sjs") >= 0) {
     event.respondWith(
       self.caches.open("origin-cache")
deleted file mode 100644
--- a/dom/workers/test/serviceworkers/fetch/origin/https/realindex.html^headers^
+++ /dev/null
@@ -1,1 +0,0 @@
-Access-Control-Allow-Origin: https://example.com
--- a/dom/workers/test/serviceworkers/fetch/origin/origin_test.js
+++ b/dom/workers/test/serviceworkers/fetch/origin/origin_test.js
@@ -1,18 +1,20 @@
 var prefix = "/tests/dom/workers/test/serviceworkers/fetch/origin/";
 
 self.addEventListener("install", function(event) {
   event.waitUntil(
     self.caches.open("origin-cache")
       .then(c => {
         return Promise.all(
           [
-            c.add(prefix + 'index.sjs'),
-            c.add(prefix + 'index-to-https.sjs'),
+            c.add(new Request(prefix + 'index.sjs',
+                              { redirect: 'manual' } )),
+            c.add(new Request(prefix + 'index-to-https.sjs',
+                              { redirect: 'manual' } ))
           ]
         );
       })
   );
 });
 
 self.addEventListener("fetch", function(event) {
   if (event.request.url.indexOf("index-cached.sjs") >= 0) {
deleted file mode 100644
--- a/dom/workers/test/serviceworkers/fetch/origin/realindex.html^headers^
+++ /dev/null
@@ -1,1 +0,0 @@
-Access-Control-Allow-Origin: http://mochi.test:8888
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -48,23 +48,21 @@ support-files =
   fetch/https/https_test.js
   fetch/https/clonedresponse/index.html
   fetch/https/clonedresponse/register.html
   fetch/https/clonedresponse/unregister.html
   fetch/https/clonedresponse/https_test.js
   fetch/origin/index.sjs
   fetch/origin/index-to-https.sjs
   fetch/origin/realindex.html
-  fetch/origin/realindex.html^headers^
   fetch/origin/register.html
   fetch/origin/unregister.html
   fetch/origin/origin_test.js
   fetch/origin/https/index-https.sjs
   fetch/origin/https/realindex.html
-  fetch/origin/https/realindex.html^headers^
   fetch/origin/https/register.html
   fetch/origin/https/unregister.html
   fetch/origin/https/origin_test.js
   fetch/requesturl/index.html
   fetch/requesturl/redirect.sjs
   fetch/requesturl/redirector.html
   fetch/requesturl/register.html
   fetch/requesturl/requesturl_test.js
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -1263,17 +1263,17 @@ nsresult
 nsXULElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
 {
     aVisitor.mForceContentDispatch = true; //FIXME! Bug 329119
     if (IsRootOfNativeAnonymousSubtree() &&
         (IsAnyOfXULElements(nsGkAtoms::scrollbar, nsGkAtoms::scrollcorner)) &&
         (aVisitor.mEvent->mMessage == eMouseClick ||
          aVisitor.mEvent->mMessage == eMouseDoubleClick ||
          aVisitor.mEvent->mMessage == NS_XUL_COMMAND ||
-         aVisitor.mEvent->mMessage == NS_CONTEXTMENU ||
+         aVisitor.mEvent->mMessage == eContextMenu ||
          aVisitor.mEvent->mMessage == NS_DRAGDROP_START ||
          aVisitor.mEvent->mMessage == NS_DRAGDROP_GESTURE)) {
         // Don't propagate these events from native anonymous scrollbar.
         aVisitor.mCanHandle = true;
         aVisitor.mParentTarget = nullptr;
         return NS_OK;
     }
     if (aVisitor.mEvent->mMessage == NS_XUL_COMMAND &&
--- a/editor/libeditor/nsHTMLEditorEventListener.cpp
+++ b/editor/libeditor/nsHTMLEditorEventListener.cpp
@@ -85,17 +85,17 @@ nsHTMLEditorEventListener::MouseDown(nsI
     // is fired outside of the active editing host), we need to commit
     // composition because it will be change the selection to the clicked
     // point.  Then, we won't be able to commit the composition.
     return nsEditorEventListener::MouseDown(aMouseEvent);
   }
 
   // Detect only "context menu" click
   // XXX This should be easier to do!
-  // But eDOMEvents_contextmenu and NS_CONTEXTMENU is not exposed in any event
+  // But eDOMEvents_contextmenu and eContextMenu is not exposed in any event
   // interface :-(
   int16_t buttonNumber;
   nsresult rv = aMouseEvent->GetButton(&buttonNumber);
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool isContextClick = buttonNumber == 2;
 
   int32_t clickCount;
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -888,17 +888,17 @@ APZCTreeManager::UpdateWheelTransaction(
    }
    case eKeyPress:
    case eKeyUp:
    case eKeyDown:
    case eMouseUp:
    case eMouseDown:
    case eMouseDoubleClick:
    case eMouseClick:
-   case NS_CONTEXTMENU:
+   case eContextMenu:
    case NS_DRAGDROP_DROP:
      txn->EndTransaction();
      return;
    default:
      break;
   }
 }
 
--- a/gfx/src/FilterSupport.cpp
+++ b/gfx/src/FilterSupport.cpp
@@ -742,18 +742,20 @@ FilterNodeFromPrimitiveDescription(const
           BLEND_MODE_DIFFERENCE,
           BLEND_MODE_EXCLUSION,
           BLEND_MODE_HUE,
           BLEND_MODE_SATURATION,
           BLEND_MODE_COLOR,
           BLEND_MODE_LUMINOSITY
         };
         filter->SetAttribute(ATT_BLEND_BLENDMODE, (uint32_t)blendModes[mode]);
-        filter->SetInput(IN_BLEND_IN, aSources[0]);
-        filter->SetInput(IN_BLEND_IN2, aSources[1]);
+        // The correct input order for both software and D2D filters is flipped
+        // from our source order, so flip here.
+        filter->SetInput(IN_BLEND_IN, aSources[1]);
+        filter->SetInput(IN_BLEND_IN2, aSources[0]);
       }
       return filter.forget();
     }
 
     case PrimitiveType::ColorMatrix:
     {
       float colorMatrix[20];
       uint32_t type = atts.GetUint(eColorMatrixType);
--- a/js/public/UbiNode.h
+++ b/js/public/UbiNode.h
@@ -92,17 +92,16 @@
 // ECMAScript specification describes objects as maps from property names to
 // sets of attributes (like ECMAScript's [[Value]]), in practice many objects
 // have only a pointer to a shape, shared with other similar objects, and
 // indexed slots that contain the [[Value]] attributes. As another example, a
 // string produced by concatenating two other strings may sometimes be
 // represented by a "rope", a structure that points to the two original
 // strings.
 //
-//
 // We intend to use ubi::Node to write tools that report memory usage, so it's
 // important that ubi::Node accurately portray how much memory nodes consume.
 // Thus, for example, when data that apparently belongs to multiple nodes is
 // in fact shared in a common structure, ubi::Node's graph uses a separate
 // node for that shared structure, and presents edges to it from the data's
 // apparent owners. For example, ubi::Node exposes SpiderMonkey objects'
 // shapes and base shapes, and exposes rope string and substring structure,
 // because these optimizations become visible when a tool reports how much
@@ -137,16 +136,35 @@
 // save their intermediate state in some rooted structure if they must GC before
 // they complete. (For algorithms like path-finding and dominator tree
 // computation, we implement the algorithm avoiding any operation that could
 // cause a GC --- and use AutoCheckCannotGC to verify this.)
 //
 // If this restriction prevents us from implementing interesting tools, we may
 // teach the GC how to root ubi::Nodes, fix up hash tables that use them as
 // keys, etc.
+//
+//
+// Hostile Graph Structure
+//
+// Analyses consuming ubi::Node graphs must be robust when presented with graphs
+// that are deliberately constructed to exploit their weaknesses. When operating
+// on live graphs, web content has control over the object graph, and less
+// direct control over shape and string structure, and analyses should be
+// prepared to handle extreme cases gracefully. For example, if an analysis were
+// to use the C++ stack in a depth-first traversal, carefully constructed
+// content could cause the analysis to overflow the stack.
+//
+// When ubi::Nodes refer to nodes deserialized from a heap snapshot, analyses
+// must be even more careful: since snapshots often come from potentially
+// compromised e10s content processes, even properties normally guaranteed by
+// the platform (the proper linking of DOM nodes, for example) might be
+// corrupted. While it is the deserializer's responsibility to check the basic
+// structure of the snapshot file, the analyses should be prepared for ubi::Node
+// graphs constructed from snapshots to be even more bizarre.
 
 class JSAtom;
 
 namespace JS {
 namespace ubi {
 
 class Edge;
 class EdgeRange;
--- a/js/src/builtin/Array.js
+++ b/js/src/builtin/Array.js
@@ -248,17 +248,17 @@ function ArrayMap(callbackfn/*, thisArg*
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.prototype.map');
     if (!IsCallable(callbackfn))
         ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
 
     /* Step 5. */
     var T = arguments.length > 1 ? arguments[1] : void 0;
 
     /* Step 6. */
-    var A = NewDenseArray(len);
+    var A = std_Array(len);
 
     /* Step 7-8. */
     /* Step a (implicit), and d. */
     for (var k = 0; k < len; k++) {
         /* Step b */
         if (k in O) {
             /* Step c.i-iii. */
             var mappedValue = callFunction(callbackfn, T, O[k], k, O);
@@ -713,19 +713,17 @@ function ArrayIteratorNext() {
     UnsafeSetReservedSlot(this, ITERATOR_SLOT_NEXT_INDEX, index + 1);
 
     if (itemKind === ITEM_KIND_VALUE) {
         result.value = a[index];
         return result;
     }
 
     if (itemKind === ITEM_KIND_KEY_AND_VALUE) {
-        var pair = NewDenseArray(2);
-        pair[0] = index;
-        pair[1] = a[index];
+        var pair = [index, a[index]];
         result.value = pair;
         return result;
     }
 
     assert(itemKind === ITEM_KIND_KEY, itemKind);
     result.value = index;
     return result;
 }
@@ -802,17 +800,17 @@ function ArrayFrom(items, mapfn=undefine
 
     // Steps 8-9.
     var arrayLike = ToObject(items);
 
     // Steps 10-11.
     var len = ToLength(arrayLike.length);
 
     // Steps 12-14.
-    var A = IsConstructor(C) ? new C(len) : NewDenseArray(len);
+    var A = IsConstructor(C) ? new C(len) : std_Array(len);
 
     // Steps 15-16.
     for (var k = 0; k < len; k++) {
         // Steps 16.a-c.
         var kValue = items[k];
 
         // Steps 16.d-e.
         var mappedValue = mapping ? callFunction(mapfn, thisArg, kValue, k) : kValue;
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -1400,38 +1400,38 @@ Parser<ParseHandler>::newFunction(Handle
         flags = JSFunction::INTERPRETED_LAMBDA;
         break;
       case Arrow:
         flags = JSFunction::INTERPRETED_LAMBDA_ARROW;
         allocKind = gc::AllocKind::FUNCTION_EXTENDED;
         break;
       case Method:
         MOZ_ASSERT(generatorKind == NotGenerator || generatorKind == StarGenerator);
-        flags = (generatorKind == NotGenerator
-                 ? JSFunction::INTERPRETED_METHOD
-                 : JSFunction::INTERPRETED_METHOD_GENERATOR);
+        if (generatorKind == NotGenerator)
+            flags = JSFunction::INTERPRETED_METHOD;
+        else
+            flags = JSFunction::INTERPRETED_METHOD_GENERATOR;
         allocKind = gc::AllocKind::FUNCTION_EXTENDED;
         break;
       case ClassConstructor:
       case DerivedClassConstructor:
         flags = JSFunction::INTERPRETED_CLASS_CONSTRUCTOR;
         allocKind = gc::AllocKind::FUNCTION_EXTENDED;
         break;
       case Getter:
         flags = JSFunction::INTERPRETED_GETTER;
         allocKind = gc::AllocKind::FUNCTION_EXTENDED;
         break;
       case Setter:
         flags = JSFunction::INTERPRETED_SETTER;
         allocKind = gc::AllocKind::FUNCTION_EXTENDED;
         break;
       default:
-        flags = (generatorKind == NotGenerator
-                 ? JSFunction::INTERPRETED_NORMAL
-                 : JSFunction::INTERPRETED_GENERATOR);
+        flags = JSFunction::INTERPRETED_NORMAL;
+        break;
     }
 
     fun = NewFunctionWithProto(context, nullptr, 0, flags, nullptr, atom, proto,
                                allocKind, TenuredObject);
     if (!fun)
         return nullptr;
     if (options().selfHostingMode)
         fun->setIsSelfHostedBuiltin();
deleted file mode 100644
--- a/js/src/jit-test/tests/basic/bug1195298.js
+++ /dev/null
@@ -1,9 +0,0 @@
-function t() {
-    var o = {l: 0xfffffffff};
-    var l = o.l - 0xffffffffe;
-    var a = getSelfHostedValue('NewDenseArray');
-    var arr = a(l);
-    assertEq(arr.length, 1);
-}
-t();
-t();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1200108.js
@@ -0,0 +1,5 @@
+// |jit-test| error: 987
+var obj = {length: -1, 0: 0};
+Array.prototype.map.call(obj, function () {
+    throw 987;
+});
--- a/js/src/jit/mips32/Trampoline-mips32.cpp
+++ b/js/src/jit/mips32/Trampoline-mips32.cpp
@@ -309,17 +309,17 @@ JitRuntime::generateEnterJIT(JSContext* 
         // Baseline OSR will return here.
         masm.bind(returnLabel.src());
         masm.addCodeLabel(returnLabel);
     }
 
     // Pop arguments off the stack.
     // s0 <- 8*argc (size of all arguments we pushed on the stack)
     masm.pop(s0);
-    masm.rshiftPtr(Imm32(4), s0);
+    masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), s0);
     masm.addPtr(s0, StackPointer);
 
     // Store the returned value into the slotVp
     masm.loadPtr(slotVp, s1);
     masm.storeValue(JSReturnOperand, Address(s1, 0));
 
     // Restore non-volatile registers and return.
     GenerateReturn(masm, ShortJump);
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -749,17 +749,16 @@ class AutoAssertNoException
 /* Exposed intrinsics so that Ion may inline them. */
 bool intrinsic_ToObject(JSContext* cx, unsigned argc, Value* vp);
 bool intrinsic_IsObject(JSContext* cx, unsigned argc, Value* vp);
 bool intrinsic_ToInteger(JSContext* cx, unsigned argc, Value* vp);
 bool intrinsic_ToString(JSContext* cx, unsigned argc, Value* vp);
 bool intrinsic_IsCallable(JSContext* cx, unsigned argc, Value* vp);
 bool intrinsic_ThrowRangeError(JSContext* cx, unsigned argc, Value* vp);
 bool intrinsic_ThrowTypeError(JSContext* cx, unsigned argc, Value* vp);
-bool intrinsic_NewDenseArray(JSContext* cx, unsigned argc, Value* vp);
 bool intrinsic_IsConstructing(JSContext* cx, unsigned argc, Value* vp);
 bool intrinsic_SubstringKernel(JSContext* cx, unsigned argc, Value* vp);
 
 bool intrinsic_DefineDataProperty(JSContext* cx, unsigned argc, Value* vp);
 bool intrinsic_UnsafeSetReservedSlot(JSContext* cx, unsigned argc, Value* vp);
 bool intrinsic_UnsafeGetReservedSlot(JSContext* cx, unsigned argc, Value* vp);
 bool intrinsic_UnsafeGetObjectFromReservedSlot(JSContext* cx, unsigned argc, Value* vp);
 bool intrinsic_UnsafeGetInt32FromReservedSlot(JSContext* cx, unsigned argc, Value* vp);
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -76,24 +76,23 @@ class JSFunction : public js::NativeObje
         SETTER_KIND = Setter << FUNCTION_KIND_SHIFT,
 
         /* Derived Flags values for convenience: */
         NATIVE_FUN = 0,
         NATIVE_CTOR = NATIVE_FUN | CONSTRUCTOR,
         ASMJS_CTOR = ASMJS_KIND | NATIVE_CTOR,
         ASMJS_LAMBDA_CTOR = ASMJS_KIND | NATIVE_CTOR | LAMBDA,
         INTERPRETED_METHOD = INTERPRETED | METHOD_KIND,
-        INTERPRETED_METHOD_GENERATOR = INTERPRETED | METHOD_KIND,
+        INTERPRETED_METHOD_GENERATOR = INTERPRETED | METHOD_KIND | CONSTRUCTOR,
         INTERPRETED_CLASS_CONSTRUCTOR = INTERPRETED | CLASSCONSTRUCTOR_KIND | CONSTRUCTOR,
         INTERPRETED_GETTER = INTERPRETED | GETTER_KIND,
         INTERPRETED_SETTER = INTERPRETED | SETTER_KIND,
         INTERPRETED_LAMBDA = INTERPRETED | LAMBDA | CONSTRUCTOR,
         INTERPRETED_LAMBDA_ARROW = INTERPRETED | LAMBDA | ARROW_KIND,
         INTERPRETED_NORMAL = INTERPRETED | CONSTRUCTOR,
-        INTERPRETED_GENERATOR = INTERPRETED,
         NO_XDR_FLAGS = RESOLVED_LENGTH | RESOLVED_NAME,
 
         STABLE_ACROSS_CLONES = IS_FUN_PROTO | CONSTRUCTOR | EXPR_BODY | HAS_GUESSED_ATOM |
                                LAMBDA | SELF_HOSTED |  HAS_REST | FUNCTION_KIND_MASK
     };
 
     static_assert((INTERPRETED | INTERPRETED_LAZY) == js::JS_FUNCTION_INTERPRETED_BITS,
                   "jsfriendapi.h's JSFunction::INTERPRETED-alike is wrong");
--- a/js/src/tests/ecma_6/Class/methDefnGen.js
+++ b/js/src/tests/ecma_6/Class/methDefnGen.js
@@ -62,21 +62,26 @@ next = it.next("hello");
 assertEq(next.done, false);
 assertEq(next.value, 2);
 next = it.next("world");
 assertEq(next.done, true);
 assertEq(next.value.hello, 2);
 assertEq(next.value.world, 3);
 
 // prototype property
-assertEq(b.g.hasOwnProperty("prototype"), false);
+assertEq(b.g.hasOwnProperty("prototype"), true);
 
 // Strict mode
 a = {*b(c){"use strict";yield c;}};
 assertEq(a.b(1).next().value, 1);
 a = {*["b"](c){"use strict";return c;}};
 assertEq(a.b(1).next().value, 1);
 
-// Generators should not have [[Construct]]
+// Constructing
 a = {*g() { yield 1; }}
-assertThrowsInstanceOf(() => { new a.g }, TypeError);
+it = new a.g;
+next = it.next();
+assertEq(next.done, false);
+assertEq(next.value, 1);
+next = it.next();
+assertEq(next.done, true);
 
 reportCompare(0, 0, "ok");
--- a/js/src/tests/ecma_6/Class/methodsPrototype.js
+++ b/js/src/tests/ecma_6/Class/methodsPrototype.js
@@ -8,27 +8,33 @@ class TestClass {
     static staticMethod() { }
     static get staticGetter() { }
     static set staticSetter(x) { }
     static *staticGenerator() { }
 }
 
 var test = new TestClass();
 
-assertEq(test.constructor.hasOwnProperty('prototype'), true);
+var hasPrototype = [
+    test.constructor,
+    test.generator,
+    TestClass.staticGenerator
+]
+
+for (var fun of hasPrototype) {
+    assertEq(fun.hasOwnProperty('prototype'), true);
+}
 
 var hasNoPrototype = [
     test.method,
-    test.generator,
-    TestClass.staticGenerator,
     Object.getOwnPropertyDescriptor(test.__proto__, 'getter').get,
     Object.getOwnPropertyDescriptor(test.__proto__, 'setter').set,
     TestClass.staticMethod,
     Object.getOwnPropertyDescriptor(TestClass, 'staticGetter').get,
-    Object.getOwnPropertyDescriptor(TestClass, 'staticSetter').set
+    Object.getOwnPropertyDescriptor(TestClass, 'staticSetter').set,
 ]
 
 for (var fun of hasNoPrototype) {
     assertEq(fun.hasOwnProperty('prototype'), false);
 }
 
 `;
 
--- a/js/src/tests/ecma_6/Class/newTargetGenerators.js
+++ b/js/src/tests/ecma_6/Class/newTargetGenerators.js
@@ -5,16 +5,20 @@ function *generatorNewTarget(expected) {
     yield (() => new.target);
 }
 
 const ITERATIONS = 25;
 
 for (let i = 0; i < ITERATIONS; i++)
     assertEq(generatorNewTarget(undefined).next().value(), undefined);
 
+for (let i = 0; i < ITERATIONS; i++)
+    assertEq(new generatorNewTarget(generatorNewTarget).next().value(),
+             generatorNewTarget);
+
 // also check to make sure it's useful in yield inside generators.
 // Plus, this code is so ugly, how could it not be a test? ;)
 // Thanks to anba for supplying this ludicrous expression.
 assertDeepEq([...new function*(i) { yield i; if(i > 0) yield* new new.target(i-1) }(10)],
              [ 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ]);
 
 if (typeof reportCompare === 'function')
     reportCompare(0,0,"OK");
--- a/js/src/tests/ecma_6/Generators/objects.js
+++ b/js/src/tests/ecma_6/Generators/objects.js
@@ -10,16 +10,24 @@ function TestGeneratorObject() {
   function* g() { yield 1; }
 
   var iter = g();
   assertEq(Object.getPrototypeOf(iter), g.prototype);
   assertTrue(iter instanceof g);
   assertEq(String(iter), "[object Generator]");
   assertDeepEq(Object.getOwnPropertyNames(iter), []);
   assertNotEq(g(), iter);
+
+  // g() is the same as new g().
+  iter = new g();
+  assertEq(Object.getPrototypeOf(iter), g.prototype);
+  assertTrue(iter instanceof g);
+  assertEq(String(iter), "[object Generator]");
+  assertDeepEq(Object.getOwnPropertyNames(iter), []);
+  assertNotEq(new g(), iter);
 }
 TestGeneratorObject();
 
 
 // Test the methods of generator objects.
 function TestGeneratorObjectMethods() {
   function* g() { yield 1; }
   var iter = g();
--- a/js/src/tests/ecma_6/Generators/runtime.js
+++ b/js/src/tests/ecma_6/Generators/runtime.js
@@ -13,22 +13,21 @@ function assertSyntaxError(str) {
 
 
 function f() { }
 function* g() { yield 1; }
 var GeneratorFunctionPrototype = Object.getPrototypeOf(g);
 var GeneratorFunction = GeneratorFunctionPrototype.constructor;
 var GeneratorObjectPrototype = GeneratorFunctionPrototype.prototype;
 
+
 // A generator function should have the same set of properties as any
-// other function, minus a prototype.
+// other function.
 function TestGeneratorFunctionInstance() {
     var f_own_property_names = Object.getOwnPropertyNames(f);
-    f_own_property_names.splice(f_own_property_names.indexOf("prototype"), 1);
-
     var g_own_property_names = Object.getOwnPropertyNames(g);
 
     f_own_property_names.sort();
     g_own_property_names.sort();
 
     assertDeepEq(f_own_property_names, g_own_property_names);
     var i;
     for (i = 0; i < f_own_property_names.length; i++) {
@@ -37,16 +36,17 @@ function TestGeneratorFunctionInstance()
         var g_desc = Object.getOwnPropertyDescriptor(g, prop);
         assertEq(f_desc.configurable, g_desc.configurable, prop);
         assertEq(f_desc.writable, g_desc.writable, prop);
         assertEq(f_desc.enumerable, g_desc.enumerable, prop);
     }
 }
 TestGeneratorFunctionInstance();
 
+
 // Generators have an additional object interposed in the chain between
 // themselves and Function.prototype.
 function TestGeneratorFunctionPrototype() {
     // Sanity check.
     assertEq(Object.getPrototypeOf(f), Function.prototype);
     assertNotEq(GeneratorFunctionPrototype, Function.prototype);
     assertEq(Object.getPrototypeOf(GeneratorFunctionPrototype),
                Function.prototype);
@@ -107,15 +107,23 @@ function TestGeneratorFunction() {
     // as it contains "function*" and "yield 10".
     assertEq(GeneratorFunction('yield 10').toString(),
              "function* anonymous() {\nyield 10\n}");
 }
 TestGeneratorFunction();
 
 
 function TestPerGeneratorPrototype() {
-    assertEq(g.hasOwnProperty("prototype"), false);
+    assertNotEq((function*(){}).prototype, (function*(){}).prototype);
+    assertNotEq((function*(){}).prototype, g.prototype);
+    assertEq(typeof GeneratorFunctionPrototype, "object");
+    assertEq(g.prototype.__proto__.constructor, GeneratorFunctionPrototype, "object");
+    assertEq(Object.getPrototypeOf(g.prototype), GeneratorObjectPrototype);
+    assertFalse(g.prototype instanceof Function);
+    assertEq(typeof (g.prototype), "object");
+
+    assertDeepEq(Object.getOwnPropertyNames(g.prototype), []);
 }
 TestPerGeneratorPrototype();
 
 
 if (typeof reportCompare == "function")
     reportCompare(true, true);
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -282,41 +282,16 @@ intrinsic_DecompileArg(JSContext* cx, un
         return false;
     RootedAtom atom(cx, Atomize(cx, str, strlen(str)));
     if (!atom)
         return false;
     args.rval().setString(atom);
     return true;
 }
 
-/*
- * NewDenseArray(length): Allocates and returns a new dense array with
- * the given length where all values are initialized to holes.
- */
-bool
-js::intrinsic_NewDenseArray(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-
-    double lengthDouble = args[0].toNumber();
-    MOZ_ASSERT(lengthDouble >= 0);
-    MOZ_ASSERT(lengthDouble < INT32_MAX);
-    MOZ_ASSERT(uint32_t(lengthDouble) == lengthDouble);
-
-    uint32_t length = uint32_t(lengthDouble);
-
-    // Make a new buffer and initialize it up to length.
-    RootedObject buffer(cx, NewFullyAllocatedArrayForCallingAllocationSite(cx, length));
-    if (!buffer)
-        return false;
-
-    args.rval().setObject(*buffer);
-    return true;
-}
-
 bool
 js::intrinsic_DefineDataProperty(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     MOZ_ASSERT(args.length() >= 3);
     MOZ_ASSERT(args[0].isObject());
 
@@ -1265,16 +1240,17 @@ intrinsic_ConstructorForTypedArray(JSCon
 // content script might have changed the builtins' prototypes' members.
 // Installing the whole set of builtins in the self-hosting compartment, OTOH,
 // would be wasteful: it increases memory usage and initialization time for
 // self-hosting compartment.
 //
 // Additionally, a set of C++-implemented helper functions is defined on the
 // self-hosting global.
 static const JSFunctionSpec intrinsic_functions[] = {
+    JS_FN("std_Array",                           ArrayConstructor,             1,0),
     JS_FN("std_Array_join",                      array_join,                   1,0),
     JS_FN("std_Array_push",                      array_push,                   1,0),
     JS_FN("std_Array_pop",                       array_pop,                    0,0),
     JS_FN("std_Array_shift",                     array_shift,                  0,0),
     JS_FN("std_Array_unshift",                   array_unshift,                1,0),
     JS_FN("std_Array_slice",                     array_slice,                  2,0),
     JS_FN("std_Array_sort",                      array_sort,                   1,0),
 
@@ -1412,18 +1388,16 @@ static const JSFunctionSpec intrinsic_fu
 
     JS_FN("CallLegacyGeneratorMethodIfWrapped",
           CallNonGenericSelfhostedMethod<Is<LegacyGeneratorObject>>, 2, 0),
     JS_FN("CallStarGeneratorMethodIfWrapped",
           CallNonGenericSelfhostedMethod<Is<StarGeneratorObject>>, 2, 0),
 
     JS_FN("IsWeakSet",               intrinsic_IsWeakSet,               1,0),
 
-    JS_FN("NewDenseArray",           intrinsic_NewDenseArray,           1,0),
-
     // See builtin/TypedObject.h for descriptors of the typedobj functions.
     JS_FN("NewOpaqueTypedObject",           js::NewOpaqueTypedObject, 1, 0),
     JS_FN("NewDerivedTypedObject",          js::NewDerivedTypedObject, 3, 0),
     JS_FN("TypedObjectBuffer",              TypedObject::GetBuffer, 1, 0),
     JS_FN("TypedObjectByteOffset",          TypedObject::GetByteOffset, 1, 0),
     JS_FN("AttachTypedObject",              js::AttachTypedObject, 3, 0),
     JS_FN("SetTypedObjectOffset",           js::SetTypedObjectOffset, 2, 0),
     JS_FN("ObjectIsTypeDescr"    ,          js::ObjectIsTypeDescr, 1, 0),
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -24,16 +24,17 @@
 #include "nsXMLHttpRequest.h"
 #include "WrapperFactory.h"
 #include "xpcprivate.h"
 #include "XPCWrapper.h"
 #include "XrayWrapper.h"
 #include "Crypto.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/BlobBinding.h"
+#include "mozilla/dom/cache/CacheStorage.h"
 #include "mozilla/dom/CSSBinding.h"
 #include "mozilla/dom/indexedDB/IndexedDatabaseManager.h"
 #include "mozilla/dom/Fetch.h"
 #include "mozilla/dom/FileBinding.h"
 #include "mozilla/dom/PromiseBinding.h"
 #include "mozilla/dom/RequestBinding.h"
 #include "mozilla/dom/ResponseBinding.h"
 #ifdef MOZ_WEBRTC
@@ -902,16 +903,18 @@ xpc::GlobalProperties::Parse(JSContext* 
         } else if (!strcmp(name.ptr(), "crypto")) {
             crypto = true;
 #ifdef MOZ_WEBRTC
         } else if (!strcmp(name.ptr(), "rtcIdentityProvider")) {
             rtcIdentityProvider = true;
 #endif
         } else if (!strcmp(name.ptr(), "fetch")) {
             fetch = true;
+        } else if (!strcmp(name.ptr(), "caches")) {
+            caches = true;
         } else {
             JS_ReportError(cx, "Unknown property name: %s", name.ptr());
             return false;
         }
     }
     return true;
 }
 
@@ -967,16 +970,19 @@ xpc::GlobalProperties::Define(JSContext*
 #ifdef MOZ_WEBRTC
     if (rtcIdentityProvider && !SandboxCreateRTCIdentityProvider(cx, obj))
         return false;
 #endif
 
     if (fetch && !SandboxCreateFetch(cx, obj))
         return false;
 
+    if (caches && !dom::cache::CacheStorage::DefineCaches(cx, obj))
+        return false;
+
     return true;
 }
 
 nsresult
 xpc::CreateSandboxObject(JSContext* cx, MutableHandleValue vp, nsISupports* prinOrSop,
                          SandboxOptions& options)
 {
     // Create the sandbox global object
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -3413,16 +3413,17 @@ struct GlobalProperties {
     bool URLSearchParams : 1;
     bool atob : 1;
     bool btoa : 1;
     bool Blob : 1;
     bool File : 1;
     bool crypto : 1;
     bool rtcIdentityProvider : 1;
     bool fetch : 1;
+    bool caches : 1;
 };
 
 // Infallible.
 already_AddRefed<nsIXPCComponents_utils_Sandbox>
 NewSandboxConstructor();
 
 // Returns true if class of 'obj' is SandboxClass.
 bool
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -8572,8 +8572,31 @@ nsLayoutUtils::ShouldUseNoFramesSheet(ns
 {
   bool allowSubframes = true;
   nsIDocShell* docShell = aDocument->GetDocShell();
   if (docShell) {
     docShell->GetAllowSubframes(&allowSubframes);
   }
   return !allowSubframes;
 }
+
+/* static */ void
+nsLayoutUtils::GetFrameTextContent(nsIFrame* aFrame, nsAString& aResult)
+{
+  aResult.Truncate();
+  AppendFrameTextContent(aFrame, aResult);
+}
+
+/* static */ void
+nsLayoutUtils::AppendFrameTextContent(nsIFrame* aFrame, nsAString& aResult)
+{
+  if (aFrame->GetType() == nsGkAtoms::textFrame) {
+    auto textFrame = static_cast<nsTextFrame*>(aFrame);
+    auto offset = textFrame->GetContentOffset();
+    auto length = textFrame->GetContentLength();
+    textFrame->GetContent()->
+      GetText()->AppendTo(aResult, offset, length);
+  } else {
+    for (nsIFrame* child : aFrame->PrincipalChildList()) {
+      AppendFrameTextContent(child, aResult);
+    }
+  }
+}
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -2714,16 +2714,36 @@ public:
    * Looks in the layer subtree rooted at aLayer for a metrics with scroll id
    * aScrollId. Returns true if such is found.
    */
   static bool ContainsMetricsWithId(const Layer* aLayer, const ViewID& aScrollId);
 
   static bool ShouldUseNoScriptSheet(nsIDocument* aDocument);
   static bool ShouldUseNoFramesSheet(nsIDocument* aDocument);
 
+  /**
+   * Get the text content inside the frame. This methods traverse the
+   * frame tree and collect the content from text frames. Note that this
+   * method is similiar to nsContentUtils::GetNodeTextContent, but it at
+   * least differs from that method in the following things:
+   * 1. it skips text content inside nodes like style, script, textarea
+   *    which don't generate an in-tree text frame for the text;
+   * 2. it skips elements with display property set to none;
+   * 3. it skips out-of-flow elements;
+   * 4. it includes content inside pseudo elements;
+   * 5. it may include part of text content of a node if a text frame
+   *    inside is split to different continuations.
+   */
+  static void GetFrameTextContent(nsIFrame* aFrame, nsAString& aResult);
+
+  /**
+   * Same as GetFrameTextContent but appends the result rather than sets it.
+   */
+  static void AppendFrameTextContent(nsIFrame* aFrame, nsAString& aResult);
+
 private:
   static uint32_t sFontSizeInflationEmPerLine;
   static uint32_t sFontSizeInflationMinTwips;
   static uint32_t sFontSizeInflationLineThreshold;
   static int32_t  sFontSizeInflationMappingIntercept;
   static uint32_t sFontSizeInflationMaxRatio;
   static bool sFontSizeInflationForceEnabled;
   static bool sFontSizeInflationDisabledInMasterProcess;
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -6346,24 +6346,24 @@ PresShell::UpdateActivePointerState(Widg
   switch (aEvent->mMessage) {
   case eMouseEnterIntoWidget:
     // In this case we have to know information about available mouse pointers
     if (WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent()) {
       gActivePointersIds->Put(mouseEvent->pointerId,
                               new PointerInfo(false, mouseEvent->inputSource, true));
     }
     break;
-  case NS_POINTER_DOWN:
+  case ePointerDown:
     // In this case we switch pointer to active state
     if (WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent()) {
       gActivePointersIds->Put(pointerEvent->pointerId,
                               new PointerInfo(true, pointerEvent->inputSource, pointerEvent->isPrimary));
     }
     break;
-  case NS_POINTER_UP:
+  case ePointerUp:
     // In this case we remove information about pointer or turn off active state
     if (WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent()) {
       if(pointerEvent->inputSource != nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) {
         gActivePointersIds->Put(pointerEvent->pointerId,
                                 new PointerInfo(false, pointerEvent->inputSource, pointerEvent->isPrimary));
       } else {
         gActivePointersIds->Remove(pointerEvent->pointerId);
       }
@@ -6667,23 +6667,23 @@ DispatchPointerFromMouseOrTouch(PresShel
       return NS_OK;
     }
     int16_t button = mouseEvent->button;
     switch (mouseEvent->mMessage) {
     case eMouseMove:
       if (mouseEvent->buttons == 0) {
         button = -1;
       }
-      pointerMessage = NS_POINTER_MOVE;
+      pointerMessage = ePointerMove;
       break;
     case eMouseUp:
-      pointerMessage = NS_POINTER_UP;
+      pointerMessage = ePointerUp;
       break;
     case eMouseDown:
-      pointerMessage = NS_POINTER_DOWN;
+      pointerMessage = ePointerDown;
       break;
     default:
       return NS_OK;
     }
 
     WidgetPointerEvent event(*mouseEvent);
     event.mMessage = pointerMessage;
     event.button = button;
@@ -6693,26 +6693,26 @@ DispatchPointerFromMouseOrTouch(PresShel
     event.convertToPointer = mouseEvent->convertToPointer = false;
     aShell->HandleEvent(aFrame, &event, aDontRetargetEvents, aStatus, aTargetContent);
   } else if (aEvent->mClass == eTouchEventClass) {
     WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
     // loop over all touches and dispatch pointer events on each touch
     // copy the event
     switch (touchEvent->mMessage) {
     case NS_TOUCH_MOVE:
-      pointerMessage = NS_POINTER_MOVE;
+      pointerMessage = ePointerMove;
       break;
     case NS_TOUCH_END:
-      pointerMessage = NS_POINTER_UP;
+      pointerMessage = ePointerUp;
       break;
     case NS_TOUCH_START:
-      pointerMessage = NS_POINTER_DOWN;
+      pointerMessage = ePointerDown;
       break;
     case NS_TOUCH_CANCEL:
-      pointerMessage = NS_POINTER_CANCEL;
+      pointerMessage = ePointerCancel;
       break;
     default:
       return NS_OK;
     }
 
     for (uint32_t i = 0; i < touchEvent->touches.Length(); ++i) {
       mozilla::dom::Touch* touch = touchEvent->touches[i];
       if (!touch || !touch->convertToPointer) {
@@ -7445,35 +7445,36 @@ PresShell::HandleEvent(nsIFrame* aFrame,
         // Prevent application crashes, in case damaged frame.
         if (!frameKeeper.IsAlive()) {
           frame = nullptr;
         }
       }
     }
 
     if (aEvent->mClass == ePointerEventClass &&
-        aEvent->mMessage != NS_POINTER_DOWN) {
+        aEvent->mMessage != ePointerDown) {
       if (WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent()) {
         uint32_t pointerId = pointerEvent->pointerId;
         nsIContent* pointerCapturingContent = GetPointerCapturingContent(pointerId);
 
         if (pointerCapturingContent) {
           if (nsIFrame* capturingFrame = pointerCapturingContent->GetPrimaryFrame()) {
             // If pointer capture is set, we should suppress pointerover/pointerenter events
             // for all elements except element which have pointer capture. (Code in EventStateManager)
             pointerEvent->retargetedByPointerCapture =
               frame && frame->GetContent() &&
               !nsContentUtils::ContentIsDescendantOf(frame->GetContent(), pointerCapturingContent);
             frame = capturingFrame;
           }
 
-          if (pointerEvent->mMessage == NS_POINTER_UP ||
-              pointerEvent->mMessage == NS_POINTER_CANCEL) {
+          if (pointerEvent->mMessage == ePointerUp ||
+              pointerEvent->mMessage == ePointerCancel) {
             // Implicitly releasing capture for given pointer.
-            // LOST_POINTER_CAPTURE should be send after NS_POINTER_UP or NS_POINTER_CANCEL.
+            // ePointerLostCapture should be send after ePointerUp or
+            // ePointerCancel.
             releasePointerCaptureCaller.SetTarget(pointerId, pointerCapturingContent);
           }
         }
       }
     }
 
     // Suppress mouse event if it's being targeted at an element inside
     // a document which needs events suppressed
@@ -7908,17 +7909,17 @@ PresShell::HandleEventInternal(WidgetEve
 
       if (!mTouchManager.PreHandleEvent(aEvent, aStatus,
                                         touchIsNew, isHandlingUserInput,
                                         mCurrentEventContent)) {
         return NS_OK;
       }
     }
 
-    if (aEvent->mMessage == NS_CONTEXTMENU) {
+    if (aEvent->mMessage == eContextMenu) {
       WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
       if (mouseEvent->context == WidgetMouseEvent::eContextMenuKey &&
           !AdjustContextMenuKeyEvent(mouseEvent)) {
         return NS_OK;
       }
       if (mouseEvent->IsShift()) {
         aEvent->mFlags.mOnlyChromeDispatch = true;
         aEvent->mFlags.mRetargetToNonNativeAnonymous = true;
--- a/layout/generic/nsRubyBaseContainerFrame.cpp
+++ b/layout/generic/nsRubyBaseContainerFrame.cpp
@@ -8,17 +8,17 @@
 
 #include "nsRubyBaseContainerFrame.h"
 #include "nsRubyTextContainerFrame.h"
 #include "nsRubyBaseFrame.h"
 #include "nsRubyTextFrame.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/WritingModes.h"
-#include "nsContentUtils.h"
+#include "nsLayoutUtils.h"
 #include "nsLineLayout.h"
 #include "nsPresContext.h"
 #include "nsStyleContext.h"
 #include "nsStyleStructInlines.h"
 #include "nsTextFrame.h"
 #include "RubyUtils.h"
 
 using namespace mozilla;
@@ -604,27 +604,25 @@ nsRubyBaseContainerFrame::ReflowOneColum
 
   const uint32_t rtcCount = aReflowState.mTextContainers.Length();
   MOZ_ASSERT(aColumn.mTextFrames.Length() == rtcCount);
   MOZ_ASSERT(textReflowStates.Length() == rtcCount);
   nscoord columnISize = 0;
 
   nsAutoString baseText;
   if (aColumn.mBaseFrame) {
-    nsContentUtils::GetNodeTextContent(aColumn.mBaseFrame->GetContent(),
-                                       true, baseText);
+    nsLayoutUtils::GetFrameTextContent(aColumn.mBaseFrame, baseText);
   }
 
   // Reflow text frames
   for (uint32_t i = 0; i < rtcCount; i++) {
     nsRubyTextFrame* textFrame = aColumn.mTextFrames[i];
     if (textFrame) {
       nsAutoString annotationText;
-      nsContentUtils::GetNodeTextContent(textFrame->GetContent(),
-                                         true, annotationText);
+      nsLayoutUtils::GetFrameTextContent(textFrame, annotationText);
 
       // Per CSS Ruby spec, the content comparison for auto-hiding
       // takes place prior to white spaces collapsing (white-space)
       // and text transformation (text-transform), and ignores elements
       // (considers only the textContent of the boxes). Which means
       // using the content tree text comparison is correct.
       if (annotationText.Equals(baseText)) {
         textFrame->AddStateBits(NS_RUBY_TEXT_FRAME_AUTOHIDE);
--- a/layout/reftests/css-mediaqueries/reftest.list
+++ b/layout/reftests/css-mediaqueries/reftest.list
@@ -1,18 +1,18 @@
 fuzzy-if(Android,8,454) skip-if(B2G||Mulet) == mq_print_height.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop, bug 1178697
 skip-if(B2G||Mulet) == mq_print_deviceheight.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) == mq_print_width.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) == mq_print_minwidth.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) == mq_print_minheight.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) == mq_print_aspectratio.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) == mq_print_deviceaspectratio.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) == mq_print_devicewidth.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
-skip-if(B2G||Mulet) == mq_print_orientation.xhtml mq_print_orientation-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
-skip-if(B2G||Mulet) == mq_print_maxheight.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
+fuzzy-if(Android,8,454) skip-if(B2G||Mulet) == mq_print_orientation.xhtml mq_print_orientation-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
+fuzzy-if(Android,8,454) skip-if(B2G||Mulet) == mq_print_maxheight.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) == mq_print_maxwidth.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 
 skip-if(B2G||Mulet) == mq_print_maxwidth_updown.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) == mq_print_maxheight_updown.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) == mq_print_minheight_updown.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) == mq_print_minwidth_updown.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 
 == scoped-mq-update.html scoped-mq-update-ref.html
--- a/layout/reftests/svg/filters/feBlend-1-ref.svg
+++ b/layout/reftests/svg/filters/feBlend-1-ref.svg
@@ -1,19 +1,20 @@
 <svg xmlns="http://www.w3.org/2000/svg">
 
-<rect x="0" y="0" width="50" height="50" fill="#ACCC10"/>
-<rect x="50" y="0" width="50" height="50" fill="#B4B43F"/>
+<rect x="0" y="0" width="50" height="50" fill="#DFB53F"/>
+<rect x="50" y="0" width="50" height="50" fill="#B5B53F"/>
 <rect x="100" y="0" width="50" height="50" fill="#DFDF3F"/>
-<rect x="150" y="0" width="50" height="50" fill="#B4B43F"/>
+<rect x="150" y="0" width="50" height="50" fill="#B5B53F"/>
 <rect x="200" y="0" width="50" height="50" fill="#DFDF3F"/>
-<rect x="250" y="0" width="50" height="50" fill="#DFB43F"/>
-<rect x="300" y="0" width="50" height="50" fill="#DFB43F"/>
-<rect x="350" y="0" width="50" height="50" fill="#DFB43F"/>
-<rect x="0" y="50" width="50" height="50" fill="#E0B440"/>
-<rect x="50" y="50" width="50" height="50" fill="#DFB43F"/>
+<rect x="250" y="0" width="50" height="50" fill="#B5DF3F"/>
+<rect x="300" y="0" width="50" height="50" fill="#B5DF3F"/>
+<rect x="350" y="0" width="50" height="50" fill="#B5DF3F"/>
+<rect x="0" y="50" width="50" height="50" fill="#DFB53F"/>
+<rect x="50" y="50" width="50" height="50" fill="#B5DF3F"/>
 <rect x="100" y="50" width="50" height="50" fill="#DFDF3F"/>
 <rect x="150" y="50" width="50" height="50" fill="#DFDF3F"/>
-<rect x="200" y="50" width="50" height="50" fill="#B4CC3F"/>
-<rect x="250" y="50" width="50" height="50" fill="#DFB43F"/>
-<rect x="300" y="50" width="50" height="50" fill="#B4CC3F"/>
-<rect x="350" y="50" width="50" height="50" fill="#DFC88D"/>
+<rect x="200" y="50" width="50" height="50" fill="#DFC88E"/>
+<rect x="250" y="50" width="50" height="50" fill="#B5DF3F"/>
+<rect x="300" y="50" width="50" height="50" fill="#DFC88E"/>
+<rect x="350" y="50" width="50" height="50" fill="#B5CC3F"/>
+<rect x="0" y="100" width="50" height="50" fill="#DFB53F"/>
 </svg>
--- a/layout/reftests/svg/filters/feBlend-1.svg
+++ b/layout/reftests/svg/filters/feBlend-1.svg
@@ -47,17 +47,17 @@
   <feBlend in="a" in2="b" mode="color-burn"/>
 </filter>
 <rect x="350" y="0" width="50" height="50" filter="url(#f7)"/>
 <filter id="f8" x="0%" y="0%" width="100%" height="100%">
   <feFlood result="a" flood-color="rgb(255,0,0)" flood-opacity="0.5"/>
   <feFlood result="b" flood-color="rgb(0,255,0)" flood-opacity="0.5"/>
   <feBlend in="a" in2="b" mode="hard-light"/>
 </filter>
-<rect x="0" y="0" width="50" height="50" filter="url(#f8)"/>
+<rect x="0" y="50" width="50" height="50" filter="url(#f8)"/>
 <filter id="f9" x="0%" y="0%" width="100%" height="100%">
   <feFlood result="a" flood-color="rgb(255,0,0)" flood-opacity="0.5"/>
   <feFlood result="b" flood-color="rgb(0,255,0)" flood-opacity="0.5"/>
   <feBlend in="a" in2="b" mode="soft-light"/>
 </filter>
 <rect x="50" y="50" width="50" height="50" filter="url(#f9)"/>
 <filter id="f10" x="0%" y="0%" width="100%" height="100%">
   <feFlood result="a" flood-color="rgb(255,0,0)" flood-opacity="0.5"/>
@@ -90,15 +90,15 @@
 </filter>
 <rect x="300" y="50" width="50" height="50" filter="url(#f14)"/>
 <filter id="f15" x="0%" y="0%" width="100%" height="100%">
   <feFlood result="a" flood-color="rgb(255,0,0)" flood-opacity="0.5"/>
   <feFlood result="b" flood-color="rgb(0,255,0)" flood-opacity="0.5"/>
   <feBlend in="a" in2="b" mode="luminosity"/>
 </filter>
 <rect x="350" y="50" width="50" height="50" filter="url(#f15)"/>
-  <filter id="f16" x="0%" y="0%" width="100%" height="100%">
+<filter id="f16" x="0%" y="0%" width="100%" height="100%">
   <feFlood result="a" flood-color="rgb(255,0,0)" flood-opacity="0.5"/>
   <feFlood result="b" flood-color="rgb(0,255,0)" flood-opacity="0.5"/>
   <feBlend in="a" in2="b" mode="undefined"/>
 </filter>
-<rect x="0" y="50" width="50" height="50" filter="url(#f16)"/>
-</svg>
\ No newline at end of file
+<rect x="0" y="100" width="50" height="50" filter="url(#f16)"/>
+</svg>
--- a/layout/reftests/svg/filters/feBlend-2-ref.svg
+++ b/layout/reftests/svg/filters/feBlend-2-ref.svg
@@ -1,5 +1,6 @@
 <svg xmlns="http://www.w3.org/2000/svg">
 
 <rect x="0" y="0" width="100" height="100" fill="#00ff00"/>
+<rect x="150" y="0" width="100" height="100" fill="#00ff00"/>
 
 </svg>
--- a/layout/reftests/svg/filters/feBlend-2.svg
+++ b/layout/reftests/svg/filters/feBlend-2.svg
@@ -1,9 +1,17 @@
 <svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
 
 <filter id="f1" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse">
   <feFlood flood-color="#ff0000" result="flood" x="0" y="0" width="100" height="100"/>
   <feBlend mode="normal" in="SourceGraphic" in2="flood"/>
 </filter>
 <rect x="0" y="0" width="100" height="100" fill="#00ff00" filter="url(#f1)"/>
 
+<!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=1181317 -->
+<filter id="f2" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse">
+  <feFlood flood-color="#ff0000" result="red" x="150" y="0"
+           width="100" height="100"/>
+  <feBlend mode="hard-light" in="SourceGraphic" in2="red"/>
+</filter>
+<rect x="150" y="0" width="100" height="100" fill="#00ff00" filter="url(#f2)"/>
+
 </svg>
--- a/layout/reftests/svg/filters/reftest.list
+++ b/layout/reftests/svg/filters/reftest.list
@@ -13,17 +13,17 @@ include css-svg-filter-chains/reftest.li
 
 # SVG filter chain tests
 include svg-filter-chains/reftest.list
 
 == dynamic-filtered-foreignObject-01.svg pass.svg
 == dynamic-filter-invalidation-01.svg pass.svg
 == dynamic-filter-invalidation-02.svg pass.svg
 
-fuzzy(1,40000) == feBlend-1.svg feBlend-1-ref.svg
+fuzzy(1,42500) == feBlend-1.svg feBlend-1-ref.svg
 == feBlend-2.svg feBlend-2-ref.svg
 
 fuzzy-if(d2d,1,6400) == feColorMatrix-1.svg feColorMatrix-1-ref.svg
 fuzzy-if(d2d,1,10000) == feColorMatrix-2.svg feColorMatrix-2-ref.svg
 
 == feComponentTransfer-1.svg feComponentTransfer-1-ref.svg
 == feComponentTransfer-2.svg feComponentTransfer-2-ref.svg
 
--- a/layout/reftests/w3c-css/submitted/ruby/reftest.list
+++ b/layout/reftests/w3c-css/submitted/ruby/reftest.list
@@ -4,9 +4,10 @@
 == ruby-inlinize-blocks-003.html ruby-inlinize-blocks-003-ref.html
 == ruby-inlinize-blocks-004.html ruby-inlinize-blocks-004-ref.html
 == ruby-inlinize-blocks-005.html ruby-inlinize-blocks-005-ref.html
 
 # Tests for autohiding base-identical annotations
 == ruby-autohide-001.html ruby-autohide-001-ref.html
 == ruby-autohide-002.html ruby-autohide-002-ref.html
 == ruby-autohide-003.html ruby-autohide-003-ref.html
+== ruby-autohide-004.html ruby-autohide-001-ref.html
 
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/ruby/ruby-autohide-004.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="ja">
+<head>
+  <meta charset="UTF-8">
+  <title>CSS Test: Autohide ruby annotations which are identical to their bases</title>
+  <link rel="author" title="Xidorn Quan" href="mailto:quanxunzhen@gmail.com">
+  <link rel="help" href="http://www.w3.org/TR/css-ruby-1/#autohide">
+  <link rel="match" href="ruby-autohide-001-ref.html">
+</head>
+<body>
+  <ruby>
+    振<rt>ふ</rt>り<rt>り</rt>仮<rt>が</rt>名<rt>な</rt>
+  </ruby>
+</body>
+</html>
--- a/layout/svg/nsSVGOuterSVGFrame.cpp
+++ b/layout/svg/nsSVGOuterSVGFrame.cpp
@@ -163,25 +163,43 @@ nsSVGOuterSVGFrame::GetMinISize(nsRender
 
 /* virtual */ nscoord
 nsSVGOuterSVGFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
 {
   nscoord result;
   DISPLAY_PREF_WIDTH(this, result);
 
   SVGSVGElement *svg = static_cast<SVGSVGElement*>(mContent);
-  nsSVGLength2 &width = svg->mLengthAttributes[SVGSVGElement::ATTR_WIDTH];
+  WritingMode wm = GetWritingMode();
+  const nsSVGLength2& isize = wm.IsVertical()
+    ? svg->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]
+    : svg->mLengthAttributes[SVGSVGElement::ATTR_WIDTH];
+
+  if (isize.IsPercentage()) {
+    // It looks like our containing block's isize may depend on our isize. In
+    // that case our behavior is undefined according to CSS 2.1 section 10.3.2.
+    // As a last resort, we'll fall back to returning zero.
+    result = nscoord(0);
 
-  if (width.IsPercentage()) {
-    // It looks like our containing block's width may depend on our width. In
-    // that case our behavior is undefined according to CSS 2.1 section 10.3.2,
-    // so return zero.
-    result = nscoord(0);
+    // Returning zero may be unhelpful, however, as it leads to unexpected
+    // disappearance of %-sized SVGs in orthogonal contexts, where our
+    // containing block wants to shrink-wrap. So let's look for an ancestor
+    // with non-zero size in this dimension, and use that as a (somewhat
+    // arbitrary) result instead.
+    nsIFrame *parent = GetParent();
+    while (parent) {
+      nscoord parentISize = parent->GetLogicalSize(wm).ISize(wm);
+      if (parentISize > 0 && parentISize != NS_UNCONSTRAINEDSIZE) {
+        result = parentISize;
+        break;
+      }
+      parent = parent->GetParent();
+    }
   } else {
-    result = nsPresContext::CSSPixelsToAppUnits(width.GetAnimValue(svg));
+    result = nsPresContext::CSSPixelsToAppUnits(isize.GetAnimValue(svg));
     if (result < 0) {
       result = nscoord(0);
     }
   }
 
   return result;
 }
 
--- a/layout/xul/nsMenuFrame.cpp
+++ b/layout/xul/nsMenuFrame.cpp
@@ -429,17 +429,17 @@ nsMenuFrame::HandleEvent(nsPresContext* 
       }
     }
   }
   else if (
 #ifndef NSCONTEXTMENUISMOUSEUP
            (aEvent->mMessage == eMouseUp &&
             aEvent->AsMouseEvent()->button == WidgetMouseEvent::eRightButton) &&
 #else
-           aEvent->mMessage == NS_CONTEXTMENU &&
+           aEvent->mMessage == eContextMenu &&
 #endif
            onmenu && !IsMenu() && !IsDisabled()) {
     // if this menu is a context menu it accepts right-clicks...fire away!
     // Make sure we cancel default processing of the context menu event so
     // that it doesn't bubble and get seen again by the popuplistener and show
     // another context menu.
     //
     // Furthermore (there's always more, isn't there?), on some platforms (win32
--- a/media/gmp-clearkey/0.1/VideoDecoder.cpp
+++ b/media/gmp-clearkey/0.1/VideoDecoder.cpp
@@ -52,17 +52,17 @@ VideoDecoder::InitDecode(const GMPVideoC
                          const uint8_t* aCodecSpecific,
                          uint32_t aCodecSpecificLength,
                          GMPVideoDecoderCallback* aCallback,
                          int32_t aCoreCount)
 {
   mCallback = aCallback;
   assert(mCallback);
   mDecoder = new WMFH264Decoder();
-  HRESULT hr = mDecoder->Init();
+  HRESULT hr = mDecoder->Init(aCoreCount);
   if (FAILED(hr)) {
     CK_LOGD("VideoDecoder::InitDecode failed to init WMFH264Decoder");
     mCallback->Error(GMPGenericErr);
     return;
   }
 
   auto err = GetPlatform()->createmutex(&mMutex);
   if (GMP_FAILED(err)) {
--- a/media/gmp-clearkey/0.1/WMFH264Decoder.cpp
+++ b/media/gmp-clearkey/0.1/WMFH264Decoder.cpp
@@ -11,47 +11,55 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "WMFH264Decoder.h"
 #include <algorithm>
+#include <codecapi.h>
 
 namespace wmf {
 
 WMFH264Decoder::WMFH264Decoder()
   : mDecoder(nullptr)
 {
   memset(&mInputStreamInfo, 0, sizeof(MFT_INPUT_STREAM_INFO));
   memset(&mOutputStreamInfo, 0, sizeof(MFT_OUTPUT_STREAM_INFO));
 }
 
 WMFH264Decoder::~WMFH264Decoder()
 {
 }
 
 HRESULT
-WMFH264Decoder::Init()
+WMFH264Decoder::Init(int32_t aCoreCount)
 {
   HRESULT hr;
 
   hr = CreateMFT(__uuidof(CMSH264DecoderMFT),
                  WMFDecoderDllNameFor(H264),
                  mDecoder);
   if (FAILED(hr)) {
     // Windows 7 Enterprise Server N (which is what Mozilla's mochitests run
     // on) need a different CLSID to instantiate the H.264 decoder.
     hr = CreateMFT(CLSID_CMSH264DecMFT,
                    WMFDecoderDllNameFor(H264),
                    mDecoder);
   }
   ENSURE(SUCCEEDED(hr), hr);
 
+  CComPtr<IMFAttributes> attr;
+  hr = mDecoder->GetAttributes(&attr);
+  ENSURE(SUCCEEDED(hr), hr);
+  hr = attr->SetUINT32(CODECAPI_AVDecNumWorkerThreads,
+                       GetNumThreads(aCoreCount));
+  ENSURE(SUCCEEDED(hr), hr);
+
   hr = SetDecoderInputType();
   ENSURE(SUCCEEDED(hr), hr);
 
   hr = SetDecoderOutputType();
   ENSURE(SUCCEEDED(hr), hr);
 
   hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
   ENSURE(SUCCEEDED(hr), hr);
--- a/media/gmp-clearkey/0.1/WMFH264Decoder.h
+++ b/media/gmp-clearkey/0.1/WMFH264Decoder.h
@@ -21,17 +21,17 @@
 
 namespace wmf {
 
 class WMFH264Decoder {
 public:
   WMFH264Decoder();
   ~WMFH264Decoder();
 
-  HRESULT Init();
+  HRESULT Init(int32_t aCoreCount);
 
   HRESULT Input(const uint8_t* aData,
                 uint32_t aDataSize,
                 Microseconds aTimestamp,
                 Microseconds aDuration);
 
   HRESULT Output(IMFSample** aOutput);
 
--- a/media/gmp-clearkey/0.1/WMFUtils.cpp
+++ b/media/gmp-clearkey/0.1/WMFUtils.cpp
@@ -13,16 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "WMFUtils.h"
 #include "ClearKeyUtils.h"
 #include <versionhelpers.h>
 
+#include <algorithm>
 #include <stdio.h>
 
 #define INITGUID
 #include <guiddef.h>
 
 #pragma comment(lib, "mfuuid.lib")
 #pragma comment(lib, "wmcodecdspuuid")
 #pragma comment(lib, "mfplat.lib")
@@ -248,9 +249,15 @@ CreateMFT(const CLSID& clsid,
   if (FAILED(hr)) {
     LOG("Failed to get create MFT\n");
     return E_FAIL;
   }
 
   return S_OK;
 }
 
+int32_t
+GetNumThreads(int32_t aCoreCount)
+{
+  return aCoreCount > 4 ? -1 : (std::max)(aCoreCount - 1, 1);
+}
+
 } // namespace
--- a/media/gmp-clearkey/0.1/WMFUtils.h
+++ b/media/gmp-clearkey/0.1/WMFUtils.h
@@ -254,11 +254,15 @@ enum CodecType {
   H264,
   AAC,
 };
 
 // Returns the name of the DLL that is needed to decode H.264 or AAC on
 // the given windows version we're running on.
 const char* WMFDecoderDllNameFor(CodecType aCodec);
 
+// Returns the maximum number of threads we want WMF to use for decoding
+// given the number of logical processors available.
+int32_t GetNumThreads(int32_t aCoreCount);
+
 } // namespace wmf
 
 #endif // __WMFUtils_h__
--- a/media/libcubeb/src/cubeb_wasapi.cpp
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
@@ -696,16 +696,23 @@ HRESULT get_default_endpoint(IMMDevice *
   return ERROR_SUCCESS;
 }
 
 double
 current_stream_delay(cubeb_stream * stm)
 {
   stm->stream_reset_lock->assert_current_thread_owns();
 
+  /* If the default audio endpoint went away during playback and we weren't
+     able to configure a new one, it's possible the caller may call this
+     before the error callback has propogated back. */
+  if (!stm->audio_clock) {
+    return 0;
+  }
+
   UINT64 freq;
   HRESULT hr = stm->audio_clock->GetFrequency(&freq);
   if (FAILED(hr)) {
     LOG("GetFrequency failed: %x\n", hr);
     return 0;
   }
 
   UINT64 pos;
@@ -723,16 +730,20 @@ current_stream_delay(cubeb_stream * stm)
   return delay;
 }
 
 int
 stream_set_volume(cubeb_stream * stm, float volume)
 {
   stm->stream_reset_lock->assert_current_thread_owns();
 
+  if (!stm->audio_stream_volume) {
+    return CUBEB_ERROR;
+  }
+
   uint32_t channels;
   HRESULT hr = stm->audio_stream_volume->GetChannelCount(&channels);
   if (hr != S_OK) {
     LOG("could not get the channel count: %x\n", hr);
     return CUBEB_ERROR;
   }
 
   /* up to 9.1 for now */
@@ -1284,16 +1295,20 @@ void wasapi_stream_destroy(cubeb_stream 
 }
 
 int wasapi_stream_start(cubeb_stream * stm)
 {
   auto_lock lock(stm->stream_reset_lock);
 
   XASSERT(stm && !stm->thread && !stm->shutdown_event);
 
+  if (!stm->client) {
+    return CUBEB_ERROR;
+  }
+
   HRESULT hr = stm->client->Start();
   if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
     LOG("audioclient invalid device, reconfiguring\n", hr);
 
     BOOL ok = ResetEvent(stm->reconfigure_event);
     if (!ok) {
       LOG("resetting reconfig event failed: %x\n", GetLastError());
     }
--- a/memory/replace/logalloc/FdPrintf.cpp
+++ b/memory/replace/logalloc/FdPrintf.cpp
@@ -8,16 +8,17 @@
 
 #ifdef _WIN32
 #include <windows.h>
 #else
 #include <unistd.h>
 #endif
 #include <cstring>
 #include "mozilla/Assertions.h"
+#include "mozilla/unused.h"
 
 /* Template class allowing a limited number of increments on a value */
 template <typename T>
 class CheckedIncrement
 {
 public:
   CheckedIncrement(T aValue, size_t aMaxIncrement)
     : mValue(aValue), mMaxIncrement(aMaxIncrement)
@@ -120,12 +121,12 @@ FdPrintf(intptr_t aFd, const char* aForm
     f++;
   }
 out:
 #ifdef _WIN32
   // See comment in FdPrintf.h as to why WriteFile is used.
   DWORD written;
   WriteFile(reinterpret_cast<HANDLE>(aFd), buf, b - buf, &written, nullptr);
 #else
-  write(aFd, buf, b - buf);
+  mozilla::unused << write(aFd, buf, b - buf);
 #endif
   va_end(ap);
 }
--- a/memory/replace/logalloc/moz.build
+++ b/memory/replace/logalloc/moz.build
@@ -33,11 +33,8 @@ include('/ipc/chromium/chromium-config.m
 if CONFIG['OS_TARGET'] == 'Android':
     USE_LIBS += [
         'mozglue',
     ]
 
 DIRS += [
     'replay',
 ]
-
-# XXX: We should fix these warnings
-ALLOW_COMPILER_WARNINGS = True
--- a/memory/replace/logalloc/replay/moz.build
+++ b/memory/replace/logalloc/replay/moz.build
@@ -16,11 +16,8 @@ LOCAL_INCLUDES += [
 ]
 
 # Link replace-malloc and the default allocator.
 USE_LIBS += [
     'memory',
 ]
 
 DISABLE_STL_WRAPPING = True
-
-# XXX: We should fix these warnings
-ALLOW_COMPILER_WARNINGS = True
--- a/mozglue/build/moz.build
+++ b/mozglue/build/moz.build
@@ -21,16 +21,20 @@ if CONFIG['OS_TARGET'] == 'Android':
 
 if CONFIG['MOZ_ASAN']:
     SOURCES += [
         'AsanOptions.cpp',
     ]
 
 if CONFIG['OS_TARGET'] == 'WINNT':
     DEFFILE = 'mozglue.def'
+    # We'll break the DLL blocklist if we immediately load user32.dll
+    DELAYLOAD_DLLS += [
+        'user32.dll',
+    ]
 
 if not CONFIG['JS_STANDALONE']:
 
     if CONFIG['MOZ_MEMORY'] and (CONFIG['MOZ_NATIVE_JEMALLOC'] or FORCE_SHARED_LIB):
         pass
         # TODO: SHARED_LIBRARY_LIBS go here
     else:
         # Temporary, until bug 662814 lands
--- a/netwerk/protocol/http/Http2Session.cpp
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -1552,21 +1552,21 @@ Http2Session::RecvPushPromise(Http2Sessi
   if (self->mInputFrameFlags & kFlag_END_PUSH_PROMISE) {
     self->mExpectedPushPromiseID = 0;
     self->mContinuedPromiseStream = 0;
   } else {
     self->mExpectedPushPromiseID = self->mInputFrameID;
     self->mContinuedPromiseStream = promisedID;
   }
 
-  if (paddingLength > self->mInputFrameDataSize) {
+  if ((paddingControlBytes + promiseLen + paddingLength) > self->mInputFrameDataSize) {
     // This is fatal to the session
     LOG3(("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X "
-          "PROTOCOL_ERROR paddingLength %d > frame size %d\n",
-          self, promisedID, associatedID, paddingLength,
+          "PROTOCOL_ERROR extra %d > frame size %d\n",
+          self, promisedID, associatedID, (paddingControlBytes + promiseLen + paddingLength),
           self->mInputFrameDataSize));
     RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
   }
 
   LOG3(("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X "
         "paddingLength %d padded %d\n",
         self, promisedID, associatedID, paddingLength,
         self->mInputFrameFlags & kFlag_PADDED));
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -83,16 +83,17 @@ HttpBaseChannel::HttpBaseChannel()
   , mProxyURI(nullptr)
   , mContentDispositionHint(UINT32_MAX)
   , mHttpHandler(gHttpHandler)
   , mReferrerPolicy(REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE)
   , mRedirectCount(0)
   , mForcePending(false)
   , mCorsIncludeCredentials(false)
   , mCorsMode(nsIHttpChannelInternal::CORS_MODE_NO_CORS)
+  , mRedirectMode(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW)
   , mOnStartRequestCalled(false)
 {
   LOG(("Creating HttpBaseChannel @%x\n", this));
 
   // Subfields of unions cannot be targeted in an initializer list.
 #ifdef MOZ_VALGRIND
   // Zero the entire unions so that Valgrind doesn't complain when we send them
   // to another process.
@@ -1997,16 +1998,30 @@ HttpBaseChannel::GetCorsMode(uint32_t* a
 
 NS_IMETHODIMP
 HttpBaseChannel::SetCorsMode(uint32_t aMode)
 {
   mCorsMode = aMode;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+HttpBaseChannel::GetRedirectMode(uint32_t* aMode)
+{
+  *aMode = mRedirectMode;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRedirectMode(uint32_t aMode)
+{
+  mRedirectMode = aMode;
+  return NS_OK;
+}
+
 //-----------------------------------------------------------------------------
 // HttpBaseChannel::nsISupportsPriority
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpBaseChannel::GetPriority(int32_t *value)
 {
   *value = mPriority;
@@ -2272,24 +2287,16 @@ HttpBaseChannel::SetupReplacementChannel
   if (mPrivateBrowsingOverriden) {
     nsCOMPtr<nsIPrivateBrowsingChannel> newPBChannel =
       do_QueryInterface(newChannel);
     if (newPBChannel) {
       newPBChannel->SetPrivate(mPrivateBrowsing);
     }
   }
 
-  // Preserve any skip-serviceworker-flag if possible.
-  if (mForceNoIntercept) {
-    nsCOMPtr<nsIHttpChannelInternal> httpChan = do_QueryInterface(newChannel);
-    if (httpChan) {
-      httpChan->ForceNoIntercept();
-    }
-  }
-
   // Propagate our loadinfo if needed.
   newChannel->SetLoadInfo(mLoadInfo);
 
   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
   if (!httpChannel)
     return NS_OK; // no other options to set
 
   if (preserveMethod) {
@@ -2379,16 +2386,27 @@ HttpBaseChannel::SetupReplacementChannel
 
     // if there is a chain of keys for redirect-responses we transfer it to
     // the new channel (see bug #561276)
     if (mRedirectedCachekeys) {
         LOG(("HttpBaseChannel::SetupReplacementChannel "
              "[this=%p] transferring chain of redirect cache-keys", this));
         httpInternal->SetCacheKeysRedirectChain(mRedirectedCachekeys.forget());
     }
+
+    // Preserve any skip-serviceworker-flag.
+    if (mForceNoIntercept) {
+      httpInternal->ForceNoIntercept();
+    }
+
+    // Preserve CORS mode flag.
+    httpInternal->SetCorsMode(mCorsMode);
+
+    // Preserve Redirect mode flag.
+    httpInternal->SetRedirectMode(mRedirectMode);
   }
 
   // transfer application cache information
   nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
     do_QueryInterface(newChannel);
   if (appCacheChannel) {
     appCacheChannel->SetApplicationCache(mApplicationCache);
     appCacheChannel->SetInheritApplicationCache(mInheritApplicationCache);
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -190,16 +190,18 @@ public:
   NS_IMETHOD SetNetworkInterfaceId(const nsACString& aNetworkInterfaceId) override;
   NS_IMETHOD ForcePending(bool aForcePending) override;
   NS_IMETHOD GetLastModifiedTime(PRTime* lastModifiedTime) override;
   NS_IMETHOD ForceNoIntercept() override;
   NS_IMETHOD GetCorsIncludeCredentials(bool* aInclude) override;
   NS_IMETHOD SetCorsIncludeCredentials(bool aInclude) override;
   NS_IMETHOD GetCorsMode(uint32_t* aCorsMode) override;
   NS_IMETHOD SetCorsMode(uint32_t aCorsMode) override;
+  NS_IMETHOD GetRedirectMode(uint32_t* aRedirectMode) override;
+  NS_IMETHOD SetRedirectMode(uint32_t aRedirectMode) override;
   NS_IMETHOD GetTopWindowURI(nsIURI **aTopWindowURI) override;
   NS_IMETHOD GetProxyURI(nsIURI **proxyURI) override;
 
   inline void CleanRedirectCacheChainIfNecessary()
   {
       mRedirectedCachekeys = nullptr;
   }
   NS_IMETHOD HTTPUpgrade(const nsACString & aProtocolName,
@@ -424,16 +426,17 @@ protected:
 
   nsCOMPtr<nsIPrincipal>            mPrincipal;
 
   bool                              mForcePending;
   nsCOMPtr<nsIURI>                  mTopWindowURI;
 
   bool mCorsIncludeCredentials;
   uint32_t mCorsMode;
+  uint32_t mRedirectMode;
 
   // This parameter is used to ensure that we do not call OnStartRequest more
   // than once.
   bool mOnStartRequestCalled;
 
   // The network interface id that's associated with this channel.
   nsCString mNetworkInterfaceId;
 
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -1027,57 +1027,65 @@ HttpChannelChild::RecvReportSecurityMess
 
 class Redirect1Event : public ChannelEvent
 {
  public:
   Redirect1Event(HttpChannelChild* child,
                  const uint32_t& newChannelId,
                  const URIParams& newURI,
                  const uint32_t& redirectFlags,
-                 const nsHttpResponseHead& responseHead)
+                 const nsHttpResponseHead& responseHead,
+                 const nsACString& securityInfoSerialization)
   : mChild(child)
   , mNewChannelId(newChannelId)
   , mNewURI(newURI)
   , mRedirectFlags(redirectFlags)
-  , mResponseHead(responseHead) {}
+  , mResponseHead(responseHead)
+  , mSecurityInfoSerialization(securityInfoSerialization) {}
 
   void Run()
   {
     mChild->Redirect1Begin(mNewChannelId, mNewURI, mRedirectFlags,
-                           mResponseHead);
+                           mResponseHead, mSecurityInfoSerialization);
   }
  private:
   HttpChannelChild*   mChild;
   uint32_t            mNewChannelId;
   URIParams           mNewURI;
   uint32_t            mRedirectFlags;
   nsHttpResponseHead  mResponseHead;
+  nsCString           mSecurityInfoSerialization;
 };
 
 bool
 HttpChannelChild::RecvRedirect1Begin(const uint32_t& newChannelId,
                                      const URIParams& newUri,
                                      const uint32_t& redirectFlags,
-                                     const nsHttpResponseHead& responseHead)
+                                     const nsHttpResponseHead& responseHead,
+                                     const nsCString& securityInfoSerialization)
 {
+  // TODO: handle security info
   LOG(("HttpChannelChild::RecvRedirect1Begin [this=%p]\n", this));
   if (mEventQ->ShouldEnqueue()) {
     mEventQ->Enqueue(new Redirect1Event(this, newChannelId, newUri,
-                                       redirectFlags, responseHead));
+                                       redirectFlags, responseHead,
+                                       securityInfoSerialization));
   } else {
-    Redirect1Begin(newChannelId, newUri, redirectFlags, responseHead);
+    Redirect1Begin(newChannelId, newUri, redirectFlags, responseHead,
+                   securityInfoSerialization);
   }
   return true;
 }
 
 void
 HttpChannelChild::Redirect1Begin(const uint32_t& newChannelId,
                                  const URIParams& newUri,
                                  const uint32_t& redirectFlags,
-                                 const nsHttpResponseHead& responseHead)
+                                 const nsHttpResponseHead& responseHead,
+                                 const nsACString& securityInfoSerialization)
 {
   LOG(("HttpChannelChild::Redirect1Begin [this=%p]\n", this));
 
   nsresult rv;
   nsCOMPtr<nsIIOService> ioService;
   rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
   if (NS_FAILED(rv)) {
     // Veto redirect.  nsHttpChannel decides to cancel or continue.
@@ -1100,16 +1108,21 @@ HttpChannelChild::Redirect1Begin(const u
     // Veto redirect.  nsHttpChannel decides to cancel or continue.
     OnRedirectVerifyCallback(rv);
     return;
   }
 
   // We won't get OnStartRequest, set cookies here.
   mResponseHead = new nsHttpResponseHead(responseHead);
 
+  if (!securityInfoSerialization.IsEmpty()) {
+    NS_DeserializeObject(securityInfoSerialization,
+                         getter_AddRefs(mSecurityInfo));
+  }
+
   bool rewriteToGET = HttpBaseChannel::ShouldRewriteRedirectToGET(mResponseHead->Status(),
                                                                   mRequestHead.ParsedMethod());
 
   rv = SetupReplacementChannel(uri, newChannel, !rewriteToGET);
   if (NS_FAILED(rv)) {
     // Veto redirect.  nsHttpChannel decides to cancel or continue.
     OnRedirectVerifyCallback(rv);
     return;
--- a/netwerk/protocol/http/HttpChannelChild.h
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -130,17 +130,18 @@ protected:
                               const uint32_t& count) override;
   bool RecvOnStopRequest(const nsresult& statusCode, const ResourceTimingStruct& timing) override;
   bool RecvOnProgress(const int64_t& progress, const int64_t& progressMax) override;
   bool RecvOnStatus(const nsresult& status) override;
   bool RecvFailedAsyncOpen(const nsresult& status) override;
   bool RecvRedirect1Begin(const uint32_t& newChannel,
                           const URIParams& newURI,
                           const uint32_t& redirectFlags,
-                          const nsHttpResponseHead& responseHead) override;
+                          const nsHttpResponseHead& responseHead,
+                          const nsCString& securityInfoSerialization) override;
   bool RecvRedirect3Complete() override;
   bool RecvAssociateApplicationCache(const nsCString& groupID,
                                      const nsCString& clientID) override;
   bool RecvFlushedForDiversion() override;
   bool RecvDivertMessages() override;
   bool RecvDeleteSelf() override;
 
   bool RecvReportSecurityMessage(const nsString& messageTag,
@@ -234,17 +235,18 @@ private:
   void OnStopRequest(const nsresult& channelStatus, const ResourceTimingStruct& timing);
   void OnProgress(const int64_t& progress, const int64_t& progressMax);
   void OnStatus(const nsresult& status);
   void FailedAsyncOpen(const nsresult& status);
   void HandleAsyncAbort();
   void Redirect1Begin(const uint32_t& newChannelId,
                       const URIParams& newUri,
                       const uint32_t& redirectFlags,
-                      const nsHttpResponseHead& responseHead);
+                      const nsHttpResponseHead& responseHead,
+                      const nsACString& securityInfoSerialization);
   void Redirect3Complete();
   void DeleteSelf();
 
   friend class AssociateApplicationCacheEvent;
   friend class StartRequestEvent;
   friend class StopRequestEvent;
   friend class TransportAndDataEvent;
   friend class ProgressEvent;
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -389,16 +389,17 @@ HttpChannelParent::DoAsyncOpen(  const U
   if (stream) {
     mChannel->InternalSetUploadStream(stream);
     mChannel->SetUploadStreamHasHeaders(uploadStreamHasHeaders);
   }
 
   if (aSynthesizedResponseHead.type() == OptionalHttpResponseHead::TnsHttpResponseHead) {
     mSynthesizedResponseHead = new nsHttpResponseHead(aSynthesizedResponseHead.get_nsHttpResponseHead());
     mShouldIntercept = true;
+    mChannel->SetCouldBeSynthesized();
 
     if (!aSecurityInfoSerialization.IsEmpty()) {
       nsCOMPtr<nsISupports> secInfo;
       NS_DeserializeObject(aSecurityInfoSerialization, getter_AddRefs(secInfo));
       mChannel->OverrideSecurityInfo(secInfo);
     }
 
   } else {
@@ -831,24 +832,17 @@ HttpChannelParent::OnStartRequest(nsIReq
   nsCOMPtr<nsISupports> cacheEntry;
   chan->GetCacheToken(getter_AddRefs(cacheEntry));
   mCacheEntry = do_QueryInterface(cacheEntry);
 
   nsresult channelStatus = NS_OK;
   chan->GetStatus(&channelStatus);
 
   nsCString secInfoSerialization;
-  nsCOMPtr<nsISupports> secInfoSupp;
-  chan->GetSecurityInfo(getter_AddRefs(secInfoSupp));
-  if (secInfoSupp) {
-    mAssociatedContentSecurity = do_QueryInterface(secInfoSupp);
-    nsCOMPtr<nsISerializable> secInfoSer = do_QueryInterface(secInfoSupp);
-    if (secInfoSer)
-      NS_SerializeToString(secInfoSer, secInfoSerialization);
-  }
+  UpdateAndSerializeSecurityInfo(secInfoSerialization);
 
   uint16_t redirectCount = 0;
   mChannel->GetRedirectCount(&redirectCount);
 
   nsCOMPtr<nsISupports> cacheKey;
   mChannel->GetCacheKey(getter_AddRefs(cacheKey));
   uint32_t cacheKeyValue = 0;
   if (cacheKey) {
@@ -1043,20 +1037,24 @@ HttpChannelParent::StartRedirect(uint32_
     return NS_BINDING_ABORTED;
 
   nsCOMPtr<nsIURI> newURI;
   newChannel->GetURI(getter_AddRefs(newURI));
 
   URIParams uriParams;
   SerializeURI(newURI, uriParams);
 
+  nsCString secInfoSerialization;
+  UpdateAndSerializeSecurityInfo(secInfoSerialization);
+
   nsHttpResponseHead *responseHead = mChannel->GetResponseHead();
   bool result = SendRedirect1Begin(newChannelId, uriParams, redirectFlags,
                                    responseHead ? *responseHead
-                                                : nsHttpResponseHead());
+                                                : nsHttpResponseHead(),
+                                   secInfoSerialization);
   if (!result) {
     // Bug 621446 investigation
     mSentRedirect1BeginFailed = true;
     return NS_BINDING_ABORTED;
   }
 
   // Bug 621446 investigation
   mSentRedirect1Begin = true;
@@ -1321,16 +1319,30 @@ HttpChannelParent::GetAuthPrompt(uint32_
                                  void** aResult)
 {
   nsCOMPtr<nsIAuthPrompt2> prompt =
     new NeckoParent::NestedFrameAuthPrompt(Manager(), mNestedFrameId);
   prompt.forget(aResult);
   return NS_OK;
 }
 
+void
+HttpChannelParent::UpdateAndSerializeSecurityInfo(nsACString& aSerializedSecurityInfoOut)
+{
+  nsCOMPtr<nsISupports> secInfoSupp;
+  mChannel->GetSecurityInfo(getter_AddRefs(secInfoSupp));
+  if (secInfoSupp) {
+    mAssociatedContentSecurity = do_QueryInterface(secInfoSupp);
+    nsCOMPtr<nsISerializable> secInfoSer = do_QueryInterface(secInfoSupp);
+    if (secInfoSer) {
+      NS_SerializeToString(secInfoSer, aSerializedSecurityInfoOut);
+    }
+  }
+}
+
 //-----------------------------------------------------------------------------
 // HttpChannelSecurityWarningReporter
 //-----------------------------------------------------------------------------
 
 nsresult
 HttpChannelParent::ReportSecurityMessage(const nsAString& aMessageTag,
                                          const nsAString& aMessageCategory)
 {
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -153,16 +153,18 @@ protected:
 
   void OfflineDisconnect() override;
   uint32_t GetAppId() override;
 
   nsresult ReportSecurityMessage(const nsAString& aMessageTag,
                                  const nsAString& aMessageCategory) override;
 
 private:
+  void UpdateAndSerializeSecurityInfo(nsACString& aSerializedSecurityInfoOut);
+
   nsRefPtr<nsHttpChannel>       mChannel;
   nsCOMPtr<nsICacheEntry>       mCacheEntry;
   nsCOMPtr<nsIAssociatedContentSecurity>  mAssociatedContentSecurity;
   bool mIPCClosed;                // PHttpChannel actor has been Closed()
 
   nsCOMPtr<nsIChannel> mRedirectChannel;
   nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
 
--- a/netwerk/protocol/http/PHttpChannel.ipdl
+++ b/netwerk/protocol/http/PHttpChannel.ipdl
@@ -116,17 +116,18 @@ child:
   // AsyncOpen of nsHttpChannel on the parent.
   FailedAsyncOpen(nsresult status);
 
   // Called to initiate content channel redirect, starts talking to sinks
   // on the content process and reports result via Redirect2Verify above
   Redirect1Begin(uint32_t           newChannelId,
                  URIParams          newUri,
                  uint32_t           redirectFlags,
-                 nsHttpResponseHead responseHead);
+                 nsHttpResponseHead responseHead,
+                 nsCString          securityInfoSerialization);
 
   // Called if redirect successful so that child can complete setup.
   Redirect3Complete();
 
   // Associate the child with an application ids
   AssociateApplicationCache(nsCString groupID,
                             nsCString clientID);
 
--- a/netwerk/protocol/http/PackagedAppService.cpp
+++ b/netwerk/protocol/http/PackagedAppService.cpp
@@ -11,16 +11,17 @@
 #include "nsICacheStorageService.h"
 #include "nsIResponseHeadProvider.h"
 #include "nsIMultiPartChannel.h"
 #include "../../cache2/CacheFileUtils.h"
 #include "nsStreamUtils.h"
 #include "mozilla/Logging.h"
 #include "mozilla/DebugOnly.h"
 #include "nsIHttpHeaderVisitor.h"
+#include "mozilla/LoadContext.h"
 
 namespace mozilla {
 namespace net {
 
 static PackagedAppService *gPackagedAppService = nullptr;
 
 static PRLogModuleInfo *gPASLog = nullptr;
 #undef LOG
@@ -806,16 +807,22 @@ PackagedAppService::GetResource(nsIChann
   }
 
   // Add the package to the hashtable.
   mDownloadingPackages.Put(key, downloader);
 
   nsRefPtr<PackagedAppChannelListener> listener =
     new PackagedAppChannelListener(downloader, mimeConverter);
 
+  nsCOMPtr<nsIInterfaceRequestor> loadContext;
+  aChannel->GetNotificationCallbacks(getter_AddRefs(loadContext));
+  if (loadContext) {
+    channel->SetNotificationCallbacks(loadContext);
+  }
+
   if (loadInfo && loadInfo->GetEnforceSecurity()) {
     return channel->AsyncOpen2(listener);
   }
 
   return channel->AsyncOpen(listener, nullptr);
 }
 
 nsresult
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -4981,17 +4981,17 @@ nsHttpChannel::AsyncOpen(nsIStreamListen
     rv = NS_CheckPortSafety(mURI);
     if (NS_FAILED(rv)) {
         ReleaseListeners();
         return rv;
     }
 
     if (ShouldIntercept()) {
         mInterceptCache = MAYBE_INTERCEPT;
-        mResponseCouldBeSynthesized = true;
+        SetCouldBeSynthesized();
     }
 
     // Remember the cookie header that was set, if any
     const char *cookieHeader = mRequestHead.PeekHeader(nsHttp::Cookie);
     if (cookieHeader) {
         mUserSetCookieHeader = cookieHeader;
     }
 
@@ -6961,10 +6961,16 @@ nsHttpChannel::OnPush(const nsACString &
     channel->mCallbacks = mCallbacks;
 
     // Link the pushed stream with the new channel and call listener
     channel->SetPushedStream(pushedStream);
     rv = pushListener->OnPush(this, pushHttpChannel);
     return rv;
 }
 
+void
+nsHttpChannel::SetCouldBeSynthesized()
+{
+  mResponseCouldBeSynthesized = true;
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -230,16 +230,17 @@ public: /* internal necko use only */
     private:
       nsHttpChannel* mChannel;
       uint32_t mKeep : 2;
     };
 
     void MarkIntercepted();
     NS_IMETHOD GetResponseSynthesized(bool* aSynthesized) override;
     bool AwaitingCacheCallbacks();
+    void SetCouldBeSynthesized();
 
 protected:
     virtual ~nsHttpChannel();
 
 private:
     typedef nsresult (nsHttpChannel::*nsContinueRedirectionFunc)(nsresult result);
 
     bool     RequestIsConditional();
--- a/netwerk/protocol/http/nsIHttpChannelInternal.idl
+++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl
@@ -33,17 +33,17 @@ interface nsIHttpUpgradeListener : nsISu
                               in nsIAsyncOutputStream aSocketOut);
 };
 
 /**
  * Dumping ground for http.  This interface will never be frozen.  If you are
  * using any feature exposed by this interface, be aware that this interface
  * will change and you will be broken.  You have been warned.
  */
-[scriptable, uuid(c4fab96f-0b10-4b14-b45b-517fc3f36842)]
+[scriptable, uuid(e2eebad8-e51f-473a-bbb7-8e2829376625)]
 
 interface nsIHttpChannelInternal : nsISupports
 {
     /**
      * An http channel can own a reference to the document URI
      */
     attribute nsIURI documentURI;
 
@@ -234,16 +234,26 @@ interface nsIHttpChannelInternal : nsISu
     const unsigned long CORS_MODE_NO_CORS = 1;
     const unsigned long CORS_MODE_CORS = 2;
     const unsigned long CORS_MODE_CORS_WITH_FORCED_PREFLIGHT = 3;
     /**
      * Set by nsCORSListenerProxy to indicate CORS load type. Defaults to CORS_MODE_NO_CORS.
      */
     attribute unsigned long corsMode;
 
+    const unsigned long REDIRECT_MODE_FOLLOW = 0;
+    const unsigned long REDIRECT_MODE_ERROR = 1;
+    const unsigned long REDIRECT_MODE_MANUAL = 2;
+    /**
+     * Set to indicate Request.redirect mode exposed during ServiceWorker
+     * interception. No policy enforcement is performed by the channel for this
+     * value.
+     */
+    attribute unsigned long redirectMode;
+
     /**
      * The URI of the top-level window that's associated with this channel.
      */
     readonly attribute nsIURI topWindowURI;
 
     /**
      * The network interface id that's associated with this channel.
      */
--- a/netwerk/protocol/websocket/WebSocketChannel.cpp
+++ b/netwerk/protocol/websocket/WebSocketChannel.cpp
@@ -2355,18 +2355,18 @@ WebSocketChannel::AbortSession(nsresult 
     CleanupConnection();
     return;
   }
 
   if (mStopped)
     return;
   mStopped = 1;
 
-  if (mTransport && reason != NS_BASE_STREAM_CLOSED &&
-      !mRequestedClose && !mClientClosed && !mServerClosed) {
+  if (mTransport && reason != NS_BASE_STREAM_CLOSED && !mRequestedClose &&
+      !mClientClosed && !mServerClosed && mConnecting == NOT_CONNECTING) {
     mRequestedClose = 1;
     mStopOnClose = reason;
     mSocketThread->Dispatch(
       new OutboundEnqueuer(this, new OutboundMessage(kMsgTypeFin, nullptr)),
                            nsIEventTarget::DISPATCH_NORMAL);
   } else {
     StopSession(reason);
   }
@@ -3116,17 +3116,21 @@ WebSocketChannel::GetSecurityInfo(nsISup
 NS_IMETHODIMP
 WebSocketChannel::AsyncOpen(nsIURI *aURI,
                             const nsACString &aOrigin,
                             nsIWebSocketListener *aListener,
                             nsISupports *aContext)
 {
   LOG(("WebSocketChannel::AsyncOpen() %p\n", this));
 
-  MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+  if (!NS_IsMainThread()) {
+    MOZ_ASSERT(false, "not main thread");
+    LOG(("WebSocketChannel::AsyncOpen() called off the main thread"));
+    return NS_ERROR_UNEXPECTED;
+  }
 
   if (!aURI || !aListener) {
     LOG(("WebSocketChannel::AsyncOpen() Uri or Listener null"));
     return NS_ERROR_UNEXPECTED;
   }
 
   if (mListenerMT || mWasOpened)
     return NS_ERROR_ALREADY_OPENED;
@@ -3260,16 +3264,36 @@ WebSocketChannel::AsyncOpen(nsIURI *aURI
   // we want the same instance of the loadInfo to be set on the channel.
   rv = localChannel->SetLoadInfo(mLoadInfo);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Pass most GetInterface() requests through to our instantiator, but handle
   // nsIChannelEventSink in this object in order to deal with redirects
   localChannel->SetNotificationCallbacks(this);
 
+  class MOZ_STACK_CLASS CleanUpOnFailure
+  {
+  public:
+    explicit CleanUpOnFailure(WebSocketChannel* aWebSocketChannel)
+      : mWebSocketChannel(aWebSocketChannel)
+    {}
+
+    ~CleanUpOnFailure()
+    {
+      if (!mWebSocketChannel->mWasOpened) {
+        mWebSocketChannel->mChannel = nullptr;
+        mWebSocketChannel->mHttpChannel = nullptr;
+      }
+    }
+
+    WebSocketChannel *mWebSocketChannel;
+  };
+
+  CleanUpOnFailure cuof(this);
+
   mChannel = do_QueryInterface(localChannel, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mHttpChannel = do_QueryInterface(localChannel, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = SetupRequest();
   if (NS_FAILED(rv))
@@ -3324,17 +3348,17 @@ WebSocketChannel::Close(uint16_t code, c
   // The API requires the UTF-8 string to be 123 or less bytes
   if (reason.Length() > 123)
     return NS_ERROR_ILLEGAL_VALUE;
 
   mRequestedClose = 1;
   mScriptCloseReason = reason;
   mScriptCloseCode = code;
 
-  if (!mTransport) {
+  if (!mTransport || mConnecting != NOT_CONNECTING) {
     nsresult rv;
     if (code == CLOSE_GOING_AWAY) {
       // Not an error: for example, tab has closed or navigated away
       LOG(("WebSocketChannel::Close() GOING_AWAY without transport."));
       rv = NS_OK;
     } else {
       LOG(("WebSocketChannel::Close() without transport - error."));
       rv = NS_ERROR_NOT_CONNECTED;
@@ -3372,16 +3396,21 @@ WebSocketChannel::SendBinaryStream(nsIIn
 }
 
 nsresult
 WebSocketChannel::SendMsgCommon(const nsACString *aMsg, bool aIsBinary,
                                 uint32_t aLength, nsIInputStream *aStream)
 {
   MOZ_ASSERT(IsOnTargetThread(), "not target thread");
 
+  if (!mDataStarted) {
+    LOG(("WebSocketChannel:: Error: data not started yet\n"));
+    return NS_ERROR_UNEXPECTED;
+  }
+
   if (mRequestedClose) {
     LOG(("WebSocketChannel:: Error: send when closed\n"));
     return NS_ERROR_UNEXPECTED;
   }
 
   if (mStopped) {
     LOG(("WebSocketChannel:: Error: send when stopped\n"));
     return NS_ERROR_NOT_CONNECTED;
--- a/netwerk/test/unit/test_packaged_app_channel.js
+++ b/netwerk/test/unit/test_packaged_app_channel.js
@@ -118,33 +118,43 @@ function run_test()
   httpserver.start(-1);
 
   // Enable the feature and save the original pref value
   originalPref = Services.prefs.getBoolPref("network.http.enable-packaged-apps");
   Services.prefs.setBoolPref("network.http.enable-packaged-apps", true);
   do_register_cleanup(reset_pref);
 
   add_test(test_channel);
+  add_test(test_channel_no_notificationCallbacks);
   add_test(test_channel_uris);
 
   // run tests
   run_next_test();
 }
 
-function test_channel() {
+function test_channel(aNullNotificationCallbacks) {
   var channel = make_channel(uri+"/package!//index.html");
+
+  if (!aNullNotificationCallbacks) {
+    channel.notificationCallbacks = new LoadContextCallback(1024, false, false, false);
+  }
+
   channel.asyncOpen(new Listener(function(l) {
     // XXX: no content length available for this resource
     //do_check_true(channel.contentLength > 0);
     do_check_true(l.gotStartRequest);
     do_check_true(l.gotStopRequest);
     run_next_test();
   }), null);
 }
 
+function test_channel_no_notificationCallbacks() {
+  test_channel(true);
+}
+
 function test_channel_uris() {
   // A `!//` in the query or ref should not be handled as a packaged app resource
   var channel = make_channel(uri+"/regular?bla!//bla#bla!//bla");
   channel.asyncOpen(new ChannelListener(check_regular_response, null), null);
 }
 
 function check_regular_response(request, buffer) {
   request.QueryInterface(Components.interfaces.nsIHttpChannel);
--- a/testing/mach_commands.py
+++ b/testing/mach_commands.py
@@ -1,24 +1,27 @@
 # 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/.
 
 from __future__ import absolute_import, print_function, unicode_literals
 
+import json
 import os
 import sys
+import tempfile
 
 from mach.decorators import (
     CommandArgument,
     CommandProvider,
     Command,
 )
 
 from mozbuild.base import MachCommandBase
+from argparse import ArgumentParser
 
 
 UNKNOWN_TEST = '''
 I was unable to find tests in the argument(s) given.
 
 You need to specify a test directory, filename, test suite name, or
 abbreviation.
 
@@ -495,8 +498,95 @@ class PushToTry(MachCommandBase):
 
         if verbose:
             print('The following try syntax was calculated:\n\n\t%s\n' % msg)
 
         if push:
             at.push_to_try(msg, verbose)
 
         return
+
+
+def get_parser(argv=None):
+    parser = ArgumentParser()
+    parser.add_argument(dest="suite_name",
+                        nargs=1,
+                        choices=['mochitest'],
+                        type=str,
+                        help="The test for which chunk should be found. It corresponds "
+                             "to the mach test invoked (only 'mochitest' currently).")
+
+    parser.add_argument(dest="test_path",
+                        nargs=1,
+                        type=str,
+                        help="The test (any mochitest) for which chunk should be found.")
+
+    parser.add_argument('--total-chunks',
+                        type=int,
+                        dest='total_chunks',
+                        required=True,
+                        help='Total number of chunks to split tests into.',
+                        default=None
+                        )
+
+    parser.add_argument('-f', "--flavor",
+                        dest="flavor",
+                        type=str,
+                        help="Flavor to which the test belongs to.")
+
+    parser.add_argument('--chunk-by-runtime',
+                        action='store_true',
+                        dest='chunk_by_runtime',
+                        help='Group tests such that each chunk has roughly the same runtime.',
+                        default=False,
+                        )
+
+    parser.add_argument('--chunk-by-dir',
+                        type=int,
+                        dest='chunk_by_dir',
+                        help='Group tests together in the same chunk that are in the same top '
+                             'chunkByDir directories.',
+                        default=None,
+                        )
+
+    return parser
+
+
+@CommandProvider
+class ChunkFinder(MachCommandBase):
+    @Command('find-test-chunk', category='testing',
+             description='Find which chunk a test belongs to (works for mochitest).',
+             parser=get_parser)
+    def chunk_finder(self, **kwargs):
+        flavor = kwargs['flavor']
+        total_chunks = kwargs['total_chunks']
+        test_path = kwargs['test_path'][0]
+        suite_name = kwargs['suite_name'][0]
+        _, dump_tests = tempfile.mkstemp()
+        args = {
+            'totalChunks': total_chunks,
+            'dump_tests': dump_tests,
+            'chunkByDir': kwargs['chunk_by_dir'],
+            'chunkByRuntime': kwargs['chunk_by_runtime'],
+        }
+
+        found = False
+        for this_chunk in range(1, total_chunks+1):
+            args['thisChunk'] = this_chunk
+            try:
+                self._mach_context.commands.dispatch(suite_name, self._mach_context, flavor=flavor, resolve_tests=False, **args)
+            except SystemExit:
+                pass
+            except KeyboardInterrupt:
+                break
+
+            fp = open(os.path.expanduser(args['dump_tests']), 'r')
+            tests = json.loads(fp.read())['active_tests']
+            paths = [t['path'] for t in tests]
+            if test_path in paths:
+                print("The test %s is present in chunk number: %d (it may be skipped)." % (test_path, this_chunk))
+                found = True
+                break
+
+        if not found:
+            raise Exception("Test %s not found." % test_path)
+        # Clean up the file
+        os.remove(dump_tests)
--- a/testing/marionette/actions.js
+++ b/testing/marionette/actions.js
@@ -35,37 +35,37 @@ this.ActionChain = function(utils, check
 
   // test utilities providing some event synthesis code
   this.utils = utils;
 };
 
 ActionChain.prototype.dispatchActions = function(
     args,
     touchId,
-    frame,
+    container,
     elementManager,
     callbacks,
     touchProvider) {
   // Some touch events code in the listener needs to do ipc, so we can't
   // share this code across chrome/content.
   if (touchProvider) {
     this.touchProvider = touchProvider;
   }
 
   this.elementManager = elementManager;
-  let commandArray = elementManager.convertWrappedArguments(args, frame);
+  let commandArray = elementManager.convertWrappedArguments(args, container);
   this.onSuccess = callbacks.onSuccess;
   this.onError = callbacks.onError;
-  this.frame = frame;
+  this.container = container;
 
   if (touchId == null) {
     touchId = this.nextTouchId++;
   }
 
-  if (!frame.document.createTouch) {
+  if (!container.frame.document.createTouch) {
     this.mouseEventsOnly = true;
   }
 
   let keyModifiers = {
     shiftKey: false,
     ctrlKey: false,
     altKey: false,
     metaKey: false
@@ -121,32 +121,32 @@ ActionChain.prototype.emitMouseEvent = f
     } else {
       mods = 0;
     }
 
     domUtils.sendMouseEvent(
         type,
         elClientX,
         elClientY,
-        button || 0, 
+        button || 0,
         clickCount || 1,
         mods,
         false,
         0,
         this.inputSource);
   }
 };
 
 /**
  * Reset any persisted values after a command completes.
  */
 ActionChain.prototype.resetValues = function() {
   this.onSuccess = null;
   this.onError = null;
-  this.frame = null;
+  this.container = null;
   this.elementManager = null;
   this.touchProvider = null;
   this.mouseEventsOnly = false;
 };
 
 /**
  * Function to emit touch events for each finger. e.g.
  * finger=[['press', id], ['wait', 5], ['release']] touchId represents
@@ -172,27 +172,27 @@ ActionChain.prototype.actions = function
     if (!(touchId in this.touchIds) && !this.mouseEventsOnly) {
       this.resetValues();
       throw new WebDriverError("Element has not been pressed");
     }
   }
 
   switch(command) {
     case "keyDown":
-      this.utils.sendKeyDown(pack[1], keyModifiers, this.frame);
+      this.utils.sendKeyDown(pack[1], keyModifiers, this.container.frame);
       this.actions(chain, touchId, i, keyModifiers);
       break;
 
     case "keyUp":
-      this.utils.sendKeyUp(pack[1], keyModifiers, this.frame);
+      this.utils.sendKeyUp(pack[1], keyModifiers, this.container.frame);
       this.actions(chain, touchId, i, keyModifiers);
       break;
 
     case "click":
-      el = this.elementManager.getKnownElement(pack[1], this.frame);
+      el = this.elementManager.getKnownElement(pack[1], this.container);
       let button = pack[2];
       let clickCount = pack[3];
       c = this.coordinates(el, null, null);
       this.mouseTap(el.ownerDocument, c.x, c.y, button, clickCount, keyModifiers);
       if (button == 2) {
         this.emitMouseEvent(el.ownerDocument, "contextmenu", c.x, c.y,
             button, clickCount, keyModifiers);
       }
@@ -212,17 +212,17 @@ ActionChain.prototype.actions = function
         throw new WebDriverError(
             "Invalid Command: press cannot follow an active touch event");
       }
 
       // look ahead to check if we're scrolling. Needed for APZ touch dispatching.
       if ((i != chain.length) && (chain[i][0].indexOf('move') !== -1)) {
         this.scrolling = true;
       }
-      el = this.elementManager.getKnownElement(pack[1], this.frame);
+      el = this.elementManager.getKnownElement(pack[1], this.container);
       c = this.coordinates(el, pack[2], pack[3]);
       touchId = this.generateEvents("press", c.x, c.y, null, el, keyModifiers);
       this.actions(chain, touchId, i, keyModifiers);
       break;
 
     case "release":
       this.generateEvents(
           "release",
@@ -231,17 +231,17 @@ ActionChain.prototype.actions = function
           touchId,
           null,
           keyModifiers);
       this.actions(chain, null, i, keyModifiers);
       this.scrolling =  false;
       break;
 
     case "move":
-      el = this.elementManager.getKnownElement(pack[1], this.frame);
+      el = this.elementManager.getKnownElement(pack[1], this.container);
       c = this.coordinates(el);
       this.generateEvents("move", c.x, c.y, touchId, null, keyModifiers);
       this.actions(chain, touchId, i, keyModifiers);
       break;
 
     case "moveByOffset":
       this.generateEvents(
           "move",
@@ -269,29 +269,29 @@ ActionChain.prototype.actions = function
         }
         this.checkTimer.initWithCallback(
             () => this.actions(chain, touchId, i, keyModifiers),
             time, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
       } else {
         this.actions(chain, touchId, i, keyModifiers);
       }
       break;
-  
+
     case "cancel":
       this.generateEvents(
           "cancel",
           this.lastCoordinates[0],
           this.lastCoordinates[1],
           touchId,
           null,
           keyModifiers);
       this.actions(chain, touchId, i, keyModifiers);
       this.scrolling = false;
       break;
-  
+
     case "longPress":
       this.generateEvents(
           "contextmenu",
           this.lastCoordinates[0],
           this.lastCoordinates[1],
           touchId,
           null,
           keyModifiers);
@@ -350,17 +350,17 @@ ActionChain.prototype.getCoordinateInfo 
  *     to the viewport.
  * @param {number} y
  *     Y coordinate of the location to generate the event that is relative
  *     to the viewport.
  */
 ActionChain.prototype.generateEvents = function(
     type, x, y, touchId, target, keyModifiers) {
   this.lastCoordinates = [x, y];
-  let doc = this.frame.document;
+  let doc = this.container.frame.document;
 
   switch (type) {
     case "tap":
       if (this.mouseEventsOnly) {
         this.mouseTap(
             touch.target.ownerDocument,
             touch.clientX,
             touch.clientY,
@@ -445,17 +445,17 @@ ActionChain.prototype.generateEvents = f
             this.touchIds[touchId].target, x, y, touchId);
         this.touchIds[touchId] = touch;
         this.touchProvider.emitTouchEvent("touchmove", touch);
       }
       break;
 
     case "contextmenu":
       this.isTap = false;
-      let event = this.frame.document.createEvent("MouseEvents");
+      let event = this.container.frame.document.createEvent("MouseEvents");
       if (this.mouseEventsOnly) {
         target = doc.elementFromPoint(this.lastCoordinates[0], this.lastCoordinates[1]);
       } else {
         target = this.touchIds[touchId].target;
       }
 
       let [clientX, clientY, pageX, pageY, screenX, screenY] =
           this.getCoordinateInfo(target, x, y);
new file mode 100644
--- /dev/null
+++ b/testing/marionette/client/marionette/tests/unit/test_shadow_dom.py
@@ -0,0 +1,65 @@
+# 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/.
+
+from marionette import MarionetteTestCase
+from marionette_driver.errors import (NoSuchElementException, StaleElementException)
+
+
+class TestShadowDom(MarionetteTestCase):
+
+    def setUp(self):
+        MarionetteTestCase.setUp(self)
+        self.marionette.enforce_gecko_prefs({"dom.webcomponents.enabled": True})
+        self.marionette.navigate(self.marionette.absolute_url("test_shadow_dom.html"))
+
+        self.host = self.marionette.find_element("id", "host")
+        self.marionette.switch_to_shadow_root(self.host)
+        self.button = self.marionette.find_element("id", "button")
+
+    def test_shadow_dom(self):
+        # Button in shadow root should be actionable
+        self.button.click()
+
+    def test_shadow_dom_after_switch_away_from_shadow_root(self):
+        # Button in shadow root should be actionable
+        self.button.click()
+        self.marionette.switch_to_shadow_root()
+        # After switching back to top content, button should be stale
+        self.assertRaises(StaleElementException, self.button.click)
+
+    def test_shadow_dom_raises_stale_element_exception_when_button_remove(self):
+        self.marionette.execute_script(
+            'document.getElementById("host").shadowRoot.getElementById("button").remove();')
+        # After removing button from shadow DOM, button should be stale
+        self.assertRaises(StaleElementException, self.button.click)
+
+    def test_shadow_dom_raises_stale_element_exception_when_host_removed(self):
+        self.marionette.execute_script('document.getElementById("host").remove();')
+        # After removing shadow DOM host element, button should be stale
+        self.assertRaises(StaleElementException, self.button.click)
+
+    def test_non_existent_shadow_dom(self):
+        # Jump back to top level content
+        self.marionette.switch_to_shadow_root()
+        # When no ShadowRoot is found, switch_to_shadow_root throws NoSuchElementException
+        self.assertRaises(NoSuchElementException, self.marionette.switch_to_shadow_root,
+                          self.marionette.find_element("id", "empty-host"))
+
+    def test_inner_shadow_dom(self):
+        # Button in shadow root should be actionable
+        self.button.click()
+        self.inner_host = self.marionette.find_element("id", "inner-host")
+        self.marionette.switch_to_shadow_root(self.inner_host)
+        self.inner_button = self.marionette.find_element("id", "inner-button")
+        # Nested nutton in nested shadow root should be actionable
+        self.inner_button.click()
+        self.marionette.switch_to_shadow_root()
+        # After jumping back to parent shadow root, button should again be actionable but inner
+        # button should now be stale
+        self.button.click()
+        self.assertRaises(StaleElementException, self.inner_button.click)
+        self.marionette.switch_to_shadow_root()
+        # After switching back to top content, both buttons should now be stale
+        self.assertRaises(StaleElementException, self.button.click)
+        self.assertRaises(StaleElementException, self.inner_button.click)
--- a/testing/marionette/client/marionette/tests/unit/unit-tests.ini
+++ b/testing/marionette/client/marionette/tests/unit/unit-tests.ini
@@ -153,8 +153,10 @@ b2g = false
 [test_teardown_context_preserved.py]
 b2g = false
 [test_file_upload.py]
 b2g = false
 skip-if = os == "win" # http://bugs.python.org/issue14574
 
 [test_execute_sandboxes.py]
 [test_using_permissions.py]
+
+[test_shadow_dom.py]
new file mode 100644
--- /dev/null
+++ b/testing/marionette/client/marionette/www/test_shadow_dom.html
@@ -0,0 +1,26 @@
+<!-- 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/. -->
+
+<!DOCTYPE html>
+
+<html>
+<meta charset="UTF-8">
+<head>
+<title>Marionette Test</title>
+</head>
+<body>
+  <div id="host"></div>
+  <div id="empty-host"></div>
+  <script>
+    'use strict';
+    var host = document.getElementById('host');
+    var root = host.createShadowRoot();
+    root.innerHTML = '<button id="button">Foo</button>' +
+      '<div id="inner-host"></div>';
+    var innerHost = host.shadowRoot.getElementById('inner-host');
+    var innerRoot = innerHost.createShadowRoot();
+    innerRoot.innerHTML = '<button id="inner-button">Bar</button>';
+  </script>
+</body>
+</html>
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -786,17 +786,18 @@ GeckoDriver.prototype.createExecuteSandb
   this.sandboxes[sandboxName] = sb;
 };
 
 /**
  * Apply arguments sent from the client to the current (possibly reused)
  * execution sandbox.
  */
 GeckoDriver.prototype.applyArgumentsToSandbox = function(win, sb, args) {
-  sb.__marionetteParams = this.curBrowser.elementManager.convertWrappedArguments(args, win);
+  sb.__marionetteParams = this.curBrowser.elementManager.convertWrappedArguments(args,
+    { frame: win });
   sb.__namedArgs = this.curBrowser.elementManager.applyNamedArgs(args);
 };
 
 /**
  * Executes a script in the given sandbox.
  *
  * @param {Response} resp
  *     Response object given to the command calling this routine.
@@ -1651,17 +1652,17 @@ GeckoDriver.prototype.switchToFrame = fu
       checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
       return;
     }
 
     if (typeof cmd.parameters.element != "undefined") {
       if (this.curBrowser.elementManager.seenItems[cmd.parameters.element]) {
         // HTMLIFrameElement
         let wantedFrame = this.curBrowser.elementManager
-            .getKnownElement(cmd.parameters.element, curWindow);
+            .getKnownElement(cmd.parameters.element, { frame: curWindow });
         // Deal with an embedded xul:browser case
         if (wantedFrame.tagName == "xul:browser" || wantedFrame.tagName == "browser") {
           curWindow = wantedFrame.contentWindow;
           this.curFrame = curWindow;
           if (cmd.parameters.focus) {
             this.curFrame.focus();
           }
           checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
@@ -1832,17 +1833,17 @@ GeckoDriver.prototype.actionChain = func
       }
 
       let cbs = {};
       cbs.onSuccess = val => resp.value = val;
       cbs.onError = err => { throw err; };
 
       let win = this.getCurrentWindow();
       let elm = this.curBrowser.elementManager;
-      this.actions.dispatchActions(chain, nextId, win, elm, cbs);
+      this.actions.dispatchActions(chain, nextId, { frame: win }, elm, cbs);
       break;
 
     case Context.CONTENT:
       this.addFrameCloseListener("action chain");
       resp.value = yield this.listener.actionChain({chain: chain, nextId: nextId});
       break;
   }
 };
@@ -1877,17 +1878,17 @@ GeckoDriver.prototype.multiAction = func
  *     Value the client is looking for.
  */
 GeckoDriver.prototype.findElement = function(cmd, resp) {
   switch (this.context) {
     case Context.CHROME:
       resp.value = yield new Promise((resolve, reject) => {
         let win = this.getCurrentWindow();
         this.curBrowser.elementManager.find(
-            win,
+            { frame: win },
             cmd.parameters,
             this.searchTimeout,
             false /* all */,
             resolve,
             reject);
       }).then(null, e => { throw e; });
       break;
 
@@ -1929,17 +1930,17 @@ GeckoDriver.prototype.findChildElement =
  *     Value the client is looking for.
  */
 GeckoDriver.prototype.findElements = function(cmd, resp) {
   switch (this.context) {
     case Context.CHROME:
       resp.value = yield new Promise((resolve, reject) => {
         let win = this.getCurrentWindow();
         this.curBrowser.elementManager.find(
-            win,
+            { frame: win },
             cmd.parameters,
             this.searchTimeout,
             true /* all */,
             resolve,
             reject);
       }).then(null, e => { throw new NoSuchElementError(e.message); });
       break;
 
@@ -1985,17 +1986,17 @@ GeckoDriver.prototype.getActiveElement =
  */
 GeckoDriver.prototype.clickElement = function(cmd, resp) {
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       // click atom fails, fall back to click() action
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, win);
+      let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
       el.click();
       break;
 
     case Context.CONTENT:
       // We need to protect against the click causing an OOP frame to close.
       // This fires the mozbrowserclose event when it closes so we need to
       // listen for it and then just send an error back. The person making the
       // call should be aware something isnt right and handle accordingly
@@ -2014,17 +2015,17 @@ GeckoDriver.prototype.clickElement = fun
  *     Name of the attribute to retrieve.
  */
 GeckoDriver.prototype.getElementAttribute = function(cmd, resp) {
   let {id, name} = cmd.parameters;
 
   switch (this.context) {
     case Context.CHROME:
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, win);
+      let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
       resp.value = utils.getElementAttribute(el, name);
       break;
 
     case Context.CONTENT:
       resp.value = yield this.listener.getElementAttribute(id, name);
       break;
   }
 };
@@ -2038,17 +2039,17 @@ GeckoDriver.prototype.getElementAttribut
  */
 GeckoDriver.prototype.getElementText = function(cmd, resp) {
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       // for chrome, we look at text nodes, and any node with a "label" field
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, win);
+      let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
       let lines = [];
       this.getVisibleText(el, lines);
       resp.value = lines.join("\n");
       break;
 
     case Context.CONTENT:
       resp.value = yield this.listener.getElementText(id);
       break;
@@ -2062,17 +2063,17 @@ GeckoDriver.prototype.getElementText = f
  *     Reference ID to the element that will be inspected.
  */
 GeckoDriver.prototype.getElementTagName = function(cmd, resp) {
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, win);
+      let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
       resp.value = el.tagName.toLowerCase();
       break;
 
     case Context.CONTENT:
       resp.value = yield this.listener.getElementTagName(id);
       break;
   }
 };
@@ -2084,17 +2085,17 @@ GeckoDriver.prototype.getElementTagName 
  *     Reference ID to the element that will be inspected.
  */
 GeckoDriver.prototype.isElementDisplayed = function(cmd, resp) {
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, win);
+      let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
       resp.value = utils.isElementDisplayed(el);
       break;
 
     case Context.CONTENT:
       resp.value = yield this.listener.isElementDisplayed(id);
       break;
   }
 };
@@ -2108,17 +2109,17 @@ GeckoDriver.prototype.isElementDisplayed
  *     CSS rule that is being requested.
  */
 GeckoDriver.prototype.getElementValueOfCssProperty = function(cmd, resp) {
   let {id, propertyName: prop} = cmd.parameters;
 
   switch (this.context) {
     case Context.CHROME:
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, win);
+      let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
       let sty = win.document.defaultView.getComputedStyle(el, null);
       resp.value = sty.getPropertyValue(prop);
       break;
 
     case Context.CONTENT:
       resp.value = yield this.listener.getElementValueOfCssProperty(id, prop);
       break;
   }
@@ -2132,17 +2133,17 @@ GeckoDriver.prototype.getElementValueOfC
  */
 GeckoDriver.prototype.isElementEnabled = function(cmd, resp) {
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       // Selenium atom doesn't quite work here
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, win);
+      let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
       resp.value = !(!!el.disabled);
       break;
 
     case Context.CONTENT:
       resp.value = yield this.listener.isElementEnabled(id);
       break;
   }
 },
@@ -2155,17 +2156,17 @@ GeckoDriver.prototype.isElementEnabled =
  */
 GeckoDriver.prototype.isElementSelected = function(cmd, resp) {
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       // Selenium atom doesn't quite work here
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, win);
+      let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
       if (typeof el.checked != "undefined") {
         resp.value = !!el.checked;
       } else if (typeof el.selected != "undefined") {
         resp.value = !!el.selected;
       } else {
         resp.value = true;
       }
       break;
@@ -2177,34 +2178,34 @@ GeckoDriver.prototype.isElementSelected 
 };
 
 GeckoDriver.prototype.getElementSize = function(cmd, resp) {
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, win);
+      let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
       let rect = el.getBoundingClientRect();
       resp.value = {width: rect.width, height: rect.height};
       break;
 
     case Context.CONTENT:
       resp.value = yield this.listener.getElementSize(id);
       break;
   }
 };
 
 GeckoDriver.prototype.getElementRect = function(cmd, resp) {
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, win);
+      let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
       let rect = el.getBoundingClientRect();
       resp.value = {
         x: rect.x + win.pageXOffset,
         y: rect.y + win.pageYOffset,
         width: rect.width,
         height: rect.height
       };
       break;
@@ -2228,17 +2229,17 @@ GeckoDriver.prototype.sendKeysToElement 
 
   if (!value) {
     throw new InvalidArgumentError(`Expected character sequence: ${value}`);
   }
 
   switch (this.context) {
     case Context.CHROME:
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, win);
+      let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
       utils.sendKeysToElement(
           win,
           el,
           value,
           () => {},
           e => { throw e; },
           cmd.id,
           true /* ignore visibility check */);
@@ -2294,17 +2295,17 @@ GeckoDriver.prototype.setTestName = func
  */
 GeckoDriver.prototype.clearElement = function(cmd, resp) {
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       // the selenium atom doesn't work here
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, win);
+      let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
       if (el.nodeName == "textbox") {
         el.value = "";
       } else if (el.nodeName == "checkbox") {
         el.checked = false;
       }
       break;
 
     case Context.CONTENT:
@@ -2322,16 +2323,27 @@ GeckoDriver.prototype.clearElement = fun
  *
  * @return {Object.<string, number>}
  *     A point containing X and Y coordinates as properties.
  */
 GeckoDriver.prototype.getElementLocation = function(cmd, resp) {
   return this.listener.getElementLocation(cmd.parameters.id);
 };
 
+/**
+ * Switch to shadow root of the given host element.
+ *
+ * @param {string} id element id.
+ */
+GeckoDriver.prototype.switchToShadowRoot = function(cmd, resp) {
+  let id;
+  if (cmd.parameters) { id = cmd.parameters.id; }
+  yield this.listener.switchToShadowRoot(id);
+};
+
 /** Add a cookie to the document. */
 GeckoDriver.prototype.addCookie = function(cmd, resp) {
   yield this.listener.addCookie({cookie: cmd.parameters.cookie});
 };
 
 /**
  * Get all the cookies for the current domain.
  *
@@ -3014,16 +3026,17 @@ GeckoDriver.prototype.commands = {
   "getChromeWindowHandles": GeckoDriver.prototype.getChromeWindowHandles,
   "getCurrentWindowHandles": GeckoDriver.prototype.getWindowHandles,  // Selenium 2 compat
   "getWindows":  GeckoDriver.prototype.getWindowHandles,  // deprecated
   "getWindowPosition": GeckoDriver.prototype.getWindowPosition,
   "setWindowPosition": GeckoDriver.prototype.setWindowPosition,
   "getActiveFrame": GeckoDriver.prototype.getActiveFrame,
   "switchToFrame": GeckoDriver.prototype.switchToFrame,
   "switchToWindow": GeckoDriver.prototype.switchToWindow,
+  "switchToShadowRoot": GeckoDriver.prototype.switchToShadowRoot,
   "deleteSession": GeckoDriver.prototype.deleteSession,
   "importScript": GeckoDriver.prototype.importScript,
   "clearImportedScripts": GeckoDriver.prototype.clearImportedScripts,
   "getAppCacheStatus": GeckoDriver.prototype.getAppCacheStatus,
   "close": GeckoDriver.prototype.close,
   "closeWindow": GeckoDriver.prototype.close,  // deprecated
   "closeChromeWindow": GeckoDriver.prototype.closeChromeWindow,
   "setTestName": GeckoDriver.prototype.setTestName,
--- a/testing/marionette/driver/marionette_driver/marionette.py
+++ b/testing/marionette/driver/marionette_driver/marionette.py
@@ -1266,16 +1266,31 @@ class Marionette(object):
         """
         body = {"focus": focus}
         if isinstance(frame, HTMLElement):
             body["element"] = frame.id
         elif frame is not None:
             body["id"] = frame
         self._send_message("switchToFrame", body)
 
+    def switch_to_shadow_root(self, host=None):
+        """Switch the current context to the specified host's Shadow DOM.
+        Subsequent commands will operate in the context of the specified Shadow
+        DOM, if applicable.
+
+        :param host: A reference to the host element containing Shadow DOM.
+            This can be an ``HTMLElement``. If you call
+            ``switch_to_shadow_root`` without an argument, it will switch to the
+            parent Shadow DOM or the top-level frame.
+        """
+        body = {}
+        if isinstance(host, HTMLElement):
+            body["id"] = host.id
+        return self._send_message("switchToShadowRoot", body)
+
     def get_url(self):
         """Get a string representing the current URL.
 
         On Desktop this returns a string representation of the URL of
         the current top level browsing context.  This is equivalent to
         document.location.href.
 
         When in the context of the chrome, this returns the canonical
--- a/testing/marionette/elements.js
+++ b/testing/marionette/elements.js
@@ -239,48 +239,83 @@ ElementManager.prototype = {
     return id;
   },
 
   /**
    * Retrieve element from its unique ID
    *
    * @param String id
    *        The DOM reference ID
-   * @param nsIDOMWindow win
-   *        The window that contains the element
+   * @param nsIDOMWindow, ShadowRoot container
+   *        The window and an optional shadow root that contains the element
    *
    * @returns nsIDOMElement
    *        Returns the element or throws Exception if not found
    */
-  getKnownElement: function EM_getKnownElement(id, win) {
+  getKnownElement: function EM_getKnownElement(id, container) {
     let el = this.seenItems[id];
     if (!el) {
       throw new JavaScriptError("Element has not been seen before. Id given was " + id);
     }
     try {
       el = el.get();
     }
     catch(e) {
       el = null;
       delete this.seenItems[id];
     }
     // use XPCNativeWrapper to compare elements; see bug 834266
-    let wrappedWin = XPCNativeWrapper(win);
+    let wrappedFrame = XPCNativeWrapper(container.frame);
+    let wrappedShadowRoot;
+    if (container.shadowRoot) {
+      wrappedShadowRoot = XPCNativeWrapper(container.shadowRoot);
+    }
+
     if (!el ||
-        !(XPCNativeWrapper(el).ownerDocument == wrappedWin.document) ||
-        (XPCNativeWrapper(el).compareDocumentPosition(wrappedWin.document.documentElement) &
-         DOCUMENT_POSITION_DISCONNECTED)) {
+        !(XPCNativeWrapper(el).ownerDocument == wrappedFrame.document) ||
+        this.isDisconnected(XPCNativeWrapper(el), wrappedShadowRoot,
+          wrappedFrame)) {
       throw new StaleElementReferenceError(
           "The element reference is stale. Either the element " +
           "is no longer attached to the DOM or the page has been refreshed.");
     }
     return el;
   },
 
   /**
+   * Check if the element is detached from the current frame as well as the
+   * optional shadow root (when inside a Shadow DOM context).
+   * @param nsIDOMElement el
+   *        element to be checked
+   * @param ShadowRoot shadowRoot
+   *        an optional shadow root containing an element
+   * @param nsIDOMWindow frame
+   *        window that contains the element or the current host of the shadow
+   *        root.
+   * @return {Boolean} a flag indicating that the element is disconnected
+   */
+  isDisconnected: function EM_isDisconnected(el, shadowRoot, frame) {
+    if (shadowRoot && frame.ShadowRoot) {
+      if (el.compareDocumentPosition(shadowRoot) &
+        DOCUMENT_POSITION_DISCONNECTED) {
+        return true;
+      }
+      // Looking for next possible ShadowRoot ancestor
+      let parent = shadowRoot.host;
+      while (parent && !(parent instanceof frame.ShadowRoot)) {
+        parent = parent.parentNode;
+      }
+      return this.isDisconnected(shadowRoot.host, parent, frame);
+    } else {
+      return el.compareDocumentPosition(frame.document.documentElement) &
+        DOCUMENT_POSITION_DISCONNECTED;
+    }
+  },
+
+  /**
    * Convert values to primitives that can be transported over the
    * Marionette protocol.
    *
    * This function implements the marshaling algorithm defined in the
    * WebDriver specification:
    *
    *     https://dvcs.w3.org/hg/webdriver/raw-file/tip/webdriver-spec.html#synchronous-javascript-execution
    *
@@ -333,54 +368,54 @@ ElementManager.prototype = {
     return result;
   },
 
   /**
    * Convert any ELEMENT references in 'args' to the actual elements
    *
    * @param object args
    *        Arguments passed in by client
-   * @param nsIDOMWindow win
-   *        The window that contains the elements
+   * @param nsIDOMWindow, ShadowRoot container
+   *        The window and an optional shadow root that contains the element
    *
    * @returns object
    *        Returns the objects passed in by the client, with the
    *        reference IDs replaced by the actual elements.
    */
-  convertWrappedArguments: function EM_convertWrappedArguments(args, win) {
+  convertWrappedArguments: function EM_convertWrappedArguments(args, container) {
     let converted;
     switch (typeof(args)) {
       case 'number':
       case 'string':
       case 'boolean':
         converted = args;
         break;
       case 'object':
         if (args == null) {
           converted = null;
         }
         else if (Object.prototype.toString.call(args) == '[object Array]') {
           converted = [];
           for (let i in args) {
-            converted.push(this.convertWrappedArguments(args[i], win));
+            converted.push(this.convertWrappedArguments(args[i], container));
           }
         }
         else if (((typeof(args[this.elementKey]) === 'string') && args.hasOwnProperty(this.elementKey)) ||
                  ((typeof(args[this.w3cElementKey]) === 'string') &&
                      args.hasOwnProperty(this.w3cElementKey))) {
           let elementUniqueIdentifier = args[this.w3cElementKey] ? args[this.w3cElementKey] : args[this.elementKey];
-          converted = this.getKnownElement(elementUniqueIdentifier,  win);
+          converted = this.getKnownElement(elementUniqueIdentifier, container);
           if (converted == null) {
             throw new WebDriverError(`Unknown element: ${elementUniqueIdentifier}`);
           }
         }
         else {
           converted = {};
           for (let prop in args) {
-            converted[prop] = this.convertWrappedArguments(args[prop], win);
+            converted[prop] = this.convertWrappedArguments(args[prop], container);
           }
         }
         break;
     }
     return converted;
   },
 
   /*
@@ -412,18 +447,18 @@ ElementManager.prototype = {
     return namedArgs;
   },
 
   /**
    * Find an element or elements starting at the document root or
    * given node, using the given search strategy. Search
    * will continue until the search timelimit has been reached.
    *
-   * @param nsIDOMWindow win
-   *        The window to search in
+   * @param nsIDOMWindow, ShadowRoot container
+   *        The window and an optional shadow root that contains the element
    * @param object values
    *        The 'using' member of values will tell us which search
    *        method to use. The 'value' member tells us the value we
    *        are looking for.
    *        If this object has an 'element' member, this will be used
    *        as the start node instead of the document root
    *        If this object has a 'time' member, this number will be
    *        used to see if we have hit the search timelimit.
@@ -433,25 +468,26 @@ ElementManager.prototype = {
    * @param function on_success
    *        Callback used when operating is successful.
    * @param function on_error
    *        Callback to invoke when an error occurs.
    *
    * @return nsIDOMElement or list of nsIDOMElements
    *        Returns the element(s) by calling the on_success function.
    */
-  find: function EM_find(win, values, searchTimeout, all, on_success, on_error, command_id) {
+  find: function EM_find(container, values, searchTimeout, all, on_success, on_error, command_id) {
     let startTime = values.time ? values.time : new Date().getTime();
+    let rootNode = container.shadowRoot || container.frame.document;
     let startNode = (values.element != undefined) ?
-                    this.getKnownElement(values.element, win) : win.document;
+                    this.getKnownElement(values.element, container) : rootNode;
     if (this.elementStrategies.indexOf(values.using) < 0) {
       throw new InvalidSelectorError("No such strategy: " + values.using);
     }
-    let found = all ? this.findElements(values.using, values.value, win.document, startNode) :
-                      this.findElement(values.using, values.value, win.document, startNode);
+    let found = all ? this.findElements(values.using, values.value, rootNode, startNode) :
+                      this.findElement(values.using, values.value, rootNode, startNode);
     let type = Object.prototype.toString.call(found);
     let isArrayLike = ((type == '[object Array]') || (type == '[object HTMLCollection]') || (type == '[object NodeList]'));
     if (found == null || (isArrayLike && found.length <= 0)) {
       if (!searchTimeout || new Date().getTime() - startTime > searchTimeout) {
         if (all) {
           on_success([], command_id); // findElements should return empty list
         } else {
           // Format message depending on strategy if necessary
@@ -460,17 +496,17 @@ ElementManager.prototype = {
             message = "Unable to locate anonymous children";
           } else if (values.using == ANON_ATTRIBUTE) {
             message = "Unable to locate anonymous element: " + JSON.stringify(values.value);
           }
           on_error(new NoSuchElementError(message), command_id);
         }
       } else {
         values.time = startTime;
-        this.timer.initWithCallback(this.find.bind(this, win, values,
+        this.timer.initWithCallback(this.find.bind(this, container, values,
                                                    searchTimeout, all,
                                                    on_success, on_error,
                                                    command_id),
                                     100,
                                     Components.interfaces.nsITimer.TYPE_ONE_SHOT);
       }
     } else {
       if (isArrayLike) {
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -30,19 +30,19 @@ loader.loadSubScript("chrome://marionett
 let marionetteLogObj = new MarionetteLogObj();
 
 let isB2G = false;
 
 let marionetteTestName;
 let winUtil = content.QueryInterface(Ci.nsIInterfaceRequestor)
     .getInterface(Ci.nsIDOMWindowUtils);
 let listenerId = null; // unique ID of this listener
-let curFrame = content;
-let isRemoteBrowser = () => curFrame.contentWindow !== null;
-let previousFrame = null;
+let curContainer = { frame: content, shadowRoot: null };
+let isRemoteBrowser = () => curContainer.frame.contentWindow !== null;
+let previousContainer = null;
 let elementManager = new ElementManager([]);
 let accessibility = new Accessibility();
 let actions = new ActionChain(utils, checkForInterrupted);
 let importedScripts = null;
 
 // Contains the last file input element that was the target of
 // sendKeysToElement.
 let fileInputElement;
@@ -79,19 +79,19 @@ let multiLast = {};
 Cu.import("resource://gre/modules/Log.jsm");
 let logger = Log.repository.getLogger("Marionette");
 logger.info("loaded listener.js");
 let modalHandler = function() {
   // This gets called on the system app only since it receives the mozbrowserprompt event
   sendSyncMessage("Marionette:switchedToFrame", { frameValue: null, storePrevious: true });
   let isLocal = sendSyncMessage("MarionetteFrame:handleModal", {})[0].value;
   if (isLocal) {
-    previousFrame = curFrame;
+    previousContainer = curContainer;
   }
-  curFrame = content;
+  curContainer = { frame: content, shadowRoot: null };
 };
 
 /**
  * Called when listener is first started up.
  * The listener sends its unique window ID and its current URI to the actor.
  * If the actor returns an ID, we start the listeners. Otherwise, nothing happens.
  */
 function registerSelf() {
@@ -118,17 +118,17 @@ function registerSelf() {
     }
   }
 }
 
 function emitTouchEventForIFrame(message) {
   message = message.json;
   let identifier = actions.nextTouchId;
 
-  let domWindowUtils = curFrame.
+  let domWindowUtils = curContainer.frame.
     QueryInterface(Components.interfaces.nsIInterfaceRequestor).
     getInterface(Components.interfaces.nsIDOMWindowUtils);
   var ratio = domWindowUtils.screenPixelsPerCSSPixel;
 
   var typeForUtils;
   switch (message.type) {
     case 'touchstart':
       typeForUtils = domWindowUtils.TOUCH_CONTACT;
@@ -207,16 +207,17 @@ let isElementEnabledFn = dispatch(isElem
 let getCurrentUrlFn = dispatch(getCurrentUrl);
 let findElementContentFn = dispatch(findElementContent);
 let findElementsContentFn = dispatch(findElementsContent);
 let isElementSelectedFn = dispatch(isElementSelected);
 let getElementLocationFn = dispatch(getElementLocation);
 let clearElementFn = dispatch(clearElement);
 let isElementDisplayedFn = dispatch(isElementDisplayed);
 let getElementValueOfCssPropertyFn = dispatch(getElementValueOfCssProperty);
+let switchToShadowRootFn = dispatch(switchToShadowRoot);
 
 /**
  * Start all message listeners
  */
 function startListeners() {
   addMessageListenerId("Marionette:receiveFiles", receiveFiles);
   addMessageListenerId("Marionette:newSession", newSession);
   addMessageListenerId("Marionette:executeScript", executeScript);
@@ -246,16 +247,17 @@ function startListeners() {
   addMessageListenerId("Marionette:getElementSize", getElementSizeFn);  // deprecated
   addMessageListenerId("Marionette:getElementRect", getElementRectFn);
   addMessageListenerId("Marionette:isElementEnabled", isElementEnabledFn);
   addMessageListenerId("Marionette:isElementSelected", isElementSelectedFn);
   addMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement);
   addMessageListenerId("Marionette:getElementLocation", getElementLocationFn); //deprecated
   addMessageListenerId("Marionette:clearElement", clearElementFn);
   addMessageListenerId("Marionette:switchToFrame", switchToFrame);
+  addMessageListenerId("Marionette:switchToShadowRoot", switchToShadowRootFn);
   addMessageListenerId("Marionette:deleteSession", deleteSession);
   addMessageListenerId("Marionette:sleepSession", sleepSession);
   addMessageListenerId("Marionette:emulatorCmdResult", emulatorCmdResult);
   addMessageListenerId("Marionette:importScript", importScript);
   addMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus);
   addMessageListenerId("Marionette:setTestName", setTestName);
   addMessageListenerId("Marionette:takeScreenshot", takeScreenshot);
   addMessageListenerId("Marionette:addCookie", addCookie);
@@ -351,34 +353,35 @@ function deleteSession(msg) {
   removeMessageListenerId("Marionette:getElementSize", getElementSizeFn); // deprecated
   removeMessageListenerId("Marionette:getElementRect", getElementRectFn);
   removeMessageListenerId("Marionette:isElementEnabled", isElementEnabledFn);
   removeMessageListenerId("Marionette:isElementSelected", isElementSelectedFn);
   removeMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement);
   removeMessageListenerId("Marionette:getElementLocation", getElementLocationFn);
   removeMessageListenerId("Marionette:clearElement", clearElementFn);
   removeMessageListenerId("Marionette:switchToFrame", switchToFrame);
+  removeMessageListenerId("Marionette:switchToShadowRoot", switchToShadowRootFn);
   removeMessageListenerId("Marionette:deleteSession", deleteSession);
   removeMessageListenerId("Marionette:sleepSession", sleepSession);
   removeMessageListenerId("Marionette:emulatorCmdResult", emulatorCmdResult);
   removeMessageListenerId("Marionette:importScript", importScript);
   removeMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus);
   removeMessageListenerId("Marionette:setTestName", setTestName);
   removeMessageListenerId("Marionette:takeScreenshot", takeScreenshot);
   removeMessageListenerId("Marionette:addCookie", addCookie);
   removeMessageListenerId("Marionette:getCookies", getCookies);
   removeMessageListenerId("Marionette:deleteAllCookies", deleteAllCookies);
   removeMessageListenerId("Marionette:deleteCookie", deleteCookie);
   if (isB2G) {
     content.removeEventListener("mozbrowsershowmodalprompt", modalHandler, false);
   }
   elementManager.reset();
-  // reset frame to the top-most frame
-  curFrame = content;
-  curFrame.focus();
+  // reset container frame to the top-most frame
+  curContainer = { frame: content, shadowRoot: null };
+  curContainer.frame.focus();
   actions.touchIds = {};
 }
 
 /*
  * Helper methods
  */
 
 /**
@@ -422,49 +425,49 @@ function sendError(err, cmdId) {
   sendToServer("Marionette:error", null, {error: err}, cmdId);
 }
 
 /**
  * Clear test values after completion of test
  */
 function resetValues() {
   sandboxes = {};
-  curFrame = content;
+  curContainer = { frame: content, shadowRoot: null };
   actions.mouseEventsOnly = false;
 }
 
 /**
  * Dump a logline to stdout. Prepends logline with a timestamp.
  */
 function dumpLog(logline) {
   dump(Date.now() + " Marionette: " + logline);
 }
 
 /**
  * Check if our context was interrupted
  */
 function wasInterrupted() {
-  if (previousFrame) {
+  if (previousContainer) {
     let element = content.document.elementFromPoint((content.innerWidth/2), (content.innerHeight/2));
     if (element.id.indexOf("modal-dialog") == -1) {
       return true;
     }
     else {
       return false;
     }
   }
   return sendSyncMessage("MarionetteFrame:getInterruptedState", {})[0].value;
 }
 
 function checkForInterrupted() {
     if (wasInterrupted()) {
-      if (previousFrame) {
-        //if previousFrame is set, then we're in a single process environment
-        curFrame = actions.frame = previousFrame;
-        previousFrame = null;
+      if (previousContainer) {
+        // if previousContainer is set, then we're in a single process environment
+        curContainer = actions.container = previousContainer;
+        previousContainer = null;
       }
       else {
         //else we're in OOP environment, so we'll switch to the original OOP frame
         sendSyncMessage("Marionette:switchToModalOrigin");
       }
       sendSyncMessage("Marionette:switchedToFrame", { restorePrevious: true });
     }
 }
@@ -507,21 +510,21 @@ function createExecuteContentSandbox(win
       sandbox[fn] = mn[fn].bind(mn);
     } else {
       sandbox[fn] = mn[fn];
     }
   });
 
   sandbox.asyncComplete = (obj, id) => {
     if (id == asyncTestCommandId) {
-      curFrame.removeEventListener("unload", onunload, false);
-      curFrame.clearTimeout(asyncTestTimeoutId);
+      curContainer.frame.removeEventListener("unload", onunload, false);
+      curContainer.frame.clearTimeout(asyncTestTimeoutId);
 
       if (inactivityTimeoutId != null) {
-        curFrame.clearTimeout(inactivityTimeoutId);
+        curContainer.frame.clearTimeout(inactivityTimeoutId);
       }
 
       sendSyncMessage("Marionette:shareData",
           {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
       marionetteLogObj.clearLogs();
 
       if (error.isError(obj)) {
         sendError(obj, id);
@@ -557,36 +560,36 @@ function createExecuteContentSandbox(win
 /**
  * Execute the given script either as a function body (executeScript)
  * or directly (for mochitest like JS Marionette tests).
  */
 function executeScript(msg, directInject) {
   // Set up inactivity timeout.
   if (msg.json.inactivityTimeout) {
     let setTimer = function() {
-      inactivityTimeoutId = curFrame.setTimeout(function() {
+      inactivityTimeoutId = curContainer.frame.setTimeout(function() {
         sendError(new ScriptTimeoutError("timed out due to inactivity"), asyncTestCommandId);
       }, msg.json.inactivityTimeout);
    };
 
     setTimer();
     heartbeatCallback = function() {
-      curFrame.clearTimeout(inactivityTimeoutId);
+      curContainer.frame.clearTimeout(inactivityTimeoutId);
       setTimer();
     };
   }
 
   asyncTestCommandId = msg.json.command_id;
   let script = msg.json.script;
   sandboxName = msg.json.sandboxName;
 
   if (msg.json.newSandbox ||
       !(sandboxName in sandboxes) ||
-      (sandboxes[sandboxName].window != curFrame)) {
-    createExecuteContentSandbox(curFrame, msg.json.timeout);
+      (sandboxes[sandboxName].window != curContainer.frame)) {
+    createExecuteContentSandbox(curContainer.frame, msg.json.timeout);
     if (!sandboxes[sandboxName]) {
       sendError(new WebDriverError("Could not create sandbox!"), asyncTestCommandId);
       return;
     }
   } else {
     sandboxes[sandboxName].asyncTestCommandId = asyncTestCommandId;
   }
 
@@ -612,17 +615,17 @@ function executeScript(msg, directInject
       }
       else {
         sendResponse({value: elementManager.wrapValue(res)}, asyncTestCommandId);
       }
     }
     else {
       try {
         sandbox.__marionetteParams = Cu.cloneInto(elementManager.convertWrappedArguments(
-          msg.json.args, curFrame), sandbox, { wrapReflectors: true });
+          msg.json.args, curContainer), sandbox, { wrapReflectors: true });
       } catch (e) {
         sendError(e, asyncTestCommandId);
         return;
       }
 
       script = "let __marionetteFunc = function(){" + script + "};" +
                    "__marionetteFunc.apply(null, __marionetteParams);";
       if (importedScripts.exists()) {
@@ -706,73 +709,73 @@ function executeJSScript(msg) {
  * For executeJSScript, it will return a message only when the finish() method is called.
  * For executeAsync, it will return a response when marionetteScriptFinished/arguments[arguments.length-1]
  * method is called, or if it times out.
  */
 function executeWithCallback(msg, useFinish) {
   // Set up inactivity timeout.
   if (msg.json.inactivityTimeout) {
     let setTimer = function() {
-      inactivityTimeoutId = curFrame.setTimeout(function() {
+      inactivityTimeoutId = curContainer.frame.setTimeout(function() {
         sandbox.asyncComplete(new ScriptTimeoutError("timed out due to inactivity"), asyncTestCommandId);
       }, msg.json.inactivityTimeout);
     };
 
     setTimer();
     heartbeatCallback = function() {
-      curFrame.clearTimeout(inactivityTimeoutId);
+      curContainer.frame.clearTimeout(inactivityTimeoutId);
       setTimer();
     };
   }
 
   let script = msg.json.script;
   asyncTestCommandId = msg.json.command_id;
   sandboxName = msg.json.sandboxName;
 
   onunload = function() {
     sendError(new JavaScriptError("unload was called"), asyncTestCommandId);
   };
-  curFrame.addEventListener("unload", onunload, false);
+  curContainer.frame.addEventListener("unload", onunload, false);
 
   if (msg.json.newSandbox ||
       !(sandboxName in sandboxes) ||
-      (sandboxes[sandboxName].window != curFrame)) {
-    createExecuteContentSandbox(curFrame, msg.json.timeout);
+      (sandboxes[sandboxName].window != curContainer.frame)) {
+    createExecuteContentSandbox(curContainer.frame, msg.json.timeout);
     if (!sandboxes[sandboxName]) {
       sendError(new JavaScriptError("Could not create sandbox!"), asyncTestCommandId);
       return;
     }
   }
   else {
     sandboxes[sandboxName].asyncTestCommandId = asyncTestCommandId;
   }
   let sandbox = sandboxes[sandboxName];
   sandbox.tag = script;
 
-  asyncTestTimeoutId = curFrame.setTimeout(function() {
+  asyncTestTimeoutId = curContainer.frame.setTimeout(function() {
     sandbox.asyncComplete(new ScriptTimeoutError("timed out"), asyncTestCommandId);
   }, msg.json.timeout);
 
-  originalOnError = curFrame.onerror;
-  curFrame.onerror = function errHandler(msg, url, line) {
+  originalOnError = curContainer.frame.onerror;
+  curContainer.frame.onerror = function errHandler(msg, url, line) {
     sandbox.asyncComplete(new JavaScriptError(msg + "@" + url + ", line " + line), asyncTestCommandId);
-    curFrame.onerror = originalOnError;
+    curContainer.frame.onerror = originalOnError;
   };
 
   let scriptSrc;
   if (useFinish) {
     if (msg.json.timeout == null || msg.json.timeout == 0) {
       sendError(new TimeoutError("Please set a timeout"), asyncTestCommandId);
     }
     scriptSrc = script;
   }
   else {
     try {
       sandbox.__marionetteParams = Cu.cloneInto(elementManager.convertWrappedArguments(
-        msg.json.args, curFrame), sandbox, { wrapReflectors: true });
+        msg.json.args, curContainer), sandbox, { wrapReflectors: true });
     } catch (e) {
       sendError(e, asyncTestCommandId);
       return;
     }
 
     scriptSrc = "__marionetteParams.push(marionetteScriptFinished);" +
                 "let __marionetteFunc = function() { " + script + "};" +
                 "__marionetteFunc.apply(null, __marionetteParams); ";
@@ -802,17 +805,17 @@ function executeWithCallback(msg, useFin
 
 /**
  * This function creates a touch event given a touch type and a touch
  */
 function emitTouchEvent(type, touch) {
   if (!wasInterrupted()) {
     let loggingInfo = "emitting Touch event of type " + type + " to element with id: " + touch.target.id + " and tag name: " + touch.target.tagName + " at coordinates (" + touch.clientX + ", " + touch.clientY + ") relative to the viewport";
     dumpLog(loggingInfo);
-    var docShell = curFrame.document.defaultView.
+    var docShell = curContainer.frame.document.defaultView.
                    QueryInterface(Components.interfaces.nsIInterfaceRequestor).
                    getInterface(Components.interfaces.nsIWebNavigation).
                    QueryInterface(Components.interfaces.nsIDocShell);
     if (docShell.asyncPanZoomEnabled && actions.scrolling) {
       // if we're in APZ and we're scrolling, we must use injectTouchEvent to dispatch our touchmove events
       let index = sendSyncMessage("MarionetteFrame:getCurrentFrameId");
       // only call emitTouchEventForIFrame if we're inside an iframe.
       if (index != null) {
@@ -828,17 +831,17 @@ function emitTouchEvent(type, touch) {
     // we get here if we're not in asyncPacZoomEnabled land, or if we're the main process
     /*
     Disabled per bug 888303
     marionetteLogObj.log(loggingInfo, "TRACE");
     sendSyncMessage("Marionette:shareData",
                     {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
     marionetteLogObj.clearLogs();
     */
-    let domWindowUtils = curFrame.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils);
+    let domWindowUtils = curContainer.frame.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils);
     domWindowUtils.sendTouchEvent(type, [touch.identifier], [touch.clientX], [touch.clientY], [touch.radiusX], [touch.radiusY], [touch.rotationAngle], [touch.force], 1, 0);
   }
 }
 
 /**
  * This function generates a pair of coordinates relative to the viewport given a
  * target element and coordinates relative to that element's top-left corner.
  * @param 'x', and 'y' are the relative to the target.
@@ -861,16 +864,17 @@ function coordinates(target, x, y) {
 
 /**
  * This function returns true if the given coordinates are in the viewport.
  * @param 'x', and 'y' are the coordinates relative to the target.
  *        If they are not specified, then the center of the target is used.
  */
 function elementInViewport(el, x, y) {
   let c = coordinates(el, x, y);
+  let curFrame = curContainer.frame;
   let viewPort = {top: curFrame.pageYOffset,
                   left: curFrame.pageXOffset,
                   bottom: (curFrame.pageYOffset + curFrame.innerHeight),
                   right:(curFrame.pageXOffset + curFrame.innerWidth)};
   return (viewPort.left <= c.x + curFrame.pageXOffset &&
           c.x + curFrame.pageXOffset <= viewPort.right &&
           viewPort.top <= c.y + curFrame.pageYOffset &&
           c.y + curFrame.pageYOffset <= viewPort.bottom);
@@ -912,27 +916,27 @@ function checkVisible(el, x, y) {
 
 
 /**
  * Function that perform a single tap
  */
 function singleTap(msg) {
   let command_id = msg.json.command_id;
   try {
-    let el = elementManager.getKnownElement(msg.json.id, curFrame);
+    let el = elementManager.getKnownElement(msg.json.id, curContainer);
     let acc = accessibility.getAccessibleObject(el, true);
     // after this block, the element will be scrolled into view
     let visible = checkVisible(el, msg.json.corx, msg.json.cory);
     checkVisibleAccessibility(acc, visible);
     if (!visible) {
       sendError(new ElementNotVisibleError("Element is not currently visible and may not be manipulated"), command_id);
       return;
     }
     checkActionableAccessibility(acc);
-    if (!curFrame.document.createTouch) {
+    if (!curContainer.frame.document.createTouch) {
       actions.mouseEventsOnly = true;
     }
     let c = coordinates(el, msg.json.corx, msg.json.cory);
     if (!actions.mouseEventsOnly) {
       let touchId = actions.nextTouchId++;
       let touch = createATouch(el, c.x, c.y, touchId);
       emitTouchEvent('touchstart', touch);
       emitTouchEvent('touchend', touch);
@@ -952,17 +956,17 @@ function singleTap(msg) {
  * @param Boolean enabled element's enabled state
  */
 function checkEnabledAccessibility(accesible, element, enabled) {
   if (!accesible) {
     return;
   }
   let disabledAccessibility = accessibility.matchState(
     accesible, 'STATE_UNAVAILABLE');
-  let explorable = curFrame.document.defaultView.getComputedStyle(
+  let explorable = curContainer.frame.document.defaultView.getComputedStyle(
     element, null).getPropertyValue('pointer-events') !== 'none';
   let message;
 
   if (!explorable && !disabledAccessibility) {
     message = 'Element is enabled but is not explorable via the ' +
       'accessibility API';
   } else if (enabled && disabledAccessibility) {
     message = 'Element is enabled but disabled via the accessibility API';
@@ -1071,17 +1075,17 @@ function actionChain(msg) {
   let touchProvider = {};
   touchProvider.createATouch = createATouch;
   touchProvider.emitTouchEvent = emitTouchEvent;
 
   try {
     actions.dispatchActions(
         args,
         touchId,
-        curFrame,
+        curContainer,
         elementManager,
         callbacks,
         touchProvider);
   } catch (e) {
     sendError(e, command_id);
   }
 }
 
@@ -1152,32 +1156,32 @@ function setDispatch(batches, touches, c
   batchIndex++;
   // loop through the batch
   for (let i = 0; i < batch.length; i++) {
     pack = batch[i];
     touchId = pack[0];
     command = pack[1];
     switch (command) {
       case 'press':
-        el = elementManager.getKnownElement(pack[2], curFrame);
+        el = elementManager.getKnownElement(pack[2], curContainer);
         c = coordinates(el, pack[3], pack[4]);
         touch = createATouch(el, c.x, c.y, touchId);
         multiLast[touchId] = touch;
         touches.push(touch);
         emitMultiEvents('touchstart', touch, touches);
         break;
       case 'release':
         touch = multiLast[touchId];
         // the index of the previous touch for the finger may change in the touches array
         touchIndex = touches.indexOf(touch);
         touches.splice(touchIndex, 1);
         emitMultiEvents('touchend', touch, touches);
         break;
       case 'move':
-        el = elementManager.getKnownElement(pack[2], curFrame);
+        el = elementManager.getKnownElement(pack[2], curContainer);
         c = coordinates(el);
         touch = createATouch(multiLast[touchId].target, c.x, c.y, touchId);
         touchIndex = touches.indexOf(lastTouch);
         touches[touchIndex] = touch;
         multiLast[touchId] = touch;
         emitMultiEvents('touchmove', touch, touches);
         break;
       case 'moveByOffset':
@@ -1221,17 +1225,17 @@ function setDispatch(batches, touches, c
  */
 function multiAction(msg) {
   let command_id = msg.json.command_id;
   let args = msg.json.value;
   // maxlen is the longest action chain for one finger
   let maxlen = msg.json.maxlen;
   try {
     // unwrap the original nested array
-    let commandArray = elementManager.convertWrappedArguments(args, curFrame);
+    let commandArray = elementManager.convertWrappedArguments(args, curContainer);
     let concurrentEvent = [];
     let temp;
     for (let i = 0; i < maxlen; i++) {
       let row = [];
       for (let j = 0; j < commandArray.length; j++) {
         if (commandArray[j][i] != undefined) {
           // add finger id to the front of each action, i.e. [finger_id, action, element]
           temp = commandArray[j][i];
@@ -1265,28 +1269,29 @@ function pollForReadyState(msg, start, c
   }
 
   let end = null;
   function checkLoad() {
     navTimer.cancel();
     end = new Date().getTime();
     let aboutErrorRegex = /about:.+(error)\?/;
     let elapse = end - start;
+    let doc = curContainer.frame.document;
     if (pageTimeout == null || elapse <= pageTimeout) {
-      if (curFrame.document.readyState == "complete") {
+      if (doc.readyState == "complete") {
         callback();
         sendOk(command_id);
-      } else if (curFrame.document.readyState == "interactive" &&
-                 aboutErrorRegex.exec(curFrame.document.baseURI) &&
-                 !curFrame.document.baseURI.startsWith(url)) {
+      } else if (doc.readyState == "interactive" &&
+                 aboutErrorRegex.exec(doc.baseURI) &&
+                 !doc.baseURI.startsWith(url)) {
         // We have reached an error url without requesting it.
         callback();
         sendError(new UnknownError("Error loading page"), command_id);
-      } else if (curFrame.document.readyState == "interactive" &&
-                 curFrame.document.baseURI.startsWith("about:")) {
+      } else if (doc.readyState == "interactive" &&
+                 doc.baseURI.startsWith("about:")) {
         callback();
         sendOk(command_id);
       } else {
         navTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
       }
     } else {
       callback();
       sendError(new TimeoutError("Error loading page, timed out (checkLoad)"), command_id);
@@ -1304,33 +1309,33 @@ function pollForReadyState(msg, start, c
 function get(msg) {
   let start = new Date().getTime();
 
   // Prevent DOMContentLoaded events from frames from invoking this
   // code, unless the event is coming from the frame associated with
   // the current window (i.e. someone has used switch_to_frame).
   onDOMContentLoaded = function onDOMContentLoaded(event) {
     if (!event.originalTarget.defaultView.frameElement ||
-        event.originalTarget.defaultView.frameElement == curFrame.frameElement) {
+        event.originalTarget.defaultView.frameElement == curContainer.frame.frameElement) {
       pollForReadyState(msg, start, () => {
         removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
         onDOMContentLoaded = null;
       });
     }
   };
 
   function timerFunc() {
     removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
     sendError(new TimeoutError("Error loading page, timed out (onDOMContentLoaded)"), msg.json.command_id);
   }
   if (msg.json.pageTimeout != null) {
     navTimer.initWithCallback(timerFunc, msg.json.pageTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
   }
   addEventListener("DOMContentLoaded", onDOMContentLoaded, false);
-  curFrame.location = msg.json.url;
+  curContainer.frame.location = msg.json.url;
 }
 
  /**
  * Cancel the polling and remove the event listener associated with a current
  * navigation request in case we're interupted by an onbeforeunload handler
  * and navigation doesn't complete.
  */
 function cancelRequest() {
@@ -1340,118 +1345,118 @@ function cancelRequest() {
   }
 }
 
 /**
  * Get URL of the top-level browsing context.
  */
 function getCurrentUrl(isB2G) {
   if (isB2G) {
-    return curFrame.location.href;
+    return curContainer.frame.location.href;
   } else {
     return content.location.href;
   }
 }
 
 /**
  * Get the title of the current browsing context.
  */
 function getTitle() {
-  return curFrame.top.document.title;
+  return curContainer.frame.top.document.title;
 }
 
 /**
  * Get source of the current browsing context's DOM.
  */
 function getPageSource() {
-  let XMLSerializer = curFrame.XMLSerializer;
-  let source = new XMLSerializer().serializeToString(curFrame.document);
+  let XMLSerializer = curContainer.frame.XMLSerializer;
+  let source = new XMLSerializer().serializeToString(curContainer.frame.document);
   return source;
 }
 
 /**
  * Cause the browser to traverse one step backward in the joint history
  * of the current top-level browsing context.
  */
 function goBack() {
-  curFrame.history.back();
+  curContainer.frame.history.back();
 }
 
 /**
  * Go forward in history
  */
 function goForward(msg) {
-  curFrame.history.forward();
+  curContainer.frame.history.forward();
   sendOk(msg.json.command_id);
 }
 
 /**
  * Refresh the page
  */
 function refresh(msg) {
   let command_id = msg.json.command_id;
-  curFrame.location.reload(true);
+  curContainer.frame.location.reload(true);
   let listen = function() {
     removeEventListener("DOMContentLoaded", arguments.callee, false);
     sendOk(command_id);
   };
   addEventListener("DOMContentLoaded", listen, false);
 }
 
 /**
  * Find an element in the current browsing context's document using the
  * given search strategy.
  */
 function findElementContent(opts) {
   return new Promise((resolve, reject) => {
     elementManager.find(
-        curFrame,
+        curContainer,
         opts,
         opts.searchTimeout,
         false /* all */,
         resolve,
         reject);
   });
 }
 
 /**
  * Find elements in the current browsing context's document using the
  * given search strategy.
  */
 function findElementsContent(opts) {
   return new Promise((resolve, reject) => {
     elementManager.find(
-        curFrame,
+        curContainer,
         opts,
         opts.searchTimeout,
         true /* all */,
         resolve,
         reject);
   });
 }
 
 /**
  * Find and return the active element on the page.
  *
  * @return {WebElement}
  *     Reference to web element.
  */
 function getActiveElement() {
-  let el = curFrame.document.activeElement;
+  let el = curContainer.frame.document.activeElement;
   return elementManager.addToKnownElements(el);
 }
 
 /**
  * Send click event to element.
  *
  * @param {WebElement} id
  *     Reference to the web element to click.
  */
 function clickElement(id) {
-  let el = elementManager.getKnownElement(id, curFrame);
+  let el = elementManager.getKnownElement(id, curContainer);
   let acc = accessibility.getAccessibleObject(el, true);
   let visible = checkVisible(el);
   checkVisibleAccessibility(acc, visible);
   if (!visible) {
     throw new ElementNotVisibleError("Element is not visible");
   }
   checkActionableAccessibility(acc);
   if (utils.isElementEnabled(el)) {
@@ -1469,56 +1474,56 @@ function clickElement(id) {
  *     Reference to the web element to get the attribute of.
  * @param {string} name
  *     Name of the attribute.
  *
  * @return {string}
  *     The value of the attribute.
  */
 function getElementAttribute(id, name) {
-  let el = elementManager.getKnownElement(id, curFrame);
+  let el = elementManager.getKnownElement(id, curContainer);
   return utils.getElementAttribute(el, name);
 }
 
 /**
  * Get the text of this element. This includes text from child elements.
  *
  * @param {WebElement} id
  *     Reference to web element.
  *
  * @return {string}
  *     Text of element.
  */
 function getElementText(id) {
-  let el = elementManager.getKnownElement(id, curFrame);
+  let el = elementManager.getKnownElement(id, curContainer);
   return utils.getElementText(el);
 }
 
 /**
  * Get the tag name of an element.
  *
  * @param {WebElement} id
  *     Reference to web element.
  *
  * @return {string}
  *     Tag name of element.
  */
 function getElementTagName(id) {
-  let el = elementManager.getKnownElement(id, curFrame);
+  let el = elementManager.getKnownElement(id, curContainer);
   return el.tagName.toLowerCase();
 }
 
 /**
  * Determine the element displayedness of the given web element.
  *
  * Also performs additional accessibility checks if enabled by session
  * capability.
  */
 function isElementDisplayed(id) {
-  let el = elementManager.getKnownElement(id, curFrame);
+  let el = elementManager.getKnownElement(id, curContainer);
   let displayed = utils.isElementDisplayed(el);
   checkVisibleAccessibility(accessibility.getAccessibleObject(el), displayed);
   return displayed;
 }
 
 /**
  * Retrieves the computed value of the given CSS property of the given
  * web element.
@@ -1527,129 +1532,129 @@ function isElementDisplayed(id) {
  *     Web element reference.
  * @param {String} prop
  *     The CSS property to get.
  *
  * @return {String}
  *     Effective value of the requested CSS property.
  */
 function getElementValueOfCssProperty(id, prop) {
-  let el = elementManager.getKnownElement(id, curFrame);
-  let st = curFrame.document.defaultView.getComputedStyle(el, null);
+  let el = elementManager.getKnownElement(id, curContainer);
+  let st = curContainer.frame.document.defaultView.getComputedStyle(el, null);
   return st.getPropertyValue(prop);
 }
 
 /**
  * Get the size of the element.
  *
  * @param {WebElement} id
  *     Web element reference.
  *
  * @return {Object.<string, number>}
  *     The width/height dimensions of th element.
  */
 function getElementSize(id) {
-  let el = elementManager.getKnownElement(id, curFrame);
+  let el = elementManager.getKnownElement(id, curContainer);
   let clientRect = el.getBoundingClientRect();
   return {width: clientRect.width, height: clientRect.height};
 }
 
 /**
  * Get the size of the element.
  *
  * @param {WebElement} id
  *     Reference to web element.
  *
  * @return {Object.<string, number>}
  *     The x, y, width, and height properties of the element.
  */
 function getElementRect(id) {
-  let el = elementManager.getKnownElement(id, curFrame);
+  let el = elementManager.getKnownElement(id, curContainer);
   let clientRect = el.getBoundingClientRect();
   return {
-    x: clientRect.x + curFrame.pageXOffset,
-    y: clientRect.y  + curFrame.pageYOffset,
+    x: clientRect.x + curContainer.frame.pageXOffset,
+    y: clientRect.y  + curContainer.frame.pageYOffset,
     width: clientRect.width,
     height: clientRect.height
   };
 }
 
 /**
  * Check if element is enabled.
  *
  * @param {WebElement} id
  *     Reference to web element.
  *
  * @return {boolean}
  *     True if enabled, false otherwise.
  */
 function isElementEnabled(id) {
-  let el = elementManager.getKnownElement(id, curFrame);
+  let el = elementManager.getKnownElement(id, curContainer);
   let enabled = utils.isElementEnabled(el);
   checkEnabledAccessibility(
     accessibility.getAccessibleObject(el), el, enabled);
   return enabled;
 }
 
 /**
  * Determines if the referenced element is selected or not.
  *
  * This operation only makes sense on input elements of the Checkbox-
  * and Radio Button states, or option elements.
  */
 function isElementSelected(id) {
-  let el = elementManager.getKnownElement(id, curFrame);
+  let el = elementManager.getKnownElement(id, curContainer);
     let selected = utils.isElementSelected(el);
     checkSelectedAccessibility(accessibility.getAccessibleObject(el), selected);
   return selected;
 }
 
 /**
  * Send keys to element
  */
 function sendKeysToElement(msg) {
   let command_id = msg.json.command_id;
   let val = msg.json.value;
 
   try {
-    let el = elementManager.getKnownElement(msg.json.id, curFrame);
+    let el = elementManager.getKnownElement(msg.json.id, curContainer);
     // Element should be actionable from the accessibility standpoint to be able
     // to send keys to it.
     checkActionableAccessibility(accessibility.getAccessibleObject(el, true));
     if (el.type == "file") {
       let p = val.join("");
       fileInputElement = el;
       // In e10s, we can only construct File objects in the parent process,
       // so pass the filename to driver.js, which in turn passes them back
       // to this frame script in receiveFiles.
       sendSyncMessage("Marionette:getFiles",
                       {value: p, command_id: command_id});
     } else {
-      utils.sendKeysToElement(curFrame, el, val, sendOk, sendError, command_id);
+      utils.sendKeysToElement(curContainer.frame, el, val, sendOk, sendError, command_id);
     }
   } catch (e) {
     sendError(e, command_id);
   }
 }
 
 /**
  * Get the element's top left-hand corner point.
  */
 function getElementLocation(id) {
-  let el = elementManager.getKnownElement(id, curFrame);
+  let el = elementManager.getKnownElement(id, curContainer);
   let rect = el.getBoundingClientRect();
   return {x: rect.left, y: rect.top};
 }
 
 /**
  * Clear the text of an element.
  */
 function clearElement(id) {
   try {
-    let el = elementManager.getKnownElement(id, curFrame);
+    let el = elementManager.getKnownElement(id, curContainer);
     if (el.type == "file") {
       el.value = null;
     } else {
       utils.clearElement(el);
     }
   } catch (e) {
     // Bug 964738: Newer atoms contain status codes which makes wrapping
     // this in an error prototype that has a status property unnecessary
@@ -1657,148 +1662,176 @@ function clearElement(id) {
       throw new InvalidElementStateError(e.message);
     } else {
       throw e;
     }
   }
<