Merge autoland to m-c, a=merge
authorPhil Ringnalda <philringnalda@gmail.com>
Wed, 25 Jan 2017 20:37:29 -0800
changeset 331162 fbdfcecf0c774d2221f11aed5a504d5591c774e0
parent 331075 52a34f9a6cf112377299ab32132384e2dc1f543b (current diff)
parent 331161 5c5b0537712323cf1d9610a6fe1b127c65902182 (diff)
child 331163 e6ed73323bb33d4bdb6278395fd9acaba58cc092
child 331181 1e036cc4bf25338708b1466a4756316390557241
push id86167
push userphilringnalda@gmail.com
push dateThu, 26 Jan 2017 04:48:58 +0000
treeherdermozilla-inbound@e6ed73323bb3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone54.0a1
first release with
nightly linux32
fbdfcecf0c77 / 54.0a1 / 20170126110046 / files
nightly linux64
fbdfcecf0c77 / 54.0a1 / 20170126110046 / files
nightly mac
fbdfcecf0c77 / 54.0a1 / 20170126030209 / files
nightly win32
fbdfcecf0c77 / 54.0a1 / 20170126030209 / files
nightly win64
fbdfcecf0c77 / 54.0a1 / 20170126030209 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to m-c, a=merge
browser/extensions/webcompat-reporter/locale/en-US/webcompat.properties
browser/extensions/webcompat-reporter/locale/jar.mn
browser/extensions/webcompat-reporter/locale/moz.build
services/sync/tests/unit/fake_login_manager.js
taskcluster/ci/test/tests.yml
taskcluster/taskgraph/transforms/task.py
taskcluster/taskgraph/transforms/tests.py
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -900,17 +900,17 @@ function _loadURIWithFlags(browser, uri,
 
       if (postData) {
         postData = serializeInputStream(postData);
       }
 
       let loadParams = {
         uri,
         triggeringPrincipal: triggeringPrincipal
-          ? gSerializationHelper.serializePrincipal(triggeringPrincipal)
+          ? gSerializationHelper.serializeToString(triggeringPrincipal)
           : null,
         flags,
         referrer: referrer ? referrer.spec : null,
         referrerPolicy,
         postData
       }
 
       if (params.userContextId) {
--- a/browser/base/content/test/general/browser_extension_permissions.js
+++ b/browser/base/content/test/general/browser_extension_permissions.js
@@ -58,25 +58,26 @@ function checkNotification(panel, url) {
 
     is(header.getAttribute("hidden"), "true", "Permission list header is hidden");
     is(ul.childElementCount, 0, "Permissions list has 0 entries");
   }
 }
 
 const INSTALL_FUNCTIONS = [
   function installMozAM(url) {
-    ContentTask.spawn(gBrowser.selectedBrowser, url, function*(cUrl) {
-      content.wrappedJSObject.installMozAM(cUrl);
+    return ContentTask.spawn(gBrowser.selectedBrowser, url, function*(cUrl) {
+      yield content.wrappedJSObject.installMozAM(cUrl);
     });
   },
 
   function installTrigger(url) {
     ContentTask.spawn(gBrowser.selectedBrowser, url, function*(cUrl) {
       content.wrappedJSObject.installTrigger(cUrl);
     });
+    return Promise.resolve();
   },
 ];
 
 add_task(function* () {
   yield SpecialPowers.pushPrefEnv({set: [
     ["extensions.webapi.testing", true],
     ["extensions.install.requireBuiltInCerts", false],
 
@@ -112,27 +113,39 @@ add_task(function* () {
         onInstallFailed() {
           AddonManager.removeInstallListener(listener);
           resolve(false);
         },
       };
       AddonManager.addInstallListener(listener);
     });
 
-    installFn(url);
+    let installMethodPromise = installFn(url);
 
     let panel = yield promisePopupNotificationShown("addon-webext-permissions");
     checkNotification(panel, url);
 
     if (cancel) {
       panel.secondaryButton.click();
+      try {
+        yield installMethodPromise;
+      } catch (err) {}
     } else {
+      // Look for post-install notification
+      let postInstallPromise = promisePopupNotificationShown("addon-installed");
       panel.button.click();
+
+      // Press OK on the post-install notification
+      panel = yield postInstallPromise;
+      panel.button.click();
+
+      yield installMethodPromise;
     }
 
+
     let result = yield installPromise;
     let addon = yield promiseGetAddonByID(ID);
     if (cancel) {
       ok(!result, "Installation was cancelled");
       is(addon, null, "Extension is not installed");
     } else {
       ok(result, "Installation completed");
       isnot(addon, null, "Extension is installed");
--- a/browser/base/content/test/general/file_install_extensions.html
+++ b/browser/base/content/test/general/file_install_extensions.html
@@ -2,19 +2,18 @@
 
 <html>
 <head>
   <meta charset="utf-8">
 </head>
 <body>
 <script type="text/javascript">
 function installMozAM(url) {
-  return navigator.mozAddonManager.createInstall({url}).then(install => {
-    install.install();
-  });
+  return navigator.mozAddonManager.createInstall({url})
+                  .then(install => install.install());
 }
 
 function installTrigger(url) {
   InstallTrigger.install({extension: url});
 }
 </script>
 </body>
 </html>
--- a/browser/components/migration/tests/marionette/test_refresh_firefox.py
+++ b/browser/components/migration/tests/marionette/test_refresh_firefox.py
@@ -125,18 +125,26 @@ class TestFirefoxRefresh(MarionetteTestC
                   if (!expectedURLs.length) {
                     gBrowser.removeTabsProgressListener(this);
                     marionetteScriptFinished();
                   }
                 });
               }
             }
           });
+          let expectedTabs = new Set();
           for (let url of expectedURLs) {
-            gBrowser.addTab(url);
+            expectedTabs.add(gBrowser.addTab(url));
+          }
+          // Close any other tabs that might be open:
+          let allTabs = Array.from(gBrowser.tabs);
+          for (let tab of allTabs) {
+            if (!expectedTabs.has(tab)) {
+              gBrowser.removeTab(tab);
+            }
           }
         """, script_args=[self._expectedURLs])
 
     def checkPassword(self):
         loginInfo = self.marionette.execute_script("""
           let ary = Services.logins.findLogins({},
             "test.marionette.mozilla.com",
             "http://test.marionette.mozilla.com/some/form/",
@@ -270,18 +278,17 @@ class TestFirefoxRefresh(MarionetteTestC
           window.addEventListener("SSWindowStateReady", function testSSPostReset() {
             window.removeEventListener("SSWindowStateReady", testSSPostReset, false);
             Promise.all(gBrowser.browsers.map(b => TabStateFlusher.flush(b))).then(function() {
               marionetteScriptFinished([... gBrowser.browsers].map(b => b.currentURI && b.currentURI.spec));
             });
           }, false);
           mm.loadFrameScript("data:application/javascript,(" + fs.toString() + ")()", true);
         """)
-        self.assertSequenceEqual(tabURIs, ["about:blank"] + self._expectedURLs)
-        pass
+        self.assertSequenceEqual(tabURIs, self._expectedURLs)
 
     def checkProfile(self, hasMigrated=False):
         self.checkPassword()
         self.checkBookmark()
         self.checkHistory()
         self.checkFormHistory()
         self.checkCookie()
         if hasMigrated:
--- a/browser/components/preferences/in-content/advanced.xul
+++ b/browser/components/preferences/in-content/advanced.xul
@@ -325,17 +325,17 @@
           </vbox>
         </hbox>
       </groupbox>
 
       <!-- Site Data -->
       <groupbox id="siteDataGroup" hidden="true">
         <caption><label>&siteData.label;</label></caption>
 
-        <hbox align="center">
+        <hbox align="baseline">
           <label id="totalSiteDataSize"></label>
           <label id="siteDataLearnMoreLink" class="learnMore text-link" value="&siteDataLearnMoreLink.label;"></label>
           <spacer flex="1" />
           <button id="clearSiteDataButton" icon="clear"
                   label="&clearSiteData.label;" accesskey="&clearSiteData.accesskey;"/>
         </hbox>
         <vbox align="end">
           <button id="siteDataSettings"
--- a/browser/config/tooltool-manifests/win32/clang.manifest
+++ b/browser/config/tooltool-manifests/win32/clang.manifest
@@ -34,16 +34,17 @@
 "version": "clang 4.0pre/r286542",
 "size": 222604502,
 "digest": "cea6119131adb66e0b7ec5030b00922ac95e4e97249fcab9561a848ea60b7f80536c9171a07136afcb79decbcdb20099a5e7ee493013710b8ba5ae072ad40851",
 "algorithm": "sha512",
 "filename": "clang.tar.bz2",
 "unpack": true
 },
 {
+"version": "makecab rev d2bc6797648b7a834782714a55d339d2fd4e58c8",
 "algorithm": "sha512",
 "visibility": "public",
 "filename": "makecab.tar.bz2",
 "unpack": true,
-"digest": "da1f7685e5bc49a5ffbe5b4a3678ac7a58a0e125031726d30e2bacbbffa53d6efb9fd9f00d6dff5b54cff9412a103efa564c2af6f8fccc63082f6939181769f8",
-"size": 296777
+"digest": "196ac6a567c85559957dfe511c3d8654d23c94d5603259e19ccafe9d71e0e4ccee63ccc9a778f2699654b786cda54266108b7d4db543d01bb0b42545b4e6ec75",
+"size": 297118
 }
 ]
--- a/browser/config/tooltool-manifests/win32/releng.manifest
+++ b/browser/config/tooltool-manifests/win32/releng.manifest
@@ -26,16 +26,17 @@
 "version": "Visual Studio 2015 Update 3 14.0.25425.01 / SDK 10.0.14393.0",
 "size": 326656969,
 "digest": "babc414ffc0457d27f5a1ed24a8e4873afbe2f1c1a4075469a27c005e1babc3b2a788f643f825efedff95b79686664c67ec4340ed535487168a3482e68559bc7",
 "algorithm": "sha512",
 "filename": "vs2015u3.zip",
 "unpack": true
 },
 {
+"version": "makecab rev d2bc6797648b7a834782714a55d339d2fd4e58c8",
 "algorithm": "sha512",
 "visibility": "public",
 "filename": "makecab.tar.bz2",
 "unpack": true,
-"digest": "da1f7685e5bc49a5ffbe5b4a3678ac7a58a0e125031726d30e2bacbbffa53d6efb9fd9f00d6dff5b54cff9412a103efa564c2af6f8fccc63082f6939181769f8",
-"size": 296777
+"digest": "196ac6a567c85559957dfe511c3d8654d23c94d5603259e19ccafe9d71e0e4ccee63ccc9a778f2699654b786cda54266108b7d4db543d01bb0b42545b4e6ec75",
+"size": 297118
 }
 ]
--- a/browser/config/tooltool-manifests/win64/clang.manifest
+++ b/browser/config/tooltool-manifests/win64/clang.manifest
@@ -35,16 +35,17 @@
 "version": "clang 4.0pre/r286542",
 "size": 226755339,
 "digest": "3c598607c36e70788ca7dbdf0d835f9e44fbcaa7b1ed77ef9971d743a5a230bebc0ccd2bcdf97f63ed4546d1b83f4c3556f35c30589c755aaaefbd674f750e22",
 "algorithm": "sha512",
 "filename": "clang.tar.bz2",
 "unpack": true
 },
 {
+"version": "makecab rev d2bc6797648b7a834782714a55d339d2fd4e58c8",
 "algorithm": "sha512",
 "visibility": "public",
 "filename": "makecab.tar.bz2",
 "unpack": true,
-"digest": "da1f7685e5bc49a5ffbe5b4a3678ac7a58a0e125031726d30e2bacbbffa53d6efb9fd9f00d6dff5b54cff9412a103efa564c2af6f8fccc63082f6939181769f8",
-"size": 296777
+"digest": "196ac6a567c85559957dfe511c3d8654d23c94d5603259e19ccafe9d71e0e4ccee63ccc9a778f2699654b786cda54266108b7d4db543d01bb0b42545b4e6ec75",
+"size": 297118
 }
 ]
--- a/browser/config/tooltool-manifests/win64/releng.manifest
+++ b/browser/config/tooltool-manifests/win64/releng.manifest
@@ -27,16 +27,17 @@
 "version": "Visual Studio 2015 Update 3 14.0.25425.01 / SDK 10.0.14393.0",
 "size": 326656969,
 "digest": "babc414ffc0457d27f5a1ed24a8e4873afbe2f1c1a4075469a27c005e1babc3b2a788f643f825efedff95b79686664c67ec4340ed535487168a3482e68559bc7",
 "algorithm": "sha512",
 "filename": "vs2015u3.zip",
 "unpack": true
 },
 {
+"version": "makecab rev d2bc6797648b7a834782714a55d339d2fd4e58c8",
 "algorithm": "sha512",
 "visibility": "public",
 "filename": "makecab.tar.bz2",
 "unpack": true,
-"digest": "da1f7685e5bc49a5ffbe5b4a3678ac7a58a0e125031726d30e2bacbbffa53d6efb9fd9f00d6dff5b54cff9412a103efa564c2af6f8fccc63082f6939181769f8",
-"size": 296777
+"digest": "196ac6a567c85559957dfe511c3d8654d23c94d5603259e19ccafe9d71e0e4ccee63ccc9a778f2699654b786cda54266108b7d4db543d01bb0b42545b4e6ec75",
+"size": 297118
 }
 ]
rename from browser/extensions/webcompat-reporter/locale/en-US/webcompat.properties
rename to browser/extensions/webcompat-reporter/locales/en-US/webcompat.properties
--- a/browser/extensions/webcompat-reporter/locale/en-US/webcompat.properties
+++ b/browser/extensions/webcompat-reporter/locales/en-US/webcompat.properties
@@ -1,2 +1,10 @@
+# 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/.
+
+# LOCALIZATION NOTE(wc-reporter.label): This string will be used in the
+# Firefox menu panel below its button. Localized length should be considered.
 wc-reporter.label=Report Site Issue
+# LOCALIZATION NOTE(wc-reporter.tooltip): A site compatibility issue is
+# a website bug that exists in one browser (Firefox), but not another.
 wc-reporter.tooltip=Report a site compatibility issue
rename from browser/extensions/webcompat-reporter/locale/jar.mn
rename to browser/extensions/webcompat-reporter/locales/jar.mn
--- a/browser/extensions/webcompat-reporter/locale/jar.mn
+++ b/browser/extensions/webcompat-reporter/locales/jar.mn
@@ -1,8 +1,8 @@
 #filter substitution
 # 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/.
 
 [features/webcompat-reporter@mozilla.org] @AB_CD@.jar:
 % locale webcompat-reporter @AB_CD@ %locale/@AB_CD@/
-  locale/@AB_CD@/                    (en-US/*)
+  locale/@AB_CD@/webcompat.properties (%webcompat.properties)
rename from browser/extensions/webcompat-reporter/locale/moz.build
rename to browser/extensions/webcompat-reporter/locales/moz.build
--- a/browser/extensions/webcompat-reporter/moz.build
+++ b/browser/extensions/webcompat-reporter/moz.build
@@ -2,17 +2,17 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
 DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
 
-DIRS += ['locale']
+DIRS += ['locales']
 
 FINAL_TARGET_FILES.features['webcompat-reporter@mozilla.org'] += [
   'bootstrap.js'
 ]
 
 FINAL_TARGET_PP_FILES.features['webcompat-reporter@mozilla.org'] += [
   'install.rdf.in'
 ]
--- a/browser/locales/Makefile.in
+++ b/browser/locales/Makefile.in
@@ -102,17 +102,17 @@ ifndef RELEASE_OR_BETA
 	@$(MAKE) -C ../extensions/presentation/locale AB_CD=$* XPI_NAME=locale-$*
 endif
 	@$(MAKE) -C ../../intl/locales AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) -C ../../devtools/client/locales AB_CD=$* XPI_NAME=locale-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)'
 	@$(MAKE) -B searchplugins AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) libs AB_CD=$* XPI_NAME=locale-$* PREF_DIR=$(PREF_DIR)
 	@$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales AB_CD=$* XPI_NAME=locale-$*
 ifdef NIGHTLY_BUILD
-	@$(MAKE) -C ../extensions/webcompat-reporter/locale AB_CD=$* XPI_NAME=locale-$*
+	@$(MAKE) -C ../extensions/webcompat-reporter/locales AB_CD=$* XPI_NAME=locale-$*
 endif
 
 repackage-win32-installer: WIN32_INSTALLER_OUT=$(ABS_DIST)/$(PKG_INST_PATH)$(PKG_INST_BASENAME).exe
 repackage-win32-installer: $(call ESCAPE_WILDCARD,$(WIN32_INSTALLER_IN)) $(SUBMAKEFILES) libs-$(AB_CD)
 	@echo 'Repackaging $(WIN32_INSTALLER_IN) into $(WIN32_INSTALLER_OUT).'
 	$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY) export
 	$(MAKE) -C ../installer/windows CONFIG_DIR=l10ngen l10ngen/setup.exe l10ngen/7zSD.sfx
 	$(MAKE) repackage-zip \
--- a/browser/locales/filter.py
+++ b/browser/locales/filter.py
@@ -3,16 +3,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 def test(mod, path, entity = None):
   import re
   # ignore anything but Firefox
   if mod not in ("netwerk", "dom", "toolkit", "security/manager",
                  "devtools/client", "devtools/shared",
                  "browser",
+                 "browser/extensions/webcompat-reporter",
                  "extensions/spellcheck",
                  "other-licenses/branding/firefox",
                  "browser/branding/official",
                  "services/sync"):
     return "ignore"
   if mod not in ("browser", "extensions/spellcheck"):
     # we only have exceptions for browser and extensions/spellcheck
     return "error"
--- a/browser/locales/l10n.ini
+++ b/browser/locales/l10n.ini
@@ -6,16 +6,17 @@
 depth = ../..
 all = browser/locales/all-locales
 
 [compare]
 dirs = browser
      other-licenses/branding/firefox
      browser/branding/official
      devtools/client
+     browser/extensions/webcompat-reporter
 
 [includes]
 # non-central apps might want to use %(topsrcdir)s here, or other vars
 # RFE: that needs to be supported by compare-locales, too, though
 toolkit = toolkit/locales/l10n.ini
 services_sync = services/sync/locales/l10n.ini
 
 [extras]
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -138,18 +138,22 @@ this.ExtensionsUI = {
       } else {
         info.permissions = perms;
         this.showPermissionsPrompt(target, info).then(reply);
       }
     } else if (topic == "webextension-update-permissions") {
       this.updates.add(subject.wrappedJSObject);
       this.emit("change");
     } else if (topic == "webextension-install-notify") {
-      let {target, addon} = subject.wrappedJSObject;
-      this.showInstallNotification(target, addon);
+      let {target, addon, callback} = subject.wrappedJSObject;
+      this.showInstallNotification(target, addon).then(() => {
+        if (callback) {
+          callback();
+        }
+      });
     }
   },
 
   // Escape &, <, and > characters in a string so that it may be
   // injected as part of raw markup.
   _sanitizeName(name) {
     return name.replace(/&/g, "&amp;")
                .replace(/</g, "&lt;")
@@ -325,34 +329,38 @@ this.ExtensionsUI = {
     let appName = brandBundle.getString("brandShortName");
 
     let bundle = win.gNavigatorBundle;
     let msg1 = bundle.getFormattedString("addonPostInstall.message1",
                                          [addonLabel, appName]);
     let msg2 = bundle.getFormattedString("addonPostInstall.message2",
                                          [addonLabel, addonIcon, toolbarIcon]);
 
-    let action = {
-      label: bundle.getString("addonPostInstall.okay.label"),
-      accessKey: bundle.getString("addonPostInstall.okay.key"),
-      callback: () => {},
-    };
+    return new Promise(resolve => {
+      let action = {
+        label: bundle.getString("addonPostInstall.okay.label"),
+        accessKey: bundle.getString("addonPostInstall.okay.key"),
+        callback: resolve,
+      };
 
-    let options = {
-      hideClose: true,
-      popupIconURL: addon.iconURL || DEFAULT_EXTENSION_ICON,
-      eventCallback(topic) {
-        if (topic == "showing") {
-          let doc = this.browser.ownerDocument;
-          doc.getElementById("addon-installed-notification-header")
-             .innerHTML = msg1;
-          doc.getElementById("addon-installed-notification-message")
-             .innerHTML = msg2;
+      let options = {
+        hideClose: true,
+        popupIconURL: addon.iconURL || DEFAULT_EXTENSION_ICON,
+        eventCallback(topic) {
+          if (topic == "showing") {
+            let doc = this.browser.ownerDocument;
+            doc.getElementById("addon-installed-notification-header")
+               .innerHTML = msg1;
+            doc.getElementById("addon-installed-notification-message")
+               .innerHTML = msg2;
+          } else if (topic == "dismissed") {
+            resolve();
+          }
         }
-      }
-    };
+      };
 
-    popups.show(target, "addon-installed", "", "addons-notification-icon",
-                action, null, options);
+      popups.show(target, "addon-installed", "", "addons-notification-icon",
+                  action, null, options);
+    });
   },
 };
 
 EventEmitter.decorate(ExtensionsUI);
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -39,17 +39,17 @@ treecol {
   min-height: 36px;
 }
 
 #selectionCol {
   min-width: 26px;
 }
 
 .learnMore {
-  margin-inline-start: 1.5em;
+  margin-inline-start: 10px;
   font-weight: normal;
   white-space: nowrap;
 }
 
 /* Category List */
 
 #categories {
   max-height: 100vh;
--- a/devtools/client/animationinspector/test/doc_keyframes.html
+++ b/devtools/client/animationinspector/test/doc_keyframes.html
@@ -1,16 +1,18 @@
 <!DOCTYPE html>
 <html lang="en">
 <head>
   <meta charset="UTF-8">
   <title>Yay! Keyframes!</title>
   <style>
     div {
       animation: wow 100s forwards;
+      /* Add a border here to avoid layout warnings in Linux debug builds: Bug 1329784 */
+      border: 1px solid transparent;
     }
     @keyframes wow {
       0% {
         width: 100px;
         height: 100px;
         border-radius: 0px;
         background: #f06;
       }
--- a/devtools/client/animationinspector/test/doc_multiple_animation_types.html
+++ b/devtools/client/animationinspector/test/doc_multiple_animation_types.html
@@ -1,16 +1,18 @@
 <!DOCTYPE html>
 <html lang="en">
 <head>
   <meta charset="UTF-8">
   <style>
     .ball {
       width: 80px;
       height: 80px;
+      /* Add a border here to avoid layout warnings in Linux debug builds: Bug 1329784 */
+      border: 1px solid transparent;
       border-radius: 50%;
     }
 
     .script-animation {
       background: #f06;
     }
 
     .css-transition {
--- a/devtools/client/animationinspector/test/doc_simple_animation.html
+++ b/devtools/client/animationinspector/test/doc_simple_animation.html
@@ -1,16 +1,18 @@
 <!DOCTYPE html>
 <html lang="en">
 <head>
   <meta charset="UTF-8">
   <style>
     .ball {
       width: 80px;
       height: 80px;
+      /* Add a border here to avoid layout warnings in Linux debug builds: Bug 1329784 */
+      border: 1px solid transparent;
       border-radius: 50%;
       background: #f06;
 
       position: absolute;
     }
 
     .still {
       top: 0;
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -8,16 +8,17 @@
 #include "AnimationUtils.h"
 #include "mozilla/dom/AnimationBinding.h"
 #include "mozilla/dom/AnimationPlaybackEvent.h"
 #include "mozilla/dom/DocumentTimeline.h"
 #include "mozilla/AnimationTarget.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/AsyncEventDispatcher.h" // For AsyncEventDispatcher
 #include "mozilla/Maybe.h" // For Maybe
+#include "mozilla/AnimationRule.h" // For AnimationRule
 #include "nsAnimationManager.h" // For CSSAnimation
 #include "nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch
 #include "nsIDocument.h" // For nsIDocument
 #include "nsIPresShell.h" // For nsIPresShell
 #include "nsThreadUtils.h" // For nsRunnableMethod and nsRevocableEventPtr
 #include "nsTransitionManager.h" // For CSSTransition
 #include "PendingAnimationTracker.h" // For PendingAnimationTracker
 
@@ -911,17 +912,17 @@ Animation::HasLowerCompositeOrderThan(co
              "Animation indices should be unique");
 
   // 3. Finally, generic animations sort by their position in the global
   // animation array.
   return mAnimationIndex < aOther.mAnimationIndex;
 }
 
 void
-Animation::ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
+Animation::ComposeStyle(AnimationRule& aStyleRule,
                         const nsCSSPropertyIDSet& aPropertiesToSkip)
 {
   if (!mEffect) {
     return;
   }
 
   // In order to prevent flicker, there are a few cases where we want to use
   // a different time for rendering that would otherwise be returned by
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -35,17 +35,17 @@
 
 struct JSContext;
 class nsCSSPropertyIDSet;
 class nsIDocument;
 class nsIFrame;
 
 namespace mozilla {
 
-class AnimValuesStyleRule;
+struct AnimationRule;
 
 namespace dom {
 
 class CSSAnimation;
 class CSSTransition;
 
 class Animation
   : public DOMEventTargetHelper
@@ -312,17 +312,17 @@ public:
    */
   bool CanThrottle() const;
   /**
    * Updates |aStyleRule| with the animation values of this animation's effect,
    * if any.
    * Any properties contained in |aPropertiesToSkip| will not be added or
    * updated in |aStyleRule|.
    */
-  void ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
+  void ComposeStyle(AnimationRule& aStyleRule,
                     const nsCSSPropertyIDSet& aPropertiesToSkip);
 
   void NotifyEffectTimingUpdated();
   void NotifyGeometricAnimationsStartingThisFrame();
 
   /**
    * Used by subclasses to synchronously queue a cancel event in situations
    * where the Animation may have been cancelled.
new file mode 100644
--- /dev/null
+++ b/dom/animation/AnimationRule.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_AnimationRule_h
+#define mozilla_AnimationRule_h
+
+#include "AnimValuesStyleRule.h"
+#include "ServoAnimationRule.h"
+
+namespace mozilla {
+
+// A wrapper for animation rules.
+struct AnimationRule
+{
+  RefPtr<AnimValuesStyleRule> mGecko;
+  RefPtr<ServoAnimationRule> mServo;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_AnimationRule_h
--- a/dom/animation/ComputedTiming.h
+++ b/dom/animation/ComputedTiming.h
@@ -59,22 +59,22 @@ struct ComputedTiming
   bool FillsBackwards() const {
     MOZ_ASSERT(mFill != dom::FillMode::Auto,
                "mFill should not be Auto in ComputedTiming.");
     return mFill == dom::FillMode::Both ||
            mFill == dom::FillMode::Backwards;
   }
 
   enum class AnimationPhase {
-    Null,   // Not sampled (null sample time)
+    Idle,   // Not sampled (null sample time)
     Before, // Sampled prior to the start of the active interval
     Active, // Sampled within the active interval
     After   // Sampled after (or at) the end of the active interval
   };
-  AnimationPhase      mPhase = AnimationPhase::Null;
+  AnimationPhase      mPhase = AnimationPhase::Idle;
 
   ComputedTimingFunction::BeforeFlag mBeforeFlag =
     ComputedTimingFunction::BeforeFlag::Unset;
 
 };
 
 } // namespace mozilla
 
--- a/dom/animation/DocumentTimeline.cpp
+++ b/dom/animation/DocumentTimeline.cpp
@@ -88,17 +88,17 @@ DocumentTimeline::GetCurrentTimeStamp() 
   // Always return the same object to benefit from return-value optimization.
   TimeStamp result = !refreshTime.IsNull()
                      ? refreshTime
                      : mLastRefreshDriverTime;
 
   // If we don't have a refresh driver and we've never had one use the
   // timeline's zero time.
   if (result.IsNull()) {
-    RefPtr<nsDOMNavigationTiming> timing = mDocument->GetNavigationTiming();
+    nsDOMNavigationTiming* timing = mDocument->GetNavigationTiming();
     if (timing) {
       result = timing->GetNavigationStartTimeStamp();
       // Also, let this time represent the current refresh time. This way
       // we'll save it as the last refresh time and skip looking up
       // navigation timing each time.
       refreshTime = result;
     }
   }
@@ -113,17 +113,17 @@ DocumentTimeline::GetCurrentTimeStamp() 
 Nullable<TimeDuration>
 DocumentTimeline::ToTimelineTime(const TimeStamp& aTimeStamp) const
 {
   Nullable<TimeDuration> result; // Initializes to null
   if (aTimeStamp.IsNull()) {
     return result;
   }
 
-  RefPtr<nsDOMNavigationTiming> timing = mDocument->GetNavigationTiming();
+  nsDOMNavigationTiming* timing = mDocument->GetNavigationTiming();
   if (MOZ_UNLIKELY(!timing)) {
     return result;
   }
 
   result.SetValue(aTimeStamp
                   - timing->GetNavigationStartTimeStamp()
                   - mOriginTime);
   return result;
--- a/dom/animation/EffectCompositor.cpp
+++ b/dom/animation/EffectCompositor.cpp
@@ -268,16 +268,18 @@ EffectCompositor::RequestRestyle(dom::El
     bool hasPendingRestyle = elementsToRestyle.Get(key);
     if (!hasPendingRestyle) {
       PostRestyleForAnimation(aElement, aPseudoType, aCascadeLevel);
     }
     elementsToRestyle.Put(key, true);
   }
 
   if (aRestyleType == RestyleType::Layer) {
+    // FIXME: we call RequestRestyle for both stylo and gecko, so we
+    // should fix this after supporting animations on the compositor.
     // Prompt layers to re-sync their animations.
     if (mPresContext->RestyleManager()->IsServo()) {
       NS_ERROR("stylo: Servo-backed style system should not be using "
                "EffectCompositor");
       return;
     }
     mPresContext->RestyleManager()->AsGecko()->IncrementAnimationGeneration();
     EffectSet* effectSet =
@@ -300,16 +302,22 @@ EffectCompositor::PostRestyleForAnimatio
   dom::Element* element = GetElementToRestyle(aElement, aPseudoType);
   if (!element) {
     return;
   }
 
   nsRestyleHint hint = aCascadeLevel == CascadeLevel::Transitions ?
                                         eRestyle_CSSTransitions :
                                         eRestyle_CSSAnimations;
+
+  // FIXME: stylo only supports Self and Subtree hints now, so we override it
+  // for stylo.
+  if (mPresContext->StyleSet()->IsServo()) {
+    hint = eRestyle_Self | eRestyle_Subtree;
+  }
   mPresContext->PresShell()->RestyleForAnimation(element, hint);
 }
 
 void
 EffectCompositor::PostRestyleForThrottledAnimations()
 {
   for (size_t i = 0; i < kCascadeLevelCount; i++) {
     CascadeLevel cascadeLevel = CascadeLevel(i);
@@ -415,17 +423,92 @@ EffectCompositor::GetAnimationRule(dom::
   }
 #endif
 
   EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType);
   if (!effectSet) {
     return nullptr;
   }
 
-  return effectSet->AnimationRule(aCascadeLevel);
+  return effectSet->AnimationRule(aCascadeLevel).mGecko;
+}
+
+namespace {
+  class EffectCompositeOrderComparator {
+  public:
+    bool Equals(const KeyframeEffectReadOnly* a,
+                const KeyframeEffectReadOnly* b) const
+    {
+      return a == b;
+    }
+
+    bool LessThan(const KeyframeEffectReadOnly* a,
+                  const KeyframeEffectReadOnly* b) const
+    {
+      MOZ_ASSERT(a->GetAnimation() && b->GetAnimation());
+      MOZ_ASSERT(
+        Equals(a, b) ||
+        a->GetAnimation()->HasLowerCompositeOrderThan(*b->GetAnimation()) !=
+          b->GetAnimation()->HasLowerCompositeOrderThan(*a->GetAnimation()));
+      return a->GetAnimation()->HasLowerCompositeOrderThan(*b->GetAnimation());
+    }
+  };
+}
+
+ServoAnimationRule*
+EffectCompositor::GetServoAnimationRule(const dom::Element* aElement,
+                                        CSSPseudoElementType aPseudoType,
+                                        CascadeLevel aCascadeLevel)
+{
+  if (!mPresContext || !mPresContext->IsDynamic()) {
+    // For print or print preview, ignore animations.
+    return nullptr;
+  }
+
+  EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType);
+  if (!effectSet) {
+    return nullptr;
+  }
+
+  // Get a list of effects sorted by composite order.
+  nsTArray<KeyframeEffectReadOnly*> sortedEffectList(effectSet->Count());
+  for (KeyframeEffectReadOnly* effect : *effectSet) {
+    sortedEffectList.AppendElement(effect);
+  }
+  sortedEffectList.Sort(EffectCompositeOrderComparator());
+
+  AnimationRule& animRule = effectSet->AnimationRule(aCascadeLevel);
+  animRule.mServo = nullptr;
+
+  // If multiple animations affect the same property, animations with higher
+  // composite order (priority) override or add or animations with lower
+  // priority.
+  // stylo: we don't support animations on compositor now, so propertiesToSkip
+  // is an empty set.
+  const nsCSSPropertyIDSet propertiesToSkip;
+  for (KeyframeEffectReadOnly* effect : sortedEffectList) {
+    effect->GetAnimation()->ComposeStyle(animRule, propertiesToSkip);
+  }
+
+  MOZ_ASSERT(effectSet == EffectSet::GetEffectSet(aElement, aPseudoType),
+             "EffectSet should not change while composing style");
+
+  effectSet->UpdateAnimationRuleRefreshTime(
+    aCascadeLevel, mPresContext->RefreshDriver()->MostRecentRefresh());
+  return animRule.mServo;
+}
+
+void
+EffectCompositor::ClearElementsToRestyle()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  const auto iterEnd = mElementsToRestyle.end();
+  for (auto iter = mElementsToRestyle.begin(); iter != iterEnd; ++iter) {
+    iter->Clear();
+  }
 }
 
 /* static */ dom::Element*
 EffectCompositor::GetElementToRestyle(dom::Element* aElement,
                                       CSSPseudoElementType aPseudoType)
 {
   if (aPseudoType == CSSPseudoElementType::NotPseudo) {
     return aElement;
@@ -586,38 +669,16 @@ EffectCompositor::MaybeUpdateCascadeResu
       }
     }
   }
   UpdateCascadeResults(*effects, aElement, aPseudoType, styleContext);
 
   MOZ_ASSERT(!effects->CascadeNeedsUpdate(), "Failed to update cascade state");
 }
 
-namespace {
-  class EffectCompositeOrderComparator {
-  public:
-    bool Equals(const KeyframeEffectReadOnly* a,
-                const KeyframeEffectReadOnly* b) const
-    {
-      return a == b;
-    }
-
-    bool LessThan(const KeyframeEffectReadOnly* a,
-                  const KeyframeEffectReadOnly* b) const
-    {
-      MOZ_ASSERT(a->GetAnimation() && b->GetAnimation());
-      MOZ_ASSERT(
-        Equals(a, b) ||
-        a->GetAnimation()->HasLowerCompositeOrderThan(*b->GetAnimation()) !=
-          b->GetAnimation()->HasLowerCompositeOrderThan(*a->GetAnimation()));
-      return a->GetAnimation()->HasLowerCompositeOrderThan(*b->GetAnimation());
-    }
-  };
-}
-
 /* static */ void
 EffectCompositor::UpdateCascadeResults(Element* aElement,
                                        CSSPseudoElementType aPseudoType,
                                        nsStyleContext* aStyleContext)
 {
   EffectSet* effects = EffectSet::GetEffectSet(aElement, aPseudoType);
   if (!effects) {
     return;
@@ -680,29 +741,28 @@ EffectCompositor::ComposeAnimationRule(d
 
   // Get a list of effects sorted by composite order.
   nsTArray<KeyframeEffectReadOnly*> sortedEffectList(effects->Count());
   for (KeyframeEffectReadOnly* effect : *effects) {
     sortedEffectList.AppendElement(effect);
   }
   sortedEffectList.Sort(EffectCompositeOrderComparator());
 
-  RefPtr<AnimValuesStyleRule>& animationRule =
-    effects->AnimationRule(aCascadeLevel);
-  animationRule = nullptr;
+  AnimationRule& animRule = effects->AnimationRule(aCascadeLevel);
+  animRule.mGecko = nullptr;
 
   // If multiple animations affect the same property, animations with higher
   // composite order (priority) override or add or animations with lower
   // priority except properties in propertiesToSkip.
   const nsCSSPropertyIDSet& propertiesToSkip =
     aCascadeLevel == CascadeLevel::Animations
     ? effects->PropertiesForAnimationsLevel().Invert()
     : effects->PropertiesForAnimationsLevel();
   for (KeyframeEffectReadOnly* effect : sortedEffectList) {
-    effect->GetAnimation()->ComposeStyle(animationRule, propertiesToSkip);
+    effect->GetAnimation()->ComposeStyle(animRule, propertiesToSkip);
   }
 
   MOZ_ASSERT(effects == EffectSet::GetEffectSet(aElement, aPseudoType),
              "EffectSet should not change while composing style");
 
   effects->UpdateAnimationRuleRefreshTime(aCascadeLevel, aRefreshTime);
 }
 
--- a/dom/animation/EffectCompositor.h
+++ b/dom/animation/EffectCompositor.h
@@ -24,16 +24,17 @@ class nsIStyleRule;
 class nsPresContext;
 class nsStyleContext;
 
 namespace mozilla {
 
 class EffectSet;
 class RestyleTracker;
 class StyleAnimationValue;
+class ServoAnimationRule;
 struct AnimationPerformanceWarning;
 struct AnimationProperty;
 struct NonOwningAnimationTarget;
 
 namespace dom {
 class Animation;
 class Element;
 }
@@ -54,22 +55,22 @@ public:
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(EffectCompositor)
   NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(EffectCompositor)
 
   void Disconnect() {
     mPresContext = nullptr;
   }
 
   // Animations can be applied at two different levels in the CSS cascade:
-  enum class CascadeLevel {
+  enum class CascadeLevel : uint32_t {
     // The animations sheet (CSS animations, script-generated animations,
     // and CSS transitions that are no longer tied to CSS markup)
-    Animations,
+    Animations = 0,
     // The transitions sheet (CSS transitions that are tied to CSS markup)
-    Transitions
+    Transitions = 1
   };
   // We don't define this as part of CascadeLevel as then we'd have to add
   // explicit checks for the Count enum value everywhere CascadeLevel is used.
   static const size_t kCascadeLevelCount =
     static_cast<size_t>(CascadeLevel::Transitions) + 1;
 
   // NOTE: This can return null after Disconnect().
   nsPresContext* PresContext() const { return mPresContext; }
@@ -145,16 +146,31 @@ public:
   // When we are not resolving style context, |aStyleContext| can be nullptr, we
   // will use a style context associated with the primary frame of the specified
   // (pseudo-)element.
   nsIStyleRule* GetAnimationRule(dom::Element* aElement,
                                  CSSPseudoElementType aPseudoType,
                                  CascadeLevel aCascadeLevel,
                                  nsStyleContext* aStyleContext);
 
+  // Get animation rule for stylo. This is an equivalent of GetAnimationRule
+  // and will be called from servo side. We need to be careful while doing any
+  // modification because it may case some thread-safe issues.
+  ServoAnimationRule* GetServoAnimationRule(const dom::Element* aElement,
+                                            CSSPseudoElementType aPseudoType,
+                                            CascadeLevel aCascadeLevel);
+
+  // Clear mElementsToRestyle hashtable. Unlike GetAnimationRule,
+  // in GetServoAnimationRule, we don't remove the entry of the composed
+  // animation, so we can prevent the thread-safe issues of dom::Element.
+  // Therefore, we need to call Clear mElementsToRestyle until we go back to
+  // Gecko side.
+  // FIXME: we shouldn't clear the animations on the compositor.
+  void ClearElementsToRestyle();
+
   bool HasPendingStyleUpdates() const;
   bool HasThrottledStyleUpdates() const;
 
   // Tell the restyle tracker about all the animated styles that have
   // pending updates so that it can update the animation rule for these
   // elements.
   void AddStyleUpdatesTo(RestyleTracker& aTracker);
 
--- a/dom/animation/EffectSet.cpp
+++ b/dom/animation/EffectSet.cpp
@@ -34,17 +34,17 @@ EffectSet::Traverse(nsCycleCollectionTra
 {
   for (auto iter = mEffects.Iter(); !iter.Done(); iter.Next()) {
     CycleCollectionNoteChild(aCallback, iter.Get()->GetKey(),
                              "EffectSet::mEffects[]", aCallback.Flags());
   }
 }
 
 /* static */ EffectSet*
-EffectSet::GetEffectSet(dom::Element* aElement,
+EffectSet::GetEffectSet(const dom::Element* aElement,
                         CSSPseudoElementType aPseudoType)
 {
   nsIAtom* propName = GetEffectSetPropertyAtom(aPseudoType);
   return static_cast<EffectSet*>(aElement->GetProperty(propName));
 }
 
 /* static */ EffectSet*
 EffectSet::GetEffectSet(const nsIFrame* aFrame)
--- a/dom/animation/EffectSet.h
+++ b/dom/animation/EffectSet.h
@@ -2,17 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_EffectSet_h
 #define mozilla_EffectSet_h
 
-#include "mozilla/AnimValuesStyleRule.h"
+#include "mozilla/AnimationRule.h" // For AnimationRule
 #include "mozilla/DebugOnly.h"
 #include "mozilla/EffectCompositor.h"
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/dom/KeyframeEffectReadOnly.h"
 #include "nsHashKeys.h" // For nsPtrHashKey
 #include "nsTHashtable.h" // For nsTHashtable
 
@@ -52,17 +52,17 @@ public:
     MOZ_COUNT_DTOR(EffectSet);
   }
   static void PropertyDtor(void* aObject, nsIAtom* aPropertyName,
                            void* aPropertyValue, void* aData);
 
   // Methods for supporting cycle-collection
   void Traverse(nsCycleCollectionTraversalCallback& aCallback);
 
-  static EffectSet* GetEffectSet(dom::Element* aElement,
+  static EffectSet* GetEffectSet(const dom::Element* aElement,
                                  CSSPseudoElementType aPseudoType);
   static EffectSet* GetEffectSet(const nsIFrame* aFrame);
   static EffectSet* GetOrCreateEffectSet(dom::Element* aElement,
                                          CSSPseudoElementType aPseudoType);
   static void DestroyEffectSet(dom::Element* aElement,
                                CSSPseudoElementType aPseudoType);
 
   void AddEffect(dom::KeyframeEffectReadOnly& aEffect);
@@ -158,18 +158,18 @@ public:
 #ifdef DEBUG
   bool IsBeingEnumerated() const { return mActiveIterators != 0; }
 #endif
 
   bool IsEmpty() const { return mEffects.IsEmpty(); }
 
   size_t Count() const { return mEffects.Count(); }
 
-  RefPtr<AnimValuesStyleRule>& AnimationRule(EffectCompositor::CascadeLevel
-                                             aCascadeLevel)
+  struct AnimationRule&
+  AnimationRule(EffectCompositor::CascadeLevel aCascadeLevel)
   {
     return mAnimationRule[aCascadeLevel];
   }
 
   const TimeStamp& AnimationRuleRefreshTime(EffectCompositor::CascadeLevel
                                               aCascadeLevel) const
   {
     return mAnimationRuleRefreshTime[aCascadeLevel];
@@ -227,17 +227,17 @@ private:
   // These style rules contain the style data for currently animating
   // values.  They only match when styling with animation.  When we
   // style without animation, we need to not use them so that we can
   // detect any new changes; if necessary we restyle immediately
   // afterwards with animation.
   EnumeratedArray<EffectCompositor::CascadeLevel,
                   EffectCompositor::CascadeLevel(
                     EffectCompositor::kCascadeLevelCount),
-                  RefPtr<AnimValuesStyleRule>> mAnimationRule;
+                  mozilla::AnimationRule> mAnimationRule;
 
   // A parallel array to mAnimationRule that records the refresh driver
   // timestamp when the rule was last updated. This is used for certain
   // animations which are updated only periodically (e.g. transform animations
   // running on the compositor that affect the scrollable overflow region).
   EnumeratedArray<EffectCompositor::CascadeLevel,
                   EffectCompositor::CascadeLevel(
                     EffectCompositor::kCascadeLevelCount),
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -5,16 +5,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/KeyframeEffectReadOnly.h"
 
 #include "mozilla/dom/KeyframeAnimationOptionsBinding.h"
   // For UnrestrictedDoubleOrKeyframeAnimationOptions;
 #include "mozilla/dom/CSSPseudoElement.h"
 #include "mozilla/dom/KeyframeEffectBinding.h"
+#include "mozilla/AnimationRule.h"
 #include "mozilla/AnimationUtils.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/EffectSet.h"
 #include "mozilla/FloatingPoint.h" // For IsFinite
 #include "mozilla/LookAndFeel.h" // For LookAndFeel::GetInt
 #include "mozilla/KeyframeUtils.h"
 #include "mozilla/ServoBindings.h"
 #include "mozilla/StyleAnimationValue.h"
@@ -459,17 +460,17 @@ KeyframeEffectReadOnly::EnsureBaseStyles
       SetNeedsBaseStyle(property.mProperty);
       break;
     }
   }
 }
 
 void
 KeyframeEffectReadOnly::ComposeStyle(
-  RefPtr<AnimValuesStyleRule>& aStyleRule,
+  AnimationRule& aStyleRule,
   const nsCSSPropertyIDSet& aPropertiesToSkip)
 {
   MOZ_DIAGNOSTIC_ASSERT(!mIsComposingStyle,
                         "Should not be called recursively");
   if (mIsComposingStyle) {
     return;
   }
 
@@ -491,16 +492,19 @@ KeyframeEffectReadOnly::ComposeStyle(
 
     // Note, however, that we don't actually send animations with a negative
     // playback rate in their end delay phase to the compositor at this stage
     // (bug 1330498).
     EnsureBaseStylesForCompositor(aPropertiesToSkip);
     return;
   }
 
+  nsPresContext* presContext = GetPresContext();
+  bool isServoBackend = presContext && presContext->StyleSet()->IsServo();
+
   mNeedsBaseStyleSet.Empty();
 
   for (size_t propIdx = 0, propEnd = mProperties.Length();
        propIdx != propEnd; ++propIdx)
   {
     const AnimationProperty& prop = mProperties[propIdx];
 
     MOZ_ASSERT(prop.mSegments[0].mFromKey == 0.0, "incorrect first from key");
@@ -526,82 +530,141 @@ KeyframeEffectReadOnly::ComposeStyle(
       MOZ_ASSERT(segment->mFromKey == (segment-1)->mToKey, "incorrect keys");
     }
     MOZ_ASSERT(segment->mFromKey <= segment->mToKey, "incorrect keys");
     MOZ_ASSERT(segment >= prop.mSegments.Elements() &&
                size_t(segment - prop.mSegments.Elements()) <
                  prop.mSegments.Length(),
                "out of array bounds");
 
-    if (!aStyleRule) {
-      // Allocate the style rule now that we know we have animation data.
-      aStyleRule = new AnimValuesStyleRule();
-    }
+    // Bug 1333311 - We use two branches for Gecko and Stylo. However, it's
+    // better to remove the duplicated code.
+    if (isServoBackend) {
+      // Servo backend
+
+      // Bug 1329878 - Stylo: Implement accumulate and addition on Servo
+      // AnimationValue.
+      RawServoAnimationValue* servoFromValue = segment->mServoFromValue;
+      RawServoAnimationValue* servoToValue = segment->mServoToValue;
+
+      // For unsupported or non-animatable animation types, we get nullptrs.
+      if (!servoFromValue || !servoToValue) {
+        NS_ERROR("Compose style for unsupported or non-animatable property, "
+                 "so get invalid RawServoAnimationValues");
+        continue;
+      }
+
+      if (!aStyleRule.mServo) {
+        // Allocate the style rule now that we know we have animation data.
+        aStyleRule.mServo = new ServoAnimationRule();
+      }
+
+      // Special handling for zero-length segments
+      if (segment->mToKey == segment->mFromKey) {
+        if (computedTiming.mProgress.Value() < 0) {
+          aStyleRule.mServo->AddValue(prop.mProperty, servoFromValue);
+        } else {
+          aStyleRule.mServo->AddValue(prop.mProperty, servoToValue);
+        }
+        continue;
+      }
 
-    StyleAnimationValue fromValue =
-      CompositeValue(prop.mProperty, aStyleRule,
-                     segment->mFromValue,
-                     segment->mFromComposite);
-    StyleAnimationValue toValue =
-      CompositeValue(prop.mProperty, aStyleRule,
-                     segment->mToValue,
-                     segment->mToComposite);
+      double positionInSegment =
+        (computedTiming.mProgress.Value() - segment->mFromKey) /
+        (segment->mToKey - segment->mFromKey);
+      double valuePosition =
+        ComputedTimingFunction::GetPortion(segment->mTimingFunction,
+                                           positionInSegment,
+                                           computedTiming.mBeforeFlag);
+
+      MOZ_ASSERT(IsFinite(valuePosition), "Position value should be finite");
+
+      RefPtr<RawServoAnimationValue> interpolated =
+        Servo_AnimationValues_Interpolate(servoFromValue,
+                                          servoToValue,
+                                          valuePosition).Consume();
 
-    // Iteration composition for accumulate
-    if (mEffectOptions.mIterationComposite ==
+      if (interpolated) {
+        aStyleRule.mServo->AddValue(prop.mProperty, interpolated);
+      } else if (valuePosition < 0.5) {
+        aStyleRule.mServo->AddValue(prop.mProperty, servoFromValue);
+      } else {
+        aStyleRule.mServo->AddValue(prop.mProperty, servoToValue);
+      }
+    } else {
+      // Gecko backend
+
+      if (!aStyleRule.mGecko) {
+        // Allocate the style rule now that we know we have animation data.
+        aStyleRule.mGecko = new AnimValuesStyleRule();
+      }
+
+      StyleAnimationValue fromValue =
+        CompositeValue(prop.mProperty, aStyleRule.mGecko,
+                       segment->mFromValue,
+                       segment->mFromComposite);
+      StyleAnimationValue toValue =
+        CompositeValue(prop.mProperty, aStyleRule.mGecko,
+                       segment->mToValue,
+                       segment->mToComposite);
+
+      // Iteration composition for accumulate
+      if (mEffectOptions.mIterationComposite ==
           IterationCompositeOperation::Accumulate &&
-        computedTiming.mCurrentIteration > 0) {
-      const AnimationPropertySegment& lastSegment =
-        prop.mSegments.LastElement();
-      // FIXME: Bug 1293492: Add a utility function to calculate both of
-      // below StyleAnimationValues.
-      StyleAnimationValue lastValue = lastSegment.mToValue.IsNull()
-        ? GetUnderlyingStyle(prop.mProperty, aStyleRule)
-        : lastSegment.mToValue;
-      fromValue =
-        StyleAnimationValue::Accumulate(prop.mProperty,
-                                        lastValue,
-                                        Move(fromValue),
-                                        computedTiming.mCurrentIteration);
-      toValue =
-        StyleAnimationValue::Accumulate(prop.mProperty,
-                                        lastValue,
-                                        Move(toValue),
-                                        computedTiming.mCurrentIteration);
-    }
+          computedTiming.mCurrentIteration > 0) {
+        const AnimationPropertySegment& lastSegment =
+          prop.mSegments.LastElement();
+        // FIXME: Bug 1293492: Add a utility function to calculate both of
+        // below StyleAnimationValues.
+        StyleAnimationValue lastValue = lastSegment.mToValue.IsNull()
+          ? GetUnderlyingStyle(prop.mProperty, aStyleRule.mGecko)
+          : lastSegment.mToValue;
+        fromValue =
+          StyleAnimationValue::Accumulate(prop.mProperty,
+                                          lastValue,
+                                          Move(fromValue),
+                                          computedTiming.mCurrentIteration);
+        toValue =
+          StyleAnimationValue::Accumulate(prop.mProperty,
+                                          lastValue,
+                                          Move(toValue),
+                                          computedTiming.mCurrentIteration);
+      }
 
-    // Special handling for zero-length segments
-    if (segment->mToKey == segment->mFromKey) {
-      if (computedTiming.mProgress.Value() < 0) {
-        aStyleRule->AddValue(prop.mProperty, Move(fromValue));
+      // Special handling for zero-length segments
+      if (segment->mToKey == segment->mFromKey) {
+        if (computedTiming.mProgress.Value() < 0) {
+          aStyleRule.mGecko->AddValue(prop.mProperty, Move(fromValue));
+        } else {
+          aStyleRule.mGecko->AddValue(prop.mProperty, Move(toValue));
+        }
+        continue;
+      }
+
+      double positionInSegment =
+        (computedTiming.mProgress.Value() - segment->mFromKey) /
+        (segment->mToKey - segment->mFromKey);
+      double valuePosition =
+        ComputedTimingFunction::GetPortion(segment->mTimingFunction,
+                                           positionInSegment,
+                                           computedTiming.mBeforeFlag);
+
+      MOZ_ASSERT(IsFinite(valuePosition), "Position value should be finite");
+
+      StyleAnimationValue val;
+      if (StyleAnimationValue::Interpolate(prop.mProperty,
+                                           fromValue,
+                                           toValue,
+                                           valuePosition, val)) {
+        aStyleRule.mGecko->AddValue(prop.mProperty, Move(val));
+      } else if (valuePosition < 0.5) {
+        aStyleRule.mGecko->AddValue(prop.mProperty, Move(fromValue));
       } else {
-        aStyleRule->AddValue(prop.mProperty, Move(toValue));
+        aStyleRule.mGecko->AddValue(prop.mProperty, Move(toValue));
       }
-      continue;
-    }
-
-    double positionInSegment =
-      (computedTiming.mProgress.Value() - segment->mFromKey) /
-      (segment->mToKey - segment->mFromKey);
-    double valuePosition =
-      ComputedTimingFunction::GetPortion(segment->mTimingFunction,
-                                         positionInSegment,
-                                         computedTiming.mBeforeFlag);
-
-    MOZ_ASSERT(IsFinite(valuePosition), "Position value should be finite");
-    StyleAnimationValue val;
-    if (StyleAnimationValue::Interpolate(prop.mProperty,
-                                         fromValue,
-                                         toValue,
-                                         valuePosition, val)) {
-      aStyleRule->AddValue(prop.mProperty, Move(val));
-    } else if (valuePosition < 0.5) {
-      aStyleRule->AddValue(prop.mProperty, Move(fromValue));
-    } else {
-      aStyleRule->AddValue(prop.mProperty, Move(toValue));
     }
   }
 
   // For properties that can be run on the compositor, we may need to prepare
   // base styles to send to the compositor even if the current processing
   // segment for properties does not have either an additive or accumulative
   // composite mode, and even if the animation is not in-effect. That's because
   // the animation may later progress to a segment which has an additive or
--- a/dom/animation/KeyframeEffectReadOnly.h
+++ b/dom/animation/KeyframeEffectReadOnly.h
@@ -35,16 +35,17 @@ class nsIFrame;
 class nsIPresShell;
 class nsPresContext;
 
 namespace mozilla {
 
 class AnimValuesStyleRule;
 enum class CSSPseudoElementType : uint8_t;
 class ErrorResult;
+struct AnimationRule;
 struct TimingParams;
 
 namespace dom {
 class ElementOrCSSPseudoElement;
 class GlobalObject;
 class OwningElementOrCSSPseudoElement;
 class UnrestrictedDoubleOrKeyframeAnimationOptions;
 class UnrestrictedDoubleOrKeyframeEffectOptions;
@@ -286,17 +287,17 @@ public:
 
   // Update |mProperties| by recalculating from |mKeyframes| using
   // |aStyleContext| to resolve specified values.
   void UpdateProperties(nsStyleContext* aStyleContext);
 
   // Updates |aStyleRule| with the animation values produced by this
   // AnimationEffect for the current time except any properties contained
   // in |aPropertiesToSkip|.
-  void ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
+  void ComposeStyle(AnimationRule& aStyleRule,
                     const nsCSSPropertyIDSet& aPropertiesToSkip);
 
   // Composite |aValueToComposite| on |aUnderlyingValue| with
   // |aCompositeOperation|.
   // Returns |aValueToComposite| if |aCompositeOperation| is Replace.
   static StyleAnimationValue CompositeValue(
     nsCSSPropertyID aProperty,
     const StyleAnimationValue& aValueToComposite,
new file mode 100644
--- /dev/null
+++ b/dom/animation/ServoAnimationRule.cpp
@@ -0,0 +1,32 @@
+/* -*- 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 "ServoAnimationRule.h"
+
+namespace mozilla {
+
+void
+ServoAnimationRule::AddValue(nsCSSPropertyID aProperty,
+                             RawServoAnimationValue* aValue)
+{
+  MOZ_ASSERT(aProperty != eCSSProperty_UNKNOWN,
+             "Unexpected css property");
+  mAnimationValues.Put(aProperty, aValue);
+}
+
+RawServoDeclarationBlockStrong
+ServoAnimationRule::GetValues() const
+{
+  // FIXME: Pass the hash table into the FFI directly.
+  nsTArray<const RawServoAnimationValue*> values(mAnimationValues.Count());
+  auto iter = mAnimationValues.ConstIter();
+  for (; !iter.Done(); iter.Next()) {
+    values.AppendElement(iter.Data());
+  }
+  return Servo_AnimationValues_Uncompute(&values);
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/animation/ServoAnimationRule.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ServoAnimationRule_h
+#define mozilla_ServoAnimationRule_h
+
+#include "nsCSSPropertyID.h"
+#include "nsHashKeys.h" // For nsUint32HashKey
+#include "nsRefPtrHashtable.h"
+#include "ServoBindings.h"
+
+namespace mozilla {
+
+/**
+ * A rule for Stylo Animation Rule.
+ */
+class ServoAnimationRule
+{
+public:
+  ServoAnimationRule() = default;
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ServoAnimationRule)
+
+  void AddValue(nsCSSPropertyID aProperty,
+                RawServoAnimationValue* aValue);
+  MOZ_MUST_USE RawServoDeclarationBlockStrong GetValues() const;
+
+private:
+  ~ServoAnimationRule() = default;
+
+  nsRefPtrHashtable<nsUint32HashKey, RawServoAnimationValue> mAnimationValues;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ServoAnimationRule_h
--- a/dom/animation/moz.build
+++ b/dom/animation/moz.build
@@ -17,27 +17,29 @@ EXPORTS.mozilla.dom += [
     'DocumentTimeline.h',
     'KeyframeEffect.h',
     'KeyframeEffectReadOnly.h',
 ]
 
 EXPORTS.mozilla += [
     'AnimationComparator.h',
     'AnimationPerformanceWarning.h',
+    'AnimationRule.h',
     'AnimationTarget.h',
     'AnimationUtils.h',
     'AnimValuesStyleRule.h',
     'ComputedTiming.h',
     'ComputedTimingFunction.h',
     'EffectCompositor.h',
     'EffectSet.h',
     'KeyframeEffectParams.h',
     'KeyframeUtils.h',
     'PendingAnimationTracker.h',
     'PseudoElementHashEntry.h',
+    'ServoAnimationRule.h',
     'TimingParams.h',
 ]
 
 UNIFIED_SOURCES += [
     'Animation.cpp',
     'AnimationEffectReadOnly.cpp',
     'AnimationEffectTiming.cpp',
     'AnimationEffectTimingReadOnly.cpp',
@@ -50,16 +52,17 @@ UNIFIED_SOURCES += [
     'DocumentTimeline.cpp',
     'EffectCompositor.cpp',
     'EffectSet.cpp',
     'KeyframeEffect.cpp',
     'KeyframeEffectParams.cpp',
     'KeyframeEffectReadOnly.cpp',
     'KeyframeUtils.cpp',
     'PendingAnimationTracker.cpp',
+    'ServoAnimationRule.cpp',
     'TimingParams.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/dom/base',
     '/layout/base',
     '/layout/style',
 ]
--- a/dom/animation/test/crashtests/crashtests.list
+++ b/dom/animation/test/crashtests/crashtests.list
@@ -1,23 +1,23 @@
 pref(dom.animations-api.core.enabled,true) load 1239889-1.html
 asserts-if(stylo,3) pref(dom.animations-api.core.enabled,true) load 1244595-1.html # bug 1324696
 asserts-if(stylo,3-10) pref(dom.animations-api.core.enabled,true) load 1216842-1.html # bug 1324695
 asserts-if(stylo,3-10) pref(dom.animations-api.core.enabled,true) load 1216842-2.html # bug 1324695
 asserts-if(stylo,3) pref(dom.animations-api.core.enabled,true) load 1216842-3.html # bug 1324691
 asserts-if(stylo,3) pref(dom.animations-api.core.enabled,true) load 1216842-4.html # bug 1324691
 asserts-if(stylo,4-10) pref(dom.animations-api.core.enabled,true) load 1216842-5.html # bug 1324693
 asserts-if(stylo,4-10) pref(dom.animations-api.core.enabled,true) load 1216842-6.html # bug 1324693
-asserts-if(stylo,3-10) pref(dom.animations-api.core.enabled,true) load 1272475-1.html # bug 1324693
-asserts-if(stylo,4-10) pref(dom.animations-api.core.enabled,true) load 1272475-2.html # bug 1324693
+skip-if(stylo) pref(dom.animations-api.core.enabled,true) load 1272475-1.html # bug 1324693 and bug 1332657
+skip-if(stylo) pref(dom.animations-api.core.enabled,true) load 1272475-2.html # bug 1324693 and bug 1332657
 asserts-if(stylo,5) pref(dom.animations-api.core.enabled,true) load 1278485-1.html # bug 1324691
 asserts-if(stylo,31) pref(dom.animations-api.core.enabled,true) load 1277272-1.html # bug 1324694
 asserts-if(stylo,2) pref(dom.animations-api.core.enabled,true) load 1290535-1.html # bug 1324690
 pref(dom.animations-api.core.enabled,true) load 1304886-1.html
 pref(dom.animations-api.core.enabled,true) load 1322382-1.html
 skip-if(stylo) pref(dom.animations-api.core.enabled,true) load 1322291-1.html # bug 1311257
-skip-if(stylo) pref(dom.animations-api.core.enabled,true) load 1322291-2.html # bug 1311257
-asserts-if(stylo,0-5) pref(dom.animations-api.core.enabled,true) load 1323114-1.html # bug 1324690
-asserts-if(stylo,0-5) pref(dom.animations-api.core.enabled,true) load 1323114-2.html # bug 1324690
+skip-if(stylo) pref(dom.animations-api.core.enabled,true) load 1322291-2.html # bug 1311257 and bug 1311257
+skip-if(stylo) pref(dom.animations-api.core.enabled,true) load 1323114-1.html # bug 1324690 and bug 1311257
+skip-if(stylo) pref(dom.animations-api.core.enabled,true) load 1323114-2.html # bug 1324690
 skip-if(stylo) pref(dom.animations-api.core.enabled,true) load 1325193-1.html # bug 1311257
 skip-if(stylo) pref(dom.animations-api.core.enabled,true) load 1330190-1.html # bug 1311257
 skip-if(stylo) pref(dom.animations-api.core.enabled,true) load 1330190-2.html # bug 1311257
 skip-if(stylo) pref(dom.animations-api.core.enabled,true) load 1330513-1.html # bug 1311257
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -2379,26 +2379,27 @@ InitializeLegacyNetscapeObject(JSContext
   /* Define PrivilegeManager object with the necessary "static" methods. */
   obj = JS_DefineObject(aCx, obj, "PrivilegeManager", nullptr);
   NS_ENSURE_TRUE(obj, false);
 
   return JS_DefineFunctions(aCx, obj, EnablePrivilegeSpec);
 }
 
 bool
-nsGlobalWindow::ComputeIsSecureContext(nsIDocument* aDocument)
+nsGlobalWindow::ComputeIsSecureContext(nsIDocument* aDocument, SecureContextFlags aFlags)
 {
   MOZ_ASSERT(IsOuterWindow());
 
   nsCOMPtr<nsIPrincipal> principal = aDocument->NodePrincipal();
   if (nsContentUtils::IsSystemPrincipal(principal)) {
     return true;
   }
 
   // Implement https://w3c.github.io/webappsec-secure-contexts/#settings-object
+  // With some modifications to allow for aFlags.
 
   bool hadNonSecureContextCreator = false;
 
   nsPIDOMWindowOuter* parentOuterWin = GetScriptableParent();
   MOZ_ASSERT(parentOuterWin, "How can we get here? No docShell somehow?");
   if (nsGlobalWindow::Cast(parentOuterWin) != this) {
     // There may be a small chance that parentOuterWin has navigated in
     // the time that it took us to start loading this sub-document.  If that
@@ -2414,19 +2415,25 @@ nsGlobalWindow::ComputeIsSecureContext(n
     nsGlobalWindow* parentWin =
       nsGlobalWindow::Cast(creatorDoc->GetInnerWindow());
     if (!parentWin) {
       return false; // we must be tearing down
     }
     MOZ_ASSERT(parentWin ==
                nsGlobalWindow::Cast(parentOuterWin->GetCurrentInnerWindow()),
                "Creator window mismatch while setting Secure Context state");
-    hadNonSecureContextCreator = !parentWin->IsSecureContext();
+    if (aFlags != SecureContextFlags::eIgnoreOpener) {
+      hadNonSecureContextCreator = !parentWin->IsSecureContext();
+    } else {
+      hadNonSecureContextCreator = !parentWin->IsSecureContextIfOpenerIgnored();
+    }
   } else if (mHadOriginalOpener) {
-    hadNonSecureContextCreator = !mOriginalOpenerWasSecureContext;
+    if (aFlags != SecureContextFlags::eIgnoreOpener) {
+      hadNonSecureContextCreator = !mOriginalOpenerWasSecureContext;
+    }
   }
 
   if (hadNonSecureContextCreator) {
     return false;
   }
 
   if (nsContentUtils::HttpsStateIsModern(aDocument)) {
     return true;
@@ -2721,16 +2728,18 @@ nsGlobalWindow::SetNewDocument(nsIDocume
       rv = CreateNativeGlobalForInner(cx, newInnerWindow,
                                       aDocument->GetDocumentURI(),
                                       aDocument->NodePrincipal(),
                                       &newInnerGlobal,
                                       ComputeIsSecureContext(aDocument));
       NS_ASSERTION(NS_SUCCEEDED(rv) && newInnerGlobal &&
                    newInnerWindow->GetWrapperPreserveColor() == newInnerGlobal,
                    "Failed to get script global");
+      newInnerWindow->mIsSecureContextIfOpenerIgnored =
+        ComputeIsSecureContext(aDocument, SecureContextFlags::eIgnoreOpener);
 
       mCreatingInnerWindow = false;
       createdInnerWindow = true;
 
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     if (currentInner && currentInner->GetWrapperPreserveColor()) {
@@ -3862,16 +3871,22 @@ nsPIDOMWindowInner::CreatePerformanceObj
 }
 
 bool
 nsPIDOMWindowInner::IsSecureContext() const
 {
   return nsGlobalWindow::Cast(this)->IsSecureContext();
 }
 
+bool
+nsPIDOMWindowInner::IsSecureContextIfOpenerIgnored() const
+{
+  return nsGlobalWindow::Cast(this)->IsSecureContextIfOpenerIgnored();
+}
+
 void
 nsPIDOMWindowInner::Suspend()
 {
   nsGlobalWindow::Cast(this)->Suspend();
 }
 
 void
 nsPIDOMWindowInner::Resume()
@@ -13822,16 +13837,24 @@ nsGlobalWindow::GetConsole(ErrorResult& 
 bool
 nsGlobalWindow::IsSecureContext() const
 {
   MOZ_RELEASE_ASSERT(IsInnerWindow());
 
   return JS_GetIsSecureContext(js::GetObjectCompartment(GetWrapperPreserveColor()));
 }
 
+bool
+nsGlobalWindow::IsSecureContextIfOpenerIgnored() const
+{
+  MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+  return mIsSecureContextIfOpenerIgnored;
+}
+
 already_AddRefed<External>
 nsGlobalWindow::GetExternal(ErrorResult& aRv)
 {
   MOZ_RELEASE_ASSERT(IsInnerWindow());
 
 #ifdef HAVE_SIDEBAR
   if (!mExternal) {
     AutoJSContext cx;
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -906,16 +906,17 @@ public:
 #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
   int16_t Orientation(mozilla::dom::CallerType aCallerType) const;
 #endif
 
   mozilla::dom::Console* GetConsole(mozilla::ErrorResult& aRv);
 
   // https://w3c.github.io/webappsec-secure-contexts/#dom-window-issecurecontext
   bool IsSecureContext() const;
+  bool IsSecureContextIfOpenerIgnored() const;
 
   void GetSidebar(mozilla::dom::OwningExternalOrWindowProxy& aResult,
                   mozilla::ErrorResult& aRv);
   already_AddRefed<mozilla::dom::External> GetExternal(mozilla::ErrorResult& aRv);
 
   // Exposed only for testing
   static bool
   TokenizeDialogOptions(nsAString& aToken, nsAString::const_iterator& aIter,
@@ -1748,19 +1749,26 @@ protected:
   void CheckForDPIChange();
 
 private:
   // Fire the JS engine's onNewGlobalObject hook.  Only used on inner windows.
   void FireOnNewGlobalObject();
 
   void DisconnectEventTargetObjects();
 
+
+  enum class SecureContextFlags {
+    eDefault,
+    eIgnoreOpener
+  };
   // Called only on outer windows to compute the value that will be returned by
   // IsSecureContext() for the inner window that corresponds to aDocument.
-  bool ComputeIsSecureContext(nsIDocument* aDocument);
+  bool ComputeIsSecureContext(nsIDocument* aDocument,
+                              SecureContextFlags aFlags =
+                                SecureContextFlags::eDefault);
 
   // nsPIDOMWindow<T> should be able to see these helper methods.
   friend class nsPIDOMWindow<mozIDOMWindowProxy>;
   friend class nsPIDOMWindow<mozIDOMWindow>;
   friend class nsPIDOMWindow<nsISupports>;
 
   mozilla::dom::TabGroup* TabGroupInner();
   mozilla::dom::TabGroup* TabGroupOuter();
@@ -1787,16 +1795,17 @@ protected:
   bool                          mIsClosed : 1;
   bool                          mInClose : 1;
   // mHavePendingClose means we've got a termination function set to
   // close us when the JS stops executing or that we have a close
   // event posted.  If this is set, just ignore window.close() calls.
   bool                          mHavePendingClose : 1;
   bool                          mHadOriginalOpener : 1;
   bool                          mOriginalOpenerWasSecureContext : 1;
+  bool                          mIsSecureContextIfOpenerIgnored : 1;
   bool                          mIsPopupSpam : 1;
 
   // Indicates whether scripts are allowed to close this window.
   bool                          mBlockScriptedClosingFlag : 1;
 
   // Window offline status. Checked to see if we need to fire offline event
   bool                          mWasOffline : 1;
 
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -846,16 +846,17 @@ public:
   {
     return mInnerObjectsFreed;
   }
 
   /**
    * Check whether this window is a secure context.
    */
   bool IsSecureContext() const;
+  bool IsSecureContextIfOpenerIgnored() const;
 
   // Calling suspend should prevent any asynchronous tasks from
   // executing javascript for this window.  This means setTimeout,
   // requestAnimationFrame, and events should not be fired. Suspending
   // a window also suspends its children and workers.  Workers may
   // continue to perform computations in the background.  A window
   // can have Suspend() called multiple times and will only resume after
   // a matching number of Resume() calls.
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -1032,21 +1032,28 @@ class RTCPeerConnection {
 
   async _addIceCandidate({ candidate, sdpMid, sdpMLineIndex }) {
     this._checkClosed();
     if (sdpMid === null && sdpMLineIndex === null) {
       throw new this._win.DOMException(
           "Invalid candidate (both sdpMid and sdpMLineIndex are null).",
           "TypeError");
     }
-    return await this._chain(() => new Promise((resolve, reject) => {
-      this._onAddIceCandidateSuccess = resolve;
-      this._onAddIceCandidateError = reject;
-      this._impl.addIceCandidate(candidate, sdpMid || "", sdpMLineIndex);
-    }));
+    return await this._chain(() => {
+      if (!this.remoteDescription) {
+        throw new this._win.DOMException(
+            "setRemoteDescription needs to called before addIceCandidate",
+            "InvalidStateError");
+      }
+      return new Promise((resolve, reject) => {
+        this._onAddIceCandidateSuccess = resolve;
+        this._onAddIceCandidateError = reject;
+        this._impl.addIceCandidate(candidate, sdpMid || "", sdpMLineIndex);
+      });
+    });
   }
 
   addStream(stream) {
     stream.getTracks().forEach(track => this.addTrack(track, stream));
   }
 
   addTrack(track, stream) {
     if (stream.currentTime === undefined) {
--- a/dom/media/encoder/TrackEncoder.cpp
+++ b/dom/media/encoder/TrackEncoder.cpp
@@ -312,21 +312,23 @@ VideoTrackEncoder::AppendVideoSegment(co
       chunk.mDuration = 0;
 
       TRACK_LOG(LogLevel::Verbose,
                 ("[VideoTrackEncoder]: Got first video chunk after %lld ticks.",
                  nullDuration));
       // Adapt to the time before the first frame. This extends the first frame
       // from [start, end] to [0, end], but it'll do for now.
       CheckedInt64 diff = FramesToUsecs(nullDuration, mTrackRate);
-      MOZ_ASSERT(diff.isValid());
-      if (diff.isValid()) {
-        mLastChunk.mTimeStamp -= TimeDuration::FromMicroseconds(diff.value());
-        mLastChunk.mDuration += nullDuration;
+      if (!diff.isValid()) {
+        NS_ERROR("null duration overflow");
+        return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
       }
+
+      mLastChunk.mTimeStamp -= TimeDuration::FromMicroseconds(diff.value());
+      mLastChunk.mDuration += nullDuration;
     }
 
     MOZ_ASSERT(!mLastChunk.IsNull());
     if (mLastChunk.CanCombineWithFollowing(chunk) || chunk.IsNull()) {
       TRACK_LOG(LogLevel::Verbose,
                 ("[VideoTrackEncoder]: Got dupe or null chunk."));
       // This is the same frame as before (or null). We extend the last chunk
       // with its duration.
@@ -352,35 +354,51 @@ VideoTrackEncoder::AppendVideoSegment(co
 
       if (chunk.IsNull()) {
         // Ensure that we don't pass null to the encoder by making mLastChunk
         // null later on.
         chunk.mFrame = mLastChunk.mFrame;
       }
     }
 
-    TimeDuration diff = chunk.mTimeStamp - mLastChunk.mTimeStamp;
-    if (diff <= TimeDuration::FromSeconds(0)) {
-      // The timestamp from mLastChunk is newer than from chunk.
-      // This means the durations reported from MediaStreamGraph for mLastChunk
-      // were larger than the timestamp diff - and durations were used to
-      // trigger the 1-second frame above. This could happen due to drift or
-      // underruns in the graph.
-      TRACK_LOG(LogLevel::Warning,
-                ("[VideoTrackEncoder]: Underrun detected. Diff=%.5fs",
-                 diff.ToSeconds()));
-      chunk.mTimeStamp = mLastChunk.mTimeStamp;
-    } else {
-      RefPtr<layers::Image> lastImage = mLastChunk.mFrame.GetImage();
-      TRACK_LOG(LogLevel::Verbose,
-                ("[VideoTrackEncoder]: Appending video frame %p, duration=%.5f",
-                 lastImage.get(), diff.ToSeconds()));
-      CheckedInt64 duration = UsecsToFrames(diff.ToMicroseconds(), mTrackRate);
-      MOZ_ASSERT(duration.isValid());
-      if (duration.isValid()) {
+    if (mStartOffset.IsNull()) {
+      mStartOffset = mLastChunk.mTimeStamp;
+    }
+
+    TimeDuration relativeTime = chunk.mTimeStamp - mStartOffset;
+    RefPtr<layers::Image> lastImage = mLastChunk.mFrame.GetImage();
+    TRACK_LOG(LogLevel::Verbose,
+              ("[VideoTrackEncoder]: Appending video frame %p, at pos %.5fs",
+               lastImage.get(), relativeTime.ToSeconds()));
+    CheckedInt64 totalDuration =
+      UsecsToFrames(relativeTime.ToMicroseconds(), mTrackRate);
+    if (!totalDuration.isValid()) {
+      NS_ERROR("Duration overflow");
+      return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
+    }
+
+    CheckedInt64 duration = totalDuration - mEncodedTicks;
+    if (!duration.isValid()) {
+      NS_ERROR("Duration overflow");
+      return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
+    }
+
+    if (duration.isValid()) {
+      if (duration.value() <= 0) {
+        // The timestamp for mLastChunk is newer than for chunk.
+        // This means the durations reported from MediaStreamGraph for
+        // mLastChunk were larger than the timestamp diff - and durations were
+        // used to trigger the 1-second frame above. This could happen due to
+        // drift or underruns in the graph.
+        TRACK_LOG(LogLevel::Warning,
+                  ("[VideoTrackEncoder]: Underrun detected. Diff=%lld",
+                   duration.value()));
+        chunk.mTimeStamp = mLastChunk.mTimeStamp;
+      } else {
+        mEncodedTicks += duration.value();
         mRawSegment.AppendFrame(lastImage.forget(),
                                 duration.value(),
                                 mLastChunk.mFrame.GetIntrinsicSize(),
                                 PRINCIPAL_HANDLE_NONE,
                                 mLastChunk.mFrame.GetForceBlack(),
                                 mLastChunk.mTimeStamp);
       }
     }
--- a/dom/media/encoder/TrackEncoder.h
+++ b/dom/media/encoder/TrackEncoder.h
@@ -71,17 +71,17 @@ public:
   /**
    * Notifies from MediaEncoder to cancel the encoding, and wakes up
    * mReentrantMonitor if encoder is waiting on it.
    */
   void NotifyCancel()
   {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     mCanceled = true;
-    mReentrantMonitor.NotifyAll();
+    NotifyEndOfStream();
   }
 
   virtual void SetBitrate(const uint32_t aBitrate) {}
 
 protected:
   /**
    * Notifies track encoder that we have reached the end of source stream, and
    * wakes up mReentrantMonitor if encoder is waiting for any source data.
@@ -250,16 +250,17 @@ class VideoTrackEncoder : public TrackEn
 public:
   explicit VideoTrackEncoder(TrackRate aTrackRate)
     : TrackEncoder()
     , mFrameWidth(0)
     , mFrameHeight(0)
     , mDisplayWidth(0)
     , mDisplayHeight(0)
     , mTrackRate(aTrackRate)
+    , mEncodedTicks(0)
     , mVideoBitrate(0)
   {
     mLastChunk.mDuration = 0;
   }
 
   /**
    * Notified by the same callback of MediaEncoder when it has received a track
    * change from MediaStreamGraph. Called on the MediaStreamGraph thread.
@@ -344,14 +345,26 @@ protected:
    */
   VideoChunk mLastChunk;
 
   /**
    * A segment queue of audio track data, protected by mReentrantMonitor.
    */
   VideoSegment mRawSegment;
 
+  /**
+   * The number of mTrackRate ticks we have passed to the encoder.
+   * Only accessed in AppendVideoSegment().
+   */
+  StreamTime mEncodedTicks;
+
+  /**
+   * The time of the first real video frame passed to the encoder.
+   * Only accessed in AppendVideoSegment().
+   */
+  TimeStamp mStartOffset;
+
   uint32_t mVideoBitrate;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/encoder/VP8TrackEncoder.cpp
+++ b/dom/media/encoder/VP8TrackEncoder.cpp
@@ -182,17 +182,17 @@ VP8TrackEncoder::GetMetadata()
   meta->mWidth = mFrameWidth;
   meta->mHeight = mFrameHeight;
   meta->mDisplayWidth = mDisplayWidth;
   meta->mDisplayHeight = mDisplayHeight;
 
   return meta.forget();
 }
 
-bool
+nsresult
 VP8TrackEncoder::GetEncodedPartitions(EncodedFrameContainer& aData)
 {
   vpx_codec_iter_t iter = nullptr;
   EncodedFrame::FrameType frameType = EncodedFrame::VP8_P_FRAME;
   nsTArray<uint8_t> frameData;
   const vpx_codec_cx_pkt_t *pkt = nullptr;
   while ((pkt = vpx_codec_get_cx_data(mVPXContext, &iter)) != nullptr) {
     switch (pkt->kind) {
@@ -214,34 +214,55 @@ VP8TrackEncoder::GetEncodedPartitions(En
       break;
     }
   }
 
   if (!frameData.IsEmpty()) {
     // Copy the encoded data to aData.
     EncodedFrame* videoData = new EncodedFrame();
     videoData->SetFrameType(frameType);
+
     // Convert the timestamp and duration to Usecs.
     CheckedInt64 timestamp = FramesToUsecs(pkt->data.frame.pts, mTrackRate);
-    if (timestamp.isValid()) {
-      videoData->SetTimeStamp((uint64_t)timestamp.value());
+    if (!timestamp.isValid()) {
+      NS_ERROR("Microsecond timestamp overflow");
+      return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
+    }
+    videoData->SetTimeStamp((uint64_t)timestamp.value());
+
+    mExtractedDuration += pkt->data.frame.duration;
+    if (!mExtractedDuration.isValid()) {
+      NS_ERROR("Duration overflow");
+      return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
     }
-    CheckedInt64 duration = FramesToUsecs(pkt->data.frame.duration, mTrackRate);
-    if (duration.isValid()) {
-      videoData->SetDuration((uint64_t)duration.value());
+
+    CheckedInt64 totalDuration =
+      FramesToUsecs(mExtractedDuration.value(), mTrackRate);
+    if (!totalDuration.isValid()) {
+      NS_ERROR("Duration overflow");
+      return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
     }
+
+    CheckedInt64 duration = totalDuration - mExtractedDurationUs;
+    if (!duration.isValid()) {
+      NS_ERROR("Duration overflow");
+      return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
+    }
+
+    mExtractedDurationUs = totalDuration;
+    videoData->SetDuration((uint64_t)duration.value());
     videoData->SwapInFrameData(frameData);
     VP8LOG(LogLevel::Verbose,
            "GetEncodedPartitions TimeStamp %lld, Duration %lld, FrameType %d",
            videoData->GetTimeStamp(), videoData->GetDuration(),
            videoData->GetFrameType());
     aData.AppendEncodedFrame(videoData);
   }
 
-  return !!pkt;
+  return pkt ? NS_OK : NS_ERROR_NOT_AVAILABLE;
 }
 
 static bool isYUV420(const PlanarYCbCrImage::Data *aData)
 {
   if (aData->mYSize == aData->mCbCrSize * 2) {
     return true;
   }
   return false;
@@ -544,29 +565,40 @@ VP8TrackEncoder::GetEncodedTrack(Encoded
         flags |= VPX_EFLAG_FORCE_KF;
       }
       if (vpx_codec_encode(mVPXContext, mVPXImageWrapper, mEncodedTimestamp,
                            (unsigned long)chunk.GetDuration(), flags,
                            VPX_DL_REALTIME)) {
         return NS_ERROR_FAILURE;
       }
       // Get the encoded data from VP8 encoder.
-      GetEncodedPartitions(aData);
+      rv = GetEncodedPartitions(aData);
+      NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
     } else {
       // SKIP_FRAME
       // Extend the duration of the last encoded data in aData
       // because this frame will be skipped.
       VP8LOG(LogLevel::Warning, "MediaRecorder lagging behind. Skipping a frame.");
       RefPtr<EncodedFrame> last = aData.GetEncodedFrames().LastElement();
       if (last) {
-        CheckedInt64 skippedDuration = FramesToUsecs(chunk.mDuration, mTrackRate);
-        if (skippedDuration.isValid() && skippedDuration.value() > 0) {
-          last->SetDuration(last->GetDuration() +
-                            (static_cast<uint64_t>(skippedDuration.value())));
+        mExtractedDuration += chunk.mDuration;
+        if (!mExtractedDuration.isValid()) {
+          NS_ERROR("skipped duration overflow");
+          return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
         }
+
+        CheckedInt64 totalDuration = FramesToUsecs(mExtractedDuration.value(), mTrackRate);
+        CheckedInt64 skippedDuration = totalDuration - mExtractedDurationUs;
+        mExtractedDurationUs = totalDuration;
+        if (!skippedDuration.isValid()) {
+          NS_ERROR("skipped duration overflow");
+          return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
+        }
+        last->SetDuration(last->GetDuration() +
+                          (static_cast<uint64_t>(skippedDuration.value())));
       }
     }
 
     // Move forward the mEncodedTimestamp.
     mEncodedTimestamp += chunk.GetDuration();
     totalProcessedDuration += chunk.GetDuration();
 
     // Check what to do next.
@@ -585,15 +617,15 @@ VP8TrackEncoder::GetEncodedTrack(Encoded
     // Bug 1243611, keep calling vpx_codec_encode and vpx_codec_get_cx_data
     // until vpx_codec_get_cx_data return null.
 
     do {
       if (vpx_codec_encode(mVPXContext, nullptr, mEncodedTimestamp,
                            0, 0, VPX_DL_REALTIME)) {
         return NS_ERROR_FAILURE;
       }
-    } while(GetEncodedPartitions(aData));
+    } while(NS_SUCCEEDED(GetEncodedPartitions(aData)));
   }
 
   return NS_OK ;
 }
 
 } // namespace mozilla
--- a/dom/media/encoder/VP8TrackEncoder.h
+++ b/dom/media/encoder/VP8TrackEncoder.h
@@ -42,24 +42,30 @@ protected:
 private:
   // Get the EncodeOperation for next target frame.
   EncodeOperation GetNextEncodeOperation(TimeDuration aTimeElapsed,
                                          StreamTime aProcessedDuration);
 
   // Get the encoded data from encoder to aData.
   // Return value: false if the vpx_codec_get_cx_data returns null
   //               for EOS detection.
-  bool GetEncodedPartitions(EncodedFrameContainer& aData);
+  nsresult GetEncodedPartitions(EncodedFrameContainer& aData);
 
   // Prepare the input data to the mVPXImageWrapper for encoding.
   nsresult PrepareRawFrame(VideoChunk &aChunk);
 
   // Encoded timestamp.
   StreamTime mEncodedTimestamp;
 
+  // Total duration in mTrackRate extracted by GetEncodedPartitions().
+  CheckedInt64 mExtractedDuration;
+
+  // Total duration in microseconds extracted by GetEncodedPartitions().
+  CheckedInt64 mExtractedDurationUs;
+
   // Muted frame, we only create it once.
   RefPtr<layers::Image> mMuteFrame;
 
   // I420 frame, for converting to I420.
   nsTArray<uint8_t> mI420Frame;
 
   /**
    * A local segment queue which takes the raw data out from mRawSegment in the
--- a/dom/media/gtest/TestVideoTrackEncoder.cpp
+++ b/dom/media/gtest/TestVideoTrackEncoder.cpp
@@ -467,16 +467,68 @@ TEST(VP8VideoTrackEncoder, SkippedFrames
   uint64_t totalDuration = 0;
   for (auto& frame : container.GetEncodedFrames()) {
     totalDuration += frame->GetDuration();
   }
   const uint64_t hundredMillis = PR_USEC_PER_SEC / 10;
   EXPECT_EQ(hundredMillis, totalDuration);
 }
 
+// Test encoding a track with frames subject to rounding errors.
+TEST(VP8VideoTrackEncoder, RoundingErrorFramesEncode)
+{
+  // Initiate VP8 encoder
+  TestVP8TrackEncoder encoder;
+  InitParam param = {true, 640, 480};
+  encoder.TestInit(param);
+  YUVBufferGenerator generator;
+  generator.Init(mozilla::gfx::IntSize(640, 480));
+  TimeStamp now = TimeStamp::Now();
+  VideoSegment segment;
+
+  // Pass nine frames with timestamps not expressable in 90kHz sample rate,
+  // then one frame to make the total duration one second.
+  uint32_t usPerFrame = 99999; //99.999ms
+  for (uint32_t i = 0; i < 9; ++i) {
+    segment.AppendFrame(generator.GenerateI420Image(),
+                        mozilla::StreamTime(9000), // 100ms
+                        generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE,
+                        false,
+                        now + TimeDuration::FromMicroseconds(i * usPerFrame));
+  }
+
+  // This last frame has timestamp start + 0.9s and duration 0.1s.
+  segment.AppendFrame(generator.GenerateI420Image(),
+                      mozilla::StreamTime(9000), // 100ms
+                      generator.GetSize(),
+                      PRINCIPAL_HANDLE_NONE,
+                      false,
+                      now + TimeDuration::FromSeconds(0.9));
+
+  encoder.SetCurrentFrames(segment);
+
+  // End the track.
+  segment.Clear();
+  encoder.NotifyQueuedTrackChanges(nullptr, 0, 0, TrackEventCommand::TRACK_EVENT_ENDED, segment);
+
+  EncodedFrameContainer container;
+  ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container)));
+
+  EXPECT_TRUE(encoder.IsEncodingComplete());
+
+  // Verify total duration being 1s.
+  uint64_t totalDuration = 0;
+  for (auto& frame : container.GetEncodedFrames()) {
+    totalDuration += frame->GetDuration();
+  }
+  const uint64_t oneSecond= PR_USEC_PER_SEC;
+  EXPECT_EQ(oneSecond, totalDuration);
+}
+
 // EOS test
 TEST(VP8VideoTrackEncoder, EncodeComplete)
 {
   // Initiate VP8 encoder
   TestVP8TrackEncoder encoder;
   InitParam param = {true, 640, 480};
   encoder.TestInit(param);
 
--- a/dom/media/test/test_mediarecorder_bitrate.html
+++ b/dom/media/test/test_mediarecorder_bitrate.html
@@ -24,108 +24,105 @@ function startTest(test, token) {
 
 function runTest(test, token, bitrate) {
   var element = document.createElement('video');
   var expectedMimeType = test.type.substring(0, test.type.indexOf(';'));
 
   element.token = token;
 
   element.src = test.name;
-  element.test = test;
-  element.stream = element.mozCaptureStreamUntilEnded();
-
-  var mediaRecorder = new MediaRecorder(element.stream , {videoBitsPerSecond: bitrate});
-  var onStopFired = false;
-  var onDataAvailableFired = false;
-  var encoded_size = 0;
-
-  mediaRecorder.onerror = function () {
-    ok(false, 'Unexpected onerror callback fired');
-  };
-
-  mediaRecorder.onwarning = function () {
-    ok(false, 'Unexpected onwarning callback fired');
-  };
-
-  // This handler verifies that only a single onstop event handler is fired.
-  mediaRecorder.onstop = function () {
-    if (onStopFired) {
-      ok(false, 'onstop unexpectedly fired more than once');
-    } else {
-      onStopFired = true;
+  element.preload = "metadata";
+  element.onloadedmetadata = function () {
+    info("loadedmetadata");
+    const stream = element.mozCaptureStreamUntilEnded();
+    element.onloadedmetadata = null;
+    element.play();
 
-      // ondataavailable should always fire before onstop
-      if (onDataAvailableFired) {
-        ok(true, 'onstop fired after ondataavailable');
-        info("test " + test.name + " encoded@" + bitrate + "=" + encoded_size);
-        if (results[test.name]) {
-          var big, small, temp;
-          big = {};
-	  big.bitrate = bitrate;
-	  big.size = encoded_size;
-	  small = results[test.name];
-	  // Don't assume the order that these will finish in
-	  if (results[test.name].bitrate > bitrate) {
-	    temp = big;
-	    big = small;
-	    small = temp;
-	  }
-	  // Ensure there is a big enough difference in the encoded
-	  // sizes
-          ok(small.size*1.25 < big.size,
-	     test.name + ' encoded@' + big.bitrate + '=' + big.size +
-	     ' > encoded@' + small.bitrate + '=' + small.size);
-          manager.finished(token);
-        } else {
-	  results[test.name] = {};
-	  results[test.name].bitrate = bitrate;
-          results[test.name].size = encoded_size;
-        }
-      } else {
-        ok(false, 'onstop fired without an ondataavailable event first');
-      }
-    }
-  };
-
-  // This handler verifies that only a single ondataavailable event handler
-  // is fired with the blob generated having greater than zero size
-  // and a correct mime type.
-  mediaRecorder.ondataavailable = function (evt) {
-    if (onDataAvailableFired) {
-      ok(false, 'ondataavailable unexpectedly fired more than once');
-    } else {
-      onDataAvailableFired = true;
-
-      ok(evt instanceof BlobEvent,
-         'Events fired from ondataavailable should be BlobEvent');
-      is(evt.type, 'dataavailable',
-         'Event type should dataavailable');
-      ok(evt.data.size > 0,
-         'Blob data received should be greater than zero');
-      encoded_size = evt.data.size;
-
-      // onstop should not have fired before ondataavailable
-      if (onStopFired) {
-        ok(false, 'ondataavailable unexpectedly fired later than onstop');
-        manager.finished(token);
-      }
-    }
-  };
-
-  element.preload = "metadata";
-
-  element.onloadedmetadata = function () {
-    element.onloadedmetadata = null;
+    const mediaRecorder = new MediaRecorder(stream, {videoBitsPerSecond: bitrate});
     mediaRecorder.start();
-    is(mediaRecorder.state, 'recording',
-     'Media recorder should be recording');
-    is(mediaRecorder.stream, element.stream,
+    is(mediaRecorder.state, 'recording', 'Media recorder should be recording');
+    is(mediaRecorder.stream, stream,
      'Media recorder stream = element stream at the start of recording');
 
-    element.play();
-  }
+    var onStopFired = false;
+    var onDataAvailableFired = false;
+    var encoded_size = 0;
+
+    mediaRecorder.onerror = function () {
+      ok(false, 'Unexpected onerror callback fired');
+    };
+
+    mediaRecorder.onwarning = function () {
+      ok(false, 'Unexpected onwarning callback fired');
+    };
+
+    // This handler verifies that only a single onstop event handler is fired.
+    mediaRecorder.onstop = function () {
+      if (onStopFired) {
+        ok(false, 'onstop unexpectedly fired more than once');
+      } else {
+        onStopFired = true;
+
+        // ondataavailable should always fire before onstop
+        if (onDataAvailableFired) {
+          ok(true, 'onstop fired after ondataavailable');
+          info("test " + test.name + " encoded@" + bitrate + "=" + encoded_size);
+          if (results[test.name]) {
+            var big, small, temp;
+            big = {};
+            big.bitrate = bitrate;
+            big.size = encoded_size;
+            small = results[test.name];
+            // Don't assume the order that these will finish in
+            if (results[test.name].bitrate > bitrate) {
+              temp = big;
+              big = small;
+              small = temp;
+            }
+            // Ensure there is a big enough difference in the encoded
+            // sizes
+            ok(small.size*1.25 < big.size,
+               test.name + ' encoded@' + big.bitrate + '=' + big.size +
+               ' > encoded@' + small.bitrate + '=' + small.size);
+            manager.finished(token);
+          } else {
+            results[test.name] = {};
+            results[test.name].bitrate = bitrate;
+            results[test.name].size = encoded_size;
+          }
+        } else {
+          ok(false, 'onstop fired without an ondataavailable event first');
+        }
+      }
+    };
+
+    // This handler verifies that only a single ondataavailable event handler
+    // is fired with the blob generated having greater than zero size
+    // and a correct mime type.
+    mediaRecorder.ondataavailable = function (evt) {
+      if (onDataAvailableFired) {
+        ok(false, 'ondataavailable unexpectedly fired more than once');
+      } else {
+        onDataAvailableFired = true;
+
+        ok(evt instanceof BlobEvent,
+           'Events fired from ondataavailable should be BlobEvent');
+        is(evt.type, 'dataavailable',
+           'Event type should dataavailable');
+        ok(evt.data.size > 0,
+           'Blob data received should be greater than zero');
+        encoded_size = evt.data.size;
+
+        // onstop should not have fired before ondataavailable
+        if (onStopFired) {
+          ok(false, 'ondataavailable unexpectedly fired later than onstop');
+          manager.finished(token);
+        }
+      }
+    };
+  };
 }
 
 manager.runTests(gMediaRecorderVideoTests, startTest);
 </script>
 </pre>
 </body>
 </html>
--- a/dom/network/TCPSocketParent.cpp
+++ b/dom/network/TCPSocketParent.cpp
@@ -355,18 +355,21 @@ TCPSocketParent::FireStringDataEvent(con
   MOZ_ASSERT(!mFilter, "Socket filtering doesn't support nsCString");
 
   SendEvent(NS_LITERAL_STRING("data"), data, aReadyState);
 }
 
 void
 TCPSocketParent::SendEvent(const nsAString& aType, CallbackData aData, TCPReadyState aReadyState)
 {
-  mozilla::Unused << PTCPSocketParent::SendCallback(nsString(aType), aData,
-                                                    static_cast<uint32_t>(aReadyState));
+  if (mIPCOpen) {
+    mozilla::Unused << PTCPSocketParent::SendCallback(nsString(aType),
+                                                      aData,
+                                                      static_cast<uint32_t>(aReadyState));
+  }
 }
 
 void
 TCPSocketParent::SetSocket(TCPSocket *socket)
 {
   mSocket = socket;
 }
 
--- a/dom/network/UDPSocketParent.cpp
+++ b/dom/network/UDPSocketParent.cpp
@@ -305,16 +305,17 @@ UDPSocketParent::DoConnect(nsCOMPtr<nsIU
 
 nsresult
 UDPSocketParent::ConnectInternal(const nsCString& aHost, const uint16_t& aPort)
 {
   nsresult rv;
 
   UDPSOCKET_LOG(("%s: %s:%u", __FUNCTION__, nsCString(aHost).get(), aPort));
   PRNetAddr prAddr;
+  memset(&prAddr, 0, sizeof(prAddr));
   PR_InitializeNetAddr(PR_IpAddrAny, aPort, &prAddr);
   PRStatus status = PR_StringToNetAddr(aHost.BeginReading(), &prAddr);
   if (status != PR_SUCCESS) {
     return NS_ERROR_FAILURE;
   }
 
   mozilla::net::NetAddr addr;
   PRNetAddrToNetAddr(&prAddr, &addr);
--- a/dom/webidl/Window.webidl
+++ b/dom/webidl/Window.webidl
@@ -509,8 +509,21 @@ partial interface Window {
   void          cancelIdleCallback(unsigned long handle);
 };
 
 dictionary IdleRequestOptions {
   unsigned long timeout;
 };
 
 callback IdleRequestCallback = void (IdleDeadline deadline);
+
+/**
+ * Similar to |isSecureContext|, but doesn't pay attention to whether the
+ * window's opener (if any) is a secure context or not.
+ *
+ * WARNING: Do not use this unless you are familiar with the issues that
+ * taking opener state into account is designed to address (or else you may
+ * introduce security issues).  If in doubt, use |isSecureContext|.  In
+ * particular do not use this to gate access to JavaScript APIs.
+ */
+partial interface Window {
+  [ChromeOnly] readonly attribute boolean isSecureContextIfOpenerIgnored;
+};
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -1441,18 +1441,16 @@ gfxWindowsPlatform::RecordContentDeviceF
     return;
   }
   Telemetry::Accumulate(Telemetry::GFX_CONTENT_FAILED_TO_ACQUIRE_DEVICE, uint32_t(aDevice));
 }
 
 void
 gfxWindowsPlatform::InitializeDevices()
 {
-  MOZ_ASSERT(!InSafeMode());
-
   if (XRE_IsParentProcess()) {
     // If we're the UI process, and the GPU process is enabled, then we don't
     // initialize any DirectX devices. We do leave them enabled in gfxConfig
     // though. If the GPU process fails to create these devices it will send
     // a message back and we'll update their status.
     if (InitGPUProcessSupport()) {
       return;
     }
--- a/gfx/vr/VRDisplayHost.cpp
+++ b/gfx/vr/VRDisplayHost.cpp
@@ -19,17 +19,17 @@ using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 
 VRDisplayHost::VRDisplayHost(VRDeviceType aType)
   : mInputFrameID(0)
 {
   MOZ_COUNT_CTOR(VRDisplayHost);
   mDisplayInfo.mType = aType;
-  mDisplayInfo.mDisplayID = VRDisplayManager::AllocateDisplayID();
+  mDisplayInfo.mDisplayID = VRSystemManager::AllocateDisplayID();
   mDisplayInfo.mIsPresenting = false;
 
   for (int i = 0; i < kMaxLatencyFrames; i++) {
     mLastSensorState[i].Clear();
   }
 }
 
 VRDisplayHost::~VRDisplayHost()
@@ -144,17 +144,17 @@ VRDisplayHost::CheckClearDisplayInfoDirt
   mLastUpdateDisplayInfo = mDisplayInfo;
   return true;
 }
 
 VRControllerHost::VRControllerHost(VRDeviceType aType)
 {
   MOZ_COUNT_CTOR(VRControllerHost);
   mControllerInfo.mType = aType;
-  mControllerInfo.mControllerID = VRDisplayManager::AllocateDisplayID();
+  mControllerInfo.mControllerID = VRSystemManager::AllocateDisplayID();
 }
 
 VRControllerHost::~VRControllerHost()
 {
   MOZ_COUNT_DTOR(VRControllerHost);
 }
 
 const VRControllerInfo&
--- a/gfx/vr/VRManager.cpp
+++ b/gfx/vr/VRManager.cpp
@@ -46,18 +46,17 @@ VRManager::ManagerInit()
 }
 
 VRManager::VRManager()
   : mInitialized(false)
 {
   MOZ_COUNT_CTOR(VRManager);
   MOZ_ASSERT(sVRManagerSingleton == nullptr);
 
-  RefPtr<VRDisplayManager> mgr;
-  RefPtr<VRControllerManager> controllerMgr;
+  RefPtr<VRSystemManager> mgr;
 
   /**
    * We must add the VRDisplayManager's to mManagers in a careful order to
    * ensure that we don't detect the same VRDisplay from multiple API's.
    *
    * Oculus comes first, as it will only enumerate Oculus HMD's and is the
    * native interface for Oculus HMD's.
    *
@@ -65,35 +64,31 @@ VRManager::VRManager()
    * which is the most common HMD at this time.
    *
    * OSVR will be used if Oculus SDK and OpenVR don't detect any HMDS,
    * to support everyone else.
    */
 
 #if defined(XP_WIN)
   // The Oculus runtime is supported only on Windows
-  mgr = VRDisplayManagerOculus::Create();
+  mgr = VRSystemManagerOculus::Create();
   if (mgr) {
     mManagers.AppendElement(mgr);
   }
 #endif
 
 #if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_LINUX)
   // OpenVR is cross platform compatible
-  mgr = VRDisplayManagerOpenVR::Create();
+  mgr = VRSystemManagerOpenVR::Create();
   if (mgr) {
     mManagers.AppendElement(mgr);
-    controllerMgr = VRControllerManagerOpenVR::Create();
-    if (controllerMgr) {
-      mControllerManagers.AppendElement(controllerMgr);
-    }
   }
 
   // OSVR is cross platform compatible
-  mgr = VRDisplayManagerOSVR::Create();
+  mgr = VRSystemManagerOSVR::Create();
   if (mgr) {
       mManagers.AppendElement(mgr);
   }
 #endif
   // Enable gamepad extensions while VR is enabled.
   // Preference only can be set at the Parent process.
   if (XRE_IsParentProcess() && gfxPrefs::VREnabled()) {
     Preferences::SetBool("dom.gamepad.extensions.enabled", true);
@@ -106,37 +101,31 @@ VRManager::~VRManager()
   MOZ_ASSERT(!mInitialized);
   MOZ_COUNT_DTOR(VRManager);
 }
 
 void
 VRManager::Destroy()
 {
   mVRDisplays.Clear();
+  mVRControllers.Clear();
   for (uint32_t i = 0; i < mManagers.Length(); ++i) {
     mManagers[i]->Destroy();
   }
 
-  mVRControllers.Clear();
-  for (uint32_t i = 0; i < mControllerManagers.Length(); ++i) {
-    mControllerManagers[i]->Destroy();
-  }
   mInitialized = false;
 }
 
 void
 VRManager::Init()
 {
   for (uint32_t i = 0; i < mManagers.Length(); ++i) {
     mManagers[i]->Init();
   }
 
-  for (uint32_t i = 0; i < mControllerManagers.Length(); ++i) {
-    mControllerManagers[i]->Init();
-  }
   mInitialized = true;
 }
 
 /* static */VRManager*
 VRManager::Get()
 {
   MOZ_ASSERT(sVRManagerSingleton != nullptr);
 
@@ -204,18 +193,18 @@ VRManager::NotifyVsync(const TimeStamp& 
       }
     }
   }
 }
 
 void
 VRManager::NotifyVRVsync(const uint32_t& aDisplayID)
 {
-  for (uint32_t i = 0; i < mControllerManagers.Length(); ++i) {
-    mControllerManagers[i]->HandleInput();
+  for (uint32_t i = 0; i < mManagers.Length(); ++i) {
+    mManagers[i]->HandleInput();
   }
   for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) {
     Unused << iter.Get()->GetKey()->SendNotifyVRVSync(aDisplayID);
   }
 }
 
 void
 VRManager::RefreshVRDisplays(bool aMustDispatch)
@@ -338,19 +327,19 @@ VRManager::GetVRControllerInfo(nsTArray<
 
 void
 VRManager::RefreshVRControllers()
 {
   nsTArray<RefPtr<gfx::VRControllerHost>> controllers;
 
   ScanForControllers();
 
-  for (uint32_t i = 0; i < mControllerManagers.Length()
+  for (uint32_t i = 0; i < mManagers.Length()
       && controllers.Length() == 0; ++i) {
-    mControllerManagers[i]->GetControllers(controllers);
+    mManagers[i]->GetControllers(controllers);
   }
 
   bool controllerInfoChanged = false;
 
   if (controllers.Length() != mVRControllers.Count()) {
     // Catch cases where VR controllers has been removed
     controllerInfoChanged = true;
   }
@@ -370,26 +359,26 @@ VRManager::RefreshVRControllers()
                          controller);
     }
   }
 }
 
 void
 VRManager::ScanForControllers()
 {
-  for (uint32_t i = 0; i < mControllerManagers.Length(); ++i) {
-    mControllerManagers[i]->ScanForDevices();
+  for (uint32_t i = 0; i < mManagers.Length(); ++i) {
+    mManagers[i]->ScanForControllers();
   }
 }
 
 void
 VRManager::RemoveControllers()
 {
-  for (uint32_t i = 0; i < mControllerManagers.Length(); ++i) {
-    mControllerManagers[i]->RemoveDevices();
+  for (uint32_t i = 0; i < mManagers.Length(); ++i) {
+    mManagers[i]->RemoveControllers();
   }
   mVRControllers.Clear();
 }
 
 template<class T>
 void
 VRManager::NotifyGamepadChange(const T& aInfo)
 {
--- a/gfx/vr/VRManager.h
+++ b/gfx/vr/VRManager.h
@@ -17,17 +17,16 @@ namespace mozilla {
 namespace layers {
 class TextureHost;
 }
 namespace gfx {
 
 class VRLayerParent;
 class VRManagerParent;
 class VRDisplayHost;
-class VRControllerManager;
 
 class VRManager
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::gfx::VRManager)
 
 public:
   static void ManagerInit();
   static VRManager* Get();
@@ -61,21 +60,18 @@ private:
   void Destroy();
 
   void DispatchVRDisplayInfoUpdate();
   void RefreshVRControllers();
 
   typedef nsTHashtable<nsRefPtrHashKey<VRManagerParent>> VRManagerParentSet;
   VRManagerParentSet mVRManagerParents;
 
-  typedef nsTArray<RefPtr<VRDisplayManager>> VRDisplayManagerArray;
-  VRDisplayManagerArray mManagers;
-
-  typedef nsTArray<RefPtr<VRControllerManager>> VRControllerManagerArray;
-  VRControllerManagerArray mControllerManagers;
+  typedef nsTArray<RefPtr<VRSystemManager>> VRSystemManagerArray;
+  VRSystemManagerArray mManagers;
 
   typedef nsRefPtrHashtable<nsUint32HashKey, gfx::VRDisplayHost> VRDisplayHostHashMap;
   VRDisplayHostHashMap mVRDisplays;
 
   typedef nsRefPtrHashtable<nsUint32HashKey, gfx::VRControllerHost> VRControllerHostHashMap;
   VRControllerHostHashMap mVRControllers;
 
   Atomic<bool> mInitialized;
--- a/gfx/vr/gfxVR.cpp
+++ b/gfx/vr/gfxVR.cpp
@@ -11,21 +11,20 @@
 
 #ifndef M_PI
 # define M_PI 3.14159265358979323846
 #endif
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 
-Atomic<uint32_t> VRDisplayManager::sDisplayBase(0);
-Atomic<uint32_t> VRControllerManager::sControllerBase(0);
+Atomic<uint32_t> VRSystemManager::sDisplayBase(0);
 
 /* static */ uint32_t
-VRDisplayManager::AllocateDisplayID()
+VRSystemManager::AllocateDisplayID()
 {
   return ++sDisplayBase;
 }
 
 Matrix4x4
 VRFieldOfView::ConstructProjectionMatrix(float zNear, float zFar,
                                          bool rightHanded) const
 {
@@ -54,72 +53,66 @@ VRFieldOfView::ConstructProjectionMatrix
   m[3*4+2] = (zFar * zNear) / (zNear - zFar);
 
   m[2*4+3] = handednessScale;
   m[3*4+3] = 0.0f;
 
   return mobj;
 }
 
-/* static */ uint32_t
-VRControllerManager::AllocateControllerID()
-{
-  return ++sControllerBase;
-}
-
 void
-VRControllerManager::AddGamepad(const char* aID, dom::GamepadMappingType aMapping,
-                                dom::GamepadHand aHand, uint32_t aNumButtons, uint32_t aNumAxes)
+VRSystemManager::AddGamepad(const char* aID, dom::GamepadMappingType aMapping,
+                            dom::GamepadHand aHand, uint32_t aNumButtons, uint32_t aNumAxes)
 {
   dom::GamepadAdded a(NS_ConvertUTF8toUTF16(nsDependentCString(aID)), mControllerCount,
                      aMapping, aHand, dom::GamepadServiceType::VR, aNumButtons,
                      aNumAxes);
 
   VRManager* vm = VRManager::Get();
   MOZ_ASSERT(vm);
   vm->NotifyGamepadChange<dom::GamepadAdded>(a);
 }
 
 void
-VRControllerManager::RemoveGamepad(uint32_t aIndex)
+VRSystemManager::RemoveGamepad(uint32_t aIndex)
 {
   dom::GamepadRemoved a(aIndex, dom::GamepadServiceType::VR);
 
   VRManager* vm = VRManager::Get();
   MOZ_ASSERT(vm);
   vm->NotifyGamepadChange<dom::GamepadRemoved>(a);
 }
 
 void
-VRControllerManager::NewButtonEvent(uint32_t aIndex, uint32_t aButton,
-                                    bool aPressed)
+VRSystemManager::NewButtonEvent(uint32_t aIndex, uint32_t aButton,
+                                bool aPressed)
 {
   dom::GamepadButtonInformation a(aIndex, dom::GamepadServiceType::VR,
                                   aButton, aPressed, aPressed ? 1.0L : 0.0L);
 
   VRManager* vm = VRManager::Get();
   MOZ_ASSERT(vm);
   vm->NotifyGamepadChange<dom::GamepadButtonInformation>(a);
 }
 
 void
-VRControllerManager::NewAxisMove(uint32_t aIndex, uint32_t aAxis,
-                                 double aValue)
+VRSystemManager::NewAxisMove(uint32_t aIndex, uint32_t aAxis,
+                             double aValue)
 {
   dom::GamepadAxisInformation a(aIndex, dom::GamepadServiceType::VR,
                                 aAxis, aValue);
 
   VRManager* vm = VRManager::Get();
   MOZ_ASSERT(vm);
   vm->NotifyGamepadChange<dom::GamepadAxisInformation>(a);
 }
 
 void
-VRControllerManager::NewPoseState(uint32_t aIndex,
-                                  const dom::GamepadPoseState& aPose)
+VRSystemManager::NewPoseState(uint32_t aIndex,
+                              const dom::GamepadPoseState& aPose)
 {
   dom::GamepadPoseInformation a(aIndex, dom::GamepadServiceType::VR,
                                 aPose);
 
   VRManager* vm = VRManager::Get();
   MOZ_ASSERT(vm);
   vm->NotifyGamepadChange<dom::GamepadPoseInformation>(a);
 }
--- a/gfx/vr/gfxVR.h
+++ b/gfx/vr/gfxVR.h
@@ -191,33 +191,54 @@ struct VRHMDSensorState {
   float linearVelocity[3];
   float linearAcceleration[3];
 
   void Clear() {
     memset(this, 0, sizeof(VRHMDSensorState));
   }
 };
 
-class VRDisplayManager {
+class VRSystemManager {
 public:
   static uint32_t AllocateDisplayID();
 
 protected:
   static Atomic<uint32_t> sDisplayBase;
 
 public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRDisplayManager)
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRSystemManager)
 
   virtual bool Init() = 0;
   virtual void Destroy() = 0;
   virtual void GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) = 0;
+  virtual void HandleInput() = 0;
+  virtual void GetControllers(nsTArray<RefPtr<VRControllerHost>>& aControllerResult) = 0;
+  virtual void ScanForControllers() = 0;
+  virtual void RemoveControllers() = 0;
+  void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed);
+  void NewAxisMove(uint32_t aIndex, uint32_t aAxis, double aValue);
+  void NewPoseState(uint32_t aIndex, const dom::GamepadPoseState& aPose);
+  void AddGamepad(const char* aID, dom::GamepadMappingType aMapping,
+                  dom::GamepadHand aHand, uint32_t aNumButtons, uint32_t aNumAxes);
+  void RemoveGamepad(uint32_t aIndex);
 
 protected:
-  VRDisplayManager() { }
-  virtual ~VRDisplayManager() { }
+  VRSystemManager() : mControllerCount(0) { }
+  virtual ~VRSystemManager() { }
+
+  uint32_t mControllerCount;
+
+private:
+  virtual void HandleButtonPress(uint32_t aControllerIdx,
+                                 uint64_t aButtonPressed) = 0;
+  virtual void HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
+                              float aValue) = 0;
+  virtual void HandlePoseTracking(uint32_t aControllerIdx,
+                                  const dom::GamepadPoseState& aPose,
+                                  VRControllerHost* aController) = 0;
 };
 
 struct VRControllerInfo
 {
   VRDeviceType GetType() const { return mType; }
   uint32_t GetControllerID() const { return mControllerID; }
   const nsCString& GetControllerName() const { return mControllerName; }
   dom::GamepadMappingType GetMappingType() const { return mMappingType; }
@@ -240,48 +261,12 @@ struct VRControllerInfo
            mNumAxes == other.mNumAxes;
   }
 
   bool operator!=(const VRControllerInfo& other) const {
     return !(*this == other);
   }
 };
 
-class VRControllerManager {
-public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRControllerManager)
-
-  static uint32_t AllocateControllerID();
-  virtual bool Init() = 0;
-  virtual void Destroy() = 0;
-  virtual void HandleInput() = 0;
-  virtual void GetControllers(nsTArray<RefPtr<VRControllerHost>>& aControllerResult) = 0;
-  virtual void ScanForDevices() = 0;
-  virtual void RemoveDevices() = 0;
-  void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed);
-  void NewAxisMove(uint32_t aIndex, uint32_t aAxis, double aValue);
-  void NewPoseState(uint32_t aIndex, const dom::GamepadPoseState& aPose);
-  void AddGamepad(const char* aID, dom::GamepadMappingType aMapping,
-                  dom::GamepadHand aHand, uint32_t aNumButtons, uint32_t aNumAxes);
-  void RemoveGamepad(uint32_t aIndex);
-
-protected:
-  VRControllerManager() : mInstalled(false), mControllerCount(0) {}
-  virtual ~VRControllerManager() {}
-
-  bool mInstalled;
-  uint32_t mControllerCount;
-  static Atomic<uint32_t> sControllerBase;
-
-private:
-  virtual void HandleButtonPress(uint32_t aControllerIdx,
-                                 uint64_t aButtonPressed) = 0;
-  virtual void HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
-                              float aValue) = 0;
-  virtual void HandlePoseTracking(uint32_t aControllerIdx,
-                                  const dom::GamepadPoseState& aPose,
-                                  VRControllerHost* aController) = 0;
-};
-
 } // namespace gfx
 } // namespace mozilla
 
 #endif /* GFX_VR_H */
--- a/gfx/vr/gfxVROSVR.cpp
+++ b/gfx/vr/gfxVROSVR.cpp
@@ -16,23 +16,27 @@
 
 #ifdef XP_WIN
 #include "../layers/d3d11/CompositorD3D11.h"
 #include "../layers/d3d11/TextureD3D11.h"
 #endif
 
 #include "gfxVROSVR.h"
 
+#include "mozilla/dom/GamepadEventTypes.h"
+#include "mozilla/dom/GamepadBinding.h"
+
 #ifndef M_PI
 #define M_PI 3.14159265358979323846
 #endif
 
 using namespace mozilla::layers;
 using namespace mozilla::gfx;
 using namespace mozilla::gfx::impl;
+using namespace mozilla::dom;
 
 namespace {
 // need to typedef functions that will be used in the code below
 extern "C" {
 typedef OSVR_ClientContext (*pfn_osvrClientInit)(
   const char applicationIdentifier[], uint32_t flags);
 typedef OSVR_ReturnCode (*pfn_osvrClientShutdown)(OSVR_ClientContext ctx);
 typedef OSVR_ReturnCode (*pfn_osvrClientUpdate)(OSVR_ClientContext ctx);
@@ -344,33 +348,33 @@ VRDisplayOSVR::StartPresentation()
 }
 
 void
 VRDisplayOSVR::StopPresentation()
 {
   // XXX Add code to end VR Presentation
 }
 
-already_AddRefed<VRDisplayManagerOSVR>
-VRDisplayManagerOSVR::Create()
+already_AddRefed<VRSystemManagerOSVR>
+VRSystemManagerOSVR::Create()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!gfxPrefs::VREnabled() || !gfxPrefs::VROSVREnabled()) {
     return nullptr;
   }
   if (!LoadOSVRRuntime()) {
     return nullptr;
   }
-  RefPtr<VRDisplayManagerOSVR> manager = new VRDisplayManagerOSVR();
+  RefPtr<VRSystemManagerOSVR> manager = new VRSystemManagerOSVR();
   return manager.forget();
 }
 
 void
-VRDisplayManagerOSVR::CheckOSVRStatus()
+VRSystemManagerOSVR::CheckOSVRStatus()
 {
   if (mOSVRInitialized) {
     return;
   }
 
   // client context must be initialized first
   InitializeClientContext();
 
@@ -384,17 +388,17 @@ VRDisplayManagerOSVR::CheckOSVRStatus()
   // OSVR is fully initialized now
   if (mClientContextInitialized && mDisplayConfigInitialized &&
       mInterfaceInitialized) {
     mOSVRInitialized = true;
   }
 }
 
 void
-VRDisplayManagerOSVR::InitializeClientContext()
+VRSystemManagerOSVR::InitializeClientContext()
 {
   // already initialized
   if (mClientContextInitialized) {
     return;
   }
 
   // first time creating
   if (!m_ctx) {
@@ -413,34 +417,34 @@ VRDisplayManagerOSVR::InitializeClientCo
     osvr_ClientUpdate(m_ctx);
     if (OSVR_RETURN_SUCCESS == osvr_ClientCheckStatus(m_ctx)) {
       mClientContextInitialized = true;
     }
   }
 }
 
 void
-VRDisplayManagerOSVR::InitializeInterface()
+VRSystemManagerOSVR::InitializeInterface()
 {
   // already initialized
   if (mInterfaceInitialized) {
     return;
   }
   //Client context must be initialized before getting interface
   if (mClientContextInitialized) {
     // m_iface will remain nullptr if no interface is returned
     if (OSVR_RETURN_SUCCESS ==
         osvr_ClientGetInterface(m_ctx, "/me/head", &m_iface)) {
       mInterfaceInitialized = true;
     }
   }
 }
 
 void
-VRDisplayManagerOSVR::InitializeDisplay()
+VRSystemManagerOSVR::InitializeDisplay()
 {
   // display is fully configured
   if (mDisplayConfigInitialized) {
     return;
   }
 
   //Client context must be initialized before getting interface
   if (mClientContextInitialized) {
@@ -465,17 +469,17 @@ VRDisplayManagerOSVR::InitializeDisplay(
       if (OSVR_RETURN_SUCCESS == osvr_ClientCheckDisplayStartup(m_display)) {
         mDisplayConfigInitialized = true;
       }
     }
   }
 }
 
 bool
-VRDisplayManagerOSVR::Init()
+VRSystemManagerOSVR::Init()
 {
 
   // OSVR server should be running in the background
   // It would load plugins and take care of detecting HMDs
   if (!mOSVRInitialized) {
     nsIThread* thread = nullptr;
     NS_GetCurrentThread(&thread);
     mOSVRThread = already_AddRefed<nsIThread>(thread);
@@ -489,17 +493,17 @@ VRDisplayManagerOSVR::Init()
     // verify all components are initialized
     CheckOSVRStatus();
   }
 
   return mOSVRInitialized;
 }
 
 void
-VRDisplayManagerOSVR::Destroy()
+VRSystemManagerOSVR::Destroy()
 {
   if (mOSVRInitialized) {
     MOZ_ASSERT(NS_GetCurrentThread() == mOSVRThread);
     mOSVRThread = nullptr;
     mHMDInfo = nullptr;
     mOSVRInitialized = false;
   }
   // client context may not have been initialized
@@ -507,23 +511,62 @@ VRDisplayManagerOSVR::Destroy()
     osvr_ClientFreeDisplay(m_display);
   }
   // osvr checks that m_ctx or m_iface are not null
   osvr_ClientFreeInterface(m_ctx, m_iface);
   osvr_ClientShutdown(m_ctx);
 }
 
 void
-VRDisplayManagerOSVR::GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult)
+VRSystemManagerOSVR::GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult)
 {
   // make sure context, interface and display are initialized
   CheckOSVRStatus();
 
   if (!mOSVRInitialized) {
     return;
   }
 
   mHMDInfo = new VRDisplayOSVR(&m_ctx, &m_iface, &m_display);
 
   if (mHMDInfo) {
     aHMDResult.AppendElement(mHMDInfo);
   }
 }
+
+void
+VRSystemManagerOSVR::HandleInput()
+{
+}
+
+void
+VRSystemManagerOSVR::HandleButtonPress(uint32_t aControllerIdx,
+                                       uint64_t aButtonPressed)
+{
+}
+
+void
+VRSystemManagerOSVR::HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
+                                    float aValue)
+{
+}
+
+void
+VRSystemManagerOSVR::HandlePoseTracking(uint32_t aControllerIdx,
+                                        const GamepadPoseState& aPose,
+                                        VRControllerHost* aController)
+{
+}
+
+void
+VRSystemManagerOSVR::GetControllers(nsTArray<RefPtr<VRControllerHost>>& aControllerResult)
+{
+}
+
+void
+VRSystemManagerOSVR::ScanForControllers()
+{
+}
+
+void
+VRSystemManagerOSVR::RemoveControllers()
+{
+}
--- a/gfx/vr/gfxVROSVR.h
+++ b/gfx/vr/gfxVROSVR.h
@@ -56,26 +56,31 @@ protected:
 
   OSVR_ClientContext* m_ctx;
   OSVR_ClientInterface* m_iface;
   OSVR_DisplayConfig* m_display;
 };
 
 } // namespace impl
 
-class VRDisplayManagerOSVR : public VRDisplayManager
+class VRSystemManagerOSVR : public VRSystemManager
 {
 public:
-  static already_AddRefed<VRDisplayManagerOSVR> Create();
+  static already_AddRefed<VRSystemManagerOSVR> Create();
   virtual bool Init() override;
   virtual void Destroy() override;
   virtual void GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) override;
+  virtual void HandleInput() override;
+  virtual void GetControllers(nsTArray<RefPtr<VRControllerHost>>&
+                              aControllerResult) override;
+  virtual void ScanForControllers() override;
+  virtual void RemoveControllers() override;
 
 protected:
-  VRDisplayManagerOSVR()
+  VRSystemManagerOSVR()
     : mOSVRInitialized(false)
     , mClientContextInitialized(false)
     , mDisplayConfigInitialized(false)
     , mInterfaceInitialized(false)
     , m_ctx(nullptr)
     , m_iface(nullptr)
     , m_display(nullptr)
   {
@@ -88,16 +93,23 @@ protected:
   bool mInterfaceInitialized;
   RefPtr<nsIThread> mOSVRThread;
 
   OSVR_ClientContext m_ctx;
   OSVR_ClientInterface m_iface;
   OSVR_DisplayConfig m_display;
 
 private:
+  virtual void HandleButtonPress(uint32_t aControllerIdx,
+                                 uint64_t aButtonPressed) override;
+  virtual void HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
+                              float aValue) override;
+  virtual void HandlePoseTracking(uint32_t aControllerIdx,
+                                  const dom::GamepadPoseState& aPose,
+                                  VRControllerHost* aController) override;
   // check if all components are initialized
   // and if not, it will try to initialize them
   void CheckOSVRStatus();
   void InitializeClientContext();
   void InitializeDisplay();
   void InitializeInterface();
 };
 
--- a/gfx/vr/gfxVROculus.cpp
+++ b/gfx/vr/gfxVROculus.cpp
@@ -24,16 +24,19 @@
 #include "mozilla/gfx/Quaternion.h"
 
 #include <d3d11.h>
 #include "CompositorD3D11.h"
 #include "TextureD3D11.h"
 
 #include "gfxVROculus.h"
 
+#include "mozilla/dom/GamepadEventTypes.h"
+#include "mozilla/dom/GamepadBinding.h"
+
 /** XXX The DX11 objects and quad blitting could be encapsulated
  *    into a separate object if either Oculus starts supporting
  *     non-Windows platforms or the blit is needed by other HMD\
  *     drivers.
  *     Alternately, we could remove the extra blit for
  *     Oculus as well with some more refactoring.
  */
 
@@ -44,16 +47,17 @@ extern ShaderBytes sLayerQuadVS;
 #ifndef M_PI
 # define M_PI 3.14159265358979323846
 #endif
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::gfx::impl;
 using namespace mozilla::layers;
+using namespace mozilla::dom;
 
 namespace {
 
 #ifdef OVR_CAPI_LIMITED_MOZILLA
 static pfn_ovr_Initialize ovr_Initialize = nullptr;
 static pfn_ovr_Shutdown ovr_Shutdown = nullptr;
 static pfn_ovr_GetLastErrorInfo ovr_GetLastErrorInfo = nullptr;
 static pfn_ovr_GetVersionString ovr_GetVersionString = nullptr;
@@ -613,36 +617,36 @@ VRDisplayOculus::StopPresentation()
   ovr_SubmitFrame(mSession, 0, nullptr, nullptr, 0);
 
   if (mTextureSet) {
     ovr_DestroyTextureSwapChain(mSession, mTextureSet);
     mTextureSet = nullptr;
   }
 }
 
-/*static*/ already_AddRefed<VRDisplayManagerOculus>
-VRDisplayManagerOculus::Create()
+/*static*/ already_AddRefed<VRSystemManagerOculus>
+VRSystemManagerOculus::Create()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!gfxPrefs::VREnabled() || !gfxPrefs::VROculusEnabled())
   {
     return nullptr;
   }
 
   if (!InitializeOculusCAPI()) {
     return nullptr;
   }
 
-  RefPtr<VRDisplayManagerOculus> manager = new VRDisplayManagerOculus();
+  RefPtr<VRSystemManagerOculus> manager = new VRSystemManagerOculus();
   return manager.forget();
 }
 
 bool
-VRDisplayManagerOculus::Init()
+VRSystemManagerOculus::Init()
 {
   if (!mOculusInitialized) {
     nsIThread* thread = nullptr;
     NS_GetCurrentThread(&thread);
     mOculusThread = already_AddRefed<nsIThread>(thread);
 
     ovrInitParams params;
     memset(&params, 0, sizeof(params));
@@ -657,31 +661,31 @@ VRDisplayManagerOculus::Init()
       mOculusInitialized = true;
     }
   }
 
   return mOculusInitialized;
 }
 
 void
-VRDisplayManagerOculus::Destroy()
+VRSystemManagerOculus::Destroy()
 {
   if (mOculusInitialized) {
     MOZ_ASSERT(NS_GetCurrentThread() == mOculusThread);
     mOculusThread = nullptr;
 
     mHMDInfo = nullptr;
 
     ovr_Shutdown();
     mOculusInitialized = false;
   }
 }
 
 void
-VRDisplayManagerOculus::GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult)
+VRSystemManagerOculus::GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult)
 {
   if (!mOculusInitialized) {
     return;
   }
 
   // ovr_Create can be slow when no HMD is present and we wish
   // to keep the same oculus session when possible, so we detect
   // presence of an HMD with ovr_GetHmdDesc before calling ovr_Create
@@ -690,25 +694,65 @@ VRDisplayManagerOculus::GetHMDs(nsTArray
     // No HMD connected.
     mHMDInfo = nullptr;
   } else if (mHMDInfo == nullptr) {
     // HMD Detected
     ovrSession session;
     ovrGraphicsLuid luid;
     ovrResult orv = ovr_Create(&session, &luid);
     if (orv == ovrSuccess) {
+      mSession = session;
       mHMDInfo = new VRDisplayOculus(session);
     }
   }
 
   if (mHMDInfo) {
     aHMDResult.AppendElement(mHMDInfo);
   }
 }
 
+void
+VRSystemManagerOculus::HandleInput()
+{
+}
+
+void
+VRSystemManagerOculus::HandleButtonPress(uint32_t aControllerIdx,
+                                         uint64_t aButtonPressed)
+{
+}
+
+void
+VRSystemManagerOculus::HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
+                                      float aValue)
+{
+}
+
+void
+VRSystemManagerOculus::HandlePoseTracking(uint32_t aControllerIdx,
+                                          const GamepadPoseState& aPose,
+                                          VRControllerHost* aController)
+{
+}
+
+void
+VRSystemManagerOculus::GetControllers(nsTArray<RefPtr<VRControllerHost>>& aControllerResult)
+{
+}
+
+void
+VRSystemManagerOculus::ScanForControllers()
+{
+}
+
+void
+VRSystemManagerOculus::RemoveControllers()
+{
+}
+
 already_AddRefed<CompositingRenderTargetD3D11>
 VRDisplayOculus::GetNextRenderTarget()
 {
   int currentRenderTarget = 0;
   DebugOnly<ovrResult> orv = ovr_GetTextureSwapChainCurrentIndex(mSession, mTextureSet, &currentRenderTarget);
   MOZ_ASSERT(orv == ovrSuccess, "ovr_GetTextureSwapChainCurrentIndex failed.");
 
   mRenderTargets[currentRenderTarget]->ClearOnBind();
--- a/gfx/vr/gfxVROculus.h
+++ b/gfx/vr/gfxVROculus.h
@@ -83,29 +83,45 @@ protected:
   struct Vertex
   {
     float position[2];
   };
 };
 
 } // namespace impl
 
-class VRDisplayManagerOculus : public VRDisplayManager
+class VRSystemManagerOculus : public VRSystemManager
 {
 public:
-  static already_AddRefed<VRDisplayManagerOculus> Create();
+  static already_AddRefed<VRSystemManagerOculus> Create();
   virtual bool Init() override;
   virtual void Destroy() override;
   virtual void GetHMDs(nsTArray<RefPtr<VRDisplayHost> >& aHMDResult) override;
+  virtual void HandleInput() override;
+  virtual void GetControllers(nsTArray<RefPtr<VRControllerHost>>&
+                              aControllerResult) override;
+  virtual void ScanForControllers() override;
+  virtual void RemoveControllers() override;
+
 protected:
-  VRDisplayManagerOculus()
+  VRSystemManagerOculus()
     : mOculusInitialized(false)
   { }
 
+private:
+  virtual void HandleButtonPress(uint32_t aControllerIdx,
+                                 uint64_t aButtonPressed) override;
+  virtual void HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
+                              float aValue) override;
+  virtual void HandlePoseTracking(uint32_t aControllerIdx,
+                                  const dom::GamepadPoseState& aPose,
+                                  VRControllerHost* aController) override;
+
   RefPtr<impl::VRDisplayOculus> mHMDInfo;
+  RefPtr<nsIThread> mOculusThread;
+  ovrSession mSession;
   bool mOculusInitialized;
-  RefPtr<nsIThread> mOculusThread;
 };
 
 } // namespace gfx
 } // namespace mozilla
 
 #endif /* GFX_VR_OCULUS_H */
--- a/gfx/vr/gfxVROpenVR.cpp
+++ b/gfx/vr/gfxVROpenVR.cpp
@@ -389,64 +389,65 @@ VRDisplayOpenVR::SubmitFrame(TextureSour
 
 void
 VRDisplayOpenVR::NotifyVSync()
 {
   // We update mIsConneced once per frame.
   mDisplayInfo.mIsConnected = vr_IsHmdPresent();
 }
 
-VRDisplayManagerOpenVR::VRDisplayManagerOpenVR()
+VRSystemManagerOpenVR::VRSystemManagerOpenVR()
   : mOpenVRInstalled(false)
 {
 }
 
-/*static*/ already_AddRefed<VRDisplayManagerOpenVR>
-VRDisplayManagerOpenVR::Create()
+/*static*/ already_AddRefed<VRSystemManagerOpenVR>
+VRSystemManagerOpenVR::Create()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!gfxPrefs::VREnabled() || !gfxPrefs::VROpenVREnabled()) {
     return nullptr;
   }
 
   if (!LoadOpenVRRuntime()) {
     return nullptr;
   }
 
-  RefPtr<VRDisplayManagerOpenVR> manager = new VRDisplayManagerOpenVR();
+  RefPtr<VRSystemManagerOpenVR> manager = new VRSystemManagerOpenVR();
   return manager.forget();
 }
 
 bool
-VRDisplayManagerOpenVR::Init()
+VRSystemManagerOpenVR::Init()
 {
   if (mOpenVRInstalled)
     return true;
 
   if (!vr_IsRuntimeInstalled())
     return false;
 
   mOpenVRInstalled = true;
   return true;
 }
 
 void
-VRDisplayManagerOpenVR::Destroy()
+VRSystemManagerOpenVR::Destroy()
 {
   if (mOpenVRInstalled) {
     if (mOpenVRHMD) {
       mOpenVRHMD = nullptr;
     }
+    RemoveControllers();
     mOpenVRInstalled = false;
   }
 }
 
 void
-VRDisplayManagerOpenVR::GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult)
+VRSystemManagerOpenVR::GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult)
 {
   if (!mOpenVRInstalled) {
     return;
   }
 
   if (!vr_IsHmdPresent()) {
     if (mOpenVRHMD) {
       mOpenVRHMD = nullptr;
@@ -470,108 +471,27 @@ VRDisplayManagerOpenVR::GetHMDs(nsTArray
       return;
     }
     ::vr::IVRCompositor *compositor = (::vr::IVRCompositor*)vr_GetGenericInterface(::vr::IVRCompositor_Version, &err);
     if (err || !compositor) {
       vr_ShutdownInternal();
       return;
     }
 
+    mVRSystem = system;
     mOpenVRHMD = new VRDisplayOpenVR(system, chaperone, compositor);
   }
 
   if (mOpenVRHMD) {
     aHMDResult.AppendElement(mOpenVRHMD);
   }
 }
 
-VRControllerOpenVR::VRControllerOpenVR()
-  : VRControllerHost(VRDeviceType::OpenVR)
-{
-  MOZ_COUNT_CTOR_INHERITED(VRControllerOpenVR, VRControllerHost);
-  mControllerInfo.mControllerName.AssignLiteral("OpenVR HMD");
-  mControllerInfo.mMappingType = GamepadMappingType::_empty;
-  mControllerInfo.mNumButtons = gNumOpenVRButtonMask;
-  mControllerInfo.mNumAxes = gNumOpenVRAxis;
-}
-
-VRControllerOpenVR::~VRControllerOpenVR()
-{
-  MOZ_COUNT_DTOR_INHERITED(VRControllerOpenVR, VRControllerHost);
-}
-
 void
-VRControllerOpenVR::SetTrackedIndex(uint32_t aTrackedIndex)
-{
-  mTrackedIndex = aTrackedIndex;
-}
-
-uint32_t
-VRControllerOpenVR::GetTrackedIndex()
-{
-  return mTrackedIndex;
-}
-
-VRControllerManagerOpenVR::VRControllerManagerOpenVR()
-  : mOpenVRInstalled(false), mVRSystem(nullptr)
-{
-}
-
-VRControllerManagerOpenVR::~VRControllerManagerOpenVR()
-{
-  Destroy();
-}
-
-/*static*/ already_AddRefed<VRControllerManagerOpenVR>
-VRControllerManagerOpenVR::Create()
-{
-  if (!gfxPrefs::VREnabled() || !gfxPrefs::VROpenVREnabled()) {
-    return nullptr;
-  }
-
-  RefPtr<VRControllerManagerOpenVR> manager = new VRControllerManagerOpenVR();
-  return manager.forget();
-}
-
-bool
-VRControllerManagerOpenVR::Init()
-{
-  if (mOpenVRInstalled)
-    return true;
-
-  if (!vr_IsRuntimeInstalled())
-    return false;
-
-  // Loading the OpenVR Runtime
-  vr::EVRInitError err = vr::VRInitError_None;
-
-  vr_InitInternal(&err, vr::VRApplication_Scene);
-  if (err != vr::VRInitError_None) {
-    return false;
-  }
-
-  mVRSystem = (vr::IVRSystem *)vr_GetGenericInterface(vr::IVRSystem_Version, &err);
-  if ((err != vr::VRInitError_None) || !mVRSystem) {
-    vr_ShutdownInternal();
-    return false;
-  }
-
-  mOpenVRInstalled = true;
-  return true;
-}
-
-void
-VRControllerManagerOpenVR::Destroy()
-{
-  RemoveDevices();
-  mOpenVRInstalled = false;
-}
-
-void
-VRControllerManagerOpenVR::HandleInput()
+VRSystemManagerOpenVR::HandleInput()
 {
   RefPtr<impl::VRControllerOpenVR> controller;
   vr::VRControllerState_t state;
   uint32_t axis = 0;
 
   if (!mOpenVRInstalled) {
     return;
   }
@@ -640,18 +560,18 @@ VRControllerManagerOpenVR::HandleInput()
       poseState.linearVelocity[1] = pose.vVelocity.v[1];
       poseState.linearVelocity[2] = pose.vVelocity.v[2];
       HandlePoseTracking(controller->GetIndex(), poseState, controller);
     }
   }
 }
 
 void
-VRControllerManagerOpenVR::HandleButtonPress(uint32_t aControllerIdx,
-                                             uint64_t aButtonPressed)
+VRSystemManagerOpenVR::HandleButtonPress(uint32_t aControllerIdx,
+                                         uint64_t aButtonPressed)
 {
   uint64_t buttonMask = 0;
   RefPtr<impl::VRControllerOpenVR> controller;
   controller = mOpenVRController[aControllerIdx];
   uint64_t diff = (controller->GetButtonPressed() ^ aButtonPressed);
 
   if (!diff) {
     return;
@@ -667,50 +587,50 @@ VRControllerManagerOpenVR::HandleButtonP
       NewButtonEvent(aControllerIdx, i, diff & aButtonPressed);
     }
   }
 
   controller->SetButtonPressed(aButtonPressed);
 }
 
 void
-VRControllerManagerOpenVR::HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
-                                          float aValue)
+VRSystemManagerOpenVR::HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
+                                      float aValue)
 {
   if (aValue != 0.0f) {
     NewAxisMove(aControllerIdx, aAxis, aValue);
   }
 }
 
 void
-VRControllerManagerOpenVR::HandlePoseTracking(uint32_t aControllerIdx,
-                                              const GamepadPoseState& aPose,
-                                              VRControllerHost* aController)
+VRSystemManagerOpenVR::HandlePoseTracking(uint32_t aControllerIdx,
+                                          const GamepadPoseState& aPose,
+                                          VRControllerHost* aController)
 {
   if (aPose != aController->GetPose()) {
     aController->SetPose(aPose);
     NewPoseState(aControllerIdx, aPose);
   }
 }
 
 void
-VRControllerManagerOpenVR::GetControllers(nsTArray<RefPtr<VRControllerHost>>& aControllerResult)
+VRSystemManagerOpenVR::GetControllers(nsTArray<RefPtr<VRControllerHost>>& aControllerResult)
 {
   if (!mOpenVRInstalled) {
     return;
   }
 
   aControllerResult.Clear();
   for (uint32_t i = 0; i < mOpenVRController.Length(); ++i) {
     aControllerResult.AppendElement(mOpenVRController[i]);
   }
 }
 
 void
-VRControllerManagerOpenVR::ScanForDevices()
+VRSystemManagerOpenVR::ScanForControllers()
 {
   if (!mVRSystem)
     return;
 
   vr::TrackedDeviceIndex_t trackedIndexArray[vr::k_unMaxTrackedDeviceCount];
   uint32_t newControllerCount = 0;
   // Basically, we would have HMDs in the tracked devices,
   // but we are just interested in the controllers.
@@ -764,13 +684,40 @@ VRControllerManagerOpenVR::ScanForDevice
       AddGamepad("OpenVR Gamepad", GamepadMappingType::_empty,
                  hand, gNumOpenVRButtonMask, gNumOpenVRAxis);
       ++mControllerCount;
     }
   }
 }
 
 void
-VRControllerManagerOpenVR::RemoveDevices()
+VRSystemManagerOpenVR::RemoveControllers()
 {
   mOpenVRController.Clear();
   mControllerCount = 0;
-}
\ No newline at end of file
+}
+
+VRControllerOpenVR::VRControllerOpenVR()
+  : VRControllerHost(VRDeviceType::OpenVR)
+{
+  MOZ_COUNT_CTOR_INHERITED(VRControllerOpenVR, VRControllerHost);
+  mControllerInfo.mControllerName.AssignLiteral("OpenVR HMD");
+  mControllerInfo.mMappingType = GamepadMappingType::_empty;
+  mControllerInfo.mNumButtons = gNumOpenVRButtonMask;
+  mControllerInfo.mNumAxes = gNumOpenVRAxis;
+}
+
+VRControllerOpenVR::~VRControllerOpenVR()
+{
+  MOZ_COUNT_DTOR_INHERITED(VRControllerOpenVR, VRControllerHost);
+}
+
+void
+VRControllerOpenVR::SetTrackedIndex(uint32_t aTrackedIndex)
+{
+  mTrackedIndex = aTrackedIndex;
+}
+
+uint32_t
+VRControllerOpenVR::GetTrackedIndex()
+{
+  return mTrackedIndex;
+}
--- a/gfx/vr/gfxVROpenVR.h
+++ b/gfx/vr/gfxVROpenVR.h
@@ -63,79 +63,62 @@ protected:
   ::vr::IVRChaperone *mVRChaperone;
   ::vr::IVRCompositor *mVRCompositor;
 
   bool mIsPresenting;
 
   void UpdateStageParameters();
 };
 
-} // namespace impl
-
-class VRDisplayManagerOpenVR : public VRDisplayManager
-{
-public:
-  static already_AddRefed<VRDisplayManagerOpenVR> Create();
-
-  virtual bool Init() override;
-  virtual void Destroy() override;
-  virtual void GetHMDs(nsTArray<RefPtr<VRDisplayHost> >& aHMDResult) override;
-protected:
-  VRDisplayManagerOpenVR();
-
-  // there can only be one
-  RefPtr<impl::VRDisplayOpenVR> mOpenVRHMD;
-  bool mOpenVRInstalled;
-};
-
-namespace impl {
-
 class VRControllerOpenVR : public VRControllerHost
 {
 public:
   explicit VRControllerOpenVR();
   void SetTrackedIndex(uint32_t aTrackedIndex);
   uint32_t GetTrackedIndex();
 
 protected:
   virtual ~VRControllerOpenVR();
 
   // The index of tracked devices from vr::IVRSystem.
   uint32_t mTrackedIndex;
 };
 
 } // namespace impl
 
-class VRControllerManagerOpenVR : public VRControllerManager
+class VRSystemManagerOpenVR : public VRSystemManager
 {
 public:
-  static already_AddRefed<VRControllerManagerOpenVR> Create();
+  static already_AddRefed<VRSystemManagerOpenVR> Create();
 
   virtual bool Init() override;
   virtual void Destroy() override;
+  virtual void GetHMDs(nsTArray<RefPtr<VRDisplayHost> >& aHMDResult) override;
   virtual void HandleInput() override;
   virtual void GetControllers(nsTArray<RefPtr<VRControllerHost>>&
                               aControllerResult) override;
-  virtual void ScanForDevices() override;
-  virtual void RemoveDevices() override;
+  virtual void ScanForControllers() override;
+  virtual void RemoveControllers() override;
+
+protected:
+  VRSystemManagerOpenVR();
 
 private:
-  VRControllerManagerOpenVR();
-  ~VRControllerManagerOpenVR();
-
   virtual void HandleButtonPress(uint32_t aControllerIdx,
                                  uint64_t aButtonPressed) override;
   virtual void HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
                               float aValue) override;
   virtual void HandlePoseTracking(uint32_t aControllerIdx,
                                   const dom::GamepadPoseState& aPose,
                                   VRControllerHost* aController) override;
 
-  bool mOpenVRInstalled;
+  // there can only be one
+  RefPtr<impl::VRDisplayOpenVR> mOpenVRHMD;
   nsTArray<RefPtr<impl::VRControllerOpenVR>> mOpenVRController;
   vr::IVRSystem *mVRSystem;
+  bool mOpenVRInstalled;
 };
 
 } // namespace gfx
 } // namespace mozilla
 
 
 #endif /* GFX_VR_OPENVR_H */
--- a/js/moz.configure
+++ b/js/moz.configure
@@ -222,8 +222,33 @@ set_config('JS_HAS_CTYPES', js_has_ctype
 set_define('JS_HAS_CTYPES', js_has_ctypes)
 add_old_configure_assignment('JS_HAS_CTYPES', js_has_ctypes)
 
 @depends('--enable-ctypes', '--enable-compile-environment', '--help')
 def ctypes_and_compile_environment(ctypes, compile_environment, _):
     return ctypes and compile_environment
 
 include('ffi.configure', when=ctypes_and_compile_environment)
+
+
+# Support various fuzzing options
+# ==============================================================
+with only_when('--enable-compile-environment'):
+    js_option('--enable-fuzzing', help='Enable fuzzing support')
+
+    @depends('--enable-fuzzing')
+    def enable_fuzzing(value):
+        if value:
+            return True
+
+    @depends(enable_fuzzing,
+             try_compile(body='__AFL_COMPILER;',
+                         check_msg='for AFL compiler',
+                         when='--enable-fuzzing'))
+    def enable_libfuzzer(fuzzing, afl):
+        if fuzzing and not afl:
+            return True
+
+    set_config('FUZZING', enable_fuzzing)
+    set_define('FUZZING', enable_fuzzing)
+
+    set_config('LIBFUZZER', enable_libfuzzer)
+    set_define('LIBFUZZER', enable_libfuzzer)
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -311,16 +311,17 @@ ServoRestyleManager::ProcessPendingResty
 
   ServoStyleSet* styleSet = StyleSet();
   nsIDocument* doc = PresContext()->Document();
 
   // XXXbholley: Should this be while() per bug 1316247?
   if (HasPendingRestyles()) {
     mInStyleRefresh = true;
     styleSet->StyleDocument();
+    PresContext()->EffectCompositor()->ClearElementsToRestyle();
 
     // First do any queued-up frame creation. (see bugs 827239 and 997506).
     //
     // XXXEmilio I'm calling this to avoid random behavior changes, since we
     // delay frame construction after styling we should re-check once our
     // model is more stable whether we can skip this call.
     //
     // Note this has to be *after* restyling, because otherwise frame
--- a/layout/base/ShapeUtils.cpp
+++ b/layout/base/ShapeUtils.cpp
@@ -72,9 +72,37 @@ ShapeUtils::ComputeCircleRadius(StyleBas
       SVGContentUtils::ComputeNormalizedHypotenuse(aRefBox.width,
                                                    aRefBox.height);
     r = nsRuleNode::ComputeCoordPercentCalc(coords[0],
                                             NSToCoordRound(referenceLength));
   }
   return r;
 }
 
+nsSize
+ShapeUtils::ComputeEllipseRadii(StyleBasicShape* const aBasicShape,
+                                const nsPoint& aCenter,
+                                const nsRect& aRefBox)
+{
+  const nsTArray<nsStyleCoord>& coords = aBasicShape->Coordinates();
+  MOZ_ASSERT(coords.Length() == 2, "wrong number of arguments");
+  nsSize radii;
+
+  if (coords[0].GetUnit() == eStyleUnit_Enumerated) {
+    const StyleShapeRadius radiusX = coords[0].GetEnumValue<StyleShapeRadius>();
+    radii.width = ComputeShapeRadius(radiusX, aCenter.x, aRefBox.x,
+                                     aRefBox.XMost());
+  } else {
+    radii.width = nsRuleNode::ComputeCoordPercentCalc(coords[0], aRefBox.width);
+  }
+
+  if (coords[1].GetUnit() == eStyleUnit_Enumerated) {
+    const StyleShapeRadius radiusY = coords[1].GetEnumValue<StyleShapeRadius>();
+    radii.height = ComputeShapeRadius(radiusY, aCenter.y, aRefBox.y,
+                                      aRefBox.YMost());
+  } else {
+    radii.height = nsRuleNode::ComputeCoordPercentCalc(coords[1], aRefBox.height);
+  }
+
+  return radii;
+}
+
 } // namespace mozilla
--- a/layout/base/ShapeUtils.h
+++ b/layout/base/ShapeUtils.h
@@ -43,13 +43,22 @@ struct ShapeUtils final
 
   // Compute the radius for a circle.
   // @param aCenter the center of the circle.
   // @param aRefBox the reference box of the circle.
   // @return The length of the radius in app units.
   static nscoord ComputeCircleRadius(
     mozilla::StyleBasicShape* const aBasicShape,
     const nsPoint& aCenter, const nsRect& aRefBox);
+
+  // Compute the radii for an ellipse.
+  // @param aCenter the center of the ellipse.
+  // @param aRefBox the reference box of the ellipse.
+  // @return The radii of the ellipse in app units. The width and height
+  // represent the x-axis and y-axis radii of the ellipse.
+  static nsSize ComputeEllipseRadii(
+    mozilla::StyleBasicShape* const aBasicShape,
+    const nsPoint& aCenter, const nsRect& aRefBox);
 };
 
 } // namespace mozilla
 
 #endif // mozilla_ShapeUtils_h
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -6780,17 +6780,17 @@ nsLayoutUtils::HasNonZeroCorner(const ns
 {
   NS_FOR_CSS_HALF_CORNERS(corner) {
     if (NonZeroStyleCoord(aCorners.Get(corner)))
       return true;
   }
   return false;
 }
 
-// aCorner is a "full corner" value, i.e. NS_CORNER_TOP_LEFT etc
+// aCorner is a "full corner" value, i.e. eCornerTopLeft etc.
 static bool IsCornerAdjacentToSide(uint8_t aCorner, Side aSide)
 {
   static_assert((int)eSideTop == eCornerTopLeft, "Check for Full Corner");
   static_assert((int)eSideRight == eCornerTopRight, "Check for Full Corner");
   static_assert((int)eSideBottom == eCornerBottomRight, "Check for Full Corner");
   static_assert((int)eSideLeft == eCornerBottomLeft, "Check for Full Corner");
   static_assert((int)eSideTop == ((eCornerTopRight - 1)&3), "Check for Full Corner");
   static_assert((int)eSideRight == ((eCornerBottomRight - 1)&3), "Check for Full Corner");
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -1030,17 +1030,17 @@ public:
                                            const nsRect& aContainedRect);
   static nsIntRegion RoundedRectIntersectIntRect(const nsIntRect& aRoundedRect,
                                                  const RectCornerRadii& aCornerRadii,
                                                  const nsIntRect& aContainedRect);
 
   /**
    * Return whether any part of aTestRect is inside of the rounded
    * rectangle formed by aBounds and aRadii (which are indexed by the
-   * NS_CORNER_* constants in nsStyleConsts.h). This is precise.
+   * enum HalfCorner constants in gfx/2d/Types.h). This is precise.
    */
   static bool RoundedRectIntersectsRect(const nsRect& aRoundedRect,
                                         const nscoord aRadii[8],
                                         const nsRect& aTestRect);
 
   enum class PaintFrameFlags : uint32_t {
     PAINT_IN_TRANSFORM = 0x01,
     PAINT_SYNC_DECODE_IMAGES = 0x02,
--- a/layout/generic/nsFloatManager.cpp
+++ b/layout/generic/nsFloatManager.cpp
@@ -613,63 +613,81 @@ nsFloatManager::BoxShapeInfo::LineRight(
   return mShapeBoxRect.XMost() - lineRightDiff;
 }
 
 /////////////////////////////////////////////////////////////////////////////
 // CircleShapeInfo
 
 nsFloatManager::CircleShapeInfo::CircleShapeInfo(
   StyleBasicShape* const aBasicShape,
-  nscoord aLineLeft,
-  nscoord aBlockStart,
   const LogicalRect& aShapeBoxRect,
   WritingMode aWM,
   const nsSize& aContainerSize)
 {
   // Use physical coordinates to compute the center of the circle() since
   // the <position> keywords such as 'left', 'top', etc. are physical.
   // https://drafts.csswg.org/css-shapes-1/#funcdef-circle
   nsRect physicalShapeBoxRect =
     aShapeBoxRect.GetPhysicalRect(aWM, aContainerSize);
   nsPoint physicalCenter =
     ShapeUtils::ComputeCircleOrEllipseCenter(aBasicShape, physicalShapeBoxRect);
-  mRadius =
-    ShapeUtils::ComputeCircleRadius(aBasicShape, physicalCenter,
-                                    physicalShapeBoxRect);
+  nscoord radius = ShapeUtils::ComputeCircleRadius(aBasicShape, physicalCenter,
+                                                   physicalShapeBoxRect);
+  mRadii = nsSize(radius, radius);
+  mCenter = ConvertPhysicalToLogical(aWM, physicalCenter, aContainerSize);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// EllipseShapeInfo
 
-  // Convert the coordinate space back to the same as FloatInfo::mRect.
-  // mCenter.x is in the line-axis of the frame manager and mCenter.y are in
-  // the frame manager's real block-axis.
-  LogicalPoint logicalCenter(aWM, physicalCenter, aContainerSize);
-  mCenter = nsPoint(logicalCenter.LineRelative(aWM, aContainerSize) + aLineLeft,
-                    logicalCenter.B(aWM) + aBlockStart);
+nsFloatManager::EllipseShapeInfo::EllipseShapeInfo(
+  StyleBasicShape* const aBasicShape,
+  const LogicalRect& aShapeBoxRect,
+  WritingMode aWM,
+  const nsSize& aContainerSize)
+{
+  // Use physical coordinates to compute the center of the ellipse() since
+  // the <position> keywords such as 'left', 'top', etc. are physical.
+  // https://drafts.csswg.org/css-shapes-1/#funcdef-ellipse
+  nsRect physicalShapeBoxRect =
+    aShapeBoxRect.GetPhysicalRect(aWM, aContainerSize);
+  nsPoint physicalCenter =
+    ShapeUtils::ComputeCircleOrEllipseCenter(aBasicShape, physicalShapeBoxRect);
+  nsSize physicalRadii =
+    ShapeUtils::ComputeEllipseRadii(aBasicShape, physicalCenter,
+                                    physicalShapeBoxRect);
+  LogicalSize logicalRadii(aWM, physicalRadii);
+  mRadii = nsSize(logicalRadii.ISize(aWM), logicalRadii.BSize(aWM));
+  mCenter = ConvertPhysicalToLogical(aWM, physicalCenter, aContainerSize);
 }
 
 nscoord
-nsFloatManager::CircleShapeInfo::LineLeft(WritingMode aWM,
-                                          const nscoord aBStart,
-                                          const nscoord aBEnd) const
+nsFloatManager::EllipseShapeInfo::LineLeft(WritingMode aWM,
+                                           const nscoord aBStart,
+                                           const nscoord aBEnd) const
 {
   nscoord lineLeftDiff =
     ComputeEllipseLineInterceptDiff(BStart(), BEnd(),
-                                    mRadius, mRadius, mRadius, mRadius,
+                                    mRadii.width, mRadii.height,
+                                    mRadii.width, mRadii.height,
                                     aBStart, aBEnd);
-  return mCenter.x - mRadius + lineLeftDiff;
+  return mCenter.x - mRadii.width + lineLeftDiff;
 }
 
 nscoord
-nsFloatManager::CircleShapeInfo::LineRight(WritingMode aWM,
-                                           const nscoord aBStart,
-                                           const nscoord aBEnd) const
+nsFloatManager::EllipseShapeInfo::LineRight(WritingMode aWM,
+                                            const nscoord aBStart,
+                                            const nscoord aBEnd) const
 {
   nscoord lineRightDiff =
     ComputeEllipseLineInterceptDiff(BStart(), BEnd(),
-                                    mRadius, mRadius, mRadius, mRadius,
+                                    mRadii.width, mRadii.height,
+                                    mRadii.width, mRadii.height,
                                     aBStart, aBEnd);
-  return mCenter.x + mRadius - lineRightDiff;
+  return mCenter.x + mRadii.width - lineRightDiff;
 }
 
 /////////////////////////////////////////////////////////////////////////////
 // FloatInfo
 
 nsFloatManager::FloatInfo::FloatInfo(nsIFrame* aFrame,
                                      nscoord aLineLeft, nscoord aBlockStart,
                                      const LogicalRect& aMarginRect,
@@ -714,30 +732,46 @@ nsFloatManager::FloatInfo::FloatInfo(nsI
       break;
     case StyleShapeOutsideShapeBox::NoBox:
       MOZ_ASSERT(shapeOutside.GetType() != StyleShapeSourceType::Box,
                  "Box source type must have <shape-box> specified!");
       break;
   }
 
   if (shapeOutside.GetType() == StyleShapeSourceType::Box) {
-    nsRect shapeBoxRect(rect.LineLeft(aWM, aContainerSize) + aLineLeft,
-                        rect.BStart(aWM) + aBlockStart,
+    nsRect shapeBoxRect(rect.LineLeft(aWM, aContainerSize), rect.BStart(aWM),
                         rect.ISize(aWM), rect.BSize(aWM));
     mShapeInfo = MakeUnique<BoxShapeInfo>(shapeBoxRect, mFrame);
   } else if (shapeOutside.GetType() == StyleShapeSourceType::Shape) {
     StyleBasicShape* const basicShape = shapeOutside.GetBasicShape();
 
-    if (basicShape->GetShapeType() == StyleBasicShapeType::Circle) {
-      mShapeInfo = MakeUnique<CircleShapeInfo>(basicShape, aLineLeft, aBlockStart,
-                                               rect, aWM, aContainerSize);
+    switch (basicShape->GetShapeType()) {
+      case StyleBasicShapeType::Polygon:
+        // Bug 1326409 - Implement the rendering of basic shape polygon()
+        // for CSS shape-outside.
+        break;
+      case StyleBasicShapeType::Circle:
+        mShapeInfo =
+          MakeUnique<CircleShapeInfo>(basicShape, rect, aWM, aContainerSize);
+        break;
+      case StyleBasicShapeType::Ellipse:
+        mShapeInfo =
+          MakeUnique<EllipseShapeInfo>(basicShape, rect, aWM, aContainerSize);
+        break;
+      case StyleBasicShapeType::Inset:
+        // Bug 1326407 - Implement the rendering of basic shape inset() for
+        // CSS shape-outside.
+        break;
     }
   } else {
     MOZ_ASSERT_UNREACHABLE("Unknown StyleShapeSourceType!");
   }
+
+  // Translate the shape to the same origin as nsFloatManager.
+  mShapeInfo->Translate(aLineLeft, aBlockStart);
 }
 
 #ifdef NS_BUILD_REFCNT_LOGGING
 nsFloatManager::FloatInfo::FloatInfo(FloatInfo&& aOther)
   : mFrame(Move(aOther.mFrame))
   , mLeftBEnd(Move(aOther.mLeftBEnd))
   , mRightBEnd(Move(aOther.mRightBEnd))
   , mRect(Move(aOther.mRect))
@@ -830,16 +864,19 @@ nsFloatManager::FloatInfo::IsEmpty(Shape
 
   MOZ_ASSERT(aShapeType == ShapeType::ShapeOutside);
   if (!mShapeInfo) {
     return IsEmpty();
   }
   return mShapeInfo->IsEmpty();
 }
 
+/////////////////////////////////////////////////////////////////////////////
+// ShapeInfo
+
 /* static */ nscoord
 nsFloatManager::ShapeInfo::ComputeEllipseLineInterceptDiff(
   const nscoord aShapeBoxBStart, const nscoord aShapeBoxBEnd,
   const nscoord aBStartCornerRadiusL, const nscoord aBStartCornerRadiusB,
   const nscoord aBEndCornerRadiusL, const nscoord aBEndCornerRadiusB,
   const nscoord aBandBStart, const nscoord aBandBEnd)
 {
   // An example for the band intersecting with the top right corner of an
@@ -910,16 +947,27 @@ nsFloatManager::ShapeInfo::XInterceptAtY
                                          const nscoord aRadiusX,
                                          const nscoord aRadiusY)
 {
   // Solve for x in the ellipse equation (x/radiusX)^2 + (y/radiusY)^2 = 1.
   MOZ_ASSERT(aRadiusY > 0);
   return aRadiusX * std::sqrt(1 - (aY * aY) / double(aRadiusY * aRadiusY));
 }
 
+/* static */ nsPoint
+nsFloatManager::ShapeInfo::ConvertPhysicalToLogical(
+  WritingMode aWM,
+  const nsPoint& aPoint,
+  const nsSize& aContainerSize)
+{
+  LogicalPoint logicalPoint(aWM, aPoint, aContainerSize);
+  return nsPoint(logicalPoint.LineRelative(aWM, aContainerSize),
+                 logicalPoint.B(aWM));
+}
+
 //----------------------------------------------------------------------
 
 nsAutoFloatManager::~nsAutoFloatManager()
 {
   // Restore the old float manager in the reflow input if necessary.
   if (mNew) {
 #ifdef DEBUG
     if (nsBlockFrame::gNoisyFloatManager) {
--- a/layout/generic/nsFloatManager.h
+++ b/layout/generic/nsFloatManager.h
@@ -59,16 +59,22 @@ struct nsFlowAreaRect {
  * nsFloatManager methods should be the containing block's writing mode.
  *
  * However, according to the abstract-to-physical mappings table [2], the
  * 'direction' property of the containing block doesn't affect the
  * interpretation of line-right and line-left. We actually implement this by
  * passing in the writing mode of the block formatting context (BFC), i.e.
  * the of BlockReflowInput's writing mode.
  *
+ * nsFloatManager uses a special logical coordinate space with inline
+ * coordinates on the line-axis and block coordinates on the block-axis
+ * based on the writing mode of the block formatting context. All the
+ * physical types like nsRect, nsPoint, etc. use this coordinate space. See
+ * FloatInfo::mRect for an example.
+ *
  * [1] https://drafts.csswg.org/css-writing-modes/#line-mappings
  * [2] https://drafts.csswg.org/css-writing-modes/#logical-to-physical
  */
 class nsFloatManager {
 public:
   explicit nsFloatManager(nsIPresShell* aPresShell, mozilla::WritingMode aWM);
   ~nsFloatManager();
 
@@ -102,17 +108,17 @@ public:
    * as a delta against the mRect, so repositioning the frame will
    * also reposition the float region.
    */
   static void StoreRegionFor(mozilla::WritingMode aWM,
                              nsIFrame* aFloat,
                              const mozilla::LogicalRect& aRegion,
                              const nsSize& aContainerSize);
 
-  // Structure that stores the current state of a frame manager for
+  // Structure that stores the current state of a float manager for
   // Save/Restore purposes.
   struct SavedState {
     explicit SavedState() {}
   private:
     uint32_t mFloatInfoCount;
     nscoord mLineLeft, mBlockStart;
     bool mPushedLeftFloatPastBreak;
     bool mPushedRightFloatPastBreak;
@@ -347,16 +353,19 @@ private:
                              const nscoord aBEnd) const = 0;
     virtual nscoord LineRight(mozilla::WritingMode aWM,
                               const nscoord aBStart,
                               const nscoord aBEnd) const = 0;
     virtual nscoord BStart() const = 0;
     virtual nscoord BEnd() const = 0;
     virtual bool IsEmpty() const = 0;
 
+    // Translate the current origin by the specified offsets.
+    virtual void Translate(nscoord aLineLeft, nscoord aBlockStart) = 0;
+
   protected:
     // Compute the minimum line-axis difference between the bounding shape
     // box and its rounded corner within the given band (block-axis region).
     // This is used as a helper function to compute the LineRight() and
     // LineLeft(). See the picture in the implementation for an example.
     // RadiusL and RadiusB stand for radius on the line-axis and block-axis.
     //
     // Returns radius-x diff on the line-axis, or 0 if there's no rounded
@@ -364,16 +373,22 @@ private:
     static nscoord ComputeEllipseLineInterceptDiff(
       const nscoord aShapeBoxBStart, const nscoord aShapeBoxBEnd,
       const nscoord aBStartCornerRadiusL, const nscoord aBStartCornerRadiusB,
       const nscoord aBEndCornerRadiusL, const nscoord aBEndCornerRadiusB,
       const nscoord aBandBStart, const nscoord aBandBEnd);
 
     static nscoord XInterceptAtY(const nscoord aY, const nscoord aRadiusX,
                                  const nscoord aRadiusY);
+
+    // Convert the coordinate space from physical to the logical space used
+    // in nsFloatManager, which is the same as FloatInfo::mRect.
+    static nsPoint ConvertPhysicalToLogical(mozilla::WritingMode aWM,
+                                            const nsPoint& aPoint,
+                                            const nsSize& aContainerSize);
   };
 
   // Implements shape-outside: <shape-box>.
   class BoxShapeInfo final : public ShapeInfo
   {
   public:
     BoxShapeInfo(const nsRect& aShapeBoxRect, nsIFrame* const aFrame)
       : mShapeBoxRect(aShapeBoxRect)
@@ -386,52 +401,73 @@ private:
                      const nscoord aBEnd) const override;
     nscoord LineRight(mozilla::WritingMode aWM,
                       const nscoord aBStart,
                       const nscoord aBEnd) const override;
     nscoord BStart() const override { return mShapeBoxRect.y; }
     nscoord BEnd() const override { return mShapeBoxRect.YMost(); }
     bool IsEmpty() const override { return mShapeBoxRect.IsEmpty(); };
 
+    void Translate(nscoord aLineLeft, nscoord aBlockStart) override
+    {
+      mShapeBoxRect.MoveBy(aLineLeft, aBlockStart);
+    }
+
   private:
     // This is the reference box of css shape-outside if specified, which
     // implements the <shape-box> value in the CSS Shapes Module Level 1.
     // The coordinate space is the same as FloatInfo::mRect.
-    const nsRect mShapeBoxRect;
+    nsRect mShapeBoxRect;
     // The frame of the float.
     nsIFrame* const mFrame;
   };
 
-  // Implements shape-outside: circle().
-  class CircleShapeInfo final : public ShapeInfo
+  // Implements shape-outside: ellipse().
+  class EllipseShapeInfo : public ShapeInfo
   {
   public:
-    CircleShapeInfo(mozilla::StyleBasicShape* const aBasicShape,
-                    nscoord aLineLeft,
-                    nscoord aBlockStart,
-                    const mozilla::LogicalRect& aShapeBoxRect,
-                    mozilla::WritingMode aWM,
-                    const nsSize& aContainerSize);
+    EllipseShapeInfo(mozilla::StyleBasicShape* const aBasicShape,
+                     const mozilla::LogicalRect& aShapeBoxRect,
+                     mozilla::WritingMode aWM,
+                     const nsSize& aContainerSize);
 
     nscoord LineLeft(mozilla::WritingMode aWM,
                      const nscoord aBStart,
                      const nscoord aBEnd) const override;
     nscoord LineRight(mozilla::WritingMode aWM,
                       const nscoord aBStart,
                       const nscoord aBEnd) const override;
-    nscoord BStart() const override { return mCenter.y - mRadius; }
-    nscoord BEnd() const override { return mCenter.y + mRadius; }
-    bool IsEmpty() const override { return mRadius == 0; };
+    nscoord BStart() const override { return mCenter.y - mRadii.height; }
+    nscoord BEnd() const override { return mCenter.y + mRadii.height; }
+    bool IsEmpty() const override { return mRadii.IsEmpty(); };
 
-  private:
-    // The position of the center of the circle. The coordinate space is the
+    void Translate(nscoord aLineLeft, nscoord aBlockStart) override
+    {
+      mCenter.MoveBy(aLineLeft, aBlockStart);
+    }
+
+  protected:
+    EllipseShapeInfo() = default;
+
+    // The position of the center of the ellipse. The coordinate space is the
     // same as FloatInfo::mRect.
     nsPoint mCenter;
-    // The radius of the circle in app units.
-    nscoord mRadius;
+    // The radii of the ellipse in app units. The width and height represent
+    // the line-axis and block-axis radii of the ellipse.
+    nsSize mRadii;
+  };
+
+  // Implements shape-outside: circle().
+  class CircleShapeInfo final : public EllipseShapeInfo
+  {
+  public:
+    CircleShapeInfo(mozilla::StyleBasicShape* const aBasicShape,
+                    const mozilla::LogicalRect& aShapeBoxRect,
+                    mozilla::WritingMode aWM,
+                    const nsSize& aContainerSize);
   };
 
   struct FloatInfo {
     nsIFrame *const mFrame;
     // The lowest block-ends of left/right floats up to and including
     // this one.
     nscoord mLeftBEnd, mRightBEnd;
 
@@ -460,20 +496,20 @@ private:
 
 #ifdef NS_BUILD_REFCNT_LOGGING
     FloatInfo(FloatInfo&& aOther);
     ~FloatInfo();
 #endif
 
     // NB! This is really a logical rect in a writing mode suitable for
     // placing floats, which is not necessarily the actual writing mode
-    // either of the block which created the frame manager or the block
-    // that is calling the frame manager. The inline coordinates are in
-    // the line-relative axis of the frame manager and its block
-    // coordinates are in the frame manager's real block direction.
+    // either of the block which created the float manager or the block
+    // that is calling the float manager. The inline coordinates are in
+    // the line-relative axis of the float manager and its block
+    // coordinates are in the float manager's block direction.
     nsRect mRect;
     // Pointer to a concrete subclass of ShapeInfo or null, which means that
     // there is no shape-outside.
     mozilla::UniquePtr<ShapeInfo> mShapeInfo;
   };
 
 #ifdef DEBUG
   // Store the writing mode from the block frame which establishes the block
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -1160,17 +1160,17 @@ public:
    */
   virtual nsRect VisualBorderRectRelativeToSelf() const {
     return nsRect(0, 0, mRect.width, mRect.height);
   }
 
   /**
    * Get the size, in app units, of the border radii. It returns FALSE iff all
    * returned radii == 0 (so no border radii), TRUE otherwise.
-   * For the aRadii indexes, use the NS_CORNER_* constants in nsStyleConsts.h
+   * For the aRadii indexes, use the enum HalfCorner constants in gfx/2d/Types.h
    * If a side is skipped via aSkipSides, its corners are forced to 0.
    *
    * All corner radii are then adjusted so they do not require more
    * space than aBorderArea, according to the algorithm in css3-background.
    *
    * aFrameSize is used as the basis for percentage widths and heights.
    * aBorderArea is used for the adjustment of radii that might be too
    * large.
@@ -1187,30 +1187,30 @@ public:
                                  nscoord aRadii[8]);
 
   /*
    * Given a set of border radii for one box (e.g., border box), convert
    * it to the equivalent set of radii for another box (e.g., in to
    * padding box, out to outline box) by reducing radii or increasing
    * nonzero radii as appropriate.
    *
-   * Indices into aRadii are the NS_CORNER_* constants in nsStyleConsts.h
+   * Indices into aRadii are the enum HalfCorner constants in gfx/2d/Types.h
    *
    * Note that InsetBorderRadii is lossy, since it can turn nonzero
    * radii into zero, and OutsetBorderRadii does not inflate zero radii.
    * Therefore, callers should always inset or outset directly from the
    * original value coming from style.
    */
   static void InsetBorderRadii(nscoord aRadii[8], const nsMargin &aOffsets);
   static void OutsetBorderRadii(nscoord aRadii[8], const nsMargin &aOffsets);
 
   /**
    * Fill in border radii for this frame.  Return whether any are nonzero.
-   * Indices into aRadii are the NS_CORNER_* constants in nsStyleConsts.h
-   * aSkipSides is a union of SIDE_BIT_LEFT/RIGHT/TOP/BOTTOM bits that says
+   * Indices into aRadii are the enum HalfCorner constants in gfx/2d/Types.h
+   * aSkipSides is a union of eSideBitsLeft/Right/Top/Bottom bits that says
    * which side(s) to skip.
    *
    * Note: GetMarginBoxBorderRadii() and GetShapeBoxBorderRadii() work only
    * on frames that establish block formatting contexts since they don't
    * participate in margin-collapsing.
    */
   virtual bool GetBorderRadii(const nsSize& aFrameSize,
                               const nsSize& aBorderArea,
--- a/layout/painting/MaskLayerImageCache.h
+++ b/layout/painting/MaskLayerImageCache.h
@@ -110,17 +110,17 @@ public:
     {
       PLDHashNumber hash = HashBytes(&mRect.x, 4*sizeof(gfxFloat));
       hash = AddToHash(hash, HashBytes(mRadii, 8*sizeof(gfxFloat)));
 
       return hash;
     }
 
     gfx::Rect mRect;
-    // Indices into mRadii are the NS_CORNER_* constants in nsStyleConsts.h
+    // Indices into mRadii are the enum HalfCorner constants in gfx/2d/Types.h
     gfxFloat mRadii[8];
 
   private:
     PixelRoundedRect() = delete;
   };
 
   struct MaskLayerImageKeyRef;
 
--- a/layout/reftests/w3c-css/submitted/shapes1/reftest.list
+++ b/layout/reftests/w3c-css/submitted/shapes1/reftest.list
@@ -56,8 +56,30 @@ fails == shape-outside-border-box-border
 == shape-outside-circle-017.html shape-outside-circle-017-ref.html
 == shape-outside-circle-018.html shape-outside-circle-018-ref.html
 == shape-outside-circle-019.html shape-outside-circle-019-ref.html
 == shape-outside-circle-020.html shape-outside-circle-020-ref.html
 == shape-outside-circle-021.html shape-outside-circle-021-ref.html
 == shape-outside-circle-022.html shape-outside-circle-022-ref.html
 == shape-outside-circle-023.html shape-outside-circle-023-ref.html
 == shape-outside-circle-024.html shape-outside-circle-024-ref.html
+
+# Basic shape: ellipse()
+== shape-outside-ellipse-001.html shape-outside-ellipse-001-ref.html
+== shape-outside-ellipse-002.html shape-outside-ellipse-002-ref.html
+== shape-outside-ellipse-003.html shape-outside-ellipse-003-ref.html
+== shape-outside-ellipse-004.html shape-outside-ellipse-004-ref.html
+== shape-outside-ellipse-005.html shape-outside-ellipse-005-ref.html
+== shape-outside-ellipse-006.html shape-outside-ellipse-006-ref.html
+== shape-outside-ellipse-007.html shape-outside-ellipse-007-ref.html
+== shape-outside-ellipse-008.html shape-outside-ellipse-008-ref.html
+== shape-outside-ellipse-009.html shape-outside-ellipse-009-ref.html
+== shape-outside-ellipse-010.html shape-outside-ellipse-009-ref.html
+== shape-outside-ellipse-011.html shape-outside-ellipse-011-ref.html
+== shape-outside-ellipse-012.html shape-outside-ellipse-011-ref.html
+== shape-outside-ellipse-013.html shape-outside-ellipse-013-ref.html
+== shape-outside-ellipse-014.html shape-outside-ellipse-014-ref.html
+== shape-outside-ellipse-015.html shape-outside-ellipse-015-ref.html
+== shape-outside-ellipse-016.html shape-outside-ellipse-016-ref.html
+== shape-outside-ellipse-017.html shape-outside-ellipse-017-ref.html
+== shape-outside-ellipse-018.html shape-outside-ellipse-018-ref.html
+== shape-outside-ellipse-019.html shape-outside-ellipse-019-ref.html
+== shape-outside-ellipse-020.html shape-outside-ellipse-020-ref.html
--- a/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-001.html
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-001.html
@@ -47,10 +47,10 @@
   <body class="container">
     <div class="shape"></div>
     <div class="longbox"></div> <!-- Saturate the margin space -->
     <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
     <div class="longbox"></div> <!-- Saturate the margin space -->
-</body>
+  </body>
 </html>
--- a/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-002.html
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-002.html
@@ -38,10 +38,10 @@
   </style>
 
   <body class="container">
     <div class="shape"></div>
     <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
-</body>
+  </body>
 </html>
--- a/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-003.html
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-003.html
@@ -48,10 +48,10 @@
   <body class="container">
     <div class="shape"></div>
     <div class="longbox"></div> <!-- Saturate the margin space -->
     <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
     <div class="longbox"></div> <!-- Saturate the margin space -->
-</body>
+  </body>
 </html>
--- a/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-004.html
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-004.html
@@ -39,10 +39,10 @@
   </style>
 
   <body class="container">
     <div class="shape"></div>
     <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
-</body>
+  </body>
 </html>
--- a/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-005.html
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-005.html
@@ -48,10 +48,10 @@
   <body class="container">
     <div class="shape"></div>
     <div class="longbox"></div> <!-- Saturate the margin space -->
     <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
     <div class="box" style="block-size: 36px;"></div>
     <div class="box" style="block-size: 36px;"></div>
     <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
     <div class="longbox"></div> <!-- Saturate the margin space -->
-</body>
+  </body>
 </html>
--- a/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-006.html
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-006.html
@@ -49,10 +49,10 @@
   <body class="container">
     <div class="shape"></div>
     <div class="longbox"></div> <!-- Saturate the margin space -->
     <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
     <div class="box" style="block-size: 36px;"></div>
     <div class="box" style="block-size: 36px;"></div>
     <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
     <div class="longbox"></div> <!-- Saturate the margin space -->
-</body>
+  </body>
 </html>
--- a/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-007.html
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-007.html
@@ -48,10 +48,10 @@
   <body class="container">
     <div class="shape"></div>
     <div class="longbox"></div> <!-- Saturate the margin space -->
     <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
     <div class="box" style="block-size: 36px;"></div>
     <div class="box" style="block-size: 36px;"></div>
     <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
     <div class="longbox"></div> <!-- Saturate the margin space -->
-</body>
+  </body>
 </html>
--- a/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-008.html
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-008.html
@@ -49,10 +49,10 @@
   <body class="container">
     <div class="shape"></div>
     <div class="longbox"></div> <!-- Saturate the margin space -->
     <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
     <div class="box" style="block-size: 36px;"></div>
     <div class="box" style="block-size: 36px;"></div>
     <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
     <div class="longbox"></div> <!-- Saturate the margin space -->
-</body>
+  </body>
 </html>
--- a/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-009.html
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-009.html
@@ -48,10 +48,10 @@
   <body class="container">
     <div class="shape"></div>
     <div class="longbox"></div> <!-- Saturate the margin space -->
     <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
     <div class="box" style="block-size: 36px;"></div>
     <div class="box" style="block-size: 36px;"></div>
     <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
     <div class="longbox"></div> <!-- Saturate the margin space -->
-</body>
+  </body>
 </html>
--- a/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-010.html
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-010.html
@@ -49,10 +49,10 @@
   <body class="container">
     <div class="shape"></div>
     <div class="longbox"></div> <!-- Saturate the margin space -->
     <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
     <div class="box" style="block-size: 36px;"></div>
     <div class="box" style="block-size: 36px;"></div>
     <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
     <div class="longbox"></div> <!-- Saturate the margin space -->
-</body>
+  </body>
 </html>
--- a/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-011.html
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-011.html
@@ -48,10 +48,10 @@
   <body class="container">
     <div class="shape"></div>
     <div class="longbox"></div> <!-- Saturate the margin space -->
     <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
     <div class="box" style="block-size: 36px;"></div>
     <div class="box" style="block-size: 36px;"></div>
     <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
     <div class="longbox"></div> <!-- Saturate the margin space -->
-</body>
+  </body>
 </html>
--- a/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-012.html
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-border-box-border-radius-012.html
@@ -49,10 +49,10 @@
   <body class="container">
     <div class="shape"></div>
     <div class="longbox"></div> <!-- Saturate the margin space -->
     <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
     <div class="box" style="block-size: 36px;"></div>
     <div class="box" style="block-size: 36px;"></div>
     <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
     <div class="longbox"></div> <!-- Saturate the margin space -->
-</body>
+  </body>
 </html>
--- a/layout/reftests/w3c-css/submitted/shapes1/shape-outside-content-box-border-radius-001.html
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-content-box-border-radius-001.html
@@ -47,10 +47,10 @@
   <body class="container">
     <div class="shape"></div>
     <div class="longbox"></div> <!-- Saturate the margin and border space -->
     <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
     <div class="longbox"></div> <!-- Saturate the margin and border space -->
-</body>
+  </body>
 </html>
--- a/layout/reftests/w3c-css/submitted/shapes1/shape-outside-content-box-border-radius-002.html
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-content-box-border-radius-002.html
@@ -48,10 +48,10 @@
   <body class="container">
     <div class="shape"></div>
     <div class="longbox"></div> <!-- Saturate the margin and border space -->
     <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
     <div class="longbox"></div> <!-- Saturate the margin and border space -->
-</body>
+  </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-001-ref.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float left, ellipse(40px 60px at left top)</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <style>
+  .container {
+    position: absolute;
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: left;
+    /* Omit shape-outside */
+    clip-path: ellipse(40px 60px at left top);
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px;
+    border: 20px solid lightgreen;
+    background-color: orange;
+  }
+
+  .box {
+    position: absolute;
+    width: 120px;
+    background-color: blue;
+  }
+
+  .long {
+    width: 200px;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="shape"></div>
+    <div class="box" style="height: 36px; top: 0px; left: 40px;"></div>
+    <div class="box" style="height: 24px; top: 36px; left: 32px;"></div> <!-- Box at corner -->
+    <div class="long box" style="height: 60px; top: 60px; left: 0px;"></div> <!-- Fill the space between two floats -->
+    <div class="box" style="height: 36px; top: 120px; left: 40px;"></div>
+    <div class="box" style="height: 24px; top: 156px; left: 32px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-001.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float left, ellipse(40px 60px at left top)</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes">
+  <link rel="match" href="shape-outside-ellipse-001-ref.html">
+  <meta name="flags" content="">
+  <meta name="assert" content="Test the boxes are wrapping around the left float shape defined by the basic shape ellipse(40px 60px at left top) value.">
+  <style>
+  .container {
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: left;
+    shape-outside: ellipse(40px 60px at left top);
+    clip-path: ellipse(40px 60px at left top);
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px;
+    border: 20px solid lightgreen;
+    background-color: orange;
+  }
+
+  .box {
+    display: inline-block;
+    width: 120px;
+    background-color: blue;
+  }
+
+  .long {
+    width: 200px;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="shape"></div>
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
+    <div class="long box" style="height: 60px;"></div> <!-- Fill the space between two floats -->
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-002-ref.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float left, ellipse(40px 60px at right bottom)</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <style>
+  .container {
+    position: absolute;
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: left;
+    /* Omit shape-outside */
+    clip-path: ellipse(40px 60px at right bottom);
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px;
+    border: 20px solid lightgreen;
+    background-color: orange;
+  }
+
+  .box {
+    position: absolute;
+    width: 60px;
+    background-color: blue;
+  }
+
+  .long {
+    width: 200px;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="shape"></div>
+    <div class="long box" style="height: 60px; top: 0px; left: 0px;"></div> <!-- Fill the space above the first float -->
+    <div class="box" style="height: 36px; top: 60px; left: 120px;"></div>
+    <div class="box" style="height: 12px; top: 96px; left: 120px;"></div>
+    <div class="box" style="height: 12px; top: 108px; left: 120px;"></div>
+    <div class="long box" style="height: 60px; top: 120px; left: 0px;"></div> <!-- Fill the space between two floats -->
+    <div class="box" style="height: 36px; top: 180px; left: 120px;"></div>
+    <div class="box" style="height: 12px; top: 216px; left: 120px;"></div>
+    <div class="box" style="height: 12px; top: 228px; left: 120px;"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-002.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float left, ellipse(40px 60px at right bottom)</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes">
+  <link rel="match" href="shape-outside-ellipse-002-ref.html">
+  <meta name="flags" content="">
+  <meta name="assert" content="Test the boxes are wrapping around the left float shape defined by the basic shape ellipse(40px 60px at right bottom) value.">
+  <style>
+  .container {
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: left;
+    shape-outside: ellipse(40px 60px at right bottom);
+    clip-path: ellipse(40px 60px at right bottom);
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px;
+    border: 20px solid lightgreen;
+    background-color: orange;
+  }
+
+  .box {
+    display: inline-block;
+    width: 60px;
+    background-color: blue;
+  }
+
+  .long {
+    width: 200px;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="shape"></div>
+    <div class="long box" style="height: 60px;"></div> <!-- Fill the space above the first float -->
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 12px;"></div>
+    <div class="box" style="height: 12px;"></div>
+    <div class="long box" style="height: 60px;"></div> <!-- Fill the space between two floats -->
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 12px;"></div>
+    <div class="box" style="height: 12px;"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-003-ref.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float right, ellipse(40px 60px at right top)</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <style>
+  .container {
+    position: absolute;
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: right;
+    /* Omit shape-outside */
+    clip-path: ellipse(40px 60px at right top);
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px;
+    border: 20px solid lightgreen;
+    background-color: orange;
+  }
+
+  .box {
+    position: absolute;
+    width: 120px;
+    background-color: blue;
+  }
+
+  .long {
+    width: 200px;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="shape"></div>
+    <div class="box" style="height: 36px; top: 0px; right: 40px;"></div>
+    <div class="box" style="height: 24px; top: 36px; right: 32px;"></div> <!-- Box at corner -->
+    <div class="long box" style="height: 60px; top: 60px; right: 0px;"></div> <!-- Fill the space between two floats -->
+    <div class="box" style="height: 36px; top: 120px; right: 40px;"></div>
+    <div class="box" style="height: 24px; top: 156px; right: 32px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-003.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float right, ellipse(40px 60px at right top)</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes">
+  <link rel="match" href="shape-outside-ellipse-003-ref.html">
+  <meta name="flags" content="">
+  <meta name="assert" content="Test the boxes are wrapping around the right float shape defined by the basic shape ellipse(40px 60px at right top)">
+  <style>
+  .container {
+    direction: rtl;
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: right;
+    shape-outside: ellipse(40px 60px at right top);
+    clip-path: ellipse(40px 60px at right top);
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px;
+    border: 20px solid lightgreen;
+    background-color: orange;
+  }
+
+  .box {
+    display: inline-block;
+    width: 120px;
+    background-color: blue;
+  }
+
+  .long {
+    width: 200px;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="shape"></div>
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
+    <div class="long box" style="height: 60px;"></div> <!-- Fill the space between two floats -->
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-004-ref.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float left, ellipse(40px 60px at left bottom)</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <style>
+  .container {
+    direction: rtl;
+    position: absolute;
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: right;
+    /* Omit shape-outside */
+    clip-path: ellipse(40px 60px at left bottom);
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px;
+    border: 20px solid lightgreen;
+    background-color: orange;
+  }
+
+  .box {
+    position: absolute;
+    width: 60px;
+    background-color: blue;
+  }
+
+  .long {
+    width: 200px;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="shape"></div>
+    <div class="long box" style="height: 60px; top: 0px; right: 0px;"></div> <!-- Fill the space above the first float -->
+    <div class="box" style="height: 36px; top: 60px; right: 120px;"></div>
+    <div class="box" style="height: 12px; top: 96px; right: 120px;"></div>
+    <div class="box" style="height: 12px; top: 108px; right: 120px;"></div>
+    <div class="long box" style="height: 60px; top: 120px; right: 0px;"></div> <!-- Fill the space between two floats -->
+    <div class="box" style="height: 36px; top: 180px; right: 120px;"></div>
+    <div class="box" style="height: 12px; top: 216px; right: 120px;"></div>
+    <div class="box" style="height: 12px; top: 228px; right: 120px;"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-004.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float right, ellipse(40px 60px at left bottom)</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes">
+  <link rel="match" href="shape-outside-ellipse-004-ref.html">
+  <meta name="flags" content="">
+  <meta name="assert" content="Test the boxes are wrapping around the right float shape defined by the basic shape ellipse(40px 60px at left bottom) value.">
+  <style>
+  .container {
+    direction: rtl;
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: right;
+    shape-outside: ellipse(40px 60px at left bottom);
+    clip-path: ellipse(40px 60px at left bottom);
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px;
+    border: 20px solid lightgreen;
+    background-color: orange;
+  }
+
+  .box {
+    display: inline-block;
+    width: 60px;
+    background-color: blue;
+  }
+
+  .long {
+    width: 200px;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="shape"></div>
+    <div class="long box" style="height: 60px;"></div> <!-- Fill the space above the first float -->
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 12px;"></div>
+    <div class="box" style="height: 12px;"></div>
+    <div class="long box" style="height: 60px;"></div> <!-- Fill the space between two floats -->
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 12px;"></div>
+    <div class="box" style="height: 12px;"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-005-ref.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float left, ellipse() reference</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <style>
+  .container {
+    position: absolute;
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: left;
+    /* Omit shape-outside */
+    clip-path: ellipse();
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px 10px;
+    border: solid lightgreen;
+    border-width: 20px 10px;
+    background-color: orange;
+  }
+
+  .box {
+    position: absolute;
+    width: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="height: 24px; top: 0px; left: 72px;"></div> <!-- Box at corner -->
+    <div class="box" style="height: 36px; top: 24px; left: 80px;"></div>
+    <div class="box" style="height: 36px; top: 60px; left: 80px;"></div>
+    <div class="box" style="height: 24px; top: 96px; left: 72px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-005.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float left, ellipse()</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes">
+  <link rel="match" href="shape-outside-ellipse-005-ref.html">
+  <meta name="flags" content="">
+  <meta name="assert" content="Test the boxes are wrapping around the left float shape defined by the basic shape ellipse() value.">
+  <style>
+  .container {
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: left;
+    shape-outside: ellipse();
+    clip-path: ellipse();
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px 10px;
+    border: solid lightgreen;
+    border-width: 20px 10px;
+    background-color: orange;
+  }
+
+  .box {
+    display: inline-block;
+    width: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-006-ref.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float left, ellipse(closest-side farthest-side at left 40px top 60px) border-box reference</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <style>
+  .container {
+    position: absolute;
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: left;
+    /* Omit shape-outside */
+    clip-path: ellipse(closest-side farthest-side at left 40px top 60px) border-box;
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px 10px;
+    border: solid lightgreen;
+    border-width: 10px;
+    background-color: orange;
+  }
+
+  .box {
+    position: absolute;
+    width: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="height: 24px; top: 0px; left: 72px;"></div> <!-- Box at corner -->
+    <div class="box" style="height: 36px; top: 24px; left: 80px;"></div>
+    <div class="box" style="height: 36px; top: 60px; left: 80px;"></div>
+    <div class="box" style="height: 24px; top: 96px; left: 72px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-006.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float left, ellipse(closest-side farthest-side at left 40px top 60px) border-box</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes">
+  <link rel="match" href="shape-outside-ellipse-006-ref.html">
+  <meta name="flags" content="">
+  <meta name="assert" content="Test the boxes are wrapping around the left float shape defined by the basic shape ellipse(closest-side farthest-side at left 40px top 60px) border-box">
+  <style>
+  .container {
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: left;
+    shape-outside: ellipse(closest-side farthest-side at left 40px top 60px) border-box;
+    clip-path: ellipse(closest-side farthest-side at left 40px top 60px) border-box;
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px 10px;
+    border: solid lightgreen;
+    border-width: 10px;
+    background-color: orange;
+  }
+
+  .box {
+    display: inline-block;
+    width: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-007-ref.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float right, ellipse() reference</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <style>
+  .container {
+    direction: rtl;
+    position: absolute;
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: right;
+    /* Omit shape-outside */
+    clip-path: ellipse();
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px 10px;
+    border: solid lightgreen;
+    border-width: 20px 10px;
+    background-color: orange;
+  }
+
+  .box {
+    position: absolute;
+    width: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="height: 24px; top: 0px; right: 72px;"></div> <!-- Box at corner -->
+    <div class="box" style="height: 36px; top: 24px; right: 80px;"></div>
+    <div class="box" style="height: 36px; top: 60px; right: 80px;"></div>
+    <div class="box" style="height: 24px; top: 96px; right: 72px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-007.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float right, ellipse()</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes">
+  <link rel="match" href="shape-outside-ellipse-007-ref.html">
+  <meta name="flags" content="">
+  <meta name="assert" content="Test the boxes are wrapping around the right float shape defined by the basic shape ellipse() value.">
+  <style>
+  .container {
+    direction: rtl;
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: right;
+    shape-outside: ellipse();
+    clip-path: ellipse();
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px 10px;
+    border: solid lightgreen;
+    border-width: 20px 10px;
+    background-color: orange;
+  }
+
+  .box {
+    display: inline-block;
+    width: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-008-ref.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float right, ellipse(closest-side farthest-side at right 40px top 60px) border-box reference</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <style>
+  .container {
+    position: absolute;
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: right;
+    /* Omit shape-outside */
+    clip-path: ellipse(closest-side farthest-side at right 40px top 60px) border-box;
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px 10px;
+    border: solid lightgreen;
+    border-width: 10px;
+    background-color: orange;
+  }
+
+  .box {
+    position: absolute;
+    width: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="height: 24px; top: 0px; right: 72px;"></div> <!-- Box at corner -->
+    <div class="box" style="height: 36px; top: 24px; right: 80px;"></div>
+    <div class="box" style="height: 36px; top: 60px; right: 80px;"></div>
+    <div class="box" style="height: 24px; top: 96px; right: 72px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-008.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float right, ellipse(closest-side farthest-side at right 40px top 60px) border-box</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes">
+  <link rel="match" href="shape-outside-ellipse-008-ref.html">
+  <meta name="flags" content="">
+  <meta name="assert" content="Test the boxes are wrapping around the right float shape defined by the basic shape ellipse(closest-side farthest-side at right 40px top 60px) border-box">
+  <style>
+  .container {
+    direction: rtl;
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: right;
+    shape-outside: ellipse(closest-side farthest-side at right 40px top 60px) border-box;
+    clip-path: ellipse(closest-side farthest-side at right 40px top 60px) border-box;
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px 10px;
+    border: solid lightgreen;
+    border-width: 10px;
+    background-color: orange;
+  }
+
+  .box {
+    display: inline-block;
+    width: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-009-ref.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float left, ellipse(0% 0%) reference</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <style>
+  .container {
+    position: absolute;
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: left;
+    /* Omit shape-outside */
+    clip-path: ellipse(0% 0%);
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px 10px;
+    border: solid lightgreen;
+    border-width: 20px 10px;
+    background-color: orange;
+  }
+
+  .box {
+    position: absolute;
+    width: 160px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="height: 24px; top: 0px; left: 0px;"></div>
+    <div class="box" style="height: 36px; top: 24px; left: 0px;"></div>
+    <div class="box" style="height: 36px; top: 60px; left: 0px;"></div>
+    <div class="box" style="height: 24px; top: 96px; left: 0px;"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-009.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float left, ellipse(0% 0%)</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes">
+  <link rel="match" href="shape-outside-ellipse-009-ref.html">
+  <meta name="flags" content="">
+  <meta name="assert" content="Test the left float shape defines an empty float area by the basic shape ellipse(0% 0%) value.">
+  <style>
+  .container {
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: left;
+    shape-outside: ellipse(0% 0%);
+    clip-path: ellipse(0% 0%);
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px 10px;
+    border: solid lightgreen;
+    border-width: 20px 10px;
+    background-color: orange;
+  }
+
+  .box {
+    display: inline-block;
+    width: 160px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="height: 24px;"></div>
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 24px;"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-010.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float left, ellipse(closest-side closest-side at top left)</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes">
+  <link rel="match" href="shape-outside-ellipse-009-ref.html">
+  <meta name="flags" content="">
+  <meta name="assert" content="Test the left float shape defines an empty float area by the basic shape ellipse(0% 0closest-side closest-side at top left) value.">
+  <style>
+  .container {
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: left;
+    shape-outside: ellipse(closest-side closest-side at top left);
+    clip-path: ellipse(closest-side closest-side at top left);
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px 10px;
+    border: solid lightgreen;
+    border-width: 20px 10px;
+    background-color: orange;
+  }
+
+  .box {
+    display: inline-block;
+    width: 160px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="height: 24px;"></div>
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 24px;"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-011-ref.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float right, ellipse(0% 0%) reference</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <style>
+  .container {
+    direction: rtl;
+    position: absolute;
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: right;
+    /* Omit shape-outside */
+    clip-path: ellipse(0% 0%);
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px 10px;
+    border: solid lightgreen;
+    border-width: 20px 10px;
+    background-color: orange;
+  }
+
+  .box {
+    position: absolute;
+    width: 160px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="height: 24px; top: 0px; right: 0px;"></div>
+    <div class="box" style="height: 36px; top: 24px; right: 0px;"></div>
+    <div class="box" style="height: 36px; top: 60px; right: 0px;"></div>
+    <div class="box" style="height: 24px; top: 96px; right: 0px;"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-011.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float right, ellipse(0% 0%)</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes">
+  <link rel="match" href="shape-outside-ellipse-011-ref.html">
+  <meta name="flags" content="">
+  <meta name="assert" content="Test the right float shape defines an empty float area by the basic shape ellipse(0% 0%) value.">
+  <style>
+  .container {
+    direction: rtl;
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: right;
+    shape-outside: ellipse(0% 0%);
+    clip-path: ellipse(0% 0%);
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px 10px;
+    border: solid lightgreen;
+    border-width: 20px 10px;
+    background-color: orange;
+  }
+
+  .box {
+    display: inline-block;
+    width: 160px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="height: 24px;"></div>
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 24px;"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-012.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float right, ellipse(closest-side closest-side at top right)</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes">
+  <link rel="match" href="shape-outside-ellipse-011-ref.html">
+  <meta name="flags" content="">
+  <meta name="assert" content="Test the right float shape defines an empty float area by the basic shape ellipse(0% 0closest-side closest-side at top right) value.">
+  <style>
+  .container {
+    direction: rtl;
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: right;
+    shape-outside: ellipse(closest-side closest-side at top right);
+    clip-path: ellipse(closest-side closest-side at top right);
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px 10px;
+    border: solid lightgreen;
+    border-width: 20px 10px;
+    background-color: orange;
+  }
+
+  .box {
+    display: inline-block;
+    width: 160px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="height: 24px;"></div>
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 24px;"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-013-ref.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float left, ellipse(100% 100%) reference</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <style>
+  .container {
+    position: absolute;
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: left;
+    /* Omit shape-outside */
+    clip-path: ellipse(100% 100%);
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px 10px;
+    border: solid lightgreen;
+    border-width: 20px 10px;
+    background-color: orange;
+  }
+
+  .box {
+    position: absolute;
+    width: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="height: 24px; top: 0px; left: 80px;"></div>
+    <div class="box" style="height: 36px; top: 24px; left: 80px;"></div>
+    <div class="box" style="height: 36px; top: 60px; left: 80px;"></div>
+    <div class="box" style="height: 24px; top: 96px; left: 80px;"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-013.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float left, ellipse(100% 100%)</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes">
+  <link rel="match" href="shape-outside-ellipse-013-ref.html">
+  <meta name="flags" content="">
+  <meta name="assert" content="Test the boxes are wrapping around the left float shape defined by the basic shape ellipse(100% 100%) value.">
+  <style>
+  .container {
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: left;
+    shape-outside: ellipse(100% 100%);
+    clip-path: ellipse(100% 100%); /* Rendered as a rectangle */
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px 10px;
+    border: solid lightgreen;
+    border-width: 20px 10px;
+    background-color: orange;
+  }
+
+  .box {
+    display: inline-block;
+    width: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="height: 24px;"></div>
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 24px;"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-014-ref.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float right, ellipse(100% 100%) reference</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <style>
+  .container {
+    direction: rtl;
+    position: absolute;
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: right;
+    /* Omit shape-outside */
+    clip-path: ellipse(100% 100%);
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px 10px;
+    border: solid lightgreen;
+    border-width: 20px 10px;
+    background-color: orange;
+  }
+
+  .box {
+    position: absolute;
+    width: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="height: 24px; top: 0px; right: 80px;"></div>
+    <div class="box" style="height: 36px; top: 24px; right: 80px;"></div>
+    <div class="box" style="height: 36px; top: 60px; right: 80px;"></div>
+    <div class="box" style="height: 24px; top: 96px; right: 80px;"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-014.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: float right, ellipse(100% 100%)</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes">
+  <link rel="match" href="shape-outside-ellipse-014-ref.html">
+  <meta name="flags" content="">
+  <meta name="assert" content="Test the boxes are wrapping around the right float shape defined by the basic shape ellipse(100% 100%) value.">
+  <style>
+  .container {
+    direction: rtl;
+    width: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: right;
+    shape-outside: ellipse(100% 100%);
+    clip-path: ellipse(100% 100%); /* Rendered as a rectangle */
+    box-sizing: content-box;
+    height: 40px;
+    width: 40px;
+    padding: 20px 10px;
+    border: solid lightgreen;
+    border-width: 20px 10px;
+    background-color: orange;
+  }
+
+  .box {
+    display: inline-block;
+    width: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="height: 24px;"></div>
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 36px;"></div>
+    <div class="box" style="height: 24px;"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-015-ref.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: vertical-rl, float left, ellipse(farthest-side closest-side at top 40px right 60px) border-box reference</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <style>
+  .container {
+    writing-mode: vertical-rl;
+    position: absolute;
+    inline-size: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: left;
+    /* Omit shape-outside */
+    clip-path: ellipse(farthest-side closest-side at top 40px right 60px) border-box;
+    box-sizing: content-box;
+    block-size: 40px;
+    inline-size: 40px;
+    padding: 10px 20px;
+    border: solid lightgreen;
+    border-width: 10px;
+    margin-block-end: 20px;
+    background-color: orange;
+  }
+
+  .box {
+    position: absolute;
+    inline-size: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="block-size: 24px; offset-block-start: 0px; offset-inline-start: 72px;"></div> <!-- Box at corner -->
+    <div class="box" style="block-size: 36px; offset-block-start: 24px; offset-inline-start: 80px;"></div>
+    <div class="box" style="block-size: 36px; offset-block-start: 60px; offset-inline-start: 80px;"></div>
+    <div class="box" style="block-size: 24px; offset-block-start: 96px; offset-inline-start: 72px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-015.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: vertical-rl, float left, ellipse(closest-side farthest-side at top 40px right 60px) border-box</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes">
+  <link rel="match" href="shape-outside-ellipse-015-ref.html">
+  <meta name="flags" content="">
+  <meta name="assert" content="Test the boxes are wrapping around the left float shape defined by the basic shape ellipse(closest-side farthest-side at top 40px right 60px) border-box">
+  <style>
+  .container {
+    writing-mode: vertical-rl;
+    inline-size: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: left;
+    shape-outside: ellipse(farthest-side closest-side at top 40px right 60px) border-box;
+    clip-path: ellipse(farthest-side closest-side at top 40px right 60px) border-box;
+    box-sizing: content-box;
+    block-size: 40px;
+    inline-size: 40px;
+    padding: 10px 20px;
+    border: solid lightgreen;
+    border-width: 10px;
+    background-color: orange;
+  }
+
+  .box {
+    display: inline-block;
+    inline-size: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
+    <div class="box" style="block-size: 36px;"></div>
+    <div class="box" style="block-size: 36px;"></div>
+    <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-016-ref.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: vertical-rl, float right, ellipse(farthest-side closest-side at top 40px right 60px) border-box reference</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <style>
+  .container {
+    writing-mode: vertical-rl;
+    direction: rtl;
+    position: absolute;
+    inline-size: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: right;
+    /* Omit shape-outside */
+    clip-path: ellipse(farthest-side closest-side at top 40px right 60px) border-box;
+    box-sizing: content-box;
+    block-size: 40px;
+    inline-size: 40px;
+    padding: 10px 20px;
+    border: solid lightgreen;
+    border-width: 10px;
+    margin-block-end: 20px;
+    background-color: orange;
+  }
+
+  .box {
+    position: absolute;
+    inline-size: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="block-size: 24px; offset-block-start: 0px; offset-inline-start: 72px;"></div> <!-- Box at corner -->
+    <div class="box" style="block-size: 36px; offset-block-start: 24px; offset-inline-start: 80px;"></div>
+    <div class="box" style="block-size: 36px; offset-block-start: 60px; offset-inline-start: 80px;"></div>
+    <div class="box" style="block-size: 24px; offset-block-start: 96px; offset-inline-start: 72px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-016.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: vertical-rl, float right, ellipse(closest-side farthest-side at top 40px right 60px) border-box</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes">
+  <link rel="match" href="shape-outside-ellipse-016-ref.html">
+  <meta name="flags" content="">
+  <meta name="assert" content="Test the boxes are wrapping around the right float shape defined by the basic shape ellipse(closest-side farthest-side at top 40px right 60px) border-box">
+  <style>
+  .container {
+    writing-mode: vertical-rl;
+    direction: rtl;
+    inline-size: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: right;
+    shape-outside: ellipse(farthest-side closest-side at top 40px right 60px) border-box;
+    clip-path: ellipse(farthest-side closest-side at top 40px right 60px) border-box;
+    box-sizing: content-box;
+    block-size: 40px;
+    inline-size: 40px;
+    padding: 10px 20px;
+    border: solid lightgreen;
+    border-width: 10px;
+    background-color: orange;
+  }
+
+  .box {
+    display: inline-block;
+    inline-size: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
+    <div class="box" style="block-size: 36px;"></div>
+    <div class="box" style="block-size: 36px;"></div>
+    <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-017-ref.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: vertical-lr, float left, ellipse(farthest-side closest-side at top 40px left 60px) border-box reference</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <style>
+  .container {
+    writing-mode: vertical-lr;
+    position: absolute;
+    inline-size: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: left;
+    /* Omit shape-outside */
+    clip-path: ellipse(farthest-side closest-side at top 40px left 60px) border-box;
+    box-sizing: content-box;
+    block-size: 40px;
+    inline-size: 40px;
+    padding: 10px 20px;
+    border: solid lightgreen;
+    border-width: 10px;
+    margin-block-end: 20px;
+    background-color: orange;
+  }
+
+  .box {
+    position: absolute;
+    inline-size: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="block-size: 24px; offset-block-start: 0px; offset-inline-start: 72px;"></div> <!-- Box at corner -->
+    <div class="box" style="block-size: 36px; offset-block-start: 24px; offset-inline-start: 80px;"></div>
+    <div class="box" style="block-size: 36px; offset-block-start: 60px; offset-inline-start: 80px;"></div>
+    <div class="box" style="block-size: 24px; offset-block-start: 96px; offset-inline-start: 72px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-017.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: vertical-lr, float left, ellipse(closest-side farthest-side at top 40px left 60px) border-box</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes">
+  <link rel="match" href="shape-outside-ellipse-017-ref.html">
+  <meta name="flags" content="">
+  <meta name="assert" content="Test the boxes are wrapping around the left float shape defined by the basic shape ellipse(closest-side farthest-side at left 40px top 60px) border-box">
+  <style>
+  .container {
+    writing-mode: vertical-lr;
+    inline-size: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: left;
+    shape-outside: ellipse(farthest-side closest-side at top 40px left 60px) border-box;
+    clip-path: ellipse(farthest-side closest-side at top 40px left 60px) border-box;
+    box-sizing: content-box;
+    block-size: 40px;
+    inline-size: 40px;
+    padding: 10px 20px;
+    border: solid lightgreen;
+    border-width: 10px;
+    background-color: orange;
+  }
+
+  .box {
+    display: inline-block;
+    inline-size: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
+    <div class="box" style="block-size: 36px;"></div>
+    <div class="box" style="block-size: 36px;"></div>
+    <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-018-ref.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: vertical-lr, float right, ellipse(farthest-side closest-side at top 40px left 60px) border-box reference</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <style>
+  .container {
+    writing-mode: vertical-lr;
+    direction: rtl;
+    position: absolute;
+    inline-size: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: right;
+    /* Omit shape-outside */
+    clip-path: ellipse(farthest-side closest-side at top 40px left 60px) border-box;
+    box-sizing: content-box;
+    block-size: 40px;
+    inline-size: 40px;
+    padding: 10px 20px;
+    border: solid lightgreen;
+    border-width: 10px;
+    margin-block-end: 20px;
+    background-color: orange;
+  }
+
+  .box {
+    position: absolute;
+    inline-size: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="block-size: 24px; offset-block-start: 0px; offset-inline-start: 72px;"></div> <!-- Box at corner -->
+    <div class="box" style="block-size: 36px; offset-block-start: 24px; offset-inline-start: 80px;"></div>
+    <div class="box" style="block-size: 36px; offset-block-start: 60px; offset-inline-start: 80px;"></div>
+    <div class="box" style="block-size: 24px; offset-block-start: 96px; offset-inline-start: 72px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-018.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: vertical-lr, float right, ellipse(closest-side farthest-side at top 40px left 60px) border-box</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes">
+  <link rel="match" href="shape-outside-ellipse-018-ref.html">
+  <meta name="flags" content="">
+  <meta name="assert" content="Test the boxes are wrapping around the right float shape defined by the basic shape ellipse(closest-side farthest-side at left 40px top 60px) border-box">
+  <style>
+  .container {
+    writing-mode: vertical-lr;
+    direction: rtl;
+    inline-size: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: right;
+    shape-outside: ellipse(farthest-side closest-side at top 40px left 60px) border-box;
+    clip-path: ellipse(farthest-side closest-side at top 40px left 60px) border-box;
+    box-sizing: content-box;
+    block-size: 40px;
+    inline-size: 40px;
+    padding: 10px 20px;
+    border: solid lightgreen;
+    border-width: 10px;
+    background-color: orange;
+  }
+
+  .box {
+    display: inline-block;
+    inline-size: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
+    <div class="box" style="block-size: 36px;"></div>
+    <div class="box" style="block-size: 36px;"></div>
+    <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-019-ref.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: sideways-lr, float left, ellipse(farthest-side closest-side at top 40px left 60px) border-box reference</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <style>
+  .container {
+    writing-mode: sideways-lr;
+    position: absolute;
+    inline-size: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: left;
+    /* Omit shape-outside */
+    clip-path: ellipse(farthest-side closest-side at top 40px left 60px) border-box;
+    box-sizing: content-box;
+    block-size: 40px;
+    inline-size: 40px;
+    padding: 10px 20px;
+    border: solid lightgreen;
+    border-width: 10px;
+    margin-block-end: 20px;
+    background-color: orange;
+  }
+
+  .box {
+    position: absolute;
+    inline-size: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="block-size: 24px; offset-block-start: 0px; offset-inline-start: 72px;"></div> <!-- Box at corner -->
+    <div class="box" style="block-size: 36px; offset-block-start: 24px; offset-inline-start: 80px;"></div>
+    <div class="box" style="block-size: 36px; offset-block-start: 60px; offset-inline-start: 80px;"></div>
+    <div class="box" style="block-size: 24px; offset-block-start: 96px; offset-inline-start: 72px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-019.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: sideways-lr, float left, ellipse(closest-side farthest-side at top 40px left 60px) border-box</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes">
+  <link rel="match" href="shape-outside-ellipse-019-ref.html">
+  <meta name="flags" content="">
+  <meta name="assert" content="Test the boxes are wrapping around the left float shape defined by the basic shape ellipse(closest-side farthest-side at left 40px top 60px) border-box">
+  <style>
+  .container {
+    writing-mode: sideways-lr;
+    inline-size: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: left;
+    shape-outside: ellipse(farthest-side closest-side at top 40px left 60px) border-box;
+    clip-path: ellipse(farthest-side closest-side at top 40px left 60px) border-box;
+    box-sizing: content-box;
+    block-size: 40px;
+    inline-size: 40px;
+    padding: 10px 20px;
+    border: solid lightgreen;
+    border-width: 10px;
+    background-color: orange;
+  }
+
+  .box {
+    display: inline-block;
+    inline-size: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
+    <div class="box" style="block-size: 36px;"></div>
+    <div class="box" style="block-size: 36px;"></div>
+    <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-020-ref.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: sideways-lr, float right, ellipse(farthest-side closest-side at top 40px left 60px) border-box reference</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <style>
+  .container {
+    writing-mode: sideways-lr;
+    direction: rtl;
+    position: absolute;
+    inline-size: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: right;
+    /* Omit shape-outside */
+    clip-path: ellipse(farthest-side closest-side at top 40px left 60px) border-box;
+    box-sizing: content-box;
+    block-size: 40px;
+    inline-size: 40px;
+    padding: 10px 20px;
+    border: solid lightgreen;
+    border-width: 10px;
+    margin-block-end: 20px;
+    background-color: orange;
+  }
+
+  .box {
+    position: absolute;
+    inline-size: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="block-size: 24px; offset-block-start: 0px; offset-inline-start: 72px;"></div> <!-- Box at corner -->
+    <div class="box" style="block-size: 36px; offset-block-start: 24px; offset-inline-start: 80px;"></div>
+    <div class="box" style="block-size: 36px; offset-block-start: 60px; offset-inline-start: 80px;"></div>
+    <div class="box" style="block-size: 24px; offset-block-start: 96px; offset-inline-start: 72px;"></div> <!-- Box at corner -->
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-ellipse-020.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+  <meta charset="utf-8">
+  <title>CSS Shape Test: sideways-lr, float right, ellipse(closest-side farthest-side at top 40px left 60px) border-box</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes">
+  <link rel="match" href="shape-outside-ellipse-020-ref.html">
+  <meta name="flags" content="">
+  <meta name="assert" content="Test the boxes are wrapping around the right float shape defined by the basic shape ellipse(closest-side farthest-side at left 40px top 60px) border-box">
+  <style>
+  .container {
+    writing-mode: sideways-lr;
+    direction: rtl;
+    inline-size: 200px;
+    line-height: 0;
+  }
+
+  .shape {
+    float: right;
+    shape-outside: ellipse(farthest-side closest-side at top 40px left 60px) border-box;
+    clip-path: ellipse(farthest-side closest-side at top 40px left 60px) border-box;
+    box-sizing: content-box;
+    block-size: 40px;
+    inline-size: 40px;
+    padding: 10px 20px;
+    border: solid lightgreen;
+    border-width: 10px;
+    background-color: orange;
+  }
+
+  .box {
+    display: inline-block;
+    inline-size: 80px;
+    background-color: blue;
+  }
+  </style>
+
+  <body class="container">
+    <div class="shape"></div>
+    <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
+    <div class="box" style="block-size: 36px;"></div>
+    <div class="box" style="block-size: 36px;"></div>
+    <div class="box" style="block-size: 24px;"></div> <!-- Box at corner -->
+  </body>
+</html>
--- a/layout/reftests/w3c-css/submitted/shapes1/shape-outside-margin-box-border-radius-001.html
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-margin-box-border-radius-001.html
@@ -40,10 +40,10 @@
   <body class="container">
     <div class="shape"></div>
     <div class="box" style="height: 12px;"></div> <!-- Box at corner -->
     <div class="box" style="height: 12px;"></div> <!-- Box at corner -->
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 12px;"></div> <!-- Box at corner -->
     <div class="box" style="height: 12px;"></div> <!-- Box at corner -->
-</body>
+  </body>
 </html>
--- a/layout/reftests/w3c-css/submitted/shapes1/shape-outside-margin-box-border-radius-002.html
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-margin-box-border-radius-002.html
@@ -38,10 +38,10 @@
   </style>
 
   <body class="container">
     <div class="shape"></div>
     <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
-</body>
+  </body>
 </html>
--- a/layout/reftests/w3c-css/submitted/shapes1/shape-outside-margin-box-border-radius-003.html
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-margin-box-border-radius-003.html
@@ -41,10 +41,10 @@
   <body class="container">
     <div class="shape"></div>
     <div class="box" style="height: 12px;"></div> <!-- Box at corner -->
     <div class="box" style="height: 12px;"></div> <!-- Box at corner -->
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 12px;"></div> <!-- Box at corner -->
     <div class="box" style="height: 12px;"></div> <!-- Box at corner -->
-</body>
+  </body>
 </html>
--- a/layout/reftests/w3c-css/submitted/shapes1/shape-outside-margin-box-border-radius-004.html
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-margin-box-border-radius-004.html
@@ -46,10 +46,10 @@
   </style>
 
   <body class="container">
     <div class="shape"></div>
     <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
-</body>
+  </body>
 </html>
--- a/layout/reftests/w3c-css/submitted/shapes1/shape-outside-padding-box-border-radius-001.html
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-padding-box-border-radius-001.html
@@ -47,10 +47,10 @@
   <body class="container">
     <div class="shape"></div>
     <div class="longbox"></div> <!-- Saturate the margin and border space -->
     <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
     <div class="longbox"></div> <!-- Saturate the margin and border space -->
-</body>
+  </body>
 </html>
--- a/layout/reftests/w3c-css/submitted/shapes1/shape-outside-padding-box-border-radius-002.html
+++ b/layout/reftests/w3c-css/submitted/shapes1/shape-outside-padding-box-border-radius-002.html
@@ -48,10 +48,10 @@
   <body class="container">
     <div class="shape"></div>
     <div class="longbox"></div> <!-- Saturate the margin and border space -->
     <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 36px;"></div>
     <div class="box" style="height: 24px;"></div> <!-- Box at corner -->
     <div class="longbox"></div> <!-- Saturate the margin and border space -->
-</body>
+  </body>
 </html>
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -108,16 +108,24 @@ SERVO_BINDING_FUNC(Servo_RestyleWithAdde
 
 // AnimationValues handling
 SERVO_BINDING_FUNC(Servo_AnimationValues_Populate, void,
                    RawGeckoAnimationValueListBorrowedMut,
                    RawServoDeclarationBlockBorrowed,
                    ServoComputedValuesBorrowed,
                    ServoComputedValuesBorrowedOrNull,
                    RawGeckoPresContextBorrowed)
+SERVO_BINDING_FUNC(Servo_AnimationValues_Interpolate,
+                   RawServoAnimationValueStrong,
+                   RawServoAnimationValueBorrowed from,
+                   RawServoAnimationValueBorrowed to,
+                   double progress)
+SERVO_BINDING_FUNC(Servo_AnimationValues_Uncompute,
+                   RawServoDeclarationBlockStrong,
+                   RawServoAnimationValueBorrowedListBorrowed value)
 
 // Style attribute
 SERVO_BINDING_FUNC(Servo_ParseStyleAttribute, RawServoDeclarationBlockStrong,
                    const nsACString* data)
 SERVO_BINDING_FUNC(Servo_DeclarationBlock_CreateEmpty,
                    RawServoDeclarationBlockStrong)
 SERVO_BINDING_FUNC(Servo_DeclarationBlock_Clone, RawServoDeclarationBlockStrong,
                    RawServoDeclarationBlockBorrowed declarations)
--- a/layout/style/ServoBindingTypes.h
+++ b/layout/style/ServoBindingTypes.h
@@ -36,16 +36,17 @@ class nsPresContext;
 using mozilla::dom::StyleChildrenIterator;
 using mozilla::ServoElementSnapshot;
 
 typedef nsINode RawGeckoNode;
 typedef mozilla::dom::Element RawGeckoElement;
 typedef nsIDocument RawGeckoDocument;
 typedef nsPresContext RawGeckoPresContext;
 typedef nsTArray<mozilla::PropertyStyleAnimationValuePair> RawGeckoAnimationValueList;
+typedef nsTArray<const RawServoAnimationValue*> RawServoAnimationValueBorrowedList;
 
 // We have these helper types so that we can directly generate
 // things like &T or Borrowed<T> on the Rust side in the function, providing
 // additional safety benefits.
 //
 // FFI has a problem with templated types, so we just use raw pointers here.
 //
 // The "Borrowed" types generate &T or Borrowed<T> in the nullable case.
@@ -105,16 +106,17 @@ DECL_BORROWED_REF_TYPE_FOR(RawGeckoDocum
 DECL_NULLABLE_BORROWED_REF_TYPE_FOR(RawGeckoDocument)
 DECL_BORROWED_MUT_REF_TYPE_FOR(StyleChildrenIterator)
 DECL_BORROWED_MUT_REF_TYPE_FOR(ServoElementSnapshot)
 DECL_BORROWED_REF_TYPE_FOR(nsCSSValue)
 DECL_BORROWED_MUT_REF_TYPE_FOR(nsCSSValue)
 DECL_OWNED_REF_TYPE_FOR(RawGeckoPresContext)
 DECL_BORROWED_REF_TYPE_FOR(RawGeckoPresContext)
 DECL_BORROWED_MUT_REF_TYPE_FOR(RawGeckoAnimationValueList)
+DECL_BORROWED_REF_TYPE_FOR(RawServoAnimationValueBorrowedList)
 
 #undef DECL_ARC_REF_TYPE_FOR
 #undef DECL_OWNED_REF_TYPE_FOR
 #undef DECL_NULLABLE_OWNED_REF_TYPE_FOR
 #undef DECL_BORROWED_REF_TYPE_FOR
 #undef DECL_NULLABLE_BORROWED_REF_TYPE_FOR
 #undef DECL_BORROWED_MUT_REF_TYPE_FOR
 #undef DECL_NULLABLE_BORROWED_MUT_REF_TYPE_FOR
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -6,16 +6,17 @@
 
 #include "mozilla/ServoBindings.h"
 
 #include "ChildIterator.h"
 #include "gfxFontFamilyList.h"
 #include "nsAttrValueInlines.h"
 #include "nsCSSProps.h"
 #include "nsCSSParser.h"
+#include "nsCSSPseudoElements.h"
 #include "nsCSSRuleProcessor.h"
 #include "nsContentUtils.h"
 #include "nsDOMTokenList.h"
 #include "nsIContentInlines.h"
 #include "nsIDOMNode.h"
 #include "nsIDocument.h"
 #include "nsIFrame.h"
 #include "nsINode.h"
@@ -24,17 +25,19 @@
 #include "nsNameSpaceManager.h"
 #include "nsNetUtil.h"
 #include "nsRuleNode.h"
 #include "nsString.h"
 #include "nsStyleStruct.h"
 #include "nsStyleUtil.h"
 #include "nsTArray.h"
 
+#include "mozilla/EffectCompositor.h"
 #include "mozilla/EventStates.h"
+#include "mozilla/ServoAnimationRule.h"
 #include "mozilla/ServoElementSnapshot.h"
 #include "mozilla/ServoRestyleManager.h"
 #include "mozilla/StyleAnimationValue.h"
 #include "mozilla/DeclarationBlockInlines.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/ElementInlines.h"
 
 using namespace mozilla;
@@ -319,16 +322,54 @@ Gecko_GetServoDeclarationBlock(RawGeckoE
     //     document into a Servo-style-backend document.  See bug 1330051.
     NS_WARNING("stylo: requesting a Gecko declaration block?");
     return nullptr;
   }
   return reinterpret_cast<const RawServoDeclarationBlockStrong*>
     (decl->AsServo()->RefRaw());
 }
 
+RawServoDeclarationBlockStrong
+Gecko_GetAnimationRule(RawGeckoElementBorrowed aElement,
+                       nsIAtom* aPseudoTag,
+                       EffectCompositor::CascadeLevel aCascadeLevel)
+{
+  MOZ_ASSERT(aElement, "Invalid GeckoElement");
+
+  const RawServoDeclarationBlockStrong emptyDeclarationBlock{ nullptr };
+  nsIDocument* doc = aElement->GetComposedDoc();
+  if (!doc || !doc->GetShell()) {
+    return emptyDeclarationBlock;
+  }
+  nsPresContext* presContext = doc->GetShell()->GetPresContext();
+  if (!presContext) {
+    return emptyDeclarationBlock;
+  }
+
+  CSSPseudoElementType pseudoType =
+    aPseudoTag
+    ? nsCSSPseudoElements::GetPseudoType(
+        aPseudoTag,
+        nsCSSProps::EnabledState::eIgnoreEnabledState)
+    : CSSPseudoElementType::NotPseudo;
+  if (pseudoType != CSSPseudoElementType::NotPseudo &&
+      pseudoType != CSSPseudoElementType::before &&
+      pseudoType != CSSPseudoElementType::after) {
+    return emptyDeclarationBlock;
+  }
+
+  ServoAnimationRule* rule =
+    presContext->EffectCompositor()
+               ->GetServoAnimationRule(aElement, pseudoType, aCascadeLevel);
+  if (!rule) {
+    return emptyDeclarationBlock;
+  }
+  return rule->GetValues();
+}
+
 void
 Gecko_FillAllBackgroundLists(nsStyleImageLayers* aLayers, uint32_t aMaxLen)
 {
   nsRuleNode::FillAllBackgroundLists(*aLayers, aMaxLen);
 }
 
 void
 Gecko_FillAllMaskLists(nsStyleImageLayers* aLayers, uint32_t aMaxLen)
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -8,16 +8,17 @@
 #define mozilla_ServoBindings_h
 
 #include <stdint.h>
 
 #include "mozilla/ServoTypes.h"
 #include "mozilla/ServoBindingTypes.h"
 #include "mozilla/ServoElementSnapshot.h"
 #include "mozilla/css/SheetParsingMode.h"
+#include "mozilla/EffectCompositor.h"
 #include "nsChangeHint.h"
 #include "nsCSSPseudoClasses.h"
 #include "nsStyleStruct.h"
 
 /*
  * API for Servo to access Gecko data structures. This file must compile as valid
  * C code in order for the binding generator to parse it.
  *
@@ -153,16 +154,22 @@ SERVO_DECLARE_ELEMENT_ATTR_MATCHING_FUNC
                                               const ServoElementSnapshot*)
 
 #undef SERVO_DECLARE_ELEMENT_ATTR_MATCHING_FUNCTIONS
 
 // Style attributes.
 RawServoDeclarationBlockStrongBorrowedOrNull
 Gecko_GetServoDeclarationBlock(RawGeckoElementBorrowed element);
 
+// Animations
+RawServoDeclarationBlockStrong
+Gecko_GetAnimationRule(RawGeckoElementBorrowed aElement,
+                       nsIAtom* aPseudoTag,
+                       mozilla::EffectCompositor::CascadeLevel aCascadeLevel);
+
 // Atoms.
 nsIAtom* Gecko_Atomize(const char* aString, uint32_t aLength);
 void Gecko_AddRefAtom(nsIAtom* aAtom);
 void Gecko_ReleaseAtom(nsIAtom* aAtom);
 const uint16_t* Gecko_GetAtomAsUTF16(nsIAtom* aAtom, uint32_t* aLength);
 bool Gecko_AtomEqualsUTF8(nsIAtom* aAtom, const char* aString, uint32_t aLength);
 bool Gecko_AtomEqualsUTF8IgnoreCase(nsIAtom* aAtom, const char* aString, uint32_t aLength);
 
--- a/layout/style/nsAnimationManager.cpp
+++ b/layout/style/nsAnimationManager.cpp
@@ -194,17 +194,17 @@ CSSAnimation::QueueEvents()
   // Get the nsAnimationManager so we can queue events on it
   nsPresContext* presContext = mOwningElement.GetRenderedPresContext();
   if (!presContext) {
     return;
   }
   nsAnimationManager* manager = presContext->AnimationManager();
   ComputedTiming computedTiming = mEffect->GetComputedTiming();
 
-  ComputedTiming::AnimationPhase currentPhase = computedTiming.mPhase;
+  AnimationPhase currentPhase = computedTiming.mPhase;
   uint64_t currentIteration  = computedTiming.mCurrentIteration;
   if (currentPhase == mPreviousPhase &&
       currentIteration == mPreviousIteration) {
     return;
   }
 
   const StickyTimeDuration zeroDuration;
   StickyTimeDuration intervalStartTime =
@@ -224,17 +224,17 @@ CSSAnimation::QueueEvents()
       (iterationBoundary - computedTiming.mIterationStart));
 
   TimeStamp startTimeStamp     = ElapsedTimeToTimeStamp(intervalStartTime);
   TimeStamp endTimeStamp       = ElapsedTimeToTimeStamp(intervalEndTime);
   TimeStamp iterationTimeStamp = ElapsedTimeToTimeStamp(iterationStartTime);
 
   AutoTArray<AnimationEventParams, 2> events;
   switch (mPreviousPhase) {
-    case AnimationPhase::Null:
+    case AnimationPhase::Idle:
     case AnimationPhase::Before:
       if (currentPhase == AnimationPhase::Active) {
         events.AppendElement(AnimationEventParams{ eAnimationStart,
                                                    intervalStartTime,
                                                    startTimeStamp });
       } else if (currentPhase == AnimationPhase::After) {
         events.AppendElement(AnimationEventParams{ eAnimationStart,
                                                    intervalStartTime,
--- a/layout/style/nsAnimationManager.h
+++ b/layout/style/nsAnimationManager.h
@@ -71,17 +71,17 @@ class CSSAnimation final : public Animat
 public:
  explicit CSSAnimation(nsIGlobalObject* aGlobal,
                        const nsSubstring& aAnimationName)
     : dom::Animation(aGlobal)
     , mAnimationName(aAnimationName)
     , mIsStylePaused(false)
     , mPauseShouldStick(false)
     , mNeedsNewAnimationIndexWhenRun(false)
-    , mPreviousPhase(ComputedTiming::AnimationPhase::Null)
+    , mPreviousPhase(ComputedTiming::AnimationPhase::Idle)
     , mPreviousIteration(0)
   {
     // We might need to drop this assertion once we add a script-accessible
     // constructor but for animations generated from CSS markup the
     // animation-name should never be empty.
     MOZ_ASSERT(!mAnimationName.IsEmpty(), "animation-name should not be empty");
   }
 
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -2709,17 +2709,17 @@ static const nsCSSPropertyID gBorderBott
   eCSSProperty_UNKNOWN
 };
 
 static_assert(eSideTop == 0 && eSideRight == 1 &&
               eSideBottom == 2 && eSideLeft == 3,
               "box side constants not top/right/bottom/left == 0/1/2/3");
 static const nsCSSPropertyID gBorderColorSubpropTable[] = {
   // Code relies on these being in top-right-bottom-left order.
-  // Code relies on these matching the NS_SIDE_* constants.
+  // Code relies on these matching the enum Side constants.
   eCSSProperty_border_top_color,
   eCSSProperty_border_right_color,
   eCSSProperty_border_bottom_color,
   eCSSProperty_border_left_color,
   eCSSProperty_UNKNOWN
 };
 
 static const nsCSSPropertyID gBorderInlineEndSubpropTable[] = {
--- a/layout/style/nsStyleCoord.h
+++ b/layout/style/nsStyleCoord.h
@@ -376,17 +376,17 @@ public:
   nsStyleCorners(const nsStyleCorners&);
   ~nsStyleCorners();
 
   // use compiler's version
   nsStyleCorners& operator=(const nsStyleCorners& aCopy);
   bool           operator==(const nsStyleCorners& aOther) const;
   bool           operator!=(const nsStyleCorners& aOther) const;
 
-  // aCorner is always one of NS_CORNER_* defined in nsStyleConsts.h
+  // aHalfCorner is always one of enum HalfCorner in gfx/2d/Types.h.
   inline nsStyleUnit GetUnit(uint8_t aHalfCorner) const;
 
   inline nsStyleCoord Get(uint8_t aHalfCorner) const;
 
   // Sets each corner to null and releases any refcounted objects.  Only use
   // this if the object is initialized (i.e. don't use it in nsStyleCorners
   // constructors).
   void Reset();
--- a/layout/style/nsTransitionManager.cpp
+++ b/layout/style/nsTransitionManager.cpp
@@ -41,18 +41,16 @@ using mozilla::TimeDuration;
 using mozilla::dom::Animation;
 using mozilla::dom::AnimationPlayState;
 using mozilla::dom::CSSTransition;
 using mozilla::dom::KeyframeEffectReadOnly;
 
 using namespace mozilla;
 using namespace mozilla::css;
 
-typedef mozilla::ComputedTiming::AnimationPhase AnimationPhase;
-
 namespace {
 struct TransitionEventParams {
   EventMessage mMessage;
   StickyTimeDuration mElapsedTime;
   TimeStamp mTimeStamp;
 };
 } // anonymous namespace
 
--- a/layout/style/nsTransitionManager.h
+++ b/layout/style/nsTransitionManager.h
@@ -267,17 +267,17 @@ protected:
   //
   // For (b) and (c) the owning element will return !IsSet().
   OwningElementRef mOwningElement;
 
   // The 'transition phase' used to determine which transition events need
   // to be queued on this tick.
   // See: https://drafts.csswg.org/css-transitions-2/#transition-phase
   enum class TransitionPhase {
-    Idle   = static_cast<int>(ComputedTiming::AnimationPhase::Null),
+    Idle   = static_cast<int>(ComputedTiming::AnimationPhase::Idle),
     Before = static_cast<int>(ComputedTiming::AnimationPhase::Before),
     Active = static_cast<int>(ComputedTiming::AnimationPhase::Active),
     After  = static_cast<int>(ComputedTiming::AnimationPhase::After),
     Pending
   };
   TransitionPhase mPreviousTransitionPhase;
 
   // When true, indicates that when this transition next leaves the idle state,
--- a/layout/svg/nsCSSClipPathInstance.cpp
+++ b/layout/svg/nsCSSClipPathInstance.cpp
@@ -130,42 +130,22 @@ nsCSSClipPathInstance::CreateClipPathEll
                                              const nsRect& aRefBox)
 {
   StyleBasicShape* basicShape = mClipPathStyle.GetBasicShape();
 
   RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
 
   nsPoint center =
     ShapeUtils::ComputeCircleOrEllipseCenter(basicShape, aRefBox);
-
-  const nsTArray<nsStyleCoord>& coords = basicShape->Coordinates();
-  MOZ_ASSERT(coords.Length() == 2, "wrong number of arguments");
-  nscoord rx = 0, ry = 0;
-  if (coords[0].GetUnit() == eStyleUnit_Enumerated) {
-    rx = ShapeUtils::ComputeShapeRadius(coords[0].GetEnumValue<StyleShapeRadius>(),
-                                        center.x,
-                                        aRefBox.x,
-                                        aRefBox.x + aRefBox.width);
-  } else {
-    rx = nsRuleNode::ComputeCoordPercentCalc(coords[0], aRefBox.width);
-  }
-  if (coords[1].GetUnit() == eStyleUnit_Enumerated) {
-    ry = ShapeUtils::ComputeShapeRadius(coords[1].GetEnumValue<StyleShapeRadius>(),
-                                        center.y,
-                                        aRefBox.y,
-                                        aRefBox.y + aRefBox.height);
-  } else {
-    ry = nsRuleNode::ComputeCoordPercentCalc(coords[1], aRefBox.height);
-  }
-
+  nsSize radii = ShapeUtils::ComputeEllipseRadii(basicShape, center, aRefBox);
   nscoord appUnitsPerDevPixel =
     mTargetFrame->PresContext()->AppUnitsPerDevPixel();
   EllipseToBezier(builder.get(),
                   Point(center.x, center.y) / appUnitsPerDevPixel,
-                  Size(rx, ry) / appUnitsPerDevPixel);
+                  Size(radii.width, radii.height) / appUnitsPerDevPixel);
   builder->Close();
   return builder->Finish();
 }
 
 already_AddRefed<Path>
 nsCSSClipPathInstance::CreateClipPathPolygon(DrawTarget* aDrawTarget,
                                              const nsRect& aRefBox)
 {
--- a/layout/tables/nsTableRowFrame.cpp
+++ b/layout/tables/nsTableRowFrame.cpp
@@ -1451,17 +1451,17 @@ void nsTableRowFrame::SetContinuousBCBor
       return;
     case eLogicalSideBStart:
       mBStartContBorderWidth = aPixelValue;
       return;
     case eLogicalSideIStart:
       mIStartContBorderWidth = aPixelValue;
       return;
     default:
-      NS_ERROR("invalid NS_SIDE arg");
+      NS_ERROR("invalid LogicalSide arg");
   }
 }
 #ifdef ACCESSIBILITY
 a11y::AccType
 nsTableRowFrame::AccessibleType()
 {
   return a11y::eHTMLTableRowType;
 }
--- a/layout/xul/nsMenuPopupFrame.cpp
+++ b/layout/xul/nsMenuPopupFrame.cpp
@@ -1692,18 +1692,18 @@ nsMenuPopupFrame::GetConstraintRect(cons
     screenRectPixels.IntersectRect(screenRectPixels, overrideConstrainRect);
     screenRectPixels.x = overrideConstrainRect.x;
     screenRectPixels.width = overrideConstrainRect.width;
   }
 
   return screenRectPixels;
 }
 
-void nsMenuPopupFrame::CanAdjustEdges(int8_t aHorizontalSide,
-                                      int8_t aVerticalSide,
+void nsMenuPopupFrame::CanAdjustEdges(Side aHorizontalSide,
+                                      Side aVerticalSide,
                                       LayoutDeviceIntPoint& aChange)
 {
   int8_t popupAlign(mPopupAlignment);
   if (IsDirectionRTL()) {
     popupAlign = -popupAlign;
   }
 
   if (aHorizontalSide == (mHFlip ? eSideRight : eSideLeft)) {
--- a/layout/xul/nsMenuPopupFrame.h
+++ b/layout/xul/nsMenuPopupFrame.h
@@ -6,16 +6,17 @@
 //
 // nsMenuPopupFrame
 //
 
 #ifndef nsMenuPopupFrame_h__
 #define nsMenuPopupFrame_h__
 
 #include "mozilla/Attributes.h"
+#include "mozilla/gfx/Types.h"
 #include "nsIAtom.h"
 #include "nsGkAtoms.h"
 #include "nsCOMPtr.h"
 #include "nsMenuFrame.h"
 
 #include "nsBoxFrame.h"
 #include "nsMenuParent.h"
 
@@ -380,27 +381,26 @@ public:
   // underneath the taskbar, dock or other fixed OS elements.
   // This operates in device pixels.
   mozilla::LayoutDeviceIntRect
   GetConstraintRect(const mozilla::LayoutDeviceIntRect& aAnchorRect,
                     const mozilla::LayoutDeviceIntRect& aRootScreenRect,
                     nsPopupLevel aPopupLevel);
 
   // Determines whether the given edges of the popup may be moved, where
-  // aHorizontalSide and aVerticalSide are one of the NS_SIDE_* constants, or
-  // 0 for no movement in that direction. aChange is the distance to move on
-  // those sides. If will be reset to 0 if the side cannot be adjusted at all
-  // in that direction. For example, a popup cannot be moved if it is anchored
-  // on a particular side.
+  // aHorizontalSide and aVerticalSide are one of the enum Side constants.
+  // aChange is the distance to move on those sides. If will be reset to 0
+  // if the side cannot be adjusted at all in that direction. For example, a
+  // popup cannot be moved if it is anchored on a particular side.
   //
   // Later, when bug 357725 is implemented, we can make this adjust aChange by
   // the amount that the side can be resized, so that minimums and maximums
   // can be taken into account.
-  void CanAdjustEdges(int8_t aHorizontalSide,
-                      int8_t aVerticalSide,
+  void CanAdjustEdges(mozilla::Side aHorizontalSide,
+                      mozilla::Side aVerticalSide,
                       mozilla::LayoutDeviceIntPoint& aChange);
 
   // Return true if the popup is positioned relative to an anchor.
   bool IsAnchored() const { return mAnchorType != MenuPopupAnchorType_Point; }
 
   // Return the anchor if there is one.
   nsIContent* GetAnchor() const { return mAnchorContent; }
 
--- a/layout/xul/nsSliderFrame.cpp
+++ b/layout/xul/nsSliderFrame.cpp
@@ -882,16 +882,17 @@ nsSliderFrame::SetCurrentPositionInterna
     nsIScrollbarMediator* mediator = scrollbarFrame->GetScrollbarMediator();
     if (mediator) {
       nscoord oldPos = nsPresContext::CSSPixelsToAppUnits(GetCurrentPosition(scrollbar));
       nscoord newPos = nsPresContext::CSSPixelsToAppUnits(aNewPos);
       mediator->ThumbMoved(scrollbarFrame, oldPos, newPos);
       if (!weakFrame.IsAlive()) {
         return;
       }
+      UpdateAttribute(scrollbar, aNewPos, /* aNotify */false, aIsSmooth);
       CurrentPositionChanged();
       mUserChanged = false;
       return;
     }
   }
 
   UpdateAttribute(scrollbar, aNewPos, true, aIsSmooth);
   if (!weakFrame.IsAlive()) {
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/PopupContextMenu.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/PopupContextMenu.java
@@ -2,26 +2,25 @@
  * 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/. */
 package org.mozilla.gecko.home.activitystream.menu;
 
 import android.content.Context;
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
 import android.support.design.widget.NavigationView;
 import android.view.LayoutInflater;
 import android.view.MenuItem;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.PopupWindow;
 
+import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.activitystream.ActivityStream;
 import org.mozilla.gecko.activitystream.ActivityStreamTelemetry;
 import org.mozilla.gecko.home.HomePager;
 import org.mozilla.gecko.home.activitystream.model.Item;
 
 /* package-private */ class PopupContextMenu
         extends ActivityStreamContextMenu {
 
     private final PopupWindow popupWindow;
@@ -51,16 +50,23 @@ import org.mozilla.gecko.home.activityst
         navigationView = (NavigationView) card.findViewById(R.id.menu);
         navigationView.setNavigationItemSelectedListener(this);
 
         popupWindow = new PopupWindow(context);
         popupWindow.setContentView(card);
         popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
         popupWindow.setFocusable(true);
 
+        // On Asus devices running Android 4, PopupWindow is assigned height/width = 0 - we therefore
+        // need to manually override that behaviour, but only on those devices:
+        if (AppConstants.Versions.preLollipop && "asus".equals(android.os.Build.MANUFACTURER)) {
+            popupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
+            popupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
+        }
+
         super.postInit();
     }
 
     @Override
     public MenuItem getItemByID(int id) {
         return navigationView.getMenu().findItem(id);
     }
 
--- a/mobile/android/docs/uitelemetry.rst
+++ b/mobile/android/docs/uitelemetry.rst
@@ -49,16 +49,48 @@ To stop a session, call ``Telemetry.stop
   A descriptive cause for ending the session. It should be brief, lowercase, and generic so it can be reused in different places. Examples reasons are:
 
     ``switched``
       The user transitioned to a UI element of equal level.
 
     ``exit``
       The user left for an entirely different element.
 
+Experiments
+===========
+
+**Experiment** is a special type of a session. Experiments denote an "experiment scope".
+Just like sessions, multiple experiments might be active at the same time. Experiments are recorded
+by tagging events with an ``experiment.1:varying_experiment_name`` session. However, on the data
+pipeline side, experiment's name is parsed and stored separately (in the ``experiments`` column).
+Original ``experiment.1`` substring of the session name is stripped away.
+
+For example, the following telemetry data:
+
+.. code-block:: js
+
+    ...
+    sessions: ["awesomebar.1", "experiment.1:onboarding_a"]
+    ...
+
+Will be stored as:
+
+.. code-block:: js
+
+    ...
+    experiments: ["onboarding_a"],
+    sessions: ["awesomebar.1"]
+    ...
+
+Consider a case of A/B testing different variations of Activity Stream. We might want to run two
+experimental cohorts, each of them seeing a unique variation of the functionality. Depending on which
+cohort the user is in, their events while interacting with the experiments will be tagged accordingly
+with a current experiment name, while also retaining session information.
+On the analytics side it's then possible to compare performance of various metrics between cohorts.
+
 Events
 ======
 
 Events capture key occurrences. They should be brief and simple, and should not contain sensitive or excess information. Context for events should come from the session (scope). An event can be created with four fields (via ``Telemetry.sendUIEvent``): ``action``, ``method``, ``extras``, and ``timestamp``.
 
 ``action``
   The name of the event. Should be brief and lowercase. If needed, you can make use of namespacing with a '``.``' separator. Example event names: ``panel.switch``, ``panel.enable``, ``panel.disable``, ``panel.install``.
 
@@ -241,19 +273,26 @@ Methods
 ``toast``
   Action triggered from an unobtrusive, temporary notification.
 
 ``widget``
   Action triggered from a widget placed on the homescreen.
 
 Sessions
 --------
+``activitystream.1``
+  Activity Stream is active.
+
 ``awesomescreen.1``
   Awesomescreen (including frecency search) is active.
 
+``experiment.1``
+  Special, non-recorded session which is used to denote experiments. See ``Experiments`` section above.
+  Must be used in conjunction with an experiment's name, e.g. ``experiment.1:varying_experiment_name``.
+
 ``firstrun.1``
   Started the very first time we believe the application has been launched.
 
 ``frecency.1``
   Awesomescreen frecency search is active.
 
 ``homepanel.1``
   Started when a user enters a given home panel.
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/TabsRecord.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/TabsRecord.java
@@ -22,17 +22,17 @@ import android.content.ContentValues;
  *
  * @author rnewman
  *
  */
 public class TabsRecord extends Record {
   public static final String LOG_TAG = "TabsRecord";
 
   public static final String COLLECTION_NAME = "tabs";
-  public static final long TABS_TTL = 7 * 24 * 60 * 60; // 7 days in seconds.
+  public static final long TABS_TTL = 21 * 24 * 60 * 60; // 21 days in seconds.
 
   public TabsRecord(String guid, String collection, long lastModified, boolean deleted) {
     super(guid, collection, lastModified, deleted);
     this.ttl = TABS_TTL;
   }
   public TabsRecord(String guid, String collection, long lastModified) {
     this(guid, collection, lastModified, false);
   }
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -511,17 +511,17 @@ pref("media.getusermedia.playout_delay",
 pref("media.navigator.audio.full_duplex", true);
 #elif defined(XP_WIN)
 pref("media.peerconnection.capture_delay", 50);
 pref("media.getusermedia.playout_delay", 40);
 pref("media.navigator.audio.full_duplex", true);
 #elif defined(ANDROID)
 pref("media.peerconnection.capture_delay", 100);
 pref("media.getusermedia.playout_delay", 100);
-pref("media.navigator.audio.full_duplex", false);
+pref("media.navigator.audio.full_duplex", true);
 // Whether to enable Webrtc Hardware acceleration support
 pref("media.navigator.hardware.vp8_encode.acceleration_enabled", false);
 pref("media.navigator.hardware.vp8_decode.acceleration_enabled", false);
 #elif defined(XP_LINUX)
 pref("media.peerconnection.capture_delay", 70);
 pref("media.getusermedia.playout_delay", 50);
 pref("media.navigator.audio.full_duplex", true);
 #else
--- a/netwerk/base/nsUDPSocket.cpp
+++ b/netwerk/base/nsUDPSocket.cpp
@@ -658,17 +658,18 @@ nsUDPSocket::InitWithAddress(const NetAd
     PR_SetSocketOption(mFD, &opt);
   }
 
   opt.option = PR_SockOpt_Nonblocking;
   opt.value.non_blocking = true;
   PR_SetSocketOption(mFD, &opt);
 
   PRNetAddr addr;
-  PR_InitializeNetAddr(PR_IpAddrAny, 0, &addr);
+  // Temporary work around for IPv6 until bug 1330490 is fixed
+  memset(&addr, 0, sizeof(addr));
   NetAddrToPRNetAddr(aAddr, &addr);
 
   if (PR_Bind(mFD, &addr) != PR_SUCCESS)
   {
     NS_WARNING("failed to bind socket");
     goto fail;
   }
 
@@ -715,16 +716,17 @@ nsUDPSocket::Connect(const NetAddr *aAdd
   bool onSTSThread = false;
   mSts->IsOnCurrentThread(&onSTSThread);
   NS_ASSERTION(onSTSThread, "NOT ON STS THREAD");
   if (!onSTSThread) {
     return NS_ERROR_FAILURE;
   }
 
   PRNetAddr prAddr;
+  memset(&prAddr, 0, sizeof(prAddr));
   NetAddrToPRNetAddr(aAddr, &prAddr);
 
   if (PR_Connect(mFD, &prAddr, PR_INTERVAL_NO_WAIT) != PR_SUCCESS) {
     NS_WARNING("Cannot PR_Connect");
     return NS_ERROR_FAILURE;
   }
 
   // get the resulting socket address, which may have been updated.
--- a/python/mozlint/mozlint/roller.py
+++ b/python/mozlint/mozlint/roller.py
@@ -138,18 +138,21 @@ class LintRoller(object):
         workers = []
         for i in range(num_procs):
             workers.append(
                 pool.apply_async(_run_worker, args=(queue, paths), kwds=self.lintargs))
         pool.close()
 
         # ignore SIGINT in parent so we can still get partial results
         # from child processes. These should shutdown quickly anyway.
-        signal.signal(signal.SIGINT, signal.SIG_IGN)
+        orig_sigint = signal.signal(signal.SIGINT, signal.SIG_IGN)
         self.failed = []
         for worker in workers:
             # parent process blocks on worker.get()
             results, failed = worker.get()
             if failed:
                 self.failed.extend(failed)
             for k, v in results.iteritems():
                 all_results[k].extend(v)
+
+        signal.signal(signal.SIGINT, orig_sigint)
+        m.shutdown()
         return all_results
--- a/services/sync/modules/engines.js
+++ b/services/sync/modules/engines.js
@@ -609,17 +609,21 @@ EngineManager.prototype = {
     }
   },
 
   unregister(val) {
     let name = val;
     if (val instanceof Engine) {
       name = val.name;
     }
-    delete this._engines[name];
+    if (name in this._engines) {
+      let engine = this._engines[name];
+      delete this._engines[name];
+      engine.finalize();
+    }
   },
 
   clear() {
     for (let name in this._engines) {
       delete this._engines[name];
     }
   },
 };
@@ -722,17 +726,22 @@ Engine.prototype = {
 
   /**
    * If one exists, initialize and return a validator for this engine (which
    * must have a `validate(engine)` method that returns a promise to an object
    * with a getSummary method). Otherwise return null.
    */
   getValidator() {
     return null;
-  }
+  },
+
+  finalize() {
+    // Ensure the tracker finishes persisting changed IDs to disk.
+    Async.promiseSpinningly(this._tracker._storage.finalize());
+  },
 };
 
 this.SyncEngine = function SyncEngine(name, service) {
   Engine.call(this, name || "SyncEngine", service);
 
   this.loadToFetch();
   this.loadPreviousFailed();
 }
--- a/services/sync/modules/engines/tabs.js
+++ b/services/sync/modules/engines/tabs.js
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 this.EXPORTED_SYMBOLS = ["TabEngine", "TabSetRecord"];
 
 var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
-const TABS_TTL = 604800;           // 7 days.
+const TABS_TTL = 1814400;          // 21 days.
 const TAB_ENTRIES_LIMIT = 25;      // How many URLs to include in tab history.
 
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/engines/clients.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/util.js");
--- a/services/sync/tests/unit/test_clients_engine.js
+++ b/services/sync/tests/unit/test_clients_engine.js
@@ -44,16 +44,24 @@ function compareCommands(actual, expecte
   let tweakedActual = JSON.parse(JSON.stringify(actual));
   tweakedActual.map(elt => delete elt.flowID);
   deepEqual(tweakedActual, expected, description);
   // each item must have a unique flowID.
   let allIDs = new Set(actual.map(elt => elt.flowID).filter(fid => !!fid));
   equal(allIDs.size, actual.length, "all items have unique IDs");
 }
 
+function cleanup() {
+  Svc.Prefs.resetBranch("");
+  engine._tracker.clearChangedIDs();
+  engine._resetClient();
+  // We don't finalize storage at cleanup, since we use the same clients engine
+  // instance across all tests.
+}
+
 add_task(async function test_bad_hmac() {
   _("Ensure that Clients engine deletes corrupt records.");
   let contents = {
     meta: {global: {engines: {clients: {version: engine.version,
                                         syncID: engine.syncID}}}},
     clients: {},
     crypto: {}
   };
@@ -177,35 +185,32 @@ add_task(async function test_bad_hmac() 
     engine._sync();
     equal(deletedItems.length, 1);
     check_client_deleted(oldLocalID);
     check_clients_count(1);
     let newKey = Service.collectionKeys.keyForCollection();
     ok(!oldKey.equals(newKey));
 
   } finally {
-    Svc.Prefs.resetBranch("");
-    Service.recordManager.clearCache();
+    cleanup();
     await promiseStopServer(server);
   }
 });
 
-add_test(function test_properties() {
+add_task(async function test_properties() {
   _("Test lastRecordUpload property");
   try {
     equal(Svc.Prefs.get("clients.lastRecordUpload"), undefined);
     equal(engine.lastRecordUpload, 0);
 
     let now = Date.now();
     engine.lastRecordUpload = now / 1000;
     equal(engine.lastRecordUpload, Math.floor(now / 1000));
   } finally {
-    Svc.Prefs.resetBranch("");
-    engine._tracker.clearChangedIDs();
-    run_next_test();
+    cleanup();
   }
 });
 
 add_task(async function test_full_sync() {
   _("Ensure that Clients engine fetches all records for each sync.");
 
   let now = Date.now() / 1000;
   let contents = {
@@ -261,18 +266,17 @@ add_task(async function test_full_sync()
     engine.lastModified = now;
     engine._sync();
 
     _("Record should be updated");
     deepEqual(Object.keys(store.getAllIDs()).sort(),
               [activeID, engine.localID].sort(),
               "Deleted client should be removed on next sync");
   } finally {
-    Svc.Prefs.resetBranch("");
-    Service.recordManager.clearCache();
+    cleanup();
 
     try {
       server.deleteCollections("foo");
     } finally {
       await promiseStopServer(server);
     }
   }
 });
@@ -320,23 +324,22 @@ add_task(async function test_sync() {
     _("Time travel one day back, no record uploaded.");
     engine.lastRecordUpload -= LESS_THAN_CLIENTS_TTL_REFRESH;
     let yesterday = engine.lastRecordUpload;
     engine._sync();
     equal(clientWBO().payload, undefined);
     equal(engine.lastRecordUpload, yesterday);
 
   } finally {
-    Svc.Prefs.resetBranch("");
-    Service.recordManager.clearCache();
+    cleanup();
     await promiseStopServer(server);
   }
 });
 
-add_test(function test_client_name_change() {
+add_task(async function test_client_name_change() {
   _("Ensure client name change incurs a client record update.");
 
   let tracker = engine._tracker;
 
   engine.localID; // Needed to increase the tracker changedIDs count.
   let initialName = engine.localName;
 
   Svc.Obs.notify("weave:engine:start-tracking");
@@ -355,21 +358,20 @@ add_test(function test_client_name_chang
   notEqual(initialName, engine.localName);
   equal(Object.keys(tracker.changedIDs).length, 1);
   ok(engine.localID in tracker.changedIDs);
   ok(tracker.score > initialScore);
   ok(tracker.score >= SCORE_INCREMENT_XLARGE);
 
   Svc.Obs.notify("weave:engine:stop-tracking");
 
-  engine._tracker.clearChangedIDs();
-  run_next_test();
+  cleanup();
 });
 
-add_test(function test_send_command() {
+add_task(async function test_send_command() {
   _("Verifies _sendCommandToClient puts commands in the outbound queue.");
 
   let store = engine._store;
   let tracker = engine._tracker;
   let remoteId = Utils.makeGUID();
   let rec = new ClientsRec("clients", remoteId);
 
   store.create(rec);
@@ -388,21 +390,20 @@ add_test(function test_send_command() {
   let command = clientCommands[0];
   equal(command.command, action);
   equal(command.args.length, 2);
   deepEqual(command.args, args);
   ok(command.flowID);
 
   notEqual(tracker.changedIDs[remoteId], undefined);
 
-  engine._tracker.clearChangedIDs();
-  run_next_test();
+  cleanup();
 });
 
-add_test(function test_command_validation() {
+add_task(async function test_command_validation() {
   _("Verifies that command validation works properly.");
 
   let store = engine._store;
 
   let testCommands = [
     ["resetAll",    [],       true ],
     ["resetAll",    ["foo"],  false],
     ["resetEngine", ["tabs"], true ],
@@ -445,21 +446,20 @@ add_test(function test_command_validatio
       equal(clientCommands, undefined);
 
       if (store._tracker) {
         equal(engine._tracker[remoteId], undefined);
       }
     }
 
   }
-  engine._tracker.clearChangedIDs();
-  run_next_test();
+  cleanup();
 });
 
-add_test(function test_command_duplication() {
+add_task(async function test_command_duplication() {
   _("Ensures duplicate commands are detected and not added");
 
   let store = engine._store;
   let remoteId = Utils.makeGUID();
   let rec = new ClientsRec("clients", remoteId);
   store.create(rec);
   store.createRecord(remoteId, "clients");
 
@@ -480,60 +480,59 @@ add_test(function test_command_duplicati
   engine.sendCommand(action, [{ x: "bar" }], remoteId);
 
   _("Make sure we spot a real dupe argument.");
   engine.sendCommand(action, [{ x: "bar" }], remoteId);
 
   clientCommands = engine._readCommands()[remoteId];
   equal(clientCommands.length, 2);
 
-  engine._tracker.clearChangedIDs();
-  run_next_test();
+  cleanup();
 });
 
-add_test(function test_command_invalid_client() {
+add_task(async function test_command_invalid_client() {
   _("Ensures invalid client IDs are caught");
 
   let id = Utils.makeGUID();
   let error;
 
   try {
     engine.sendCommand("wipeAll", [], id);
   } catch (ex) {
     error = ex;
   }
 
   equal(error.message.indexOf("Unknown remote client ID: "), 0);
 
-  engine._tracker.clearChangedIDs();
-  run_next_test();
+  cleanup();
 });
 
-add_test(function test_process_incoming_commands() {
+add_task(async function test_process_incoming_commands() {
   _("Ensures local commands are executed");
 
   engine.localCommands = [{ command: "logout", args: [] }];
 
   let ev = "weave:service:logout:finish";
 
-  var handler = function() {
-    Svc.Obs.remove(ev, handler);
+  let logoutPromise = new Promise(resolve => {
+    var handler = function() {
+      Svc.Obs.remove(ev, handler);
 
-    Svc.Prefs.resetBranch("");
-    Service.recordManager.clearCache();
-    engine._resetClient();
+      resolve();
+    };
 
-    engine._tracker.clearChangedIDs();
-    run_next_test();
-  };
-
-  Svc.Obs.add(ev, handler);
+    Svc.Obs.add(ev, handler);
+  });
 
   // logout command causes processIncomingCommands to return explicit false.
   ok(!engine.processIncomingCommands());
+
+  await logoutPromise;
+
+  cleanup();
 });
 
 add_task(async function test_filter_duplicate_names() {
   _("Ensure that we exclude clients with identical names that haven't synced in a week.");
 
   let now = Date.now() / 1000;
   let contents = {
     meta: {global: {engines: {clients: {version: engine.version,
@@ -671,18 +670,17 @@ add_task(async function test_filter_dupl
       names: [engine.localName, "My Phone", engine.localName, "My old desktop"],
       numClients: 4,
     });
 
     ok(engine.remoteClientExists(dupeID), "recently synced dupe ID should now exist");
     equal(engine.remoteClients.length, 3, "recently synced dupe should now be in remoteClients");
 
   } finally {
-    Svc.Prefs.resetBranch("");
-    Service.recordManager.clearCache();
+    cleanup();
 
     try {
       server.deleteCollections("foo");
     } finally {
       await promiseStopServer(server);
     }
   }
 });
@@ -748,29 +746,28 @@ add_task(async function test_command_syn
     notEqual(engine.localCommands, undefined);
     equal(engine.localCommands.length, 1);
 
     let command = engine.localCommands[0];
     equal(command.command, "wipeAll");
     equal(command.args.length, 0);
 
   } finally {
-    Svc.Prefs.resetBranch("");
-    Service.recordManager.clearCache();
+    cleanup();
 
     try {
       let collection = server.getCollection("foo", "clients");
       collection.remove(remoteId);
     } finally {
       await promiseStopServer(server);
     }
   }
 });
 
-add_test(function test_send_uri_to_client_for_display() {
+add_task(async function test_send_uri_to_client_for_display() {
   _("Ensure sendURIToClientForDisplay() sends command properly.");
 
   let tracker = engine._tracker;
   let store = engine._store;
 
   let remoteId = Utils.makeGUID();
   let rec = new ClientsRec("clients", remoteId);
   rec.name = "remote";
@@ -807,25 +804,20 @@ add_test(function test_send_uri_to_clien
   try {
     engine.sendURIToClientForDisplay(uri, unknownId);
   } catch (ex) {
     error = ex;
   }
 
   equal(error.message.indexOf("Unknown remote client ID: "), 0);
 
-  Svc.Prefs.resetBranch("");
-  Service.recordManager.clearCache();
-  engine._resetClient();
-
-  engine._tracker.clearChangedIDs();
-  run_next_test();
+  cleanup();
 });
 
-add_test(function test_receive_display_uri() {
+add_task(async function test_receive_display_uri() {
   _("Ensure processing of received 'displayURI' commands works.");
 
   // We don't set up WBOs and perform syncing because other tests verify
   // the command API works as advertised. This saves us a little work.
 
   let uri = "http://www.mozilla.org/";
   let remoteId = Utils.makeGUID();
   let title = "Page Title!";
@@ -836,38 +828,39 @@ add_test(function test_receive_display_u
   };
 
   engine.localCommands = [command];
 
   // Received 'displayURI' command should result in the topic defined below
   // being called.
   let ev = "weave:engine:clients:display-uris";
 
-  let handler = function(subject, data) {
-    Svc.Obs.remove(ev, handler);
+  let promiseDisplayURI = new Promise(resolve => {
+    let handler = function(subject, data) {
+      Svc.Obs.remove(ev, handler);
 
-    equal(subject[0].uri, uri);
-    equal(subject[0].clientId, remoteId);
-    equal(subject[0].title, title);
-    equal(data, null);
+      resolve({ subject, data });
+    };
 
-    engine._tracker.clearChangedIDs();
-    run_next_test();
-  };
-
-  Svc.Obs.add(ev, handler);
+    Svc.Obs.add(ev, handler);
+  });
 
   ok(engine.processIncomingCommands());
 
-  Svc.Prefs.resetBranch("");
-  Service.recordManager.clearCache();
-  engine._resetClient();
+  let { subject, data } = await promiseDisplayURI;
+
+  equal(subject[0].uri, uri);
+  equal(subject[0].clientId, remoteId);
+  equal(subject[0].title, title);
+  equal(data, null);
+
+  cleanup();
 });
 
-add_test(function test_optional_client_fields() {
+add_task(async function test_optional_client_fields() {
   _("Ensure that we produce records with the fields added in Bug 1097222.");
 
   const SUPPORTED_PROTOCOL_VERSIONS = ["1.1", "1.5"];
   let local = engine._store.createRecord(engine.localID, "clients");
   equal(local.name, engine.localName);
   equal(local.type, engine.localType);
   equal(local.version, Services.appinfo.version);
   deepEqual(local.protocols, SUPPORTED_PROTOCOL_VERSIONS);
@@ -880,19 +873,17 @@ add_test(function test_optional_client_f
   // ... and also that they're non-empty.
   ok(!!local.os);
   ok(!!local.appPackage);
   ok(!!local.application);
 
   // We don't currently populate device or formfactor.
   // See Bug 1100722, Bug 1100723.
 
-  engine._resetClient();
-  engine._tracker.clearChangedIDs();
-  run_next_test();
+  cleanup();
 });
 
 add_task(async function test_merge_commands() {
   _("Verifies local commands for remote clients are merged with the server's");
 
   let now = Date.now() / 1000;
   let contents = {
     meta: {global: {engines: {clients: {version: engine.version,
@@ -951,19 +942,17 @@ add_task(async function test_merge_comma
       command: "logout",
       args: [],
     }], "Should send the logout command to the desktop client");
 
     let mobilePayload = JSON.parse(JSON.parse(collection.payload(mobileID)).ciphertext);
     compareCommands(mobilePayload.commands, [{ command: "logout", args: [] }],
                     "Should not send a duplicate logout to the mobile client");
   } finally {
-    Svc.Prefs.resetBranch("");
-    Service.recordManager.clearCache();
-    engine._resetClient();
+    cleanup();
 
     try {
       server.deleteCollections("foo");
     } finally {
       await promiseStopServer(server);
     }
   }
 });
@@ -1018,19 +1007,17 @@ add_task(async function test_duplicate_r
 
     let collection = server.getCollection("foo", "clients");
     let desktopPayload = JSON.parse(JSON.parse(collection.payload(desktopID)).ciphertext);
     compareCommands(desktopPayload.commands, [{
       command: "displayURI",
       args: ["https://foobar.com", engine.localID, "Foo bar!"],
     }], "Should only send the second command to the desktop client");
   } finally {
-    Svc.Prefs.resetBranch("");
-    Service.recordManager.clearCache();
-    engine._resetClient();
+    cleanup();
 
     try {
       server.deleteCollections("foo");
     } finally {
       await promiseStopServer(server);
     }
   }
 });
@@ -1108,19 +1095,17 @@ add_task(async function test_upload_afte
     engine._sync();
 
     deviceBPayload = JSON.parse(JSON.parse(collection.payload(deviceBID)).ciphertext);
     compareCommands(deviceBPayload.commands, [{
       command: "displayURI",
       args: ["https://example.com", engine.localID, "Yak Herders Anonymous"],
     }], "Should only had written our outgoing command");
   } finally {
-    Svc.Prefs.resetBranch("");
-    Service.recordManager.clearCache();
-    engine._resetClient();
+    cleanup();
 
     try {
       server.deleteCollections("foo");
     } finally {
       await promiseStopServer(server);
     }
   }
 });
@@ -1230,18 +1215,17 @@ add_task(async function test_keep_cleare
     engine._handleDisplayURIs = (uris) => { commandsProcessed = uris.length };
     engine._sync();
     engine.processIncomingCommands();
     equal(commandsProcessed, 1, "We processed one command (the other were cleared)");
 
     localRemoteRecord = JSON.parse(JSON.parse(collection.payload(deviceBID)).ciphertext);
     deepEqual(localRemoteRecord.commands, [], "Should be empty");
   } finally {
-    Svc.Prefs.resetBranch("");
-    Service.recordManager.clearCache();
+    cleanup();
 
     // Reset service (remove mocks)
     engine = Service.clientsEngine = new ClientEngine(Service);
     engine._resetClient();
 
     try {
       server.deleteCollections("foo");
     } finally {
@@ -1299,19 +1283,17 @@ add_task(async function test_deleted_com
 
     deepEqual(collection.keys().sort(), [activeID, engine.localID].sort(),
       "Should not reupload deleted clients");
 
     let activePayload = JSON.parse(JSON.parse(collection.payload(activeID)).ciphertext);
     compareCommands(activePayload.commands, [{ command: "logout", args: [] }],
       "Should send the command to the active client");
   } finally {
-    Svc.Prefs.resetBranch("");
-    Service.recordManager.clearCache();
-    engine._resetClient();
+    cleanup();
 
     try {
       server.deleteCollections("foo");
     } finally {
       await promiseStopServer(server);
     }
   }
 });
@@ -1362,19 +1344,17 @@ add_task(async function test_send_uri_ac
     }], "Should mark the commands as cleared after processing");
 
     _("Check that the command was removed on the server");
     engine._sync();
     ourPayload = JSON.parse(JSON.parse(collection.payload(engine.localID)).ciphertext);
     ok(ourPayload, "Should upload the synced client record");
     deepEqual(ourPayload.commands, [], "Should not reupload cleared commands");
   } finally {
-    Svc.Prefs.resetBranch("");
-    Service.recordManager.clearCache();
-    engine._resetClient();
+    cleanup();
 
     try {
       server.deleteCollections("foo");
     } finally {
       await promiseStopServer(server);
     }
   }
 });
@@ -1430,18 +1410,17 @@ add_task(async function test_command_syn
     engine._notifyCollectionChanged = (ids) => (notifiedIds = ids);
     _("Syncing.");
     engine._sync();
     deepEqual(notifiedIds, ["fxa-fake-guid-00", "fxa-fake-guid-01"]);
     ok(!notifiedIds.includes(engine.getClientFxaDeviceId(engine.localID)),
       "We never notify the local device");
 
   } finally {
-    Svc.Prefs.resetBranch("");
-    Service.recordManager.clearCache();
+    cleanup();
     engine._tracker.clearChangedIDs();
 
     try {
       server.deleteCollections("foo");
     } finally {
       await promiseStopServer(server);
     }
   }
--- a/services/sync/tests/unit/test_engine.js
+++ b/services/sync/tests/unit/test_engine.js
@@ -59,16 +59,25 @@ var engineObserver = {
 };
 Observers.add("weave:engine:reset-client:start", engineObserver);
 Observers.add("weave:engine:reset-client:finish", engineObserver);
 Observers.add("weave:engine:wipe-client:start", engineObserver);
 Observers.add("weave:engine:wipe-client:finish", engineObserver);
 Observers.add("weave:engine:sync:start", engineObserver);
 Observers.add("weave:engine:sync:finish", engineObserver);
 
+async function cleanup(engine) {
+  Svc.Prefs.resetBranch("");
+  engine.wasReset = false;
+  engine.wasSynced = false;
+  engineObserver.reset();
+  engine._tracker.clearChangedIDs();
+  await engine._tracker._storage.finalize();
+}
+
 add_task(async function test_members() {
   _("Engine object members");
   let engine = new SteamEngine("Steam", Service);
   do_check_eq(engine.Name, "Steam");
   do_check_eq(engine.prefName, "steam");
   do_check_true(engine._store instanceof SteamStore);
   do_check_true(engine._tracker instanceof SteamTracker);
 });
@@ -95,19 +104,17 @@ add_task(async function test_resetClient
   let engine = new SteamEngine("Steam", Service);
   do_check_false(engine.wasReset);
 
   engine.resetClient();
   do_check_true(engine.wasReset);
   do_check_eq(engineObserver.topics[0], "weave:engine:reset-client:start");
   do_check_eq(engineObserver.topics[1], "weave:engine:reset-client:finish");
 
-  engine.wasReset = false;
-  engineObserver.reset();
-  engine._tracker.clearChangedIDs();
+  await cleanup(engine);
 });
 
 add_task(async function test_invalidChangedIDs() {
   _("Test that invalid changed IDs on disk don't end up live.");
   let engine = new SteamEngine("Steam", Service);
   let tracker = engine._tracker;
 
   await tracker._beforeSave();
@@ -116,17 +123,17 @@ add_task(async function test_invalidChan
 
   ok(!tracker._storage.dataReady);
   tracker.changedIDs.placeholder = true;
   deepEqual(tracker.changedIDs, { placeholder: true },
     "Accessing changed IDs should load changes from disk as a side effect");
   ok(tracker._storage.dataReady);
 
   do_check_true(tracker.changedIDs.placeholder);
-  engine._tracker.clearChangedIDs();
+  await cleanup(engine);
 });
 
 add_task(async function test_wipeClient() {
   _("Engine.wipeClient calls resetClient, wipes store, clears changed IDs");
   let engine = new SteamEngine("Steam", Service);
   do_check_false(engine.wasReset);
   do_check_false(engine._store.wasWiped);
   do_check_true(engine._tracker.addChangedID("a-changed-id"));
@@ -136,34 +143,31 @@ add_task(async function test_wipeClient(
   do_check_true(engine.wasReset);
   do_check_true(engine._store.wasWiped);
   do_check_eq(JSON.stringify(engine._tracker.changedIDs), "{}");
   do_check_eq(engineObserver.topics[0], "weave:engine:wipe-client:start");
   do_check_eq(engineObserver.topics[1], "weave:engine:reset-client:start");
   do_check_eq(engineObserver.topics[2], "weave:engine:reset-client:finish");
   do_check_eq(engineObserver.topics[3], "weave:engine:wipe-client:finish");
 
-  engine.wasReset = false;
-  engine._store.wasWiped = false;
-  engineObserver.reset();
-  engine._tracker.clearChangedIDs();
+  await cleanup(engine);
 });
 
 add_task(async function test_enabled() {
   _("Engine.enabled corresponds to preference");
   let engine = new SteamEngine("Steam", Service);
   try {
     do_check_false(engine.enabled);
     Svc.Prefs.set("engine.steam", true);
     do_check_true(engine.enabled);
 
     engine.enabled = false;
     do_check_false(Svc.Prefs.get("engine.steam"));
   } finally {
-    Svc.Prefs.resetBranch("");
+    await cleanup(engine);
   }
 });
 
 add_task(async function test_sync() {
   let engine = new SteamEngine("Steam", Service);
   try {
     _("Engine.sync doesn't call _sync if it's not enabled");
     do_check_false(engine.enabled);
@@ -175,20 +179,17 @@ add_task(async function test_sync() {
     _("Engine.sync calls _sync if it's enabled");
     engine.enabled = true;
 
     engine.sync();
     do_check_true(engine.wasSynced);
     do_check_eq(engineObserver.topics[0], "weave:engine:sync:start");
     do_check_eq(engineObserver.topics[1], "weave:engine:sync:finish");
   } finally {
-    Svc.Prefs.resetBranch("");
-    engine.wasSynced = false;
-    engineObserver.reset();
-    engine._tracker.clearChangedIDs();
+    await cleanup(engine);
   }
 });
 
 add_task(async function test_disabled_no_track() {
   _("When an engine is disabled, its tracker is not tracking.");
   let engine = new SteamEngine("Steam", Service);
   let tracker = engine._tracker;
   do_check_eq(engine, tracker.engine);
@@ -208,10 +209,10 @@ add_task(async function test_disabled_no
   do_check_empty(tracker.changedIDs);
 
   tracker.addChangedID("abcdefghijkl");
   do_check_true(0 < tracker.changedIDs["abcdefghijkl"]);
   Svc.Prefs.set("engine." + engine.prefName, false);
   do_check_false(tracker._isTracking);
   do_check_empty(tracker.changedIDs);
 
-  engine._tracker.clearChangedIDs();
+  await cleanup(engine);
 });
--- a/services/sync/tests/unit/test_enginemanager.js
+++ b/services/sync/tests/unit/test_enginemanager.js
@@ -5,22 +5,25 @@ Cu.import("resource://services-sync/engi
 Cu.import("resource://services-sync/service.js");
 
 function run_test() {
   run_next_test();
 }
 
 function PetrolEngine() {}
 PetrolEngine.prototype.name = "petrol";
+PetrolEngine.prototype.finalize = function() {};
 
 function DieselEngine() {}
 DieselEngine.prototype.name = "diesel";
+DieselEngine.prototype.finalize = function() {};
 
 function DummyEngine() {}
 DummyEngine.prototype.name = "dummy";
+DummyEngine.prototype.finalize = function() {};
 
 function ActualEngine() {}
 ActualEngine.prototype = {__proto__: Engine.prototype,
                           name: "actual"};
 
 add_test(function test_basics() {
   _("We start out with a clean slate");
 
--- a/services/sync/tests/unit/test_telemetry.js
+++ b/services/sync/tests/unit/test_telemetry.js
@@ -347,18 +347,18 @@ add_task(async function test_generic_eng
       JSON.stringify(engine._tracker.changedIDs)}`);
     let ping = await sync_and_validate_telem(true);
     equal(ping.status.service, SYNC_FAILED_PARTIAL);
     deepEqual(ping.engines.find(e => e.name === "steam").failureReason, {
       name: "unexpectederror",
       error: String(e)
     });
   } finally {
+    await cleanAndGo(engine, server);
     Service.engineManager.unregister(engine);
-    await cleanAndGo(engine, server);
   }
 });
 
 add_task(async function test_engine_fail_ioerror() {
   Service.engineManager.register(SteamEngine);
   let engine = Service.engineManager.get("steam");
   engine.enabled = true;
   let server = serverForUsers({"foo": "password"}, {
@@ -384,18 +384,18 @@ add_task(async function test_engine_fail
     let ping = await sync_and_validate_telem(true);
     equal(ping.status.service, SYNC_FAILED_PARTIAL);
     let failureReason = ping.engines.find(e => e.name === "steam").failureReason;
     equal(failureReason.name, "unexpectederror");
     // ensure the profile dir in the exception message has been stripped.
     ok(!failureReason.error.includes(OS.Constants.Path.profileDir), failureReason.error);
     ok(failureReason.error.includes("[profileDir]"), failureReason.error);
   } finally {
+    await cleanAndGo(engine, server);
     Service.engineManager.unregister(engine);
-    await cleanAndGo(engine, server);
   }
 });
 
 add_task(async function test_initial_sync_engines() {
   Service.engineManager.register(SteamEngine);
   let engine = Service.engineManager.get("steam");
   engine.enabled = true;
   let engines = {};
@@ -424,16 +424,17 @@ add_task(async function test_initial_syn
       greaterOrEqual(e.took, 1);
       ok(!!e.outgoing)
       equal(e.outgoing.length, 1);
       notEqual(e.outgoing[0].sent, undefined);
       equal(e.outgoing[0].failed, undefined);
     }
   } finally {
     await cleanAndGo(engine, server);
+    Service.engineManager.unregister(engine);
   }
 });
 
 add_task(async function test_nserror() {
   Service.engineManager.register(SteamEngine);
   let engine = Service.engineManager.get("steam");
   engine.enabled = true;
   let server = serverForUsers({"foo": "password"}, {
@@ -452,18 +453,18 @@ add_task(async function test_nserror() {
       sync: LOGIN_FAILED_NETWORK_ERROR
     });
     let enginePing = ping.engines.find(e => e.name === "steam");
     deepEqual(enginePing.failureReason, {
       name: "nserror",
       code: Cr.NS_ERROR_UNKNOWN_HOST
     });
   } finally {
+    await cleanAndGo(engine, server);
     Service.engineManager.unregister(engine);
-    await cleanAndGo(engine, server);
   }
 });
 
 add_identity_test(this, async function test_discarding() {
   let helper = track_collections_helper();
   let upd = helper.with_updated_collection;
   let telem = get_sync_test_telemetry();
   telem.maxPayloadCount = 2;
@@ -517,18 +518,18 @@ add_task(async function test_no_foreign_
   });
   engine._errToThrow = new Error("Oh no!");
   await SyncTestingInfrastructure(server);
   try {
     let ping = await sync_and_validate_telem(true);
     equal(ping.status.service, SYNC_FAILED_PARTIAL);
     ok(ping.engines.every(e => e.name !== "bogus"));
   } finally {
+    await cleanAndGo(engine, server);
     Service.engineManager.unregister(engine);
-    await cleanAndGo(engine, server);
   }
 });
 
 add_task(async function test_sql_error() {
   Service.engineManager.register(SteamEngine);
   let engine = Service.engineManager.get("steam");
   engine.enabled = true;
   let server = serverForUsers({"foo": "password"}, {
@@ -544,18 +545,18 @@ add_task(async function test_sql_error()
   };
   try {
     _(`test_sql_error: Steam tracker contents: ${
       JSON.stringify(engine._tracker.changedIDs)}`);
     let ping = await sync_and_validate_telem(true);
     let enginePing = ping.engines.find(e => e.name === "steam");
     deepEqual(enginePing.failureReason, { name: "sqlerror", code: 1 });
   } finally {
+    await cleanAndGo(engine, server);
     Service.engineManager.unregister(engine);
-    await cleanAndGo(engine, server);
   }
 });
 
 add_task(async function test_no_foreign_engines_in_success_ping() {
   Service.engineManager.register(BogusEngine);
   let engine = Service.engineManager.get("bogus");
   engine.enabled = true;
   let server = serverForUsers({"foo": "password"}, {
@@ -563,18 +564,18 @@ add_task(async function test_no_foreign_
     steam: {}
   });
 
   await SyncTestingInfrastructure(server);
   try {
     let ping = await sync_and_validate_telem();
     ok(ping.engines.every(e => e.name !== "bogus"));
   } finally {
+    await cleanAndGo(engine, server);
     Service.engineManager.unregister(engine);
-    await cleanAndGo(engine, server);
   }
 });
 
 add_task(async function test_events() {
   Service.engineManager.register(BogusEngine);
   let engine = Service.engineManager.get("bogus");
   engine.enabled = true;
   let server = serverForUsers({"foo": "password"}, {
@@ -607,18 +608,18 @@ add_task(async function test_events() {
 
     Service.recordTelemetryEvent("object", "method", undefined, { foo: "bar" });
     ping = await wait_for_ping(() => Service.sync(), false, true);
     equal(ping.events.length, 1);
     equal(ping.events[0].length, 6);
     [timestamp, category, method, object, value, extra] = ping.events[0];
     equal(value, null);
   } finally {
+    await cleanAndGo(engine, server);
     Service.engineManager.unregister(engine);
-    await cleanAndGo(engine, server);
   }
 });
 
 add_task(async function test_invalid_events() {
   Service.engineManager.register(BogusEngine);
   let engine = Service.engineManager.get("bogus");
   engine.enabled = true;
   let server = serverForUsers({"foo": "password"}, {
@@ -651,18 +652,18 @@ add_task(async function test_invalid_eve
     await checkNotRecorded("object", "method", "value", badextra);
     badextra = { "x": long86 };
     await checkNotRecorded("object", "method", "value", badextra);
     for (let i = 0; i < 10; i++) {
       badextra["name" + i] = "x";
     }
     await checkNotRecorded("object", "method", "value", badextra);
   } finally {
+    await cleanAndGo(engine, server);
     Service.engineManager.unregister(engine);
-    await cleanAndGo(engine, server);
   }
 });
 
 add_task(async function test_no_ping_for_self_hosters() {
   let telem = get_sync_test_telemetry();
   let oldSubmit = telem.submit;
 
   Service.engineManager.register(BogusEngine);
@@ -683,12 +684,12 @@ add_task(async function test_no_ping_for
     });
     Service.sync();
     let pingSubmitted = await submitPromise;
     // The Sync testing infrastructure already sets up a custom token server,
     // so we don't need to do anything to simulate a self-hosted user.
     ok(!pingSubmitted, "Should not submit ping with custom token server URL");
   } finally {
     telem.submit = oldSubmit;
+    await cleanAndGo(engine, server);
     Service.engineManager.unregister(engine);
-    await cleanAndGo(engine, server);
   }
 });
--- a/taskcluster/ci/android-stuff/kind.yml
+++ b/taskcluster/ci/android-stuff/kind.yml
@@ -56,17 +56,16 @@ jobs:
               - "/home/worker/bin/before.sh && /home/worker/bin/build.sh && /home/worker/bin/after.sh && true\n"
             max-run-time: 36000
         scopes:
           - docker-worker:relengapi-proxy:tooltool.download.internal
           - docker-worker:relengapi-proxy:tooltool.download.public
         when:
             files-changed:
               - "mobile/android/config/**"
-              - "taskcluster/docker/android-gradle-build/**"
               - "testing/mozharness/configs/builds/releng_sub_android_configs/*gradle_dependencies.py"
               - "**/*.gradle"
 
     android-test:
         description: "Android armv7 unit tests"
         attributes:
             build_platform: android-test
             build_type: opt
--- a/taskcluster/ci/source-check/mozlint.yml
+++ b/taskcluster/ci/source-check/mozlint.yml
@@ -35,17 +35,16 @@ mozlint-eslint/opt:
             # Run when eslint policies change.
             - '**/.eslintignore'
             - '**/*eslintrc*'
             # The plugin implementing custom checks.
             - 'tools/lint/eslint/eslint-plugin-mozilla/**'
             # Other misc lint related files.
             - 'python/mozlint/**'
             - 'tools/lint/**'
-            - 'taskcluster/docker/lint/**'
 
 mozlint-flake8/opt:
     description: flake8 run over the gecko codebase
     treeherder:
         symbol: f8
         kind: test
         tier: 1
         platform: lint/opt
@@ -61,17 +60,16 @@ mozlint-flake8/opt:
         - integration
         - release
     when:
         files-changed:
             - '**/*.py'
             - '**/.flake8'
             - 'python/mozlint/**'
             - 'tools/lint/**'
-            - 'taskcluster/docker/lint/**'
 
 wptlint-gecko/opt:
     description: web-platform-tests linter
     treeherder:
         symbol: W
         kind: test
         tier: 1
         platform: lint/opt
@@ -89,9 +87,8 @@ wptlint-gecko/opt:
     when:
         files-changed:
             - 'testing/web-platform/tests/**'
             - 'testing/web-platform/mozilla/tests/**'
             - 'testing/web-platform/meta/MANIFEST.json'
             - 'testing/web-platform/mozilla/meta/MANIFEST.json'
             - 'python/mozlint/**'
             - 'tools/lint/**'
-            - 'taskcluster/docker/lint/**'
--- a/taskcluster/ci/test/test-sets.yml
+++ b/taskcluster/ci/test/test-sets.yml
@@ -65,19 +65,22 @@ talos:
 
 stylo-tests:
     - cppunit
     - crashtest
     - reftest-stylo
     - mochitest-style
 
 ccov-code-coverage-tests:
+    - crashtest
+    - jsreftest
     - mochitest
     - mochitest-browser-chrome
     - mochitest-devtools-chrome
+    - reftest
     - xpcshell
 
 jsdcov-code-coverage-tests:
     - mochitest-browser-chrome
     - mochitest-devtools-chrome
 
 ##
 # Test sets still being greened up in various ways
@@ -106,26 +109,16 @@ windows-vm-tests:
 
 # these tests currently run on hardware, but may migrate above when validated
 # note: on win, mochitest-a11y and mochitest-chrome come under mochitest-other
 # windows-hw-tests:
 #    - mochitest-clipboard
 #    - mochitest-gpu
 #    - mochitest-other
 
-ccov-code-coverage-tests:
-    - mochitest
-    - mochitest-browser-chrome
-    - mochitest-devtools-chrome
-    - xpcshell
-
-jsdcov-code-coverage-tests:
-    - mochitest-browser-chrome
-    - mochitest-devtools-chrome
-
 macosx64-tests-debug:
     - cppunit
     - crashtest
     # - gtest
     - jsreftest
     # - marionette
     # - mochitest
     # - mochitest-browser-chrome
--- a/taskcluster/ci/test/tests.yml
+++ b/taskcluster/ci/test/tests.yml
@@ -54,16 +54,17 @@ crashtest:
     chunks:
         by-test-platform:
             android-4.3-arm7-api-15/debug: 10
             android.*: 4
             default: 1
     e10s:
         by-test-platform:
             windows.*: false # Bug 1304435
+            linux64-ccov/opt: false
             default: both
     mozharness:
         by-test-platform:
             android.*:
                 script: android_emulator_unittest.py
                 no-read-buildbot-config: true
                 config:
                     - android/androidarm_4_3.py
@@ -206,16 +207,17 @@ jsreftest:
             android.*: 6
             windows.*: 1
             default: 2
     e10s:
         by-test-platform:
             # Bug 1321782
             windows.*: false
             android.*: false
+            linux64-ccov/opt: false
             default: both
     max-run-time:
         by-test-platform:
             android.*: 7200
             default: 3600
     run-on-projects:
         by-test-platform:
             windows.*: ['mozilla-central', 'try']
@@ -294,20 +296,16 @@ mochitest:
     description: "Mochitest plain run"
     suite: mochitest/plain-chunked
     treeherder-symbol: tc-M()
     loopback-video: true
     instance-size:
         by-test-platform:
             android.*: xlarge
             default: legacy # Bug 1281241: migrating to m3.large instances
-    run-on-projects:
-        by-test-platform:
-            linux64-ccov/opt: []
-            default: ['all']
     chunks:
         by-test-platform:
             android-4.3-arm7-api-15/debug: 32
             android.*: 20
             macosx.*: 5
             windows.*: 5
             linux.*: 10
     e10s:
@@ -341,22 +339,17 @@ mochitest:
                             - unittests/win_taskcluster_unittest.py
                         macosx.*:
                             - remove_executables.py
                             - unittests/mac_unittest.py
                         linux.*:
                             - unittests/linux_unittest.py
                             - remove_executables.py
                 extra-options:
-                    by-test-platform:
-                        linux64-ccov/opt:
-                            - --mochitest-suite=plain-chunked
-                            - --code-coverage
-                        default:
-                            - --mochitest-suite=plain-chunked
+                    - --mochitest-suite=plain-chunked
 
 mochitest-a11y:
     description: "Mochitest a11y run"
     suite: mochitest/a11y
     treeherder-symbol: tc-M(a11y)
     loopback-video: true
     e10s: false
     mozharness:
@@ -382,17 +375,16 @@ mochitest-browser-chrome:
         by-test-platform:
             linux64-jsdcov/opt: mochitest/browser-chrome-coverage
             default: mochitest/browser-chrome-chunked
     treeherder-symbol: tc-M(bc)
     loopback-video: true
     run-on-projects:
         by-test-platform:
             linux64-jsdcov/opt: []
-            linux64-ccov/opt: []
             default: ['all']
     chunks:
         by-test-platform:
             linux64-jsdcov/opt: 35
             linux64/debug: 12
             linux32/debug: 12
             linux64-asan/opt: 10
             default: 7
@@ -420,19 +412,16 @@ mochitest-browser-chrome:
                     - unittests/mac_unittest.py
                 linux.*:
                     - unittests/linux_unittest.py
                     - remove_executables.py
         extra-options:
             by-test-platform:
                 linux64-jsdcov/opt:
                     - --mochitest-suite=browser-chrome-coverage
-                linux64-ccov/opt:
-                    - --mochitest-suite=browser-chrome-chunked
-                    - --code-coverage
                 default:
                     - --mochitest-suite=browser-chrome-chunked
     # Bug 1281241: migrating to m3.large instances
     instance-size:
         by-test-platform:
             linux64-jsdcov/opt: xlarge
             linux64-ccov/opt: xlarge
             default: legacy
@@ -560,17 +549,16 @@ mochitest-devtools-chrome:
     loopback-video: true
     max-run-time: 5400
     chunks:
         by-test-platform:
             windows.*: 8
             default: 10
     run-on-projects:
         by-test-platform:
-            linux64-ccov/opt: []
             linux64-jsdcov/opt: []
             windows.*: ['mozilla-central', 'try']
             default: ['all']
     e10s:
         by-test-platform:
             linux64-ccov/opt: false
             linux64-jsdcov/opt: false
             # Bug 1304433: mochitest-devtools-chrome (e10s) not greened on windows
@@ -586,19 +574,16 @@ mochitest-devtools-chrome:
                 macosx.*:
                     - remove_executables.py
                     - unittests/mac_unittest.py
                 linux.*:
                     - unittests/linux_unittest.py
                     - remove_executables.py
         extra-options:
             by-test-platform:
-                linux64-ccov/opt:
-                    - --mochitest-suite=mochitest-devtools-chrome-chunked
-                    - --code-coverage
                 linux64-jsdcov/opt:
                     - --mochitest-suite=mochitest-devtools-chrome-coverage
                 default:
                     - --mochitest-suite=mochitest-devtools-chrome-chunked
     instance-size:
         by-test-platform:
             # Bug 1281241: migrating to m3.large instances
             linux64-asan/opt: legacy
@@ -844,16 +829,20 @@ reftest:
             android-4.3-arm7-api-15/debug: 48
             android.*: 16
             macosx.*: 1
             default: 8
     max-run-time:
         by-test-platform:
             android.*: 10800
             default: 3600
+    e10s:
+        by-test-platform:
+            linux64-ccov/opt: false
+            default: both
     mozharness:
         by-test-platform:
             android.*:
                 script: android_emulator_unittest.py
                 no-read-buildbot-config: true
                 config:
                     - android/androidarm_4_3.py
                     - remove_executables.py
@@ -1182,20 +1171,16 @@ web-platform-tests-wdspec:
                     - remove_executables.py
         extra-options:
             - --test-type=wdspec
 
 xpcshell:
     description: "xpcshell test run"
     suite: xpcshell
     treeherder-symbol: tc-X()
-    run-on-projects:
-        by-test-platform:
-            linux64-ccov/opt: []
-            default: ['all']
     chunks:
         by-test-platform:
             linux64/debug: 10
             android-4.2-x86/opt: 6
             # windows.*: 1
             macosx.*: 1
             default: 8
     instance-size:
@@ -1229,14 +1214,9 @@ xpcshell:
                             - unittests/win_taskcluster_unittest.py
                         macosx.*:
                             - remove_executables.py
                             - unittests/mac_unittest.py
                         linux.*:
                             - unittests/linux_unittest.py
                             - remove_executables.py
                 extra-options:
-                    by-test-platform:
-                        linux64-ccov/opt:
-                            - --xpcshell-suite=xpcshell
-                            - --code-coverage
-                        default:
-                            - --xpcshell-suite=xpcshell
+                    - --xpcshell-suite=xpcshell
--- a/taskcluster/taskgraph/test/test_try_option_syntax.py
+++ b/taskcluster/taskgraph/test/test_try_option_syntax.py
@@ -1,16 +1,15 @@
 # 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 unittest
-import itertools
 
 from ..try_option_syntax import TryOptionSyntax
 from ..try_option_syntax import RIDEALONG_BUILDS
 from ..graph import Graph
 from ..taskgraph import TaskGraph
 from .util import TestTask
 from mozunit import main
 
@@ -132,22 +131,19 @@ class TestTryOptionSyntax(unittest.TestC
     def test_p_linux_win32(self):
         "-p linux,win32 sets platforms=['linux', 'linux-l10n', 'win32']"
         tos = TryOptionSyntax('try: -p linux,win32', empty_graph)
         self.assertEqual(sorted(tos.platforms), ['linux', 'linux-l10n', 'win32'])
 
     def test_p_expands_ridealongs(self):
         "-p linux,linux64 includes the RIDEALONG_BUILDS"
         tos = TryOptionSyntax('try: -p linux,linux64', empty_graph)
-        ridealongs = list(task
-                          for task in itertools.chain.from_iterable(
-                                RIDEALONG_BUILDS.itervalues()
-                          )
-                          if 'android' not in task)  # Don't include android-l10n
-        self.assertEqual(sorted(tos.platforms), sorted(['linux', 'linux64'] + ridealongs))
+        platforms = set(['linux'] + RIDEALONG_BUILDS['linux'])
+        platforms |= set(['linux64'] + RIDEALONG_BUILDS['linux64'])
+        self.assertEqual(sorted(tos.platforms), sorted(platforms))
 
     def test_u_none(self):
         "-u none sets unittests=[]"
         tos = TryOptionSyntax('try: -u none', graph_with_jobs)
         self.assertEqual(sorted(tos.unittests), [])
 
     def test_u_all(self):
         "-u all sets unittests=[..whole list..]"
@@ -199,31 +195,31 @@ class TestTryOptionSyntax(unittest.TestC
         self.assertEqual(sorted(tos.unittests), sorted([
             {'test': 'gtest', 'platforms': ['linux', 'win32']},
         ]))
 
     def test_u_platforms_pretty(self):
         "-u gtest[Ubuntu] selects the linux, linux64 and linux64-asan platforms for gtest"
         tos = TryOptionSyntax('try: -u gtest[Ubuntu]', graph_with_jobs)
         self.assertEqual(sorted(tos.unittests), sorted([
-            {'test': 'gtest', 'platforms': ['linux', 'linux64', 'linux64-asan']},
+            {'test': 'gtest', 'platforms': ['linux32', 'linux64', 'linux64-asan']},
         ]))
 
     def test_u_platforms_negated(self):
         "-u gtest[-linux] selects all platforms but linux for gtest"
         tos = TryOptionSyntax('try: -u gtest[-linux]', graph_with_jobs)
         self.assertEqual(sorted(tos.unittests), sorted([
             {'test': 'gtest', 'platforms': ['linux64']},
         ]))
 
     def test_u_platforms_negated_pretty(self):
         "-u gtest[Ubuntu,-x64] selects just linux for gtest"
         tos = TryOptionSyntax('try: -u gtest[Ubuntu,-x64]', graph_with_jobs)
         self.assertEqual(sorted(tos.unittests), sorted([
-            {'test': 'gtest', 'platforms': ['linux']},
+            {'test': 'gtest', 'platforms': ['linux32']},
         ]))
 
     def test_u_chunks_platforms(self):
         "-u gtest-1[linux,win32] selects the linux and win32 platforms for chunk 1 of gtest"
         tos = TryOptionSyntax('try: -u gtest-1[linux,win32]', graph_with_jobs)
         self.assertEqual(sorted(tos.unittests), sorted([
             {'test': 'gtest', 'platforms': ['linux', 'win32'], 'only_chunks': set('1')},
         ]))
--- a/taskcluster/taskgraph/transforms/task.py
+++ b/taskcluster/taskgraph/transforms/task.py
@@ -742,16 +742,35 @@ def add_index_routes(config, tasks):
         else:
             extra_index['rank'] = rank
 
         del task['index']
         yield task
 
 
 @transforms.add
+def add_files_changed(config, tasks):
+    for task in tasks:
+        if 'files-changed' not in task.get('when', {}):
+            yield task
+            continue
+
+        task['when']['files-changed'].extend([
+            '{}/**'.format(config.path),
+            'taskcluster/taskgraph/**',
+        ])
+
+        if 'in-tree' in task['worker'].get('docker-image', {}):
+            task['when']['files-changed'].append('taskcluster/docker/{}/**'.format(
+                task['worker']['docker-image']['in-tree']))
+
+        yield task
+
+
+@transforms.add
 def build_task(config, tasks):
     for task in tasks:
         worker_type = task['worker-type'].format(level=str(config.params['level']))
         provisioner_id, worker_type = worker_type.split('/', 1)
 
         routes = task.get('routes', [])
         scopes = task.get('scopes', [])
 
--- a/taskcluster/taskgraph/transforms/tests.py
+++ b/taskcluster/taskgraph/transforms/tests.py
@@ -478,16 +478,26 @@ def handle_keyed_by(config, tests):
     ]
     for test in tests:
         for field in fields:
             resolve_keyed_by(test, field, item_name=test['test-name'])
         yield test
 
 
 @transforms.add
+def enable_code_coverage(config, tests):
+    """Enable code coverage for linux64-ccov/opt build-platforms"""
+    for test in tests:
+        if test['build-platform'] == 'linux64-ccov/opt':
+            test['mozharness'].setdefault('extra-options', []).append('--code-coverage')
+            test['run-on-projects'] = []
+        yield test
+
+
+@transforms.add
 def split_e10s(config, tests):
     for test in tests:
         e10s = test['e10s']
 
         test.setdefault('attributes', {})
         test['e10s'] = False
         test['attributes']['e10s'] = False
 
--- a/taskcluster/taskgraph/try_option_syntax.py
+++ b/taskcluster/taskgraph/try_option_syntax.py
@@ -119,17 +119,17 @@ UNITTEST_ALIASES = {
 }
 
 # unittest platforms can be specified by substring of the "pretty name", which
 # is basically the old Buildbot builder name.  This dict has {pretty name,
 # [test_platforms]} translations, This includes only the most commonly-used
 # substrings.  This is intended only for backward-compatibility.  New test
 # platforms should have their `test_platform` spelled out fully in try syntax.
 UNITTEST_PLATFORM_PRETTY_NAMES = {
-    'Ubuntu': ['linux', 'linux64', 'linux64-asan'],
+    'Ubuntu': ['linux32', 'linux64', 'linux64-asan'],
     'x64': ['linux64', 'linux64-asan'],
     'Android 4.3': ['android-4.3-arm7-api-15'],
     # other commonly-used substrings for platforms not yet supported with
     # in-tree taskgraphs:
     # '10.10': [..TODO..],
     # '10.10.5': [..TODO..],
     # '10.6': [..TODO..],
     # '10.8': [..TODO..],
@@ -554,10 +554,10 @@ class TryOptionSyntax(object):
         return "\n".join([
             "build_types: " + ", ".join(self.build_types),
             "platforms: " + none_for_all(self.platforms),
             "unittests: " + none_for_all(self.unittests),
             "talos: " + none_for_all(self.talos),
             "jobs: " + none_for_all(self.jobs),
             "trigger_tests: " + str(self.trigger_tests),
             "interactive: " + str(self.interactive),
-            "notifications: " + self.notifications,
+            "notifications: " + str(self.notifications),
         ])
--- a/testing/marionette/client/marionette_driver/marionette.py
+++ b/testing/marionette/client/marionette_driver/marionette.py
@@ -1,13 +1,14 @@
 # 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/.
 
 import base64
+import copy
 import datetime
 import json
 import os
 import socket
 import sys
 import time
 import traceback
 import warnings
@@ -539,17 +540,17 @@ class Alert(object):
         self.marionette._send_message("sendKeysToDialog", body)
 
 
 class Marionette(object):
     """Represents a Marionette connection to a browser or device."""
 
     CONTEXT_CHROME = "chrome"  # non-browser content: windows, dialogs, etc.
     CONTEXT_CONTENT = "content"  # browser content: iframes, divs, etc.
-    DEFAULT_SOCKET_TIMEOUT = 60
+    DEFAULT_SOCKET_TIMEOUT = 65
     DEFAULT_STARTUP_TIMEOUT = 120
     DEFAULT_SHUTDOWN_TIMEOUT = 65  # Firefox will kill hanging threads after 60s
 
     def __init__(self, host="localhost", port=2828, app=None, bin=None,
                  baseurl=None, socket_timeout=DEFAULT_SOCKET_TIMEOUT,
                  startup_timeout=None, **instance_args):
         """Construct a holder for the Marionette connection.
 
@@ -1242,23 +1243,22 @@ class Marionette(object):
         '''
         Returns an absolute url for files served from Marionette's www directory.
 
         :param relative_url: The url of a static file, relative to Marionette's www directory.
         '''
         return "{0}{1}".format(self.baseurl, relative_url)
 
     @do_process_check
-    def start_session(self, desired_capabilities=None, session_id=None, timeout=60):
+    def start_session(self, capabilities=None, session_id=None, timeout=60):
         """Create a new Marionette session.
 
         This method must be called before performing any other action.
 
-        :param desired_capabilities: An optional dict of desired
-            capabilities.  This is currently ignored.
+        :param capabilities: An optional dict of desired or required capabilities.
         :param timeout: Timeout in seconds for the server to be ready.
         :param session_id: unique identifier for the session. If no session id is
             passed in then one will be generated by the marionette server.
 
         :returns: A dict of the capabilities offered.
 
         """
         self.crashed = 0
@@ -1274,17 +1274,35 @@ class Marionette(object):
             self.port,
             self.socket_timeout)
 
         # Call wait_for_port() before attempting to connect in
         # the event gecko hasn't started yet.
         self.wait_for_port(timeout=timeout)
         self.protocol, _ = self.client.connect()
 
-        body = {"capabilities": desired_capabilities, "sessionId": session_id}
+        if capabilities is not None:
+            caps = copy.deepcopy(capabilities)
+        else:
+            caps = {}
+
+        # Bug 1322277 - Override some default capabilities which are defined by
+        # the Webdriver spec but which don't really apply to tests executed with
+        # the Marionette client. Using "desiredCapabilities" here will allow tests
+        # to override the settings via "desiredCapabilities" and requiredCapabilities".
+        if "desiredCapabilities" not in caps:
+            caps.update({
+                "desiredCapabilities": {
+                    "timeouts": {
+                        "page load": 60000,  # webdriver specifies 300000ms
+                    }
+                }
+            })
+
+        body = {"capabilities": caps, "sessionId": session_id}
         resp = self._send_message("newSession", body)
 
         self.session_id = resp["sessionId"]
         self.session = resp["value"] if self.protocol == 1 else resp["capabilities"]
         # fallback to processId can be removed in Firefox 55
         self.process_id = self.session.get("moz:processID", self.session.get("processId"))
         self.profile = self.session.get("moz:profile")
 
--- a/testing/marionette/harness/marionette_harness/runner/base.py
+++ b/testing/marionette/harness/marionette_harness/runner/base.py
@@ -235,17 +235,17 @@ class MarionetteTextTestRunner(Structure
 
     def run(self, test):
         result = super(MarionetteTextTestRunner, self).run(test)
         result.printLogs(test)
         return result
 
 
 class BaseMarionetteArguments(ArgumentParser):
-    socket_timeout_default = 60.0
+    socket_timeout_default = 65.0
 
     def __init__(self, **kwargs):
         ArgumentParser.__init__(self, **kwargs)
 
         def dir_path(path):
             path = os.path.abspath(os.path.expanduser(path))
             if not os.access(path, os.F_OK):
                 os.makedirs(path)
@@ -342,17 +342,18 @@ class BaseMarionetteArguments(ArgumentPa
                           default=False,
                           help='Enable the jsdebugger for marionette javascript.')
         self.add_argument('--pydebugger',
                           help='Enable python post-mortem debugger when a test fails.'
                                ' Pass in the debugger you want to use, eg pdb or ipdb.')
         self.add_argument('--socket-timeout',
                           type=float,
                           default=self.socket_timeout_default,
-                          help='Set the global timeout for marionette socket operations.')
+                          help='Set the global timeout for marionette socket operations.'
+                               ' Default: %(default)ss.')
         self.add_argument('--disable-e10s',
                           action='store_false',
                           dest='e10s',
                           default=True,
                           help='Disable e10s when running marionette tests.')
         self.add_argument('--tag',
                           action='append', dest='test_tags',
                           default=None,
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_capabilities.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_capabilities.py
@@ -6,17 +6,24 @@ from marionette_driver.errors import Ses
 
 from marionette_harness import MarionetteTestCase
 
 
 class TestCapabilities(MarionetteTestCase):
 
     def setUp(self):
         super(TestCapabilities, self).setUp()
+
+        # Force default webdriver capabilities by default to test
+        # Marionette server.
+        self.marionette.delete_session()
+        self.marionette.start_session({"desiredCapabilities": {}})
+
         self.caps = self.marionette.session_capabilities
+
         with self.marionette.using_context("chrome"):
             self.appinfo = self.marionette.execute_script(
                 "return Services.appinfo")
             self.os_name = self.marionette.execute_script(
                 "return Services.sysinfo.getProperty('name')").lower()
             self.os_version = self.marionette.execute_script(
                 "return Services.sysinfo.getProperty('version')")
 
@@ -28,20 +35,18 @@ class TestCapabilities(MarionetteTestCas
         self.assertIn("acceptInsecureCerts", self.caps)
         self.assertIn("timeouts", self.caps)
 
         self.assertEqual(self.caps["browserName"], self.appinfo["name"].lower())
         self.assertEqual(self.caps["browserVersion"], self.appinfo["version"])
         self.assertEqual(self.caps["platformName"], self.os_name)
         self.assertEqual(self.caps["platformVersion"], self.os_version)
         self.assertFalse(self.caps["acceptInsecureCerts"])
-        self.assertDictEqual(self.caps["timeouts"],
-                             {"implicit": 0,
-                              "page load": 300000,
-                              "script": 30000})
+        self.assertEqual(self.caps["timeouts"],
+                         {"implicit": 0, "page load": 300000, "script": 30000})
 
     def test_supported_features(self):
         self.assertIn("rotatable", self.caps)
 
     def test_additional_capabilities(self):
         self.assertIn("moz:processID", self.caps)
         self.assertEqual(self.caps["moz:processID"], self.appinfo["processID"])
         self.assertEqual(self.marionette.process_id, self.appinfo["processID"])
@@ -55,16 +60,24 @@ class TestCapabilities(MarionetteTestCas
             self.assertEqual(self.caps["moz:profile"], current_profile)
             self.assertEqual(self.marionette.profile, current_profile)
 
         self.assertIn("moz:accessibilityChecks", self.caps)
         self.assertFalse(self.caps["moz:accessibilityChecks"])
         self.assertIn("specificationLevel", self.caps)
         self.assertEqual(self.caps["specificationLevel"], 0)
 
+    def test_default_client_capabilities(self):
+        self.marionette.delete_session()
+        self.marionette.start_session()
+        caps = self.marionette.session_capabilities
+
+        self.assertEqual(caps["timeouts"],
+                         {"implicit": 0, "page load": 60000, "script": 30000})
+
     def test_set_specification_level(self):
         self.marionette.delete_session()
         self.marionette.start_session({"desiredCapabilities": {"specificationLevel": 2}})
         caps = self.marionette.session_capabilities
         self.assertEqual(2, caps["specificationLevel"])
 
         self.marionette.delete_session()
         self.marionette.start_session({"requiredCapabilities": {"specificationLevel": 3}})
@@ -117,97 +130,104 @@ class TestCapabilityMatching(MarionetteT
     def test_browser_name_required(self):
         self.marionette.start_session({"requiredCapabilities": {"browserName": self.browser_name}})
         self.assertEqual(self.marionette.session_capabilities["browserName"], self.browser_name)
 
     def test_browser_name_desired_allowed_types(self):
         for typ in self.allowed:
             self.marionette.delete_session()
             self.marionette.start_session({"desiredCapabilities": {"browserName": typ}})
-            self.assertEqual(self.marionette.session_capabilities["browserName"], self.browser_name)
+            self.assertEqual(self.marionette.session_capabilities["browserName"],
+                             self.browser_name)
 
     def test_browser_name_desired_disallowed_types(self):
         for typ in self.disallowed:
             with self.assertRaises(SessionNotCreatedException):
                 self.marionette.start_session({"desiredCapabilities": {"browserName": typ}})
 
     def test_browser_name_required_allowed_types(self):
         for typ in self.allowed:
             self.marionette.delete_session()
             self.marionette.start_session({"requiredCapabilities": {"browserName": typ}})
-            self.assertEqual(self.marionette.session_capabilities["browserName"], self.browser_name)
+            self.assertEqual(self.marionette.session_capabilities["browserName"],
+                             self.browser_name)
 
     def test_browser_name_requried_disallowed_types(self):
         for typ in self.disallowed:
             with self.assertRaises(SessionNotCreatedException):
                 self.marionette.start_session({"requiredCapabilities": {"browserName": typ}})
 
     def test_browser_name_prefers_required(self):
         caps = {"desiredCapabilities": {"browserName": "invalid"},
-                    "requiredCapabilities": {"browserName": "*"}}
+                "requiredCapabilities": {"browserName": "*"}}
         self.marionette.start_session(caps)
 
     def test_browser_name_error_on_invalid_required(self):
         with self.assertRaises(SessionNotCreatedException):
             caps = {"desiredCapabilities": {"browserName": "*"},
-                        "requiredCapabilities": {"browserName": "invalid"}}
+                    "requiredCapabilities": {"browserName": "invalid"}}
             self.marionette.start_session(caps)
 
     # TODO(ato): browser version comparison not implemented yet
 
     def test_platform_name_desired(self):
-        self.marionette.start_session({"desiredCapabilities": {"platformName": self.platform_name}})
+        self.marionette.start_session(
+            {"desiredCapabilities": {"platformName": self.platform_name}})
         self.assertEqual(self.marionette.session_capabilities["platformName"], self.platform_name)
 
     def test_platform_name_required(self):
-        self.marionette.start_session({"requiredCapabilities": {"platformName": self.platform_name}})
+        self.marionette.start_session(
+            {"requiredCapabilities": {"platformName": self.platform_name}})
         self.assertEqual(self.marionette.session_capabilities["platformName"], self.platform_name)
 
     def test_platform_name_desired_allowed_types(self):
         for typ in self.allowed:
             self.marionette.delete_session()
             self.marionette.start_session({"desiredCapabilities": {"platformName": typ}})
-            self.assertEqual(self.marionette.session_capabilities["platformName"], self.platform_name)
+            self.assertEqual(self.marionette.session_capabilities["platformName"],
+                             self.platform_name)
 
     def test_platform_name_desired_disallowed_types(self):
         for typ in self.disallowed:
             with self.assertRaises(SessionNotCreatedException):
                 self.marionette.start_session({"desiredCapabilities": {"platformName": typ}})
 
     def test_platform_name_required_allowed_types(self):
         for typ in self.allowed:
             self.marionette.delete_session()
             self.marionette.start_session({"requiredCapabilities": {"platformName": typ}})
-            self.assertEqual(self.marionette.session_capabilities["platformName"], self.platform_name)
+            self.assertEqual(self.marionette.session_capabilities["platformName"],
+                             self.platform_name)
 
     def test_platform_name_requried_disallowed_types(self):
         for typ in self.disallowed:
             with self.assertRaises(SessionNotCreatedException):
                 self.marionette.start_session({"requiredCapabilities": {"platformName": typ}})
 
     def test_platform_name_prefers_required(self):
         caps = {"desiredCapabilities": {"platformName": "invalid"},
-                    "requiredCapabilities": {"platformName": "*"}}
+                "requiredCapabilities": {"platformName": "*"}}
         self.marionette.start_session(caps)
 
     def test_platform_name_error_on_invalid_required(self):
         with self.assertRaises(SessionNotCreatedException):
             caps = {"desiredCapabilities": {"platformName": "*"},
-                        "requiredCapabilities": {"platformName": "invalid"}}
+                    "requiredCapabilities": {"platformName": "invalid"}}
             self.marionette.start_session(caps)
 
     # TODO(ato): platform version comparison not imlpemented yet
 
     def test_accept_insecure_certs(self):
         for capability_type in ["desiredCapabilities", "requiredCapabilities"]:
             print("testing {}".format(capability_type))
             for value in ["", 42, {}, []]:
                 print("  type {}".format(type(value)))
                 with self.assertRaises(SessionNotCreatedException):
-                    self.marionette.start_session({capability_type: {"acceptInsecureCerts": value}})
+                    self.marionette.start_session(
+                        {capability_type: {"acceptInsecureCerts": value}})
 
         self.marionette.delete_session()
         self.marionette.start_session({"desiredCapabilities": {"acceptInsecureCerts": True}})
         self.assertTrue(self.marionette.session_capabilities["acceptInsecureCerts"])
         self.marionette.delete_session()
         self.marionette.start_session({"requiredCapabilities": {"acceptInsecureCerts": True}})
 
         self.assertTrue(self.marionette.session_capabilities["acceptInsecureCerts"])
@@ -236,14 +256,14 @@ class TestCapabilityMatching(MarionetteT
 
     def test_proxy_required(self):
         self.marionette.start_session({"requiredCapabilities": {"proxy": {"proxyType": "manual"}}})
         self.assertIn("proxy", self.marionette.session_capabilities)
         self.assertEqual(self.marionette.session_capabilities["proxy"]["proxyType"], "manual")
         self.assertEqual(self.marionette.get_pref("network.proxy.type"), 1)
 
     def test_timeouts(self):
-        timeouts = {u"implicit": 123, u"page load": 456, u"script": 789}
+        timeouts = {"implicit": 123, "page load": 456, "script": 789}
         caps = {"desiredCapabilities": {"timeouts": timeouts}}
         self.marionette.start_session(caps)
         self.assertIn("timeouts", self.marionette.session_capabilities)
-        self.assertDictEqual(self.marionette.session_capabilities["timeouts"], timeouts)
-        self.assertDictEqual(self.marionette._send_message("getTimeouts"), timeouts)
+        self.assertEqual(self.marionette.session_capabilities["timeouts"], timeouts)
+        self.assertEqual(self.marionette._send_message("getTimeouts"), timeouts)
--- a/testing/mozharness/configs/builds/taskcluster_firefox_win32_clang.py
+++ b/testing/mozharness/configs/builds/taskcluster_firefox_win32_clang.py
@@ -56,17 +56,17 @@ config = {
     'max_build_output_timeout': 60 * 80,
     #########################################################################
 
 
      #########################################################################
      ###### 32 bit specific ######
     'base_name': 'WINNT_5.2_%(branch)s',
     'platform': 'win32',
-    'stage_platform': 'win32',
+    'stage_platform': 'win32-st-an',
     'publish_nightly_en_US_routes': True,
     'env': {
         'BINSCOPE': os.path.join(
             os.environ['ProgramFiles(x86)'], 'Microsoft', 'SDL BinScope', 'BinScope.exe'
         ),
         'HG_SHARE_BASE_DIR': os.path.join('y:', os.sep, 'hg-shared'),
         'MOZBUILD_STATE_PATH': os.path.join(os.getcwd(), '.mozbuild'),
         'MOZ_AUTOMATION': '1',
--- a/testing/mozharness/configs/builds/taskcluster_firefox_win32_clang_debug.py
+++ b/testing/mozharness/configs/builds/taskcluster_firefox_win32_clang_debug.py
@@ -56,17 +56,17 @@ config = {
     'max_build_output_timeout': 60 * 80,
     #########################################################################
 
 
      #########################################################################
      ###### 32 bit specific ######
     'base_name': 'WINNT_5.2_%(branch)s',
     'platform': 'win32',
-    'stage_platform': 'win32-debug',
+    'stage_platform': 'win32-st-an-debug',
     'debug_build': True,
     'publish_nightly_en_US_routes': True,
     'env': {
         'BINSCOPE': os.path.join(
             os.environ['ProgramFiles(x86)'], 'Microsoft', 'SDL BinScope', 'BinScope.exe'
         ),
         'HG_SHARE_BASE_DIR': os.path.join('y:', os.sep, 'hg-shared'),
         'MOZBUILD_STATE_PATH': os.path.join(os.getcwd(), '.mozbuild'),
--- a/testing/mozharness/configs/builds/taskcluster_firefox_win64_clang.py
+++ b/testing/mozharness/configs/builds/taskcluster_firefox_win64_clang.py
@@ -56,17 +56,17 @@ config = {
     'max_build_output_timeout': 60 * 80,
     #########################################################################
 
 
      #########################################################################
      ###### 64 bit specific ######
     'base_name': 'WINNT_6.1_x86-64_%(branch)s',
     'platform': 'win64',
-    'stage_platform': 'win64',
+    'stage_platform': 'win64-st-an',
     'publish_nightly_en_US_routes': True,
     'env': {
         'HG_SHARE_BASE_DIR': os.path.join('y:', os.sep, 'hg-shared'),
         'MOZ_AUTOMATION': '1',
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PDBSTR_PATH': '/c/Program Files (x86)/Windows Kits/10/Debuggers/x64/srcsrv/pdbstr.exe',
         'TINDERBOX_OUTPUT': '1',
--- a/testing/mozharness/configs/builds/taskcluster_firefox_win64_clang_debug.py
+++ b/testing/mozharness/configs/builds/taskcluster_firefox_win64_clang_debug.py
@@ -56,17 +56,17 @@ config = {
     'max_build_output_timeout': 60 * 80,
     #########################################################################
 
 
      #########################################################################
      ###### 64 bit specific ######
     'base_name': 'WINNT_6.1_x86-64_%(branch)s',
     'platform': 'win64',
-    'stage_platform': 'win64-debug',
+    'stage_platform': 'win64-st-an-debug',
     'debug_build': True,
     'publish_nightly_en_US_routes': True,
     'env': {
         'HG_SHARE_BASE_DIR': os.path.join('y:', os.sep, 'hg-shared'),
         'MOZ_AUTOMATION': '1',
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PDBSTR_PATH': '/c/Program Files (x86)/Windows Kits/10/Debuggers/x64/srcsrv/pdbstr.exe',
--- a/toolkit/components/passwordmgr/InsecurePasswordUtils.jsm
+++ b/toolkit/components/passwordmgr/InsecurePasswordUtils.jsm
@@ -70,17 +70,25 @@ this.InsecurePasswordUtils = {
    * Checks if there are insecure password fields present on the form's document
    * i.e. passwords inside forms with http action, inside iframes with http src,
    * or on insecure web pages.
    *
    * @param {FormLike} aForm A form-like object. @See {LoginFormFactory}
    * @return {boolean} whether the form is secure
    */
   isFormSecure(aForm) {
-    let isSafePage = aForm.ownerDocument.defaultView.isSecureContext;
+    // We don't want to expose JavaScript APIs in a non-Secure Context even if
+    // the context is only insecure because the windows has an insecure opener.
+    // Doing so prevents sites from implementing postMessage workarounds to enable
+    // an insecure opener to gain access to Secure Context-only APIs. However,
+    // in the case of form fields such as password fields we don't need to worry
+    // about whether the opener is secure or not. In fact to flag a password
+    // field as insecure in such circumstances would unnecessarily confuse our
+    // users. Hence we use isSecureContextIfOpenerIgnored here.
+    let isSafePage = aForm.ownerDocument.defaultView.isSecureContextIfOpenerIgnored;
     let { isFormSubmitSecure, isFormSubmitHTTP } = this._checkFormSecurity(aForm);
 
     return isSafePage && (isFormSubmitSecure || !isFormSubmitHTTP);
   },
 
   /**
    * Report insecure password fields in a form to the web console to warn developers.
    *
--- a/toolkit/content/widgets/datepicker.js
+++ b/toolkit/content/widgets/datepicker.js
@@ -279,30 +279,30 @@ function DatePicker(context) {
    *          {String} locale
    *          {Function} setYear
    *          {Function} setMonth
    *        }
    * @param {DOMElement} context
    */
   function MonthYear(options, context) {
     const spinnerSize = 5;
-    const monthFormat = new Intl.DateTimeFormat(options.locale, { month: "short" }).format;
+    const monthFormat = new Intl.DateTimeFormat(options.locale, { month: "short", timeZone: "UTC" }).format;
     const yearFormat = new Intl.DateTimeFormat(options.locale, { year: "numeric" }).format;
     const dateFormat = new Intl.DateTimeFormat(options.locale, { year: "numeric", month: "long" }).format;
 
     this.context = context;
     this.state = { dateFormat };
     this.props = {};
     this.components = {
       month: new Spinner({
         setValue: month => {
           this.state.isMonthSet = true;
           options.setMonth(month);
         },
-        getDisplayString: month => monthFormat(new Date(0, month)),
+        getDisplayString: month => monthFormat(new Date(Date.UTC(0, month))),
         viewportSize: spinnerSize
       }, context.monthYearView),
       year: new Spinner({
         setValue: year => {
           this.state.isYearSet = true;
           options.setYear(year);
         },
         getDisplayString: year => yearFormat(new Date(new Date(0).setFullYear(year))),
--- a/toolkit/content/widgets/datetimepicker.xml
+++ b/toolkit/content/widgets/datetimepicker.xml
@@ -982,23 +982,23 @@
           return val;
         </setter>
       </property>
 
       <method name="_init">
         <body>
           <![CDATA[
             var locale = Intl.DateTimeFormat().resolvedOptions().locale + "-u-ca-gregory";
-            var dtfMonth = Intl.DateTimeFormat(locale, {month: "long"});
+            var dtfMonth = Intl.DateTimeFormat(locale, {month: "long", timeZone: "UTC"});
             var dtfWeekday = Intl.DateTimeFormat(locale, {weekday: "narrow"});
 
             var monthLabel = this.monthField.firstChild;
-            var tempDate = new Date(2005, 0, 1);
+            var tempDate = new Date(Date.UTC(2005, 0, 1));
             for (var month = 0; month < 12; month++) {
-              tempDate.setMonth(month);
+              tempDate.setUTCMonth(month);
               monthLabel.setAttribute("value", dtfMonth.format(tempDate));
               monthLabel = monthLabel.nextSibling;
             }
 
             var fdow = Number(this.getAttribute("firstdayofweek"));
             if (!isNaN(fdow) && fdow >= 0 && fdow <= 6)
               this._weekStart = fdow;
 
--- a/toolkit/modules/JSONFile.jsm
+++ b/toolkit/modules/JSONFile.jsm
@@ -81,34 +81,40 @@ const kSaveDelayMs = 1500;
  *        - saveDelayMs: Number indicating the delay (in milliseconds) between a
  *                       change to the data and the related save operation. The
  *                       default value will be applied if omitted.
  *        - beforeSave: Promise-returning function triggered just before the
  *                      data is written to disk. This can be used to create any
  *                      intermediate directories before saving. The file will
  *                      not be saved if the promise rejects or the function
  *                      throws an exception.
+ *        - finalizeAt: An `AsyncShutdown` phase or barrier client that should
+ *                      automatically finalize the file when triggered. Defaults
+ *                      to `profileBeforeChange`; exposed as an option for
+ *                      testing.
  */
 function JSONFile(config) {
   this.path = config.path;
 
   if (typeof config.dataPostProcessor === "function") {
     this._dataPostProcessor = config.dataPostProcessor;
   }
   if (typeof config.beforeSave === "function") {
     this._beforeSave = config.beforeSave;
   }
 
   if (config.saveDelayMs === undefined) {
     config.saveDelayMs = kSaveDelayMs;
   }
   this._saver = new DeferredTask(() => this._save(), config.saveDelayMs);
 
-  AsyncShutdown.profileBeforeChange.addBlocker("JSON store: writing data",
-                                               () => this._saver.finalize());
+  this._finalizeAt = config.finalizeAt || AsyncShutdown.profileBeforeChange;
+  this._finalizeInternalBound = this._finalizeInternal.bind(this);
+  this._finalizeAt.addBlocker("JSON store: writing data",
+                              this._finalizeInternalBound);
 }
 
 JSONFile.prototype = {
   /**
    * String containing the file path where data should be saved.
    */
   path: "",
 
@@ -123,16 +129,23 @@ JSONFile.prototype = {
   _saver: null,
 
   /**
    * Internal data object.
    */
   _data: null,
 
   /**
+   * Internal fields used during finalization.
+   */
+  _finalizeAt: null,
+  _finalizePromise: null,
+  _finalizeInternalBound: null,
+
+  /**
    * Serializable object containing the data. This is populated directly with
    * the data loaded from the file, and is saved without modifications.
    *
    * The raw data should be manipulated synchronously, without waiting for the
    * event loop or for promise resolution, so that the saved file is always
    * consistent.
    */
   get data() {
@@ -275,11 +288,50 @@ JSONFile.prototype = {
     yield OS.File.writeAtomic(this.path, bytes,
                               { tmpPath: this.path + ".tmp" });
   }),
 
   /**
    * Synchronously work on the data just loaded into memory.
    */
   _processLoadedData(data) {
+    if (this._finalizePromise) {
+      // It's possible for `load` to race with `finalize`. In that case, don't
+      // process or set the loaded data.
+      return;
+    }
     this.data = this._dataPostProcessor ? this._dataPostProcessor(data) : data;
   },
+
+  /**
+   * Finishes persisting data to disk and resets all state for this file.
+   *
+   * @return {Promise}
+   * @resolves When the object is finalized.
+   */
+  _finalizeInternal() {
+    if (this._finalizePromise) {
+      // Finalization already in progress; return the pending promise. This is
+      // possible if `finalize` is called concurrently with shutdown.
+      return this._finalizePromise;
+    }
+    this._finalizePromise = Task.spawn(function* () {
+      yield this._saver.finalize();
+      this._data = null;
+      this.dataReady = false;
+    }.bind(this));
+    return this._finalizePromise;
+  },
+
+  /**
+   * Ensures that all data is persisted to disk, and prevents future calls to
+   * `saveSoon`. This is called automatically on shutdown, but can also be
+   * called explicitly when the file is no longer needed.
+   */
+  finalize: Task.async(function* () {
+    if (this._finalizePromise) {
+      throw new Error(`The file ${this.path} has already been finalized`);
+    }
+    // Wait for finalization before removing the shutdown blocker.
+    yield this._finalizeInternal();
+    this._finalizeAt.removeBlocker(this._finalizeInternalBound);
+  }),
 };
--- a/toolkit/modules/tests/xpcshell/test_JSONFile.js
+++ b/toolkit/modules/tests/xpcshell/test_JSONFile.js
@@ -5,16 +5,18 @@
 "use strict";
 
 // Globals
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
+                                  "resource://gre/modules/AsyncShutdown.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
                                   "resource://gre/modules/DownloadPaths.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "JSONFile",
                                   "resource://gre/modules/JSONFile.jsm");
@@ -296,8 +298,63 @@ add_task(function* test_beforeSave_rejec
     };
     storeForSave.saveSoon();
   });
 
   yield Assert.rejects(promiseSave, function(ex) {
     return ex.message == "oops";
   });
 });
+
+add_task(function* test_finalize() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+
+  let barrier = new AsyncShutdown.Barrier("test-auto-finalize");
+  let storeForSave = new JSONFile({
+    path,
+    saveDelayMs: 2000,
+    finalizeAt: barrier.client,
+  });
+  yield storeForSave.load();
+  storeForSave.data = TEST_DATA;
+  storeForSave.saveSoon();
+
+  let promiseFinalize = storeForSave.finalize();
+  yield Assert.rejects(storeForSave.finalize(), /has already been finalized$/);
+  yield promiseFinalize;
+  do_check_false(storeForSave.dataReady);
+
+  // Finalization removes the blocker, so waiting should not log an unhandled
+  // error even though the object has been explicitly finalized.
+  yield barrier.wait();
+
+  let storeForLoad = new JSONFile({ path });
+  yield storeForLoad.load();
+  do_check_matches(storeForLoad.data, TEST_DATA);
+});
+
+add_task(function* test_finalize_on_shutdown() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+
+  let barrier = new AsyncShutdown.Barrier("test-finalize-shutdown");
+  let storeForSave = new JSONFile({
+    path,
+    saveDelayMs: 2000,
+    finalizeAt: barrier.client,
+  });
+  yield storeForSave.load();
+  storeForSave.data = TEST_DATA;
+  // Arm the saver, then simulate shutdown and ensure the file is
+  // automatically finalized.
+  storeForSave.saveSoon();
+
+  yield barrier.wait();
+  // It's possible for `finalize` to reject when called concurrently with
+  // shutdown. We don't distinguish between explicit `finalize` calls and
+  // finalization on shutdown because we expect most consumers to rely on the
+  // latter. However, this behavior can be safely changed if needed.
+  yield Assert.rejects(storeForSave.finalize(), /has already been finalized$/);
+  do_check_false(storeForSave.dataReady);
+
+  let storeForLoad = new JSONFile({ path });
+  yield storeForLoad.load();
+  do_check_matches(storeForLoad.data, TEST_DATA);
+});
--- a/toolkit/moz.configure
+++ b/toolkit/moz.configure
@@ -956,40 +956,16 @@ def skia_includes(skia, skia_gpu):
             '/gfx/skia/skia/include/gpu',
             '/gfx/skia/skia/include/utils',
         ]
 
     return includes
 
 set_config('SKIA_INCLUDES', skia_includes)
 
-# Support various fuzzing options
-# ==============================================================
-with only_when('--enable-compile-environment'):
-    option('--enable-fuzzing', help='Enable fuzzing support')
-
-    @depends('--enable-fuzzing')
-    def enable_fuzzing(value):
-        if value:
-            return True
-
-    @depends(enable_fuzzing,
-             try_compile(body='__AFL_COMPILER;',
-                         check_msg='for AFL compiler',
-                         when='--enable-fuzzing'))
-    def enable_libfuzzer(fuzzing, afl):
-        if fuzzing and not afl:
-            return True
-
-    set_config('FUZZING', enable_fuzzing)
-    set_define('FUZZING', enable_fuzzing)
-
-    set_config('LIBFUZZER', enable_libfuzzer)
-    set_define('LIBFUZZER', enable_libfuzzer)
-
 # Mortar
 # ==============================================================
 option('--enable-mortar', help='Enable mortar extension')
 
 set_config('MOZ_MORTAR', True, when='--enable-mortar')
 
 # Marionette is a Web Driver / Selenium comamnd server and client automation
 # driver for Mozilla's Gecko engine.  For more, see
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -2987,24 +2987,39 @@ var AddonManagerInternal = {
         "onDownloadFailed",
         "onInstallStarted",
         "onInstallEnded",
         "onInstallCancelled",
         "onInstallFailed",
       ];
 
       let listener = {};
-      events.forEach(event => {
-        listener[event] = (install) => {
-          let data = {event, id};
-          AddonManager.webAPI.copyProps(install, data);
-          this.sendEvent(mm, data);
-        }
+      let installPromise = new Promise((resolve, reject) => {
+        events.forEach(event => {
+          listener[event] = (install, addon) => {
+            let data = {event, id};
+            AddonManager.webAPI.copyProps(install, data);
+            this.sendEvent(mm, data);
+            if (event == "onInstallEnded") {
+              resolve(addon);
+            } else if (event == "onDownloadFailed" || event == "onInstallFailed") {
+              reject({message: "install failed"});
+            } else if (event == "onDownloadCancelled" || event == "onInstallCancelled") {
+              reject({message: "install cancelled"});
+            }
+          }
+        });
       });
-      return listener;
+
+      // We create the promise here since this is where we're setting
+      // up the InstallListener, but if the install is never started,
+      // no handlers will be attached so make sure we terminate errors.
+      installPromise.catch(() => {});
+
+      return {listener, installPromise};
     },
 
     forgetInstall(id) {
       let info = this.installs.get(id);
       if (!info) {
         throw new Error(`forgetInstall cannot find ${id}`);
       }
       info.install.removeListener(info.listener);
@@ -3028,25 +3043,25 @@ var AddonManagerInternal = {
       }
 
       try {
         checkInstallUrl(options.url);
       } catch (err) {
         return Promise.reject({message: err.message});
       }
 
-      return AddonManagerInternal.getInstallForURL(options.url, "application/x-xpinstall",
-                                                   options.hash).then(install => {
+      return AddonManagerInternal.getInstallForURL(options.url, "application/x-xpinstall", options.hash)
+                                 .then(install => {
         AddonManagerInternal.setupPromptHandler(target, null, install, false);
 
         let id = this.nextInstall++;
-        let listener = this.makeListener(id, target.messageManager);
+        let {listener, installPromise} = this.makeListener(id, target.messageManager);
         install.addListener(listener);
 
-        this.installs.set(id, {install, target, listener});
+        this.installs.set(id, {install, target, listener, installPromise});
 
         let result = {id};
         this.copyProps(install, result);
         return result;
       });
     },
 
     addonUninstall(target, id) {
@@ -3079,17 +3094,27 @@ var AddonManagerInternal = {
       });
     },
 
     addonInstallDoInstall(target, id) {
       let state = this.installs.get(id);
       if (!state) {
         return Promise.reject(`invalid id ${id}`);
       }
-      return Promise.resolve(state.install.install());
+      let result = state.install.install();
+
+      return state.installPromise.then(addon => new Promise(resolve => {
+        let callback = () => resolve(result);
+        if (Preferences.get(PREF_WEBEXT_PERM_PROMPTS, false)) {
+          let subject = {wrappedJSObject: {target, addon, callback}};
+          Services.obs.notifyObservers(subject, "webextension-install-notify", null)
+        } else {
+          callback();
+        }
+      }));
     },
 
     addonInstallCancel(target, id) {
       let state = this.installs.get(id);
       if (!state) {
         return Promise.reject(`invalid id ${id}`);
       }
       return Promise.resolve(state.install.cancel());
--- a/toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
@@ -120,17 +120,26 @@ function* testInstall(browser, args, ste
         check();
       });
     }
 
     while (steps.length > 0) {
       let nextStep = steps.shift();
       if (nextStep.action) {
         if (nextStep.action == "install") {
-          yield install.install();
+          try {
+            yield install.install();
+            if (nextStep.expectError) {
+              throw new Error("Expected install to fail but it did not");
+            }
+          } catch (err) {
+            if (!nextStep.expectError) {
+              throw new Error("Install failed unexpectedly");
+            }
+          }
         } else if (nextStep.action == "cancel") {
           yield install.cancel();
         } else {
           throw new Error(`unknown action ${nextStep.action}`);
         }
       } else {
         yield expectEvent(nextStep.event, nextStep.props);
       }
@@ -229,17 +238,17 @@ add_task(makeInstallTest(function* (brow
   let addons = yield promiseAddonsByIDs([ID]);
   is(addons[0], null, "The addon was not installed");
 
   ok(AddonManager.webAPI.installs.size > 0, "webAPI is tracking the AddonInstall");
 }));
 
 add_task(makeInstallTest(function* (browser) {
   let steps = [
-    {action: "install"},
+    {action: "install", expectError: true},
     {
       event: "onDownloadStarted",
       props: {state: "STATE_DOWNLOADING"},
     },
     {event: "onDownloadProgress"},
     {
       event: "onDownloadFailed",
       props: {
@@ -254,17 +263,17 @@ add_task(makeInstallTest(function* (brow
   let addons = yield promiseAddonsByIDs([ID]);
   is(addons[0], null, "The addon was not installed");
 
   ok(AddonManager.webAPI.installs.size > 0, "webAPI is tracking the AddonInstall");
 }));
 
 add_task(makeInstallTest(function* (browser) {
   let steps = [
-    {action: "install"},
+    {action: "install", expectError: true},
     {
       event: "onDownloadStarted",
       props: {state: "STATE_DOWNLOADING"},
     },
     {event: "onDownloadProgress"},
     {
       event: "onDownloadFailed",
       props: {