merge autoland to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 22 Mar 2017 14:24:35 +0100
changeset 348776 ee30286771eb83c0878621485009dc2722daa78f
parent 348711 201231223cd4354a450c3e5d80959f35b8e4cf0c (current diff)
parent 348775 4a8979c4c9df308fa51af4d198bb0ccf770cdede (diff)
child 348847 27f7aadeea833eca8c9c0e24c858f798e4d6fdd8
push id31535
push usercbook@mozilla.com
push dateWed, 22 Mar 2017 13:25:07 +0000
treeherdermozilla-central@ee30286771eb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge autoland to mozilla-central a=merge
testing/mochitest/browser.eslintrc.js
testing/mochitest/chrome.eslintrc.js
testing/mochitest/mochitest.eslintrc.js
testing/xpcshell/xpcshell.eslintrc.js
--- a/README.txt
+++ b/README.txt
@@ -1,27 +1,27 @@
 An explanation of the Mozilla Source Code Directory Structure and links to
 project pages with documentation can be found at:
 
     https://developer.mozilla.org/en/Mozilla_Source_Code_Directory_Structure
 
 For information on how to build Mozilla from the source code, see:
 
-    http://developer.mozilla.org/en/docs/Build_Documentation
+    https://developer.mozilla.org/en/docs/Build_Documentation
 
 To have your bug fix / feature added to Mozilla, you should create a patch and
 submit it to Bugzilla (https://bugzilla.mozilla.org). Instructions are at:
 
-    http://developer.mozilla.org/en/docs/Creating_a_patch
-    http://developer.mozilla.org/en/docs/Getting_your_patch_in_the_tree
+    https://developer.mozilla.org/en/docs/Creating_a_patch
+    https://developer.mozilla.org/en/docs/Getting_your_patch_in_the_tree
 
 If you have a question about developing Mozilla, and can't find the solution
-on http://developer.mozilla.org, you can try asking your question in a
+on https://developer.mozilla.org, you can try asking your question in a
 mozilla.* Usenet group, or on IRC at irc.mozilla.org. [The Mozilla news groups
 are accessible on Google Groups, or news.mozilla.org with a NNTP reader.]
 
 You can download nightly development builds from the Mozilla FTP server.
 Keep in mind that nightly builds, which are used by Mozilla developers for
 testing, may be buggy. Firefox nightlies, for example, can be found at:
 
     https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/
             - or -
-    http://nightly.mozilla.org/
+    https://nightly.mozilla.org/
--- a/accessible/tests/browser/.eslintrc.js
+++ b/accessible/tests/browser/.eslintrc.js
@@ -1,13 +1,13 @@
 "use strict";
 
 module.exports = { // eslint-disable-line no-undef
   "extends": [
-    "../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ],
   // All globals made available in the test environment.
   "globals": {
     // Content scripts have global 'content' object
     "content": true,
 
     "add_task": true,
 
--- a/browser/.eslintrc.js
+++ b/browser/.eslintrc.js
@@ -1,12 +1,15 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../toolkit/.eslintrc.js"
+    "plugin:mozilla/recommended"
   ],
 
   "rules": {
+    // XXX Bug 1326071 - This should be reduced down - probably to 20 or to
+    // be removed & synced with the mozilla/recommended value.
+    "complexity": ["error", {"max": 40}],
+
     "no-shadow": "error",
-    "no-undef": "error"
   }
 };
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -799,48 +799,51 @@ addMessageListener("ContextMenu:SaveVide
   let ctxDraw = canvas.getContext("2d");
   ctxDraw.drawImage(video, 0, 0);
   sendAsyncMessage("ContextMenu:SaveVideoFrameAsImage:Result", {
     dataURL: canvas.toDataURL("image/jpeg", ""),
   });
 });
 
 addMessageListener("ContextMenu:MediaCommand", (message) => {
-  let media = message.objects.element;
-
-  switch (message.data.command) {
-    case "play":
-      media.play();
-      break;
-    case "pause":
-      media.pause();
-      break;
-    case "loop":
-      media.loop = !media.loop;
-      break;
-    case "mute":
-      media.muted = true;
-      break;
-    case "unmute":
-      media.muted = false;
-      break;
-    case "playbackRate":
-      media.playbackRate = message.data.data;
-      break;
-    case "hidecontrols":
-      media.removeAttribute("controls");
-      break;
-    case "showcontrols":
-      media.setAttribute("controls", "true");
-      break;
-    case "fullscreen":
-      if (content.document.fullscreenEnabled)
-        media.requestFullscreen();
-      break;
-  }
+  E10SUtils.wrapHandlingUserInput(
+    content, message.data.handlingUserInput,
+    () => {
+      let media = message.objects.element;
+      switch (message.data.command) {
+        case "play":
+          media.play();
+          break;
+        case "pause":
+          media.pause();
+          break;
+        case "loop":
+          media.loop = !media.loop;
+          break;
+        case "mute":
+          media.muted = true;
+          break;
+        case "unmute":
+          media.muted = false;
+          break;
+        case "playbackRate":
+          media.playbackRate = message.data.data;
+          break;
+        case "hidecontrols":
+          media.removeAttribute("controls");
+          break;
+        case "showcontrols":
+          media.setAttribute("controls", "true");
+          break;
+        case "fullscreen":
+          if (content.document.fullscreenEnabled)
+            media.requestFullscreen();
+          break;
+      }
+    });
 });
 
 addMessageListener("ContextMenu:Canvas:ToBlobURL", (message) => {
   message.objects.target.toBlob((blob) => {
     let blobURL = URL.createObjectURL(blob);
     sendAsyncMessage("ContextMenu:Canvas:ToBlobURL:Result", { blobURL });
   });
 });
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -1736,18 +1736,23 @@ nsContextMenu.prototype = {
   },
 
   switchPageDirection: function CM_switchPageDirection() {
     this.browser.messageManager.sendAsyncMessage("SwitchDocumentDirection");
   },
 
   mediaCommand : function CM_mediaCommand(command, data) {
     let mm = this.browser.messageManager;
+    let win = this.browser.ownerGlobal;
+    let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                         .getInterface(Ci.nsIDOMWindowUtils);
     mm.sendAsyncMessage("ContextMenu:MediaCommand",
-                        {command: command, data: data},
+                        {command: command,
+                         data: data,
+                         handlingUserInput: windowUtils.isHandlingUserInput},
                         {element: this.target});
   },
 
   copyMediaLocation : function () {
     var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
                     getService(Ci.nsIClipboardHelper);
     clipboard.copyString(this.mediaURL);
   },
--- a/browser/base/content/test/alerts/.eslintrc.js
+++ b/browser/base/content/test/alerts/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/base/content/test/captivePortal/.eslintrc.js
+++ b/browser/base/content/test/captivePortal/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/base/content/test/chrome/.eslintrc.js
+++ b/browser/base/content/test/chrome/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/chrome-test"
   ]
 };
--- a/browser/base/content/test/forms/browser_selectpopup_colors.js
+++ b/browser/base/content/test/forms/browser_selectpopup_colors.js
@@ -55,16 +55,23 @@ const GENERIC_OPTION_STYLED_AS_IMPORTANT
 
 const TRANSLUCENT_SELECT_BECOMES_OPAQUE =
   "<html><head>" +
   "<body><select id='one' style='background-color: rgba(255,255,255,.55);'>" +
   '  <option value="One">{"color": "rgb(0, 0, 0)", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
   '  <option value="Two" selected="true">{"end": "true"}</option>' +
   "</select></body></html>";
 
+const TRANSLUCENT_SELECT_APPLIES_ON_BASE_COLOR =
+  "<html><head>" +
+  "<body><select id='one' style='background-color: rgba(255,0,0,.55);'>" +
+  '  <option value="One">{"color": "rgb(0, 0, 0)", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
+  '  <option value="Two" selected="true">{"end": "true"}</option>' +
+  "</select></body></html>";
+
 const DISABLED_OPTGROUP_AND_OPTIONS =
   "<html><head>" +
   "<body><select id='one'>" +
   "  <optgroup label='{\"color\": \"rgb(0, 0, 0)\", \"backgroundColor\": \"buttonface\"}'>" +
   '    <option disabled="">{"color": "GrayText", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
   '    <option>{"color": "rgb(0, 0, 0)", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
   '    <option disabled="">{"color": "GrayText", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
   '    <option>{"color": "rgb(0, 0, 0)", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
@@ -135,18 +142,43 @@ function* testSelectColors(select, itemC
 
   is(selectPopup.parentNode.itemCount, itemCount, "Correct number of items");
   let child = selectPopup.firstChild;
   let idx = 1;
 
   if (!options.skipSelectColorTest) {
     is(getComputedStyle(selectPopup).color, options.selectColor,
       "popup has expected foreground color");
-    is(getComputedStyle(selectPopup).backgroundColor, options.selectBgColor,
-      "popup has expected background color");
+
+    // Combine the select popup's backgroundColor and the
+    // backgroundImage color to get the color that is seen by
+    // the user.
+    let base = getComputedStyle(selectPopup).backgroundColor;
+    let [/* unused */, bR, bG, bB] =
+      base.match(/rgb\((\d+), (\d+), (\d+)\)/);
+    bR = parseInt(bR, 10);
+    bG = parseInt(bG, 10);
+    bB = parseInt(bB, 10);
+    let topCoat = getComputedStyle(selectPopup).backgroundImage;
+    if (topCoat == "none") {
+      is(`rgb(${bR}, ${bG}, ${bB})`, options.selectBgColor,
+        "popup has expected background color");
+    } else {
+      let [/* unused */, /* unused */, tR, tG, tB, tA] =
+        topCoat.match(/(rgba?\((\d+), (\d+), (\d+)(?:, (0\.\d+))?\)), \1/);
+      tR = parseInt(tR, 10);
+      tG = parseInt(tG, 10);
+      tB = parseInt(tB, 10);
+      tA = parseFloat(tA) || 1;
+      let actualR = Math.round(tR * tA + bR * (1 - tA));
+      let actualG = Math.round(tG * tA + bG * (1 - tA));
+      let actualB = Math.round(tB * tA + bB * (1 - tA));
+      is(`rgb(${actualR}, ${actualG}, ${actualB})`, options.selectBgColor,
+        "popup has expected background color");
+    }
   }
 
   ok(!child.selected, "The first child should not be selected");
   while (child) {
     testOptionColors(idx, child, menulist);
     idx++;
     child = child.nextSibling;
   }
@@ -212,24 +244,37 @@ add_task(function* test_select_backgroun
 });
 
 // This test checks when a <select> element has a background set, and the
 // options have their own background set which is equal to the default
 // user-agent background color, but should be used because the select
 // background color has been changed.
 add_task(function* test_translucent_select_becomes_opaque() {
   // The popup is requested to show a translucent background
-  // but we force the alpha channel to 1 on the popup.
+  // but we apply the requested background color on the system's base color.
   let options = {
     selectColor: "rgb(0, 0, 0)",
     selectBgColor: "rgb(255, 255, 255)"
   };
   yield testSelectColors(TRANSLUCENT_SELECT_BECOMES_OPAQUE, 2, options);
 });
 
+// This test checks when a popup has a translucent background color,
+// and that the color painted to the screen of the translucent background
+// matches what the user expects.
+add_task(function* test_translucent_select_applies_on_base_color() {
+  // The popup is requested to show a translucent background
+  // but we apply the requested background color on the system's base color.
+  let options = {
+    selectColor: "rgb(0, 0, 0)",
+    selectBgColor: "rgb(255, 115, 115)"
+  };
+  yield testSelectColors(TRANSLUCENT_SELECT_APPLIES_ON_BASE_COLOR, 2, options);
+});
+
 add_task(function* test_disabled_optgroup_and_options() {
   // The colors used by this test are platform-specific.
   if (AppConstants.platform != "win") {
     return;
   }
 
   yield testSelectColors(DISABLED_OPTGROUP_AND_OPTIONS, 17,
                          {skipSelectColorTest: true});
--- a/browser/base/content/test/general/.eslintrc.js
+++ b/browser/base/content/test/general/.eslintrc.js
@@ -1,8 +1,8 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js",
-    "../../../../../testing/mochitest/mochitest.eslintrc.js",
+    "plugin:mozilla/browser-test",
+    "plugin:mozilla/mochitest-test",
   ]
 };
--- a/browser/base/content/test/newtab/.eslintrc.js
+++ b/browser/base/content/test/newtab/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/base/content/test/pageinfo/.eslintrc.js
+++ b/browser/base/content/test/pageinfo/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/base/content/test/plugins/.eslintrc.js
+++ b/browser/base/content/test/plugins/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/base/content/test/popupNotifications/.eslintrc.js
+++ b/browser/base/content/test/popupNotifications/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/base/content/test/referrer/.eslintrc.js
+++ b/browser/base/content/test/referrer/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/base/content/test/siteIdentity/.eslintrc.js
+++ b/browser/base/content/test/siteIdentity/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/base/content/test/social/.eslintrc.js
+++ b/browser/base/content/test/social/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/base/content/test/tabPrompts/.eslintrc.js
+++ b/browser/base/content/test/tabPrompts/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/base/content/test/tabcrashed/.eslintrc.js
+++ b/browser/base/content/test/tabcrashed/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/base/content/test/tabs/.eslintrc.js
+++ b/browser/base/content/test/tabs/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/base/content/test/urlbar/.eslintrc.js
+++ b/browser/base/content/test/urlbar/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/base/content/test/webrtc/.eslintrc.js
+++ b/browser/base/content/test/webrtc/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/components/.eslintrc.js
+++ b/browser/components/.eslintrc.js
@@ -1,9 +1,9 @@
 "use strict";
 
 module.exports = {
   rules: {
     // XXX Bug 1326071 - This should be reduced down - probably to 20 or to
-    // be removed & synced with the toolkit/.eslintrc.js value.
+    // be removed & synced with the mozilla/recommended value.
     "complexity": ["error", {"max": 69}],
   }
 };
--- a/browser/components/contextualidentity/test/browser/.eslintrc.js
+++ b/browser/components/contextualidentity/test/browser/.eslintrc.js
@@ -1,11 +1,11 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ],
 
   "rules": {
     "no-undef": "error"
   }
 };
--- a/browser/components/customizableui/test/.eslintrc.js
+++ b/browser/components/customizableui/test/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/components/dirprovider/tests/unit/.eslintrc.js
+++ b/browser/components/dirprovider/tests/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/browser/components/downloads/test/browser/.eslintrc.js
+++ b/browser/components/downloads/test/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/components/downloads/test/unit/.eslintrc.js
+++ b/browser/components/downloads/test/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/browser/components/extensions/test/browser/.eslintrc.js
+++ b/browser/components/extensions/test/browser/.eslintrc.js
@@ -1,12 +1,12 @@
 "use strict";
 
-module.exports = {  // eslint-disable-line no-undef
-  "extends": "../../../../../testing/mochitest/browser.eslintrc.js",
+module.exports = {
+  "extends": "plugin:mozilla/browser-test",
 
   "env": {
     "webextensions": true,
   },
 
   "globals": {
     "NetUtil": true,
     "XPCOMUtils": true,
--- a/browser/components/extensions/test/xpcshell/.eslintrc.js
+++ b/browser/components/extensions/test/xpcshell/.eslintrc.js
@@ -1,9 +1,9 @@
 "use strict";
 
-module.exports = {  // eslint-disable-line no-undef
-  "extends": "../../../../../testing/xpcshell/xpcshell.eslintrc.js",
+module.exports = {
+  "extends": "plugin:mozilla/xpcshell-test",
 
   "globals": {
     "browser": false,
   },
 };
--- a/browser/components/feeds/test/.eslintrc.js
+++ b/browser/components/feeds/test/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/mochitest.eslintrc.js"
+    "plugin:mozilla/mochitest-test"
   ]
 };
--- a/browser/components/feeds/test/chrome/.eslintrc.js
+++ b/browser/components/feeds/test/chrome/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/chrome-test"
   ]
 };
--- a/browser/components/feeds/test/unit/.eslintrc.js
+++ b/browser/components/feeds/test/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/browser/components/migration/tests/browser/.eslintrc.js
+++ b/browser/components/migration/tests/browser/.eslintrc.js
@@ -1,9 +1,9 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js",
-    "../../../../../testing/mochitest/mochitest.eslintrc.js",
+    "plugin:mozilla/browser-test",
+    "plugin:mozilla/mochitest-test",
   ]
 };
 
--- a/browser/components/migration/tests/unit/.eslintrc.js
+++ b/browser/components/migration/tests/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
-module.exports = { // eslint-disable-line no-undef
+module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/browser/components/newtab/tests/browser/.eslintrc.js
+++ b/browser/components/newtab/tests/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/components/newtab/tests/xpcshell/.eslintrc.js
+++ b/browser/components/newtab/tests/xpcshell/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/browser/components/originattributes/test/browser/.eslintrc.js
+++ b/browser/components/originattributes/test/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/components/places/tests/browser/.eslintrc.js
+++ b/browser/components/places/tests/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/components/places/tests/chrome/.eslintrc.js
+++ b/browser/components/places/tests/chrome/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/chrome-test"
   ]
 };
--- a/browser/components/places/tests/unit/.eslintrc.js
+++ b/browser/components/places/tests/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/browser/components/preferences/in-content/tests/.eslintrc.js
+++ b/browser/components/preferences/in-content/tests/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/components/privatebrowsing/test/browser/.eslintrc.js
+++ b/browser/components/privatebrowsing/test/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/components/safebrowsing/content/test/.eslintrc.js
+++ b/browser/components/safebrowsing/content/test/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/components/search/test/.eslintrc.js
+++ b/browser/components/search/test/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/components/selfsupport/test/.eslintrc.js
+++ b/browser/components/selfsupport/test/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/components/sessionstore/test/.eslintrc.js
+++ b/browser/components/sessionstore/test/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/components/sessionstore/test/unit/.eslintrc.js
+++ b/browser/components/sessionstore/test/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/browser/components/shell/test/.eslintrc.js
+++ b/browser/components/shell/test/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/components/shell/test/unit/.eslintrc.js
+++ b/browser/components/shell/test/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/browser/components/syncedtabs/test/browser/.eslintrc.js
+++ b/browser/components/syncedtabs/test/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/components/syncedtabs/test/xpcshell/.eslintrc.js
+++ b/browser/components/syncedtabs/test/xpcshell/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/browser/components/tests/browser/.eslintrc.js
+++ b/browser/components/tests/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/components/tests/unit/.eslintrc.js
+++ b/browser/components/tests/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/browser/components/translation/test/.eslintrc.js
+++ b/browser/components/translation/test/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/components/translation/test/unit/.eslintrc.js
+++ b/browser/components/translation/test/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/browser/components/uitour/test/.eslintrc.js
+++ b/browser/components/uitour/test/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/experiments/test/xpcshell/.eslintrc.js
+++ b/browser/experiments/test/xpcshell/.eslintrc.js
@@ -1,13 +1,13 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ],
 
   "rules": {
     "no-unused-vars": ["error", {
       "vars": "all",
       "varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$",
       "args": "none"
     }]
--- a/browser/extensions/formautofill/test/browser/.eslintrc.js
+++ b/browser/extensions/formautofill/test/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
-module.exports = { // eslint-disable-line no-undef
+module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js",
+    "plugin:mozilla/browser-test",
   ],
 };
--- a/browser/extensions/formautofill/test/unit/.eslintrc.js
+++ b/browser/extensions/formautofill/test/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
-module.exports = { // eslint-disable-line no-undef
+module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js",
+    "plugin:mozilla/xpcshell-test",
   ],
 };
--- a/browser/extensions/pdfjs/test/.eslintrc.js
+++ b/browser/extensions/pdfjs/test/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/extensions/pocket/test/.eslintrc.js
+++ b/browser/extensions/pocket/test/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/extensions/webcompat-reporter/test/browser/.eslintrc.js
+++ b/browser/extensions/webcompat-reporter/test/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/extensions/webcompat/test/.eslintrc.js
+++ b/browser/extensions/webcompat/test/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/modules/test/browser/.eslintrc.js
+++ b/browser/modules/test/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/modules/test/unit/.eslintrc.js
+++ b/browser/modules/test/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/browser/tools/mozscreenshots/.eslintrc.js
+++ b/browser/tools/mozscreenshots/.eslintrc.js
@@ -1,13 +1,13 @@
 "use strict";
 
-module.exports = { // eslint-disable-line no-undef
+module.exports = {
   "extends": [
-    "../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ],
 
   "rules": {
     "no-unused-vars": ["error", {
       "vars": "all",
       "varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$",
       "args": "none"
     }]
--- a/devtools/.eslintrc.mochitests.js
+++ b/devtools/.eslintrc.mochitests.js
@@ -1,12 +1,12 @@
 // Parent config file for all devtools browser mochitest files.
 module.exports = {
   "extends": [
-    "../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ],
   // All globals made available in the test environment.
   "globals": {
     "DevToolsUtils": true,
     "gDevTools": true,
     "once": true,
     "synthesizeKeyFromKeyTag": true,
     "TargetFactory": true,
--- a/devtools/.eslintrc.xpcshell.js
+++ b/devtools/.eslintrc.xpcshell.js
@@ -1,19 +1,19 @@
 // Parent config file for all devtools xpcshell files.
 module.exports = {
   "extends": [
-    "../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ],
   "rules": {
     // Allow non-camelcase so that run_test doesn't produce a warning.
-    "camelcase": 0,
+    "camelcase": "off",
     // Allow using undefined variables so that tests can refer to functions
     // and variables defined in head.js files, without having to maintain a
     // list of globals in each .eslintrc file.
     // Note that bug 1168340 will eventually help auto-registering globals
     // from head.js files.
-    "no-undef": 0,
-    "block-scoped-var": 0,
+    "no-undef": "off",
+    "block-scoped-var": "off",
     // Tests can always import anything.
-    "mozilla/reject-some-requires": 0,
+    "mozilla/reject-some-requires": "off",
   }
 };
--- a/devtools/client/memory/.eslintrc.js
+++ b/devtools/client/memory/.eslintrc.js
@@ -1,11 +1,11 @@
-"use strict";
-
-module.exports = {
-  "env": {
-    "browser": true,
-  },
-  "globals": {
-    "d3": true,
-    "dagreD3": true,
-  }
-};
+"use strict";
+
+module.exports = {
+  "env": {
+    "browser": true,
+  },
+  "globals": {
+    "d3": true,
+    "dagreD3": true,
+  }
+};
--- a/devtools/shared/security/tests/chrome/.eslintrc.js
+++ b/devtools/shared/security/tests/chrome/.eslintrc.js
@@ -1,6 +1,6 @@
 "use strict";
 
 module.exports = {
   // Extend from the shared list of defined globals for mochitests.
-  "extends": "../../../../../testing/mochitest/chrome.eslintrc.js"
+  "extends": "plugin:mozilla/chrome-test"
 };
--- a/devtools/shared/webconsole/test/.eslintrc.js
+++ b/devtools/shared/webconsole/test/.eslintrc.js
@@ -1,6 +1,6 @@
 "use strict";
 
 module.exports = {
   // Extend from the shared list of defined globals for mochitests.
-  "extends": "../../../../testing/mochitest/chrome.eslintrc.js"
+  "extends": "plugin:mozilla/chrome-test"
 };
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -2067,16 +2067,22 @@ FragmentOrElement::AppendText(const char
 
 bool
 FragmentOrElement::TextIsOnlyWhitespace()
 {
   return false;
 }
 
 bool
+FragmentOrElement::ThreadSafeTextIsOnlyWhitespace() const
+{
+  return false;
+}
+
+bool
 FragmentOrElement::HasTextForTranslation()
 {
   return false;
 }
 
 void
 FragmentOrElement::AppendTextTo(nsAString& aResult)
 {
--- a/dom/base/FragmentOrElement.h
+++ b/dom/base/FragmentOrElement.h
@@ -138,16 +138,17 @@ public:
   // Need to implement this here too to avoid hiding.
   nsresult SetText(const nsAString& aStr, bool aNotify)
   {
     return SetText(aStr.BeginReading(), aStr.Length(), aNotify);
   }
   virtual nsresult AppendText(const char16_t* aBuffer, uint32_t aLength,
                               bool aNotify) override;
   virtual bool TextIsOnlyWhitespace() override;
+  virtual bool ThreadSafeTextIsOnlyWhitespace() const override;
   virtual bool HasTextForTranslation() override;
   virtual void AppendTextTo(nsAString& aResult) override;
   MOZ_MUST_USE
   virtual bool AppendTextTo(nsAString& aResult, const mozilla::fallible_t&) override;
   virtual nsIContent *GetBindingParent() const override;
   virtual nsXBLBinding *GetXBLBinding() const override;
   virtual void SetXBLBinding(nsXBLBinding* aBinding,
                              nsBindingManager* aOldBindingManager = nullptr) override;
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -9626,36 +9626,48 @@ nsDocument::ForgetImagePreload(nsIURI* a
     mPreloadingImages.Remove(aURI, getter_AddRefs(req));
     if (req) {
       // Make sure to cancel the request so imagelib knows it's gone.
       req->CancelAndForgetObserver(NS_BINDING_ABORTED);
     }
   }
 }
 
-EventStates
-nsDocument::GetDocumentState()
+void
+nsDocument::UpdatePossiblyStaleDocumentState()
 {
   if (!mGotDocumentState.HasState(NS_DOCUMENT_STATE_RTL_LOCALE)) {
     if (IsDocumentRightToLeft()) {
       mDocumentState |= NS_DOCUMENT_STATE_RTL_LOCALE;
     }
     mGotDocumentState |= NS_DOCUMENT_STATE_RTL_LOCALE;
   }
   if (!mGotDocumentState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
     nsIPresShell* shell = GetShell();
     if (shell && shell->GetPresContext() &&
         shell->GetPresContext()->IsTopLevelWindowInactive()) {
       mDocumentState |= NS_DOCUMENT_STATE_WINDOW_INACTIVE;
     }
     mGotDocumentState |= NS_DOCUMENT_STATE_WINDOW_INACTIVE;
   }
+}
+
+EventStates
+nsDocument::ThreadSafeGetDocumentState() const
+{
   return mDocumentState;
 }
 
+EventStates
+nsDocument::GetDocumentState()
+{
+  UpdatePossiblyStaleDocumentState();
+  return ThreadSafeGetDocumentState();
+}
+
 namespace {
 
 /**
  * Stub for LoadSheet(), since all we want is to get the sheet into
  * the CSSLoader's style cache
  */
 class StubCSSLoaderObserver final : public nsICSSLoaderObserver {
   ~StubCSSLoaderObserver() {}
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -975,17 +975,22 @@ public:
                             ReferrerPolicy aReferrerPolicy,
                             const nsAString& aIntegrity) override;
 
   virtual nsresult LoadChromeSheetSync(nsIURI* uri, bool isAgentSheet,
                                        RefPtr<mozilla::StyleSheet>* aSheet) override;
 
   virtual nsISupports* GetCurrentContentSink() override;
 
-  virtual mozilla::EventStates GetDocumentState() override;
+  virtual mozilla::EventStates GetDocumentState() final;
+  // GetDocumentState() mutates the state due to lazy resolution;
+  // and can't be used during parallel traversal. Use this instead,
+  // and ensure GetDocumentState() has been called first.
+  // This will assert if the state is stale.
+  virtual mozilla::EventStates ThreadSafeGetDocumentState() const final;
 
   // Only BlockOnload should call this!
   void AsyncBlockOnload();
 
   virtual void SetScrollToRef(nsIURI *aDocumentURI) override;
   virtual void ScrollToRef() override;
   virtual void ResetScrolledToRefAlready() override;
   virtual void SetChangeScrollPosWhenScrollingToRef(bool aValue) override;
@@ -1386,16 +1391,17 @@ protected:
   // non-null when this document is in fullscreen mode.
   nsWeakPtr mFullscreenRoot;
 
   mozilla::dom::FlashClassification mFlashClassification;
   // Do not use this value directly. Call the |IsThirdParty()| method, which
   // caches its result here.
   mozilla::Maybe<bool> mIsThirdParty;
 private:
+  void UpdatePossiblyStaleDocumentState();
   static bool CustomElementConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp);
 
   /**
    * Check if the passed custom element name, aOptions.mIs, is a registered
    * custom element type or not, then return the custom element name for future
    * usage.
    *
    * If there is no existing custom element definition for this name, throw a
--- a/dom/base/nsGenericDOMDataNode.cpp
+++ b/dom/base/nsGenericDOMDataNode.cpp
@@ -978,16 +978,31 @@ nsGenericDOMDataNode::AppendText(const c
                                  bool aNotify)
 {
   return SetTextInternal(mText.GetLength(), 0, aBuffer, aLength, aNotify);
 }
 
 bool
 nsGenericDOMDataNode::TextIsOnlyWhitespace()
 {
+
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!ThreadSafeTextIsOnlyWhitespace()) {
+    UnsetFlags(NS_TEXT_IS_ONLY_WHITESPACE);
+    SetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE);
+    return false;
+  }
+
+  SetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE | NS_TEXT_IS_ONLY_WHITESPACE);
+  return true;
+}
+
+bool
+nsGenericDOMDataNode::ThreadSafeTextIsOnlyWhitespace() const
+{
   // FIXME: should this method take content language into account?
   if (mText.Is2b()) {
     // The fragment contains non-8bit characters and such characters
     // are never considered whitespace.
     return false;
   }
 
   if (HasFlag(NS_CACHED_TEXT_IS_ONLY_WHITESPACE)) {
@@ -996,25 +1011,22 @@ nsGenericDOMDataNode::TextIsOnlyWhitespa
 
   const char* cp = mText.Get1b();
   const char* end = cp + mText.GetLength();
 
   while (cp < end) {
     char ch = *cp;
 
     if (!dom::IsSpaceCharacter(ch)) {
-      UnsetFlags(NS_TEXT_IS_ONLY_WHITESPACE);
-      SetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE);
       return false;
     }
 
     ++cp;
   }
 
-  SetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE | NS_TEXT_IS_ONLY_WHITESPACE);
   return true;
 }
 
 bool
 nsGenericDOMDataNode::HasTextForTranslation()
 {
   if (NodeType() != nsIDOMNode::TEXT_NODE &&
       NodeType() != nsIDOMNode::CDATA_SECTION_NODE) {
--- a/dom/base/nsGenericDOMDataNode.h
+++ b/dom/base/nsGenericDOMDataNode.h
@@ -131,16 +131,17 @@ public:
   // Need to implement this here too to avoid hiding.
   nsresult SetText(const nsAString& aStr, bool aNotify)
   {
     return SetText(aStr.BeginReading(), aStr.Length(), aNotify);
   }
   virtual nsresult AppendText(const char16_t* aBuffer, uint32_t aLength,
                               bool aNotify) override;
   virtual bool TextIsOnlyWhitespace() override;
+  virtual bool ThreadSafeTextIsOnlyWhitespace() const final;
   virtual bool HasTextForTranslation() override;
   virtual void AppendTextTo(nsAString& aResult) override;
   MOZ_MUST_USE
   virtual bool AppendTextTo(nsAString& aResult,
                             const mozilla::fallible_t&) override;
   virtual void SaveSubtreeState() override;
 
 #ifdef DEBUG
--- a/dom/base/nsIContent.h
+++ b/dom/base/nsIContent.h
@@ -554,16 +554,21 @@ public:
 
   /**
    * Query method to see if the frame is nothing but whitespace
    * NOTE: Always returns false for elements
    */
   virtual bool TextIsOnlyWhitespace() = 0;
 
   /**
+   * Thread-safe version of TextIsOnlyWhitespace.
+   */
+  virtual bool ThreadSafeTextIsOnlyWhitespace() const = 0;
+
+  /**
    * Method to see if the text node contains data that is useful
    * for a translation: i.e., it consists of more than just whitespace,
    * digits and punctuation.
    * NOTE: Always returns false for elements.
    */
   virtual bool HasTextForTranslation() = 0;
 
   /**
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2376,16 +2376,17 @@ public:
   virtual int GetDocumentLWTheme() { return Doc_Theme_None; }
 
   /**
    * Returns the document state.
    * Document state bits have the form NS_DOCUMENT_STATE_* and are declared in
    * nsIDocument.h.
    */
   virtual mozilla::EventStates GetDocumentState() = 0;
+  virtual mozilla::EventStates ThreadSafeGetDocumentState() const = 0;
 
   virtual nsISupports* GetCurrentContentSink() = 0;
 
   virtual void SetScrollToRef(nsIURI *aDocumentURI) = 0;
   virtual void ScrollToRef() = 0;
   virtual void ResetScrolledToRefAlready() = 0;
   virtual void SetChangeScrollPosWhenScrollingToRef(bool aValue) = 0;
 
--- a/dom/canvas/ImageBitmap.cpp
+++ b/dom/canvas/ImageBitmap.cpp
@@ -822,25 +822,17 @@ ImageBitmap::CreateInternal(nsIGlobalObj
   nsCOMPtr<nsIPrincipal> principal = aVideoEl.GetCurrentVideoPrincipal();
   bool CORSUsed = aVideoEl.GetCORSMode() != CORS_NONE;
   if (!CheckSecurityForHTMLElements(false, CORSUsed, principal)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return nullptr;
   }
 
   // Create ImageBitmap.
-  ImageContainer *container = aVideoEl.GetImageContainer();
-
-  if (!container) {
-    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
-    return nullptr;
-  }
-
-  AutoLockImage lockImage(container);
-  layers::Image* data = lockImage.GetImage();
+  RefPtr<layers::Image> data = aVideoEl.GetCurrentImage();
   if (!data) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
   RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
 
   // Set the picture rectangle.
   if (ret && aCropRect.isSome()) {
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1025,31 +1025,31 @@ private:
   bool
   IsPlayingThroughTheAudioChannel() const
   {
     // If we have an error, we are not playing.
     if (mOwner->GetError()) {
       return false;
     }
 
+    // We should consider any bfcached page or inactive document as non-playing.
+    if (!mOwner->IsActive()) {
+      return false;
+    }
+
     // It might be resumed from remote, we should keep the audio channel agent.
     if (IsSuspended()) {
       return true;
     }
 
     // Are we paused
     if (mOwner->mPaused) {
       return false;
     }
 
-    // We should consider any bfcached page or inactive document as non-playing.
-    if (!mOwner->IsActive()) {
-      return false;
-    }
-
     // A loop always is playing
     if (mOwner->HasAttr(kNameSpaceID_None, nsGkAtoms::loop)) {
       return true;
     }
 
     // If we are actually playing...
     if (mOwner->IsCurrentlyPlaying()) {
       return true;
@@ -1527,22 +1527,17 @@ HTMLMediaElement::SetVisible(bool aVisib
   }
 
   mDecoder->SetForcedHidden(!aVisible);
 }
 
 already_AddRefed<layers::Image>
 HTMLMediaElement::GetCurrentImage()
 {
-  // Mark the decoder owned by the element as tainted so that the
-  // suspend-video-decoder is disabled.
-  mHasSuspendTaint = true;
-  if (mDecoder) {
-    mDecoder->SetSuspendTaint(true);
-  }
+  MarkAsTainted();
 
   // TODO: In bug 1345404, handle case when video decoder is already suspended.
   ImageContainer* container = GetImageContainer();
   if (!container) {
     return nullptr;
   }
 
   AutoLockImage lockImage(container);
@@ -3329,17 +3324,18 @@ HTMLMediaElement::AddCaptureMediaTrackTo
 
 already_AddRefed<DOMMediaStream>
 HTMLMediaElement::CaptureStreamInternal(bool aFinishWhenEnded,
                                         bool aCaptureAudio,
                                         MediaStreamGraph* aGraph)
 {
   MOZ_RELEASE_ASSERT(aGraph);
 
-    MarkAsContentSource(CallerAPI::CAPTURE_STREAM);
+  MarkAsContentSource(CallerAPI::CAPTURE_STREAM);
+  MarkAsTainted();
 
   nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
   if (!window) {
     return nullptr;
   }
   if (ContainsRestrictedContent()) {
     return nullptr;
   }
@@ -3718,17 +3714,16 @@ HTMLMediaElement::HTMLMediaElement(alrea
     mDownloadSuspendedByCache(false, "HTMLMediaElement::mDownloadSuspendedByCache"),
     mAudioChannel(AudioChannelService::GetDefaultAudioChannel()),
     mDisableVideo(false),
     mHasUserInteraction(false),
     mFirstFrameLoaded(false),
     mDefaultPlaybackStartPosition(0.0),
     mIsAudioTrackAudible(false),
     mHasSuspendTaint(false),
-    mMediaTracksConstructed(false),
     mVisibilityState(Visibility::UNTRACKED),
     mErrorSink(new ErrorSink(this)),
     mAudioChannelWrapper(new AudioChannelAgentCallback(this, mAudioChannel))
 {
   ErrorResult rv;
 
   double defaultVolume = Preferences::GetFloat("media.default_volume", 1.0);
   SetVolume(defaultVolume, rv);
@@ -7418,21 +7413,17 @@ nsIDocument*
 HTMLMediaElement::GetDocument() const
 {
   return OwnerDoc();
 }
 
 void
 HTMLMediaElement::ConstructMediaTracks(const MediaInfo* aInfo)
 {
-  if (mMediaTracksConstructed || !aInfo) {
-    return;
-  }
-
-  mMediaTracksConstructed = true;
+  MOZ_ASSERT(aInfo);
 
   AudioTrackList* audioList = AudioTracks();
   if (audioList && aInfo->HasAudio()) {
     const TrackInfo& info = aInfo->mAudio;
     RefPtr<AudioTrack> track = MediaTrackList::CreateAudioTrack(
     info.mId, info.mKind, info.mLabel, info.mLanguage, info.mEnabled);
 
     audioList->AddTrack(track);
@@ -7456,18 +7447,16 @@ HTMLMediaElement::RemoveMediaTracks()
   if (audioList) {
     audioList->RemoveTracks();
   }
 
   VideoTrackList* videoList = VideoTracks();
   if (videoList) {
     videoList->RemoveTracks();
   }
-
-  mMediaTracksConstructed = false;
 }
 
 class MediaElementGMPCrashHelper : public GMPCrashHelper
 {
 public:
   explicit MediaElementGMPCrashHelper(HTMLMediaElement* aElement)
     : mElement(aElement)
   {
@@ -7486,16 +7475,26 @@ private:
 };
 
 already_AddRefed<GMPCrashHelper>
 HTMLMediaElement::CreateGMPCrashHelper()
 {
   return MakeAndAddRef<MediaElementGMPCrashHelper>(this);
 }
 
+void
+HTMLMediaElement::MarkAsTainted()
+{
+  mHasSuspendTaint = true;
+
+  if (mDecoder) {
+    mDecoder->SetSuspendTaint(true);
+  }
+}
+
 bool HasDebuggerPrivilege(JSContext* aCx, JSObject* aObj)
 {
   return nsContentUtils::CallerHasPermission(aCx,
                                              NS_LITERAL_STRING("debugger"));
  }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -1305,16 +1305,20 @@ protected:
   // and queues a task to resolve them also to dispatch a "playing" event.
   void NotifyAboutPlaying();
 
   already_AddRefed<Promise> CreateDOMPromise(ErrorResult& aRv) const;
 
   // Pass information for deciding the video decode mode to decoder.
   void NotifyDecoderActivityChanges() const;
 
+  // Mark the decoder owned by the element as tainted so that the
+  // suspend-video-decoder is disabled.
+  void MarkAsTainted();
+
   // The current decoder. Load() has been called on this decoder.
   // At most one of mDecoder and mSrcStream can be non-null.
   RefPtr<MediaDecoder> mDecoder;
 
   // The DocGroup-specific AbstractThread::MainThread() of this HTML element.
   RefPtr<AbstractThread> mAbstractMainThread;
 
   // Observers listening to changes to the mDecoder principal.
@@ -1736,20 +1740,16 @@ private:
 
   // True if the audio track is not silent.
   bool mIsAudioTrackAudible;
 
   // True if media element has been marked as 'tainted' and can't
   // participate in video decoder suspending.
   bool mHasSuspendTaint;
 
-  // True if audio tracks and video tracks are constructed and added into the
-  // track list, false if all tracks are removed from the track list.
-  bool mMediaTracksConstructed;
-
   Visibility mVisibilityState;
 
   UniquePtr<ErrorSink> mErrorSink;
 
   // This wrapper will handle all audio channel related stuffs, eg. the operations
   // of tab audio indicator, Fennec's media control.
   // Note: mAudioChannelWrapper might be null after GC happened.
   RefPtr<AudioChannelAgentCallback> mAudioChannelWrapper;
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -388,16 +388,17 @@ MediaDecoder::MediaDecoder(MediaDecoderO
   , mInfiniteStream(false)
   , mOwner(aOwner)
   , mAbstractMainThread(aOwner->AbstractMainThread())
   , mFrameStats(new FrameStatistics())
   , mVideoFrameContainer(aOwner->GetVideoFrameContainer())
   , mPlaybackStatistics(new MediaChannelStatistics())
   , mPinnedForSeek(false)
   , mMinimizePreroll(false)
+  , mMediaTracksConstructed(false)
   , mFiredMetadataLoaded(false)
   , mIsDocumentVisible(false)
   , mElementVisibility(Visibility::UNTRACKED)
   , mIsElementInTree(false)
   , mForcedHidden(false)
   , mHasSuspendTaint(false)
   , INIT_MIRROR(mStateMachineIsShutdown, true)
   , INIT_MIRROR(mBuffered, TimeIntervals())
@@ -801,17 +802,17 @@ MediaDecoder::GetCurrentPrincipal()
   MOZ_ASSERT(NS_IsMainThread());
   return mResource ? mResource->GetCurrentPrincipal() : nullptr;
 }
 
 void
 MediaDecoder::OnMetadataUpdate(TimedMetadata&& aMetadata)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  GetOwner()->RemoveMediaTracks();
+  RemoveMediaTracks();
   MetadataLoaded(nsAutoPtr<MediaInfo>(new MediaInfo(*aMetadata.mInfo)),
                  Move(aMetadata.mTags),
                  MediaDecoderEventVisibility::Observable);
   FirstFrameLoaded(Move(aMetadata.mInfo),
                    MediaDecoderEventVisibility::Observable);
 }
 
 void
@@ -824,17 +825,17 @@ MediaDecoder::MetadataLoaded(nsAutoPtr<M
 
   DECODER_LOG("MetadataLoaded, channels=%u rate=%u hasAudio=%d hasVideo=%d",
               aInfo->mAudio.mChannels, aInfo->mAudio.mRate,
               aInfo->HasAudio(), aInfo->HasVideo());
 
   mMediaSeekable = aInfo->mMediaSeekable;
   mMediaSeekableOnlyInBufferedRanges = aInfo->mMediaSeekableOnlyInBufferedRanges;
   mInfo = aInfo.forget();
-  GetOwner()->ConstructMediaTracks(mInfo);
+  ConstructMediaTracks();
 
   // Make sure the element and the frame (if any) are told about
   // our new size.
   if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
     mFiredMetadataLoaded = true;
     GetOwner()->MetadataLoaded(mInfo,
                                nsAutoPtr<const MetadataTags>(aTags.forget()));
   }
@@ -1189,19 +1190,19 @@ MediaDecoder::ChangeState(PlayState aSta
   if (mNextState == aState) {
     mNextState = PLAY_STATE_PAUSED;
   }
 
   DECODER_LOG("ChangeState %s => %s", PlayStateStr(), ToPlayStateStr(aState));
   mPlayState = aState;
 
   if (mPlayState == PLAY_STATE_PLAYING) {
-    GetOwner()->ConstructMediaTracks(mInfo);
+    ConstructMediaTracks();
   } else if (IsEnded()) {
-    GetOwner()->RemoveMediaTracks();
+    RemoveMediaTracks();
   }
 }
 
 void
 MediaDecoder::UpdateLogicalPositionInternal()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
@@ -1758,16 +1759,42 @@ MediaDecoder::GetOwner() const
 {
   MOZ_ASSERT(NS_IsMainThread());
   // Check object lifetime when mOwner points to a media element.
   MOZ_DIAGNOSTIC_ASSERT(!mOwner || !mIsMediaElement || mElement);
   // mOwner is valid until shutdown.
   return mOwner;
 }
 
+void
+MediaDecoder::ConstructMediaTracks()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
+
+  if (mMediaTracksConstructed || !mInfo) {
+    return;
+  }
+
+  GetOwner()->ConstructMediaTracks(mInfo);
+
+  mMediaTracksConstructed = true;
+}
+
+void
+MediaDecoder::RemoveMediaTracks()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
+
+  GetOwner()->RemoveMediaTracks();
+
+  mMediaTracksConstructed = false;
+}
+
 MediaDecoderOwner::NextFrameStatus
 MediaDecoder::NextFrameBufferedStatus()
 {
   MOZ_ASSERT(NS_IsMainThread());
   // Next frame hasn't been decoded yet.
   // Use the buffered range to consider if we have the next frame available.
   media::TimeUnit currentPosition =
     media::TimeUnit::FromMicroseconds(CurrentPosition());
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -395,16 +395,27 @@ private:
    * thread.
    ******/
 
   // Change to a new play state. This updates the mState variable and
   // notifies any thread blocking on this object's monitor of the
   // change. Call on the main thread only.
   virtual void ChangeState(PlayState aState);
 
+  // Called from MetadataLoaded(). Ask its owner to create audio/video tracks
+  // and adds them to its owner's audio/video track list.
+  // Call on the main thread only.
+  void ConstructMediaTracks();
+
+  // Ask its owner to remove all audio tracks and video tracks that are
+  // previously added into the track list.
+  // Call on the main thread only.
+  void RemoveMediaTracks();
+
+
   // Called when the video has completed playing.
   // Call on the main thread only.
   void PlaybackEnded();
 
   void OnSeekRejected();
   void OnSeekResolved();
 
   void SeekingChanged()
@@ -689,16 +700,21 @@ protected:
   dom::AudioChannel mAudioChannel;
 
   // True if the decoder has been directed to minimize its preroll before
   // playback starts. After the first time playback starts, we don't attempt
   // to minimize preroll, as we assume the user is likely to keep playing,
   // or play the media again.
   bool mMinimizePreroll;
 
+  // True if audio tracks and video tracks are constructed and added into the
+  // owenr's track list, false if all tracks are removed from the owner's track
+  // list.
+  bool mMediaTracksConstructed;
+
   // True if we've already fired metadataloaded.
   bool mFiredMetadataLoaded;
 
   // True if the media is seekable (i.e. supports random access).
   bool mMediaSeekable = true;
 
   // True if the media is only seekable within its buffered ranges
   // like WebMs with no cues.
--- a/dom/media/TextTrackList.cpp
+++ b/dom/media/TextTrackList.cpp
@@ -2,16 +2,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/. */
 
 #include "mozilla/dom/TextTrackList.h"
 #include "mozilla/dom/TextTrackListBinding.h"
 #include "mozilla/dom/TrackEvent.h"
 #include "nsThreadUtils.h"
+#include "nsGlobalWindow.h"
 #include "mozilla/dom/TextTrackCue.h"
 #include "mozilla/dom/TextTrackManager.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(TextTrackList,
                                    DOMEventTargetHelper,
@@ -169,24 +170,31 @@ TextTrackList::DispatchTrackEvent(nsIDOM
   return DispatchTrustedEvent(aEvent);
 }
 
 void
 TextTrackList::CreateAndDispatchChangeEvent()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!mPendingTextTrackChange) {
+    nsPIDOMWindowInner* win = GetOwner();
+    if (!win) {
+      return;
+    }
+
     mPendingTextTrackChange = true;
     RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
 
     event->InitEvent(NS_LITERAL_STRING("change"), false, false);
     event->SetTrusted(true);
 
     nsCOMPtr<nsIRunnable> eventRunner = new ChangeEventRunner(this, event);
-    NS_DispatchToMainThread(eventRunner);
+    nsGlobalWindow::Cast(win)->Dispatch(
+      "TextTrackList::CreateAndDispatchChangeEvent", TaskCategory::Other,
+      eventRunner.forget());
   }
 }
 
 void
 TextTrackList::CreateAndDispatchTrackEventRunner(TextTrack* aTrack,
                                                  const nsAString& aEventName)
 {
   nsCOMPtr<nsIThread> thread;
--- a/dom/media/gmp/GMPVideoEncoderParent.cpp
+++ b/dom/media/gmp/GMPVideoEncoderParent.cpp
@@ -11,16 +11,17 @@
 #include "GMPMessageUtils.h"
 #include "nsAutoRef.h"
 #include "GMPContentParent.h"
 #include "mozilla/gmp/GMPTypes.h"
 #include "nsThread.h"
 #include "nsThreadUtils.h"
 #include "runnable_utils.h"
 #include "GMPUtils.h"
+#include "mozilla/SystemGroup.h"
 
 namespace mozilla {
 
 #ifdef LOG
 #undef LOG
 #endif
 
 extern LogModule* GetGMPLog();
--- a/dom/media/gtest/TestBlankVideoDataCreator.cpp
+++ b/dom/media/gtest/TestBlankVideoDataCreator.cpp
@@ -5,23 +5,25 @@
 
 #include "gtest/gtest.h"
 #include "BlankDecoderModule.h"
 
 using namespace mozilla;
 
 TEST(BlankVideoDataCreator, ShouldNotOverflow)
 {
+  RefPtr<MediaRawData> mrd = new MediaRawData();
   const uint32_t width = 1;
   const uint32_t height = 1;
   BlankVideoDataCreator creater(width, height, nullptr);
-  RefPtr<MediaData> data = creater.Create(new MediaRawData());
+  RefPtr<MediaData> data = creater.Create(mrd);
   EXPECT_NE(data.get(), nullptr);
 }
 
 TEST(BlankVideoDataCreator, ShouldOverflow)
 {
+  RefPtr<MediaRawData> mrd = new MediaRawData();
   const uint32_t width = UINT_MAX;
   const uint32_t height = UINT_MAX;
   BlankVideoDataCreator creater(width, height, nullptr);
-  RefPtr<MediaData> data = creater.Create(new MediaRawData());
+  RefPtr<MediaData> data = creater.Create(mrd);
   EXPECT_EQ(data.get(), nullptr);
 }
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -1144,16 +1144,22 @@ tags = suspend
 skip-if = toolkit == 'android' # bug 1346705
 tags = suspend
 [test_background_video_suspend.html]
 skip-if = toolkit == 'android' # android(bug 1304480)
 tags = suspend
 [test_background_video_suspend_ends.html]
 skip-if = toolkit == 'android' # bug 1295884, android(bug 1304480, bug 1232305)
 tags = suspend
+[test_background_video_tainted_by_capturestream.html]
+skip-if = toolkit == 'android' # bug 1346705
+tags = suspend
+[test_background_video_tainted_by_createimagebitmap.html]
+skip-if = toolkit == 'android' # bug 1346705
+tags = suspend
 [test_background_video_tainted_by_drawimage.html]
 skip-if = toolkit == 'android' # bug 1346705
 tags = suspend
 [test_background_video_drawimage_with_suspended_video.html]
 skip-if = toolkit == 'android' # bug 1346705
 tags = suspend
 
 [test_temporary_file_blob_video_plays.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_background_video_tainted_by_capturestream.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test Background Video Is Tainted By captureStream</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="manifest.js"></script>
+<script src="background_video.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script type="text/javascript">
+"use strict";
+
+var manager = new MediaTestManager;
+
+function captureVideoAsStream(v) {
+  let stream = v.mozCaptureStream();
+}
+
+startTest({
+  desc: 'Test Background Video Is Tainted By captureStream',
+  prefs: [
+    [ "media.test.video-suspend", true ],
+    [ "media.suspend-bkgnd-video.enabled", true ],
+    [ "media.suspend-bkgnd-video.delay-ms", 1000 ]
+  ],
+  tests: gDecodeSuspendTests,
+  runTest: (test, token) => {
+    ok(true, `${test.name}`);
+    let v = appendVideoToDoc(test.name, token);
+    manager.started(token);
+
+    waitUntilPlaying(v)
+      .then(() => {
+        captureVideoAsStream(v);
+        ok(v.hasSuspendTaint(), "Video is tainted after captured");
+        return checkVideoDoesntSuspend(v);
+      })
+      .then(() => {
+        ok(true, 'Video ended before decode was suspended');
+        manager.finished(token);
+      })
+      .catch((e) => {
+        ok(false, 'Test failed: ' + e.toString());
+        manager.finished(token);
+      });
+  }
+});
+</script>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_background_video_tainted_by_createimagebitmap.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test Background Video Is Tainted By createImageBitmap</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="manifest.js"></script>
+<script src="background_video.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script type="text/javascript">
+"use strict";
+
+var manager = new MediaTestManager;
+
+startTest({
+  desc: 'Test Background Video Is Tainted By createImageBitmap',
+  prefs: [
+    [ "media.test.video-suspend", true ],
+    [ "media.suspend-bkgnd-video.enabled", true ],
+    [ "media.suspend-bkgnd-video.delay-ms", 1000 ]
+  ],
+  tests: gDecodeSuspendTests,
+  runTest: (test, token) => {
+    ok(true, `${test.name}`);
+    let v = appendVideoToDoc(test.name, token);
+    manager.started(token);
+
+    waitUntilPlaying(v)
+      .then(() => createImageBitmap(v))
+      .then(() => {
+        ok(v.hasSuspendTaint(), "Video is tainted after drawing to canvas");
+        return checkVideoDoesntSuspend(v);
+      })
+      .then(() => {
+        ok(true, 'Video ended before decode was suspended');
+        manager.finished(token);
+      })
+      .catch((e) => {
+        ok(false, 'Test failed: ' + e.toString());
+        manager.finished(token);
+      });
+  }
+});
+</script>
\ No newline at end of file
--- a/dom/media/test/test_load_same_resource.html
+++ b/dom/media/test/test_load_same_resource.html
@@ -78,15 +78,19 @@ function initTest(test, token) {
   ok(true, `Trying to load ${test.name}, duration=${test.duration}`);
   e.addEventListener("loadeddata", tryClone, {once: true});
   e.token = token;
   manager.started(token);
 }
 
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPrefEnv(
-  {"set": [["logging.MediaDecoder", "Debug"]]},
+  {"set": [
+    ["logging.MediaDecoder", "Debug"],
+    ["logging.nsMediaElement", "Debug"],
+    ["logging.MediaCache", "Debug"],
+  ]},
   manager.runTests.bind(manager, gCloneTests, initTest));
 
 </script>
 </pre>
 </body>
 </html>
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -783,26 +783,16 @@ EditorBase::GetTransactionManager(nsITra
   *aTxnManager = nullptr;
   NS_ENSURE_TRUE(mTxnMgr, NS_ERROR_FAILURE);
 
   NS_ADDREF(*aTxnManager = mTxnMgr);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-EditorBase::SetTransactionManager(nsITransactionManager* aTxnManager)
-{
-  NS_ENSURE_TRUE(aTxnManager, NS_ERROR_FAILURE);
-
-  // nsITransactionManager is builtinclass, so this is safe
-  mTxnMgr = static_cast<nsTransactionManager*>(aTxnManager);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 EditorBase::Undo(uint32_t aCount)
 {
   ForceCompositionEnd();
 
   bool hasTxnMgr, hasTransaction = false;
   CanUndo(&hasTxnMgr, &hasTransaction);
   NS_ENSURE_TRUE(hasTransaction, NS_OK);
 
--- a/editor/nsIEditor.idl
+++ b/editor/nsIEditor.idl
@@ -167,17 +167,17 @@ interface nsIEditor  : nsISupports
     *                    to increase or decrease the count
     */
   void incrementModificationCount(in long aModCount);
 
   /* ------------ Transaction methods -------------- */
 
   /** transactionManager Get the transaction manager the editor is using.
     */
-  attribute nsITransactionManager transactionManager;
+  readonly attribute nsITransactionManager transactionManager;
 
   /** doTransaction() fires a transaction.
     * It is provided here so clients can create their own transactions.
     * If a transaction manager is present, it is used.
     * Otherwise, the transaction is just executed directly.
     *
     * @param aTxn the transaction to execute
     */
--- a/gfx/vr/ipc/PVRManager.ipdl
+++ b/gfx/vr/ipc/PVRManager.ipdl
@@ -45,17 +45,17 @@ parent:
   // asynchronously to children via UpdateDisplayInfo.
   async RefreshDisplays();
 
   // Reset the sensor of the display identified by aDisplayID so that the current
   // sensor state is the "Zero" position.
   async ResetSensor(uint32_t aDisplayID);
 
   sync GetSensorState(uint32_t aDisplayID) returns(VRHMDSensorState aState);
-  sync SetHaveEventListener(bool aHaveEventListener);
+  async SetHaveEventListener(bool aHaveEventListener);
 
   async ControllerListenerAdded();
   async ControllerListenerRemoved();
   async CreateVRTestSystem();
   async CreateVRServiceTestDisplay(nsCString aID, uint32_t aPromiseID);
   async CreateVRServiceTestController(nsCString aID, uint32_t aPromiseID);
   async SetDisplayInfoToMockDisplay(uint32_t aDeviceID, VRDisplayInfo aDisplayInfo);
   async SetSensorStateToMockDisplay(uint32_t aDeviceID, VRHMDSensorState aSensorState);
--- a/intl/locale/nsLanguageAtomService.cpp
+++ b/intl/locale/nsLanguageAtomService.cpp
@@ -4,22 +4,22 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsLanguageAtomService.h"
 #include "nsUConvPropertySearch.h"
 #include "nsUnicharUtils.h"
 #include "nsIAtom.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Services.h"
-#include "mozilla/intl/LocaleService.h"
+#include "mozilla/intl/OSPreferences.h"
 #include "nsServiceManagerUtils.h"
 #include "mozilla/dom/EncodingUtils.h"
 
 using namespace mozilla;
-using mozilla::intl::LocaleService;
+using mozilla::intl::OSPreferences;
 
 static constexpr nsUConvProp kLangGroups[] = {
 #include "langGroups.properties.h"
 };
 
 NS_IMPL_ISUPPORTS(nsLanguageAtomService, nsILanguageAtomService)
 
 nsLanguageAtomService::nsLanguageAtomService()
@@ -46,17 +46,17 @@ nsLanguageAtomService::LookupCharSet(con
 }
 
 nsIAtom*
 nsLanguageAtomService::GetLocaleLanguage()
 {
   do {
     if (!mLocaleLanguage) {
       nsAutoCString locale;
-      LocaleService::GetInstance()->GetAppLocaleAsLangTag(locale);
+      OSPreferences::GetInstance()->GetSystemLocale(locale);
 
       ToLowerCase(locale); // use lowercase for all language atoms
       mLocaleLanguage = NS_Atomize(locale);
     }
   } while (0);
 
   return mLocaleLanguage;
 }
--- a/ipc/ipdl/sync-messages.ini
+++ b/ipc/ipdl/sync-messages.ini
@@ -942,18 +942,16 @@ description =
 [PWebRenderBridge::DeleteImage]
 description =
 [PWebRenderBridge::DPSyncEnd]
 description =
 [PWebRenderBridge::DPGetSnapshot]
 description =
 [PVRManager::GetSensorState]
 description =
-[PVRManager::SetHaveEventListener]
-description =
 [PHal::GetCurrentBatteryInformation]
 description =
 [PHal::GetCurrentNetworkInformation]
 description =
 [PHal::GetScreenEnabled]
 description =
 [PHal::GetKeyLightEnabled]
 description =
--- a/js/src/builtin/.eslintrc.js
+++ b/js/src/builtin/.eslintrc.js
@@ -1,13 +1,13 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../toolkit/.eslintrc.js"
+    "plugin:mozilla/recommended"
   ],
 
   "plugins": [
     "spidermonkey-js"
   ],
 
   "rules": {
     // We should fix those at some point, but we use this to detect NaNs.
--- a/js/src/proxy/CrossCompartmentWrapper.cpp
+++ b/js/src/proxy/CrossCompartmentWrapper.cpp
@@ -538,16 +538,22 @@ js::NukeCrossCompartmentWrappers(JSConte
             // interested in those.
             const CrossCompartmentKey& k = e.front().key();
             if (!k.is<JSObject*>())
                 continue;
 
             AutoWrapperRooter wobj(cx, WrapperValue(e));
             JSObject* wrapped = UncheckedUnwrap(wobj);
 
+            // We never nuke script source objects, since only ever used internally by the JS
+            // engine, and are expected to remain valid throughout a scripts lifetime.
+            if (MOZ_UNLIKELY(wrapped->is<ScriptSourceObject>())) {
+                continue;
+            }
+
             // We only skip nuking window references that point to a target
             // compartment, not the ones that belong to it.
             if (nukeReferencesToWindow == DontNukeWindowReferences &&
                 MOZ_LIKELY(!nukeAll) && IsWindowProxy(wrapped))
             {
                 continue;
             }
 
--- a/js/src/shell/.eslintrc.js
+++ b/js/src/shell/.eslintrc.js
@@ -1,12 +1,12 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../toolkit/.eslintrc.js"
+    "plugin:mozilla/recommended"
   ],
 
   "rules": {
     // SpiderMonkey's style doesn't match any of the possible options.
     "brace-style": "off",
   }
 };
--- a/layout/reftests/bidi/dirAuto/reftest-stylo.list
+++ b/layout/reftests/bidi/dirAuto/reftest-stylo.list
@@ -1,10 +1,10 @@
 # DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
-fails == bdi-auto-dir-default.html bdi-auto-dir-default.html
+== bdi-auto-dir-default.html bdi-auto-dir-default.html
 fails == dir_auto-set-contained-dir-L.html dir_auto-set-contained-dir-L.html
 fails == dir_auto-set-contained-dir-R.html dir_auto-set-contained-dir-R.html
 fails == dir_auto-set-contained-invalid-dir-L.html dir_auto-set-contained-invalid-dir-L.html
 fails == dir_auto-set-contained-invalid-dir-R.html dir_auto-set-contained-invalid-dir-R.html
 fails == dir_auto-unset-contained-dir-L.html dir_auto-unset-contained-dir-L.html
 fails == dir_auto-unset-contained-dir-R.html dir_auto-unset-contained-dir-R.html
 fails == dynamicDirAuto-setLTR-Auto1.html dynamicDirAuto-setLTR-Auto1.html
 fails == dynamicDirAuto-setLTR-Auto2.html dynamicDirAuto-setLTR-Auto2.html
@@ -107,14 +107,14 @@ fails == dynamicDirAuto-ChangeText-RTL10
 fails == dynamicDirAuto-DeleteText-LTR1.html dynamicDirAuto-DeleteText-LTR1.html
 fails == dynamicDirAuto-DeleteText-LTR2.html dynamicDirAuto-DeleteText-LTR2.html
 fails == dynamicDirAuto-DeleteText-LTR3.html dynamicDirAuto-DeleteText-LTR3.html
 fails == dynamicDirAuto-DeleteText-RTL1.html dynamicDirAuto-DeleteText-RTL1.html
 fails == dynamicDirAuto-DeleteText-RTL2.html dynamicDirAuto-DeleteText-RTL2.html
 fails == dynamicDirAuto-DeleteText-RTL3.html dynamicDirAuto-DeleteText-RTL3.html
 fails == 839886-1.html 839886-1.html
 == 859093-1.html 859093-1.html
-fails == 889742-1.html 889742-1.html
+== 889742-1.html 889742-1.html
 fails == 1103348-1.html 1103348-1.html
 fails == 1169267-delete-add-1a.html 1169267-delete-add-1a.html
 fails == 1169267-delete-add-1b.html 1169267-delete-add-1b.html
 fails == 1169267-delete-add-2a.html 1169267-delete-add-2a.html
 fails == 1169267-delete-add-2b.html 1169267-delete-add-2b.html
--- a/layout/reftests/bidi/reftest-stylo.list
+++ b/layout/reftests/bidi/reftest-stylo.list
@@ -94,23 +94,23 @@ fails == 413928-2.html 413928-2.html
 == 489517-1.html 489517-1.html
 == 489887-1.html 489887-1.html
 == 492231-1.html 492231-1.html
 == 496006-1.html 496006-1.html
 == 503269-1.html 503269-1.html
 == 503957-1.html 503957-1.html
 == 525740-1.html 525740-1.html
 fails == 536963-1.html 536963-1.html
-fails == 562169-1.html 562169-1.html
+== 562169-1.html 562169-1.html
 fails == 562169-1a.html 562169-1a.html
-fails == 562169-2.html 562169-2.html
+== 562169-2.html 562169-2.html
 fails == 562169-2a.html 562169-2a.html
 == 562169-3.html 562169-3.html
-== 562169-3a.html 562169-3a.html
-fails == 562169-4.html 562169-4.html
+fails == 562169-3a.html 562169-3a.html # bug 1338982
+== 562169-4.html 562169-4.html
 == 588739-1.html 588739-1.html
 == 588739-2.html 588739-2.html
 == 588739-3.html 588739-3.html
 == 612843-1.html 612843-1.html
 fails == 613149-1a.html 613149-1a.html
 fails == 613149-1b.html 613149-1b.html
 == 613149-2a.html 613149-2a.html
 == 613149-2b.html 613149-2b.html
--- a/layout/reftests/bugs/reftest-stylo.list
+++ b/layout/reftests/bugs/reftest-stylo.list
@@ -813,17 +813,17 @@ skip-if(stylo) == 394676-1.xhtml 394676-
 fails == 395107-1.html 395107-1.html
 fails == 395107-2.html 395107-2.html
 fails == 395107-3.html 395107-3.html
 fails == 395107-4.html 395107-4.html
 fails == 395107-5.html 395107-5.html
 fails == 395130-1.html 395130-1.html
 fails == 395130-2.html 395130-2.html
 == 395331-1.xml 395331-1.xml
-fails == 395390-1.html 395390-1.html
+== 395390-1.html 395390-1.html
 == 396286-1.html 396286-1.html
 fails == 397428-1.html 397428-1.html
 == 397844-1.xhtml 397844-1.xhtml
 == 398092-1.html 398092-1.html
 fails == 398101-1.html 398101-1.html
 == 398144-1.html 398144-1.html
 == 398682-1.html 398682-1.html
 == 398797-1a.html 398797-1a.html
@@ -1647,17 +1647,17 @@ fails HTTP(..) == 619511-1.html 619511-1
 HTTP(..) == 621253-2-externalFilter.html 621253-2-externalFilter.html
 == 621253-2-internalFilter.html 621253-2-internalFilter.html
 random-if(winWidget) fuzzy-if(OSX==1008,19,17) == 621918-1.svg 621918-1.svg
 random-if(winWidget) HTTP(..) == 621918-2.svg 621918-2.svg
 fails == 622585-1.html 622585-1.html
 == 625409-1.html 625409-1.html
 == 627393-1.html 627393-1.html
 fuzzy-if(skiaContent,1,500) == 630835-1.html 630835-1.html
-== 631352-1.html 631352-1.html
+fails == 631352-1.html 631352-1.html
 == 632423-1.html 632423-1.html
 skip-if(Android) random-if(winWidget||OSX==1010) == 632781-verybig.html 632781-verybig.html
 == 632781-normalsize.html 632781-normalsize.html
 == 633344-1.html 633344-1.html
 == 634232-1.html 634232-1.html
 skip-if(stylo) == 635302-1.html 635302-1.html # Too intermittent.
 == 635373-1.html 635373-1.html
 == 635373-2.html 635373-2.html
@@ -1824,17 +1824,17 @@ fails == 985303-1b.html 985303-1b.html
 fails == 987680-1.html 987680-1.html
 == 991046-1.html 991046-1.html
 fails == 992447.html 992447.html
 == 1003425-1.html 1003425-1.html
 == 1003425-2.html 1003425-2.html
 == 1005405-1.html 1005405-1.html
 fails == 1012640-1.html 1012640-1.html
 == 1013054-1.html 1013054-1.html
-fails == 1018522-1.html 1018522-1.html
+== 1018522-1.html 1018522-1.html
 == 1021564-1.html 1021564-1.html
 == 1021564-2.html 1021564-2.html
 == 1021564-3.html 1021564-3.html
 == 1021564-4.html 1021564-4.html
 pref(browser.display.use_document_fonts,0) == 1022481-1.html 1022481-1.html
 == 1022612-1.html 1022612-1.html
 == 1024473-1.html 1024473-1.html
 == 1025914-1.html 1025914-1.html
@@ -1948,17 +1948,17 @@ fails == 1202512-2.html 1202512-2.html
 fails == 1209994-2.html 1209994-2.html
 == 1209994-3.html 1209994-3.html
 fails == 1209994-4.html 1209994-4.html
 == 1222226-1.html 1222226-1.html
 fails pref(layout.css.overflow-clip-box.enabled,true) == 1226278.html 1226278.html
 == 1230466.html 1230466.html
 == 1238243-1.html 1238243-1.html
 == 1238243-2.html 1238243-2.html
-fails == 1239564.html 1239564.html
+== 1239564.html 1239564.html
 fails == 1242172-1.html 1242172-1.html
 fails == 1242172-2.html 1242172-2.html
 == 1242781.html 1242781.html
 == 1263845.html 1263845.html
 == 1260543-1.html 1260543-1.html
 == 1271714-1.html 1271714-1.html
 == 1272997-1.html 1272997-1.html
 == 1273154-1.html 1273154-1.html
--- a/layout/reftests/css-required/reftest-stylo.list
+++ b/layout/reftests/css-required/reftest-stylo.list
@@ -17,14 +17,14 @@ fails == css-required-dyn-3.html css-req
 fails == css-required-dyn-4.html css-required-dyn-4.html
 fails == css-required-dyn-5.html css-required-dyn-5.html
 fails == css-required-dyn-6.html css-required-dyn-6.html
 
 # Following input types do not support :required
 == css-required-hidden.html css-required-hidden.html
 fails == css-required-button.html css-required-button.html
 fails == css-required-submit.html css-required-submit.html
-== css-required-image.html css-required-image.html
+fails == css-required-image.html css-required-image.html
 fails == css-required-reset.html css-required-reset.html
 
 # Following elements can be optional but can't be required
 fails == css-required-button-element.html css-required-button-element.html
 
--- a/layout/reftests/w3c-css/received/reftest-stylo.list
+++ b/layout/reftests/w3c-css/received/reftest-stylo.list
@@ -249,10 +249,10 @@ fails needs-focus == selectors-4/focus-w
 fails needs-focus == selectors-4/focus-within-006.html selectors-4/focus-within-006.html
 needs-focus == selectors-4/focus-within-shadow-001.html selectors-4/focus-within-shadow-001.html
 skip-if(stylo) pref(dom.webcomponents.enabled,true) needs-focus == selectors-4/focus-within-shadow-002.html selectors-4/focus-within-shadow-002.html # Bug 1292285
 skip-if(stylo) pref(dom.webcomponents.enabled,true) needs-focus == selectors-4/focus-within-shadow-003.html selectors-4/focus-within-shadow-003.html # Bug 1292285
 skip-if(stylo) pref(dom.webcomponents.enabled,true) needs-focus == selectors-4/focus-within-shadow-004.html selectors-4/focus-within-shadow-004.html # Bug 1292285
 skip-if(stylo) pref(dom.webcomponents.enabled,true) needs-focus == selectors-4/focus-within-shadow-005.html selectors-4/focus-within-shadow-005.html # Bug 1292285
 == selectors-4/of-type-selectors.xhtml selectors-4/of-type-selectors.xhtml
 fails == selectors-4/selector-required.html selectors-4/selector-required.html
-fails == selectors-4/selectors-dir-selector-ltr-001.html selectors-4/selectors-dir-selector-ltr-001.html
-fails == selectors-4/selectors-dir-selector-rtl-001.html selectors-4/selectors-dir-selector-rtl-001.html
+== selectors-4/selectors-dir-selector-ltr-001.html selectors-4/selectors-dir-selector-ltr-001.html
+== selectors-4/selectors-dir-selector-rtl-001.html selectors-4/selectors-dir-selector-rtl-001.html
--- a/layout/reftests/w3c-css/submitted/selectors4/reftest-stylo.list
+++ b/layout/reftests/w3c-css/submitted/selectors4/reftest-stylo.list
@@ -1,12 +1,12 @@
 # DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
 fails needs-focus == focus-within-1.html focus-within-1.html
 needs-focus == focus-within-2.html focus-within-2.html
 fails needs-focus == focus-within-3.html focus-within-3.html
-fails == dir-style-01a.html dir-style-01a.html
+== dir-style-01a.html dir-style-01a.html
 fails == dir-style-01b.html dir-style-01b.html
-fails == dir-style-02a.html dir-style-02a.html
+== dir-style-02a.html dir-style-02a.html
 fails == dir-style-02b.html dir-style-02b.html
-fails == dir-style-03a.html dir-style-03a.html
+== dir-style-03a.html dir-style-03a.html
 fails == dir-style-03b.html dir-style-03b.html
-fails == dir-style-04.html dir-style-04.html
+== dir-style-04.html dir-style-04.html
 == child-index-no-parent-01.html child-index-no-parent-01.html
--- a/layout/reftests/w3c-css/submitted/will-change/reftest-stylo.list
+++ b/layout/reftests/w3c-css/submitted/will-change/reftest-stylo.list
@@ -1,20 +1,20 @@
 # DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
-fails == will-change-stacking-context-clip-path-1.html will-change-stacking-context-clip-path-1.html
-fails == will-change-stacking-context-filter-1.html will-change-stacking-context-filter-1.html
+== will-change-stacking-context-clip-path-1.html will-change-stacking-context-clip-path-1.html
+== will-change-stacking-context-filter-1.html will-change-stacking-context-filter-1.html
 == will-change-stacking-context-height-1.html will-change-stacking-context-height-1.html
-fails == will-change-stacking-context-isolation-1.html will-change-stacking-context-isolation-1.html
-fails == will-change-stacking-context-mask-1.html will-change-stacking-context-mask-1.html
-fails == will-change-stacking-context-mix-blend-mode-1.html will-change-stacking-context-mix-blend-mode-1.html
-fails == will-change-stacking-context-opacity-1.html will-change-stacking-context-opacity-1.html
-fails == will-change-stacking-context-perspective-1.html will-change-stacking-context-perspective-1.html
-fails == will-change-stacking-context-position-1.html will-change-stacking-context-position-1.html
-fails == will-change-stacking-context-transform-1.html will-change-stacking-context-transform-1.html
-fails == will-change-stacking-context-transform-style-1.html will-change-stacking-context-transform-style-1.html
-fails == will-change-stacking-context-z-index-1.html will-change-stacking-context-z-index-1.html
+== will-change-stacking-context-isolation-1.html will-change-stacking-context-isolation-1.html
+== will-change-stacking-context-mask-1.html will-change-stacking-context-mask-1.html
+== will-change-stacking-context-mix-blend-mode-1.html will-change-stacking-context-mix-blend-mode-1.html
+== will-change-stacking-context-opacity-1.html will-change-stacking-context-opacity-1.html
+== will-change-stacking-context-perspective-1.html will-change-stacking-context-perspective-1.html
+== will-change-stacking-context-position-1.html will-change-stacking-context-position-1.html
+== will-change-stacking-context-transform-1.html will-change-stacking-context-transform-1.html
+== will-change-stacking-context-transform-style-1.html will-change-stacking-context-transform-style-1.html
+== will-change-stacking-context-z-index-1.html will-change-stacking-context-z-index-1.html
 fails pref(layout.css.contain.enabled,true) == will-change-fixpos-cb-contain-1.html will-change-fixpos-cb-contain-1.html # Bug 1342139
-fails == will-change-fixpos-cb-filter-1.html will-change-fixpos-cb-filter-1.html
+== will-change-fixpos-cb-filter-1.html will-change-fixpos-cb-filter-1.html
 == will-change-fixpos-cb-height-1.html will-change-fixpos-cb-height-1.html
-fails == will-change-fixpos-cb-perspective-1.html will-change-fixpos-cb-perspective-1.html
-fails == will-change-fixpos-cb-position-1.html will-change-fixpos-cb-position-1.html
-fails == will-change-fixpos-cb-transform-1.html will-change-fixpos-cb-transform-1.html
-fails == will-change-fixpos-cb-transform-style-1.html will-change-fixpos-cb-transform-style-1.html
+== will-change-fixpos-cb-perspective-1.html will-change-fixpos-cb-perspective-1.html
+== will-change-fixpos-cb-position-1.html will-change-fixpos-cb-position-1.html
+== will-change-fixpos-cb-transform-1.html will-change-fixpos-cb-transform-1.html
+== will-change-fixpos-cb-transform-style-1.html will-change-fixpos-cb-transform-style-1.html
--- a/layout/reftests/webkit-box/reftest-stylo.list
+++ b/layout/reftests/webkit-box/reftest-stylo.list
@@ -3,18 +3,18 @@
 # CSS properties. These tests require webkit prefix support to be enabled.
 default-preferences pref(layout.css.prefixes.webkit,true)
 
 # Tests for anonymous flex item formation inside of a "-webkit-box":
 # Note: some of these tests are marked as failing, because we don't match
 # WebKit/Blink on them.  (The reference case represents the WebKit/Blink
 # rendering.) We could probably make them pass by implementing some quirks, if
 # it turns out that the web depends on WebKit/Blink's behavior in these cases.
-fails == webkit-box-anon-flex-items-1a.html webkit-box-anon-flex-items-1a.html
-fails == webkit-box-anon-flex-items-1b.html webkit-box-anon-flex-items-1b.html
+== webkit-box-anon-flex-items-1a.html webkit-box-anon-flex-items-1a.html
+== webkit-box-anon-flex-items-1b.html webkit-box-anon-flex-items-1b.html
 == webkit-box-anon-flex-items-2.html webkit-box-anon-flex-items-2.html
 == webkit-box-anon-flex-items-3.html webkit-box-anon-flex-items-3.html
 
 # Tests for "-webkit-box" & "-webkit-inline-box" as display values:
 == webkit-display-values-1.html webkit-display-values-1.html
 
 # Tests for "-webkit-box-align" (cross-axis alignment):
 == webkit-box-align-horiz-1a.html webkit-box-align-horiz-1a.html
--- a/layout/reftests/writing-mode/reftest-stylo.list
+++ b/layout/reftests/writing-mode/reftest-stylo.list
@@ -43,18 +43,18 @@ fails == 1124636-2-fieldset-min-height.h
 == ua-style-sheet-margin-1.html ua-style-sheet-margin-1.html
 == ua-style-sheet-margin-2.html ua-style-sheet-margin-2.html
 == ua-style-sheet-margin-3.html ua-style-sheet-margin-3.html
 == ua-style-sheet-margin-4.html ua-style-sheet-margin-4.html
 == ua-style-sheet-margin-5.html ua-style-sheet-margin-5.html
 fails == ua-style-sheet-margin-6.html ua-style-sheet-margin-6.html
 fails == ua-style-sheet-margin-7.html ua-style-sheet-margin-7.html
 == ua-style-sheet-margin-8.html ua-style-sheet-margin-8.html
-fails == ua-style-sheet-margin-9.html ua-style-sheet-margin-9.html
-fails == ua-style-sheet-margin-10.html ua-style-sheet-margin-10.html
+== ua-style-sheet-margin-9.html ua-style-sheet-margin-9.html
+== ua-style-sheet-margin-10.html ua-style-sheet-margin-10.html
 == ua-style-sheet-margin-11.html ua-style-sheet-margin-11.html
 == ua-style-sheet-margin-12.html ua-style-sheet-margin-12.html
 == ua-style-sheet-margin-13.html ua-style-sheet-margin-13.html
 == ua-style-sheet-margin-14.html ua-style-sheet-margin-14.html
 == ua-style-sheet-border-1.html ua-style-sheet-border-1.html
 == ua-style-sheet-border-2.html ua-style-sheet-border-2.html
 == ua-style-sheet-border-3.html ua-style-sheet-border-3.html
 == ua-style-sheet-border-4.html ua-style-sheet-border-4.html
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -481,16 +481,28 @@ nscolor Gecko_GetLookAndFeelSystemColor(
 {
   bool useStandinsForNativeColors = aPresContext && !aPresContext->IsChrome();
   nscolor result;
   LookAndFeel::ColorID colorId = static_cast<LookAndFeel::ColorID>(aId);
   LookAndFeel::GetColor(colorId, useStandinsForNativeColors, &result);
   return result;
 }
 
+bool
+Gecko_MatchStringArgPseudo(RawGeckoElementBorrowed aElement,
+                           CSSPseudoClassType aType,
+                           const char16_t* aIdent,
+                           bool* aSetSlowSelectorFlag)
+{
+  EventStates dummyMask; // mask is never read because we pass aDependence=nullptr
+  return nsCSSRuleProcessor::StringPseudoMatches(aElement, aType, aIdent,
+                                                 aElement->OwnerDoc(), true,
+                                                 dummyMask, false, aSetSlowSelectorFlag, nullptr);
+}
+
 template <typename Implementor>
 static nsIAtom*
 AtomAttrValue(Implementor* aElement, nsIAtom* aName)
 {
   const nsAttrValue* attr = aElement->GetParsedAttr(aName);
   return attr ? attr->GetAtomValue() : nullptr;
 }
 
@@ -1103,16 +1115,36 @@ Gecko_EnsureStyleTransitionArrayLength(v
 
   base->EnsureLengthAtLeast(aLen);
 
   for (size_t i = oldLength; i < aLen; ++i) {
     (*base)[i].SetInitialValues();
   }
 }
 
+void
+Gecko_ClearWillChange(nsStyleDisplay* aDisplay, size_t aLength)
+{
+  aDisplay->mWillChange.Clear();
+  aDisplay->mWillChange.SetCapacity(aLength);
+}
+
+void
+Gecko_AppendWillChange(nsStyleDisplay* aDisplay, nsIAtom* aAtom)
+{
+  aDisplay->mWillChange.AppendElement(aAtom);
+}
+
+void
+Gecko_CopyWillChangeFrom(nsStyleDisplay* aDest, nsStyleDisplay* aSrc)
+{
+  aDest->mWillChange.Clear();
+  aDest->mWillChange.AppendElements(aSrc->mWillChange);
+}
+
 Keyframe*
 Gecko_AnimationAppendKeyframe(RawGeckoKeyframeListBorrowedMut aKeyframes,
                               float aOffset,
                               const nsTimingFunction* aTimingFunction)
 {
   Keyframe* keyframe = aKeyframes->AppendElement();
   keyframe->mOffset.emplace(aOffset);
   if (aTimingFunction &&
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -292,16 +292,20 @@ void Gecko_CopyCounterResetsFrom(nsStyle
 void Gecko_CopyCounterIncrementsFrom(nsStyleContent* content, const nsStyleContent* other);
 
 void Gecko_EnsureImageLayersLength(nsStyleImageLayers* layers, size_t len,
                                    nsStyleImageLayers::LayerType layer_type);
 
 void Gecko_EnsureStyleAnimationArrayLength(void* array, size_t len);
 void Gecko_EnsureStyleTransitionArrayLength(void* array, size_t len);
 
+void Gecko_ClearWillChange(nsStyleDisplay* display, size_t length);
+void Gecko_AppendWillChange(nsStyleDisplay* display, nsIAtom* atom);
+void Gecko_CopyWillChangeFrom(nsStyleDisplay* dest, nsStyleDisplay* src);
+
 mozilla::Keyframe* Gecko_AnimationAppendKeyframe(RawGeckoKeyframeListBorrowedMut keyframes,
                                                  float offset,
                                                  const nsTimingFunction* timingFunction);
 
 // Clean up pointer-based coordinates
 void Gecko_ResetStyleCoord(nsStyleUnit* unit, nsStyleUnion* value);
 
 // Set an nsStyleCoord to a computed `calc()` value
@@ -372,16 +376,21 @@ void Gecko_nsStyleFont_CopyLangFrom(nsSt
 
 const nsMediaFeature* Gecko_GetMediaFeatures();
 
 // We use an int32_t here instead of a LookAndFeel::ColorID
 // because forward-declaring a nested enum/struct is impossible
 nscolor Gecko_GetLookAndFeelSystemColor(int32_t color_id,
                                         RawGeckoPresContextBorrowed pres_context);
 
+bool Gecko_MatchStringArgPseudo(RawGeckoElementBorrowed element,
+                                mozilla::CSSPseudoClassType type,
+                                const char16_t* ident,
+                                bool* set_slow_selector);
+
 // Style-struct management.
 #define STYLE_STRUCT(name, checkdata_cb)                                       \
   void Gecko_Construct_Default_nsStyle##name(                                  \
     nsStyle##name* ptr,                                                        \
     RawGeckoPresContextBorrowed pres_context);                                 \
   void Gecko_CopyConstruct_nsStyle##name(nsStyle##name* ptr,                   \
                                          const nsStyle##name* other);          \
   void Gecko_Destroy_nsStyle##name(nsStyle##name* ptr);
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -143,17 +143,17 @@ ServoStyleSet::GetContext(nsIContent* aC
                           nsIAtom* aPseudoTag,
                           CSSPseudoElementType aPseudoType,
                           LazyComputeBehavior aMayCompute)
 {
   MOZ_ASSERT(aContent->IsElement());
   Element* element = aContent->AsElement();
 
 
-  ResolveMappedAttrDeclarationBlocks();
+  PreTraverseSync();
   RefPtr<ServoComputedValues> computedValues;
   if (aMayCompute == LazyComputeBehavior::Allow) {
     computedValues = ResolveStyleLazily(element, nullptr);
   } else {
     computedValues = ResolveServoStyle(element);
   }
 
   MOZ_ASSERT(computedValues);
@@ -185,19 +185,29 @@ ServoStyleSet::ResolveMappedAttrDeclarat
   if (nsHTMLStyleSheet* sheet = mPresContext->Document()->GetAttributeStyleSheet()) {
     sheet->CalculateMappedServoDeclarations();
   }
 
   mPresContext->Document()->ResolveScheduledSVGPresAttrs();
 }
 
 void
+ServoStyleSet::PreTraverseSync()
+{
+  ResolveMappedAttrDeclarationBlocks();
+
+  // This is lazily computed and pseudo matching needs to access
+  // it so force computation early.
+  mPresContext->Document()->GetDocumentState();
+}
+
+void
 ServoStyleSet::PreTraverse()
 {
-  ResolveMappedAttrDeclarationBlocks();
+  PreTraverseSync();
 
   // Process animation stuff that we should avoid doing during the parallel
   // traversal.
   mPresContext->EffectCompositor()->PreTraverse();
 }
 
 bool
 ServoStyleSet::PrepareAndTraverseSubtree(RawGeckoElementBorrowed aRoot,
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -281,16 +281,18 @@ private:
    * all style data or when shutting down the style set).
    */
   void ClearNonInheritingStyleContexts();
 
   /**
    * Perform processes that we should do before traversing.
    */
   void PreTraverse();
+  // Subset of the pre-traverse steps that involve syncing up data
+  void PreTraverseSync();
 
   already_AddRefed<ServoComputedValues> ResolveStyleLazily(dom::Element* aElement,
                                                            nsIAtom* aPseudoTag);
 
   nsPresContext* mPresContext;
   UniquePtr<RawServoStyleSet> mRawSet;
   EnumeratedArray<SheetType, SheetType::Count,
                   nsTArray<RefPtr<ServoStyleSheet>>> mSheets;
--- a/layout/style/nsCSSRuleProcessor.cpp
+++ b/layout/style/nsCSSRuleProcessor.cpp
@@ -1632,16 +1632,208 @@ StateSelectorMatches(Element* aElement,
                               aTreeMatchContext, aSelectorFlags, nullptr,
                               statesToCheck)) {
       return false;
     }
   }
   return true;
 }
 
+// Chooses the thread safe version in Servo mode, and
+// the non-thread safe one in Gecko mode. The non thread safe one does
+// some extra caching, and is preferred when possible.
+static inline bool
+IsSignificantChildMaybeThreadSafe(const nsIContent* aContent,
+                                  bool aTextIsSignificant,
+                                  bool aWhitespaceIsSignificant,
+                                  bool aIsGecko)
+{
+  if (aIsGecko) {
+    auto content = const_cast<nsIContent*>(aContent);
+    return IsSignificantChild(content, aTextIsSignificant, aWhitespaceIsSignificant);
+  } else {
+    // See bug 1349100 for optimizing this
+    return nsStyleUtil::ThreadSafeIsSignificantChild(aContent,
+                                                     aTextIsSignificant,
+                                                     aWhitespaceIsSignificant);
+  }
+}
+
+/* static */ bool
+nsCSSRuleProcessor::StringPseudoMatches(const mozilla::dom::Element* aElement,
+                                        CSSPseudoClassType aPseudo,
+                                        const char16_t* aString,
+                                        const nsIDocument* aDocument,
+                                        bool aForStyling,
+                                        EventStates aStateMask,
+                                        bool aIsGecko,
+                                        bool* aSetSlowSelectorFlag,
+                                        bool* const aDependence)
+{
+  MOZ_ASSERT(aSetSlowSelectorFlag);
+
+  switch (aPseudo) {
+    case CSSPseudoClassType::mozLocaleDir:
+      {
+        bool docIsRTL;
+        if (aIsGecko) {
+          auto doc = const_cast<nsIDocument*>(aDocument);
+          docIsRTL = doc->GetDocumentState()
+                        .HasState(NS_DOCUMENT_STATE_RTL_LOCALE);
+        } else {
+          docIsRTL = aDocument->ThreadSafeGetDocumentState()
+                              .HasState(NS_DOCUMENT_STATE_RTL_LOCALE);
+        }
+
+        nsDependentString dirString(aString);
+
+        if (dirString.EqualsLiteral("rtl")) {
+          if (!docIsRTL) {
+            return false;
+          }
+        } else if (dirString.EqualsLiteral("ltr")) {
+          if (docIsRTL) {
+            return false;
+          }
+        } else {
+          // Selectors specifying other directions never match.
+          return false;
+        }
+      }
+      break;
+
+    case CSSPseudoClassType::mozSystemMetric:
+      {
+        nsCOMPtr<nsIAtom> metric = NS_Atomize(aString);
+        if (!nsCSSRuleProcessor::HasSystemMetric(metric)) {
+          return false;
+        }
+      }
+      break;
+
+    case CSSPseudoClassType::mozEmptyExceptChildrenWithLocalname:
+      {
+        NS_ASSERTION(aString, "Must have string!");
+        const nsIContent *child = nullptr;
+        int32_t index = -1;
+
+        if (aForStyling) {
+          // FIXME:  This isn't sufficient to handle:
+          //   :-moz-empty-except-children-with-localname() + E
+          //   :-moz-empty-except-children-with-localname() ~ E
+          // because we don't know to restyle the grandparent of the
+          // inserted/removed element (as in bug 534804 for :empty).
+          *aSetSlowSelectorFlag = true;
+        }
+        do {
+          child = aElement->GetChildAt(++index);
+        } while (child &&
+                  (!IsSignificantChildMaybeThreadSafe(child, true, false, aIsGecko) ||
+                  (child->GetNameSpaceID() == aElement->GetNameSpaceID() &&
+                    child->NodeInfo()->NameAtom()->Equals(nsDependentString(aString)))));
+        if (child) {
+          return false;
+        }
+      }
+      break;
+
+    case CSSPseudoClassType::dir:
+      {
+        if (aDependence) {
+          EventStates states = sPseudoClassStateDependences[
+            static_cast<CSSPseudoClassTypeBase>(aPseudo)];
+          if (aStateMask.HasAtLeastOneOfStates(states)) {
+            *aDependence = true;
+            return false;
+          }
+        }
+
+        // If we only had to consider HTML, directionality would be
+        // exclusively LTR or RTL.
+        //
+        // However, in markup languages where there is no direction attribute
+        // we have to consider the possibility that neither dir(rtl) nor
+        // dir(ltr) matches.
+        EventStates state = aElement->StyleState();
+        nsDependentString dirString(aString);
+
+        if (dirString.EqualsLiteral("rtl")) {
+          if (!state.HasState(NS_EVENT_STATE_RTL)) {
+            return false;
+          }
+        } else if (dirString.EqualsLiteral("ltr")) {
+          if (!state.HasState(NS_EVENT_STATE_LTR)) {
+            return false;
+          }
+        } else {
+          // Selectors specifying other directions never match.
+          return false;
+        }
+      }
+      break;
+
+    case CSSPseudoClassType::lang:
+      {
+        NS_ASSERTION(aString, "null lang parameter");
+        if (!aString || !*aString) {
+          return false;
+        }
+
+        // We have to determine the language of the current element.  Since
+        // this is currently no property and since the language is inherited
+        // from the parent we have to be prepared to look at all parent
+        // nodes.  The language itself is encoded in the LANG attribute.
+        nsAutoString language;
+        if (aElement->GetLang(language)) {
+          if (!nsStyleUtil::DashMatchCompare(language,
+                                             nsDependentString(aString),
+                                             nsASCIICaseInsensitiveStringComparator())) {
+            return false;
+          }
+          // This pseudo-class matched; move on to the next thing
+          break;
+        }
+
+        if (aDocument) {
+          // Try to get the language from the HTTP header or if this
+          // is missing as well from the preferences.
+          // The content language can be a comma-separated list of
+          // language codes.
+          aDocument->GetContentLanguage(language);
+
+          nsDependentString langString(aString);
+          language.StripWhitespace();
+          int32_t begin = 0;
+          int32_t len = language.Length();
+          while (begin < len) {
+            int32_t end = language.FindChar(char16_t(','), begin);
+            if (end == kNotFound) {
+              end = len;
+            }
+            if (nsStyleUtil::DashMatchCompare(Substring(language, begin,
+                                                        end-begin),
+                                              langString,
+                                              nsASCIICaseInsensitiveStringComparator())) {
+              break;
+            }
+            begin = end + 1;
+          }
+          if (begin < len) {
+            // This pseudo-class matched
+            break;
+          }
+        }
+      }
+      return false;
+
+    default: MOZ_ASSERT_UNREACHABLE("Called StringPseudoMatches() with unknown string-like pseudo");
+  }
+  return true;
+}
+
 // |aDependence| has two functions:
 //  * when non-null, it indicates that we're processing a negation,
 //    which is done only when SelectorMatches calls itself recursively
 //  * what it points to should be set to true whenever a test is skipped
 //    because of aNodeMatchContent.mStateMask
 static bool SelectorMatches(Element* aElement,
                             nsCSSSelector* aSelector,
                             NodeMatchContext& aNodeMatchContext,
@@ -1771,96 +1963,16 @@ static bool SelectorMatches(Element* aEl
       break;
 
     case CSSPseudoClassType::mozOnlyWhitespace:
       if (!checkGenericEmptyMatches(aElement, aTreeMatchContext, false)) {
         return false;
       }
       break;
 
-    case CSSPseudoClassType::mozEmptyExceptChildrenWithLocalname:
-      {
-        NS_ASSERTION(pseudoClass->u.mString, "Must have string!");
-        nsIContent *child = nullptr;
-        int32_t index = -1;
-
-        if (aTreeMatchContext.mForStyling)
-          // FIXME:  This isn't sufficient to handle:
-          //   :-moz-empty-except-children-with-localname() + E
-          //   :-moz-empty-except-children-with-localname() ~ E
-          // because we don't know to restyle the grandparent of the
-          // inserted/removed element (as in bug 534804 for :empty).
-          aElement->SetFlags(NODE_HAS_SLOW_SELECTOR);
-        do {
-          child = aElement->GetChildAt(++index);
-        } while (child &&
-                  (!IsSignificantChild(child, true, false) ||
-                  (child->GetNameSpaceID() == aElement->GetNameSpaceID() &&
-                    child->NodeInfo()->NameAtom()->Equals(nsDependentString(pseudoClass->u.mString)))));
-        if (child != nullptr) {
-          return false;
-        }
-      }
-      break;
-
-    case CSSPseudoClassType::lang:
-      {
-        NS_ASSERTION(nullptr != pseudoClass->u.mString, "null lang parameter");
-        if (!pseudoClass->u.mString || !*pseudoClass->u.mString) {
-          return false;
-        }
-
-        // We have to determine the language of the current element.  Since
-        // this is currently no property and since the language is inherited
-        // from the parent we have to be prepared to look at all parent
-        // nodes.  The language itself is encoded in the LANG attribute.
-        nsAutoString language;
-        if (aElement->GetLang(language)) {
-          if (!nsStyleUtil::DashMatchCompare(language,
-                                              nsDependentString(pseudoClass->u.mString),
-                                              nsASCIICaseInsensitiveStringComparator())) {
-            return false;
-          }
-          // This pseudo-class matched; move on to the next thing
-          break;
-        }
-
-        nsIDocument* doc = aTreeMatchContext.mDocument;
-        if (doc) {
-          // Try to get the language from the HTTP header or if this
-          // is missing as well from the preferences.
-          // The content language can be a comma-separated list of
-          // language codes.
-          doc->GetContentLanguage(language);
-
-          nsDependentString langString(pseudoClass->u.mString);
-          language.StripWhitespace();
-          int32_t begin = 0;
-          int32_t len = language.Length();
-          while (begin < len) {
-            int32_t end = language.FindChar(char16_t(','), begin);
-            if (end == kNotFound) {
-              end = len;
-            }
-            if (nsStyleUtil::DashMatchCompare(Substring(language, begin,
-                                                        end-begin),
-                                              langString,
-                                              nsASCIICaseInsensitiveStringComparator())) {
-              break;
-            }
-            begin = end + 1;
-          }
-          if (begin < len) {
-            // This pseudo-class matched
-            break;
-          }
-        }
-      }
-      return false;
-
     case CSSPseudoClassType::mozBoundElement:
       if (aTreeMatchContext.mScopedRoot != aElement) {
         return false;
       }
       break;
 
     case CSSPseudoClassType::root:
       if (aElement != aElement->OwnerDoc()->GetRootElement()) {
@@ -1994,48 +2106,16 @@ static bool SelectorMatches(Element* aEl
       break;
 
     case CSSPseudoClassType::mozIsHTML:
       if (!aTreeMatchContext.mIsHTMLDocument || !aElement->IsHTMLElement()) {
         return false;
       }
       break;
 
-    case CSSPseudoClassType::mozSystemMetric:
-      {
-        nsCOMPtr<nsIAtom> metric = NS_Atomize(pseudoClass->u.mString);
-        if (!nsCSSRuleProcessor::HasSystemMetric(metric)) {
-          return false;
-        }
-      }
-      break;
-
-    case CSSPseudoClassType::mozLocaleDir:
-      {
-        bool docIsRTL =
-          aTreeMatchContext.mDocument->GetDocumentState().
-            HasState(NS_DOCUMENT_STATE_RTL_LOCALE);
-
-        nsDependentString dirString(pseudoClass->u.mString);
-
-        if (dirString.EqualsLiteral("rtl")) {
-          if (!docIsRTL) {
-            return false;
-          }
-        } else if (dirString.EqualsLiteral("ltr")) {
-          if (docIsRTL) {
-            return false;
-          }
-        } else {
-          // Selectors specifying other directions never match.
-          return false;
-        }
-      }
-      break;
-
     case CSSPseudoClassType::mozLWTheme:
       {
         if (aTreeMatchContext.mDocument->GetDocumentLWTheme() <=
               nsIDocument::Doc_Theme_None) {
           return false;
         }
       }
       break;
@@ -2060,51 +2140,16 @@ static bool SelectorMatches(Element* aEl
 
     case CSSPseudoClassType::mozWindowInactive:
       if (!aTreeMatchContext.mDocument->GetDocumentState().
               HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
         return false;
       }
       break;
 
-    case CSSPseudoClassType::dir:
-      {
-        if (aDependence) {
-          EventStates states = sPseudoClassStateDependences[
-            static_cast<CSSPseudoClassTypeBase>(pseudoClass->mType)];
-          if (aNodeMatchContext.mStateMask.HasAtLeastOneOfStates(states)) {
-            *aDependence = true;
-            return false;
-          }
-        }
-
-        // If we only had to consider HTML, directionality would be
-        // exclusively LTR or RTL.
-        //
-        // However, in markup languages where there is no direction attribute
-        // we have to consider the possibility that neither dir(rtl) nor
-        // dir(ltr) matches.
-        EventStates state = aElement->StyleState();
-        nsDependentString dirString(pseudoClass->u.mString);
-
-        if (dirString.EqualsLiteral("rtl")) {
-          if (!state.HasState(NS_EVENT_STATE_RTL)) {
-            return false;
-          }
-        } else if (dirString.EqualsLiteral("ltr")) {
-          if (!state.HasState(NS_EVENT_STATE_LTR)) {
-            return false;
-          }
-        } else {
-          // Selectors specifying other directions never match.
-          return false;
-        }
-      }
-      break;
-
     case CSSPseudoClassType::scope:
       if (aTreeMatchContext.mForScopedStyle) {
         if (aTreeMatchContext.mCurrentStyleScope) {
           // If mCurrentStyleScope is null, aElement must be the style
           // scope root.  This is because the PopStyleScopeForSelectorMatching
           // call in SelectorMatchesTree sets mCurrentStyleScope to null
           // as soon as we visit the style scope element, and we won't
           // progress further up the tree after this call to
@@ -2119,17 +2164,36 @@ static bool SelectorMatches(Element* aEl
       } else {
         if (aElement != aElement->OwnerDoc()->GetRootElement()) {
           return false;
         }
       }
       break;
 
     default:
-      MOZ_ASSERT(false, "How did that happen?");
+      {
+        MOZ_ASSERT(nsCSSPseudoClasses::HasStringArg(pseudoClass->mType));
+        bool setSlowSelectorFlag = false;
+        bool matched = nsCSSRuleProcessor::StringPseudoMatches(aElement,
+                                                               pseudoClass->mType,
+                                                               pseudoClass->u.mString,
+                                                               aTreeMatchContext.mDocument,
+                                                               aTreeMatchContext.mForStyling,
+                                                               aNodeMatchContext.mStateMask,
+                                                               true,
+                                                               &setSlowSelectorFlag,
+                                                               aDependence);
+        if (setSlowSelectorFlag) {
+          aElement->SetFlags(NODE_HAS_SLOW_SELECTOR);
+        }
+
+        if (!matched) {
+          return false;
+        }
+      }
     }
   }
 
   bool result = true;
   if (aSelector->mAttrList) {
     // test for attribute match
     if (!aElement->HasAttrs()) {
       // if no attributes on the content, no match
--- a/layout/style/nsCSSRuleProcessor.h
+++ b/layout/style/nsCSSRuleProcessor.h
@@ -34,16 +34,17 @@ struct TreeMatchContext;
 class nsCSSKeyframesRule;
 class nsCSSPageRule;
 class nsCSSFontFeatureValuesRule;
 class nsCSSCounterStyleRule;
 
 namespace mozilla {
 class CSSStyleSheet;
 enum class CSSPseudoElementType : uint8_t;
+enum class CSSPseudoClassType : uint8_t;
 namespace css {
 class DocumentRule;
 } // namespace css
 } // namespace mozilla
 
 /**
  * The CSS style rule processor provides a mechanism for sibling style
  * sheets to combine their rule processing in order to allow proper
@@ -127,16 +128,50 @@ public:
    * :visited and :link will match both visited and non-visited links,
    * as if aTreeMatchContext->mVisitedHandling were eLinksVisitedOrUnvisited.
    *
    * aSelector is restricted to not containing pseudo-elements.
    */
   static bool RestrictedSelectorMatches(mozilla::dom::Element* aElement,
                                         nsCSSSelector* aSelector,
                                         TreeMatchContext& aTreeMatchContext);
+  /**
+   * Checks if a function-like ident-containing pseudo (:pseudo(ident))
+   * matches a given element.
+   *
+   * Returns true if it parses and matches, Some(false) if it
+   * parses but does not match. Asserts if it fails to parse; only
+   * call this when you're sure it's a string-like pseudo.
+   *
+   * In Servo mode, please ensure that UpdatePossiblyStaleDocumentState()
+   * has been called first.
+   *
+   * @param aElement The element we are trying to match
+   * @param aPseudo The name of the pseudoselector
+   * @param aString The identifier inside the pseudoselector (cannot be null)
+   * @param aDocument The document
+   * @param aForStyling Is this matching operation for the creation of a style context?
+   *                    (For setting the slow selector flag)
+   * @param aStateMask Mask containing states which we should exclude.
+   *                   Ignored if aDependence is null
+   * @param aIsGecko Set if Gecko.
+   * @param aSetSlowSelectorFlag Outparam, set if the caller is
+   *                             supposed to set the slow selector flag.
+   * @param aDependence Pointer to be set to true if we ignored a state due to
+   *                    aStateMask. Can be null.
+   */
+  static bool StringPseudoMatches(const mozilla::dom::Element* aElement,
+                                  mozilla::CSSPseudoClassType aPseudo,
+                                  const char16_t* aString,
+                                  const nsIDocument* aDocument,
+                                  bool aForStyling,
+                                  mozilla::EventStates aStateMask,
+                                  bool aIsGecko,
+                                  bool* aSetSlowSelectorFlag,
+                                  bool* const aDependence = nullptr);
 
   // nsIStyleRuleProcessor
   virtual void RulesMatching(ElementRuleProcessorData* aData) override;
 
   virtual void RulesMatching(PseudoElementRuleProcessorData* aData) override;
 
   virtual void RulesMatching(AnonBoxRuleProcessorData* aData) override;
 
--- a/layout/style/nsStyleUtil.cpp
+++ b/layout/style/nsStyleUtil.cpp
@@ -736,16 +736,36 @@ nsStyleUtil::IsSignificantChild(nsIConte
     return true;
   }
 
   return aTextIsSignificant && isText && aChild->TextLength() != 0 &&
          (aWhitespaceIsSignificant ||
           !aChild->TextIsOnlyWhitespace());
 }
 
+/* static */ bool
+nsStyleUtil::ThreadSafeIsSignificantChild(const nsIContent* aChild,
+                                          bool aTextIsSignificant,
+                                          bool aWhitespaceIsSignificant)
+{
+  NS_ASSERTION(!aWhitespaceIsSignificant || aTextIsSignificant,
+               "Nonsensical arguments");
+
+  bool isText = aChild->IsNodeOfType(nsINode::eTEXT);
+
+  if (!isText && !aChild->IsNodeOfType(nsINode::eCOMMENT) &&
+      !aChild->IsNodeOfType(nsINode::ePROCESSING_INSTRUCTION)) {
+    return true;
+  }
+
+  return aTextIsSignificant && isText && aChild->TextLength() != 0 &&
+         (aWhitespaceIsSignificant ||
+          !aChild->ThreadSafeTextIsOnlyWhitespace());
+}
+
 // For a replaced element whose concrete object size is no larger than the
 // element's content-box, this method checks whether the given
 // "object-position" coordinate might cause overflow in its dimension.
 static bool
 ObjectPositionCoordMightCauseOverflow(const Position::Coord& aCoord)
 {
   // Any nonzero length in "object-position" can push us to overflow
   // (particularly if our concrete object size is exactly the same size as the
--- a/layout/style/nsStyleUtil.h
+++ b/layout/style/nsStyleUtil.h
@@ -133,16 +133,23 @@ public:
   static float ColorComponentToFloat(uint8_t aAlpha);
 
   /*
    * Does this child count as significant for selector matching?
    */
   static bool IsSignificantChild(nsIContent* aChild,
                                    bool aTextIsSignificant,
                                    bool aWhitespaceIsSignificant);
+
+  /*
+   * Thread-safe version of IsSignificantChild()
+   */
+  static bool ThreadSafeIsSignificantChild(const nsIContent* aChild,
+                                           bool aTextIsSignificant,
+                                           bool aWhitespaceIsSignificant);
   /**
    * Returns true if our object-fit & object-position properties might cause
    * a replaced element's contents to overflow its content-box (requiring
    * clipping), or false if we can be sure that this won't happen.
    *
    * This lets us optimize by skipping clipping when we can tell it's
    * unnecessary (particularly with the default values of these properties).
    *
--- a/layout/style/test/stylo-failures.md
+++ b/layout/style/test/stylo-failures.md
@@ -117,17 +117,17 @@ to mochitest command.
 * @counter-style support:
   * test_counter_descriptor_storage.html [1]
   * test_counter_style.html [1]
   * test_rule_insertion.html `@counter-style` [4]
   * ... `cjk-decimal` [1]
   * test_value_storage.html `symbols(` [30]
   * ... `list-style-type` [60]
   * ... `'list-style'` [30]
-  * ... `'content`: various value as list-style-type in counter functions [15]
+  * ... `'content`: various value as list-style-type in counter functions [13]
 * test_default_computed_style.html: support of getDefaultComputedStyle [1]
 * @font-face support bug 1290237
   * test_descriptor_storage.html [1]
   * test_descriptor_syntax_errors.html [1]
   * test_font_face_parser.html `@font-face` [447]
   * test_redundant_font_download.html [3]
 * @namespace support:
   * test_namespace_rule.html [17]
@@ -177,24 +177,16 @@ to mochitest command.
   * -moz-transform: need different parsing rules servo/servo#16003
     * test_inherit_computation.html `-moz-transform`: need different parsing rules [2]
     * test_inherit_storage.html `transform`: for -moz-transform [3]
     * test_initial_computation.html `-moz-transform`: need different parsing rules [4]
     * test_initial_storage.html `transform`: for -moz-transform [6]
     * test_value_storage.html `-moz-transform`: need different parsing rules [284]
   * test_variables.html `var(--var6)`: -x-system-font [1]
 * Unimplemented CSS properties:
-  * will-change longhand property servo/servo#15706
-    * test_change_hint_optimizations.html [1]
-    * test_compute_data_with_start_struct.html `will-change` [2]
-    * test_inherit_computation.html `will-change` [2]
-    * test_inherit_storage.html `will-change` [2]
-    * test_initial_computation.html `will-change` [4]
-    * test_initial_storage.html `will-change` [4]
-    * test_value_storage.html `will-change` [16]
   * contain longhand property servo/servo#15955
     * test_contain_formatting_context.html [1]
     * test_compute_data_with_start_struct.html `contain` [2]
     * test_inherit_computation.html `contain` [2]
     * test_inherit_storage.html `contain` [2]
     * test_initial_computation.html `contain` [4]
     * test_initial_storage.html `contain` [4]
     * test_value_storage.html `'contain'` [30]
@@ -268,18 +260,18 @@ to mochitest command.
     * ... `justify-` [6]
     * test_value_storage.html `align-` [57]
     * ... `justify-` [46]
 * @page support
   * test_bug887741_at-rules_in_declaration_lists.html [1]
   * test_page_parser.html [30]
   * test_rule_insertion.html `@page` [4]
 * Stylesheet cloning is somehow busted bug 1348481
-  * test_selectors.html `cloned correctly` [150]
-  * ... `matched clone` [195]
+  * test_selectors.html `cloned correctly` [155]
+  * ... `matched clone` [198]
 * Unsupported prefixed values
   * moz-prefixed gradient functions bug 1337655
     * test_value_storage.html `-moz-linear-gradient` [322]
     * ... `-moz-radial-gradient` [309]
     * ... `-moz-repeating-` [298]
   * webkit-prefixed gradient functions servo/servo#15441
     * test_value_storage.html `-webkit-gradient` [225]
     * ... `-webkit-linear-gradient` [40]
@@ -405,19 +397,18 @@ to mochitest command.
   * :-moz-locale-dir
     * test_selectors.html `:-moz-locale-dir` [15]
   * :-moz-lwtheme-*
     * test_selectors.html `:-moz-lwtheme` [3]
   * :-moz-window-inactive bug 1348489
     * test_selectors.html `:-moz-window-inactive` [2]
   * :-moz-{first,last}-node
     * test_selectors.html `:-moz-` [6]
-    * ... `unexpected rule index` [5]
   * :dir
-    * test_selectors.html `:dir` [10]
+    * test_selectors.html `:dir` [18]
 * issues arround font shorthand servo/servo#15032 servo/servo#15036
   * test_bug377947.html [1]
   * test_value_storage.html `'font'` [144]
   * test_shorthand_property_getters.html `font shorthand` [1]
   * test_system_font_serialization.html [10]
 * test_value_storage.html `font-size: calc(`: clamp negative value servo/servo#15296 [3]
 * rounding issue
   * test_value_storage.html `33.5833px` [2]
@@ -485,15 +476,15 @@ to mochitest command.
 ## Unknown / Unsure
 
 * test_additional_sheets.html: one sub-test cascade order is wrong [1]
 * test_flexbox_layout.html: resolved width doesn't match expectation [5]
 * test_selectors.html `:nth-child`: &lt;an+b&gt; parsing difference [14]
 * test_selectors_on_anonymous_content.html: xbl and :nth-child [1]
 * test_variables.html `url`: url in custom property [1]
 * test_pseudoelement_state.html: doesn't seem to work at all, but only range-thumb fails... [4]
-* test_parse_rule.html `rgb(0, 128, 0)`: color properties not getting computed [8]
+* test_parse_rule.html `rgb(0, 128, 0)`: color properties not getting computed [6]
 
 ## Ignore
 
 * Ignore for now since should be mostly identical to test_value_storage.html
   * test_value_cloning.html [*]
   * test_value_computation.html [*]
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -2387,22 +2387,23 @@ public class BrowserApp extends GeckoApp
      * @return true if we successfully switched to a tab, false otherwise.
      */
     private boolean maybeSwitchToTab(String url, EnumSet<OnUrlOpenListener.Flags> flags) {
         if (!flags.contains(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB)) {
             return false;
         }
 
         final Tabs tabs = Tabs.getInstance();
+        final Tab selectedTab = tabs.getSelectedTab();
         final Tab tab;
 
         if (AboutPages.isAboutReader(url)) {
-            tab = tabs.getFirstReaderTabForUrl(url, tabs.getSelectedTab().isPrivate());
+            tab = tabs.getFirstReaderTabForUrl(url, selectedTab.isPrivate(), selectedTab.getType());
         } else {
-            tab = tabs.getFirstTabForUrl(url, tabs.getSelectedTab().isPrivate());
+            tab = tabs.getFirstTabForUrl(url, selectedTab.isPrivate(), selectedTab.getType());
         }
 
         if (tab == null) {
             return false;
         }
 
         return maybeSwitchToTab(tab.getId());
     }
--- a/mobile/android/base/java/org/mozilla/gecko/PrivateTab.java
+++ b/mobile/android/base/java/org/mozilla/gecko/PrivateTab.java
@@ -6,18 +6,18 @@
 package org.mozilla.gecko;
 
 import android.content.Context;
 
 import org.json.JSONObject;
 import org.mozilla.gecko.db.BrowserDB;
 
 public class PrivateTab extends Tab {
-    public PrivateTab(Context context, int id, String url, boolean external, int parentId, String title) {
-        super(context, id, url, external, parentId, title);
+    public PrivateTab(Context context, int id, String url, boolean external, int parentId, String title, TabType type) {
+        super(context, id, url, external, parentId, title, type);
     }
 
     @Override
     protected void saveThumbnailToDB(final BrowserDB db) {}
 
     @Override
     public void setMetadata(JSONObject metadata) {}
 
--- a/mobile/android/base/java/org/mozilla/gecko/Tab.java
+++ b/mobile/android/base/java/org/mozilla/gecko/Tab.java
@@ -1,21 +1,18 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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;
 
 import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.Future;
-import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import org.json.JSONObject;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.URLMetadata;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.icons.IconCallback;
@@ -28,30 +25,30 @@ import org.mozilla.gecko.reader.ReadingL
 import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.widget.SiteLogins;
 
 import android.content.ContentResolver;
 import android.content.Context;
 import android.graphics.Bitmap;
-import android.graphics.Color;
 import android.graphics.drawable.BitmapDrawable;
 import android.os.Build;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 
 public class Tab {
     private static final String LOGTAG = "GeckoTab";
 
     private static Pattern sColorPattern;
     private final int mId;
+    private TabType mType;
     private final BrowserDB mDB;
     private long mLastUsed;
     private String mUrl;
     private String mBaseDomain;
     private String mUserRequested; // The original url requested. May be typed by the user or sent by an extneral app for example.
     private String mTitle;
     private Bitmap mFavicon;
     private String mFaviconUrl;
@@ -117,20 +114,21 @@ public class Tab {
 
     public enum ErrorType {
         CERT_ERROR,  // Pages with certificate problems
         BLOCKED,     // Pages blocked for phishing or malware warnings
         NET_ERROR,   // All other types of error
         NONE         // Non error pages
     }
 
-    public Tab(Context context, int id, String url, boolean external, int parentId, String title) {
+    public Tab(Context context, int id, String url, boolean external, int parentId, String title, TabType type) {
         mAppContext = context.getApplicationContext();
         mDB = BrowserDB.from(context);
         mId = id;
+        mType = type;
         mUrl = url;
         mBaseDomain = "";
         mUserRequested = "";
         mExternal = external;
         mParentId = parentId;
         mTitle = title == null ? "" : title;
         mSiteIdentity = new SiteIdentity();
         mContentType = "";
@@ -764,16 +762,26 @@ public class Tab {
     public boolean getDesktopMode() {
         return mDesktopMode;
     }
 
     public boolean isPrivate() {
         return false;
     }
 
+    public TabType getType() {
+        return mType;
+    }
+
+    public enum TabType {
+        BROWSING,
+        CUSTOMTAB,
+        WEBAPP
+    }
+
     /**
      * Sets the tab load progress to the given percentage.
      *
      * @param progressPercentage Percentage to set progress to (0-100)
      */
     void setLoadProgress(int progressPercentage) {
         mLoadProgress = progressPercentage;
     }
--- a/mobile/android/base/java/org/mozilla/gecko/Tabs.java
+++ b/mobile/android/base/java/org/mozilla/gecko/Tabs.java
@@ -8,22 +8,19 @@ package org.mozilla.gecko;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import android.support.annotation.Nullable;
-import org.json.JSONException;
-import org.json.JSONObject;
 
 import org.mozilla.gecko.annotation.JNITarget;
 import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.mozglue.SafeIntent;
 import org.mozilla.gecko.notifications.WhatsNewReceiver;
 import org.mozilla.gecko.reader.ReaderModeUtils;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
@@ -40,16 +37,18 @@ import android.database.sqlite.SQLiteExc
 import android.graphics.Color;
 import android.net.Uri;
 import android.os.Handler;
 import android.provider.Browser;
 import android.support.annotation.UiThread;
 import android.support.v4.content.ContextCompat;
 import android.util.Log;
 
+import static org.mozilla.gecko.Tab.TabType;
+
 public class Tabs implements BundleEventListener {
     private static final String LOGTAG = "GeckoTabs";
 
     // mOrder and mTabs are always of the same cardinality, and contain the same values.
     private volatile CopyOnWriteArrayList<Tab> mOrder = new CopyOnWriteArrayList<Tab>();
 
     // A cache that maps a tab ID to an mOrder tab position.  All access should be synchronized.
     private final TabPositionCache tabPositionCache = new TabPositionCache();
@@ -179,32 +178,33 @@ public class Tabs implements BundleEvent
         if (mBookmarksContentObserver != null) {
             // It's safe to use the db here since we aren't doing any I/O.
             final GeckoProfile profile = GeckoProfile.get(context);
             BrowserDB.from(profile).registerBookmarkObserver(getContentResolver(), mBookmarksContentObserver);
         }
     }
 
     /**
-     * Gets the tab count corresponding to the private state of the selected
-     * tab.
+     * Gets the tab count corresponding to the category and private state of the
+     * selected tab.
      *
      * If the selected tab is a non-private tab, this will return the number of
      * non-private tabs; likewise, if this is a private tab, this will return
      * the number of private tabs.
      *
      * @return the number of tabs in the current private state
      */
     public synchronized int getDisplayCount() {
         // Once mSelectedTab is non-null, it cannot be null for the remainder
         // of the object's lifetime.
         boolean getPrivate = mSelectedTab != null && mSelectedTab.isPrivate();
+        TabType type = mSelectedTab != null ? mSelectedTab.getType() : TabType.BROWSING;
         int count = 0;
         for (Tab tab : mOrder) {
-            if (tab.isPrivate() == getPrivate) {
+            if (tab.isPrivate() == getPrivate && tab.getType() == type) {
                 count++;
             }
         }
         return count;
     }
 
     public int isOpen(String url) {
         for (Tab tab : mOrder) {
@@ -228,19 +228,19 @@ public class Tabs implements BundleEvent
             };
 
             // It's safe to use the db here since we aren't doing any I/O.
             final GeckoProfile profile = GeckoProfile.get(mAppContext);
             BrowserDB.from(profile).registerBookmarkObserver(getContentResolver(), mBookmarksContentObserver);
         }
     }
 
-    private Tab addTab(int id, String url, boolean external, int parentId, String title, boolean isPrivate, int tabIndex) {
-        final Tab tab = isPrivate ? new PrivateTab(mAppContext, id, url, external, parentId, title) :
-                                    new Tab(mAppContext, id, url, external, parentId, title);
+    private Tab addTab(int id, String url, boolean external, int parentId, String title, boolean isPrivate, int tabIndex, TabType type) {
+        final Tab tab = isPrivate ? new PrivateTab(mAppContext, id, url, external, parentId, title, type) :
+                                    new Tab(mAppContext, id, url, external, parentId, title, type);
         synchronized (this) {
             lazyRegisterBookmarkObserver();
             mTabs.put(id, tab);
 
             if (tabIndex > -1) {
                 mOrder.add(tabIndex, tab);
                 if (tabPositionCache.mOrderPosition >= tabIndex) {
                     tabPositionCache.mTabId = INVALID_TAB_ID;
@@ -248,29 +248,30 @@ public class Tabs implements BundleEvent
             } else {
                 mOrder.add(tab);
             }
         }
 
         // Suppress the ADDED event to prevent animation of tabs created via session restore.
         if (mInitialTabsAdded) {
             notifyListeners(tab, TabEvents.ADDED,
-                    Integer.toString(getPrivacySpecificTabIndex(tabIndex, isPrivate)));
+                    Integer.toString(getPrivacySpecificTabIndex(tabIndex, isPrivate, type)));
         }
 
         return tab;
     }
 
-    // Return the index, among those tabs whose privacy setting matches isPrivate, of the tab at
-    // position index in mOrder.  Returns -1, for "new last tab", when index is -1.
-    private int getPrivacySpecificTabIndex(int index, boolean isPrivate) {
+    // Return the index, among those tabs of the chosen type whose privacy setting matches
+    // isPrivate, of the tab at position index in mOrder.  Returns -1, for "new last tab",
+    // when index is -1.
+    private int getPrivacySpecificTabIndex(int index, boolean isPrivate, TabType type) {
         int privacySpecificIndex = -1;
         for (int i = 0; i <= index; i++) {
             final Tab tab = mOrder.get(i);
-            if (tab.isPrivate() == isPrivate) {
+            if (tab.isPrivate() == isPrivate && tab.getType() == type) {
                 privacySpecificIndex++;
             }
         }
         return privacySpecificIndex;
     }
 
     public synchronized void removeTab(int id) {
         if (mTabs.containsKey(id)) {
@@ -320,33 +321,33 @@ public class Tabs implements BundleEvent
         selectTab(mOrder.get(mOrder.size() - 1).getId());
         return true;
     }
 
     private int getIndexOf(Tab tab) {
         return mOrder.lastIndexOf(tab);
     }
 
-    private Tab getNextTabFrom(Tab tab, boolean getPrivate) {
+    private Tab getNextTabFrom(Tab tab, boolean getPrivate, TabType type) {
         int numTabs = mOrder.size();
         int index = getIndexOf(tab);
         for (int i = index + 1; i < numTabs; i++) {
             Tab next = mOrder.get(i);
-            if (next.isPrivate() == getPrivate) {
+            if (next.isPrivate() == getPrivate && next.getType() == type) {
                 return next;
             }
         }
         return null;
     }
 
-    private Tab getPreviousTabFrom(Tab tab, boolean getPrivate) {
+    private Tab getPreviousTabFrom(Tab tab, boolean getPrivate, TabType type) {
         int index = getIndexOf(tab);
         for (int i = index - 1; i >= 0; i--) {
             Tab prev = mOrder.get(i);
-            if (prev.isPrivate() == getPrivate) {
+            if (prev.isPrivate() == getPrivate && prev.getType() == type) {
                 return prev;
             }
         }
         return null;
     }
 
     /**
      * Gets the selected tab.
@@ -437,40 +438,61 @@ public class Tabs implements BundleEvent
 
     /** Return the tab that will be selected by default after this one is closed */
     public Tab getNextTab(Tab tab) {
         Tab selectedTab = getSelectedTab();
         if (selectedTab != tab)
             return selectedTab;
 
         boolean getPrivate = tab.isPrivate();
-        Tab nextTab = getNextTabFrom(tab, getPrivate);
+        TabType type = tab.getType();
+        Tab nextTab = getNextTabFrom(tab, getPrivate, type);
         if (nextTab == null)
-            nextTab = getPreviousTabFrom(tab, getPrivate);
+            nextTab = getPreviousTabFrom(tab, getPrivate, type);
         if (nextTab == null && getPrivate) {
-            // If there are no private tabs remaining, get the last normal tab
-            Tab lastTab = mOrder.get(mOrder.size() - 1);
-            if (!lastTab.isPrivate()) {
-                nextTab = lastTab;
-            } else {
-                nextTab = getPreviousTabFrom(lastTab, false);
-            }
+            // If there are no private tabs remaining, get the last normal tab.
+            nextTab = getFallbackNextTab(type);
+        }
+        if (nextTab == null && type != TabType.BROWSING) {
+            // If there are no non-private tabs of the same type remaining,
+            // fall back to TabType.BROWSING.
+            nextTab = getFallbackNextTab(TabType.BROWSING);
         }
 
         Tab parent = getTab(tab.getParentId());
         if (parent != null) {
             // If the next tab is a sibling, switch to it. Otherwise go back to the parent.
             if (nextTab != null && nextTab.getParentId() == tab.getParentId())
                 return nextTab;
             else
                 return parent;
         }
         return nextTab;
     }
 
+    /**
+     * Normally, {@link #getNextTab(Tab)} will attempt to find a tab of the same privacy mode and
+     * {@link TabType} as the currently selected tab. If no such tab exists, we will first fall back
+     * to non-private tabs if the current tab is a private tab. If we can't find any non-private
+     * tabs of the same type, we then start looking for any non-private {@link TabType#BROWSING} tabs.
+     *
+     * @param type The {@link TabType} of tab to be searched.
+     * @return A non-private tab of the type specified or null if none could be found.
+     */
+    private Tab getFallbackNextTab(TabType type) {
+        Tab nextTab;
+        Tab lastTab = mOrder.get(mOrder.size() - 1);
+        if (!lastTab.isPrivate() && lastTab.getType() == type) {
+            nextTab = lastTab;
+        } else {
+            nextTab = getPreviousTabFrom(lastTab, false, type);
+        }
+        return nextTab;
+    }
+
     public Iterable<Tab> getTabsInOrder() {
         return mOrder;
     }
 
     /**
      * @return the current GeckoApp instance, or throws if
      *         we aren't correctly initialized.
      */
@@ -533,17 +555,18 @@ public class Tabs implements BundleEvent
                     // Tab was already closed; abort
                     return;
                 }
             } else {
                 tab = addTab(id, url, message.getBoolean("external"),
                                       message.getInt("parentId"),
                                       message.getString("title"),
                                       message.getBoolean("isPrivate"),
-                                      message.getInt("tabIndex"));
+                                      message.getInt("tabIndex"),
+                                      TabType.valueOf(message.getString("tabType")));
                 // If we added the tab as a stub, we should have already
                 // selected it, so ignore this flag for stubbed tabs.
                 if (message.getBoolean("selected"))
                     selectTab(id);
             }
 
             if (message.getBoolean("delayLoad"))
                 tab.setState(Tab.STATE_DELAYED);
@@ -796,38 +819,39 @@ public class Tabs implements BundleEvent
 
     /**
      * Looks for an open tab with the given URL.
      * @param url       the URL of the tab we're looking for
      *
      * @return first Tab with the given URL, or null if there is no such tab.
      */
     public Tab getFirstTabForUrl(String url) {
-        return getFirstTabForUrlHelper(url, null);
+        return getFirstTabForUrlHelper(url, null, TabType.BROWSING);
     }
 
     /**
      * Looks for an open tab with the given URL and private state.
      * @param url       the URL of the tab we're looking for
      * @param isPrivate if true, only look for tabs that are private. if false,
      *                  only look for tabs that are non-private.
+     * @param type      the type of the tab we're looking for
      *
      * @return first Tab with the given URL, or null if there is no such tab.
      */
-    public Tab getFirstTabForUrl(String url, boolean isPrivate) {
-        return getFirstTabForUrlHelper(url, isPrivate);
+    public Tab getFirstTabForUrl(String url, boolean isPrivate, TabType type) {
+        return getFirstTabForUrlHelper(url, isPrivate, type);
     }
 
-    private Tab getFirstTabForUrlHelper(String url, Boolean isPrivate) {
+    private Tab getFirstTabForUrlHelper(String url, Boolean isPrivate, TabType type) {
         if (url == null) {
             return null;
         }
 
         for (Tab tab : mOrder) {
-            if (isPrivate != null && isPrivate != tab.isPrivate()) {
+            if (isPrivate != null && isPrivate != tab.isPrivate() || type != tab.getType()) {
                 continue;
             }
             if (url.equals(tab.getURL())) {
                 return tab;
             }
         }
 
         return null;
@@ -838,29 +862,31 @@ public class Tabs implements BundleEvent
      * state.
      *
      * @param url
      *            The URL of the tab we're looking for. The url parameter can be
      *            the actual article URL or the reader mode article URL.
      * @param isPrivate
      *            If true, only look for tabs that are private. If false, only
      *            look for tabs that are not private.
+     * @param type
+     *            The type of the tab we're looking for.
      *
      * @return The first Tab with the given URL, or null if there is no such
      *         tab.
      */
-    public Tab getFirstReaderTabForUrl(String url, boolean isPrivate) {
+    public Tab getFirstReaderTabForUrl(String url, boolean isPrivate, TabType type) {
         if (url == null) {
             return null;
         }
 
         url = ReaderModeUtils.stripAboutReaderUrl(url);
 
         for (Tab tab : mOrder) {
-            if (isPrivate != tab.isPrivate()) {
+            if (isPrivate != tab.isPrivate() || type != tab.getType()) {
                 continue;
             }
             String tabUrl = tab.getURL();
             if (AboutPages.isAboutReader(tabUrl)) {
                 tabUrl = ReaderModeUtils.stripAboutReaderUrl(tabUrl);
                 if (url.equals(tabUrl)) {
                     return tab;
                 }
@@ -868,27 +894,27 @@ public class Tabs implements BundleEvent
         }
 
         return null;
     }
 
     /**
      * Loads a tab with the given URL in the currently selected tab.
      *
-     * @param url URL of page to load, or search term used if searchEngine is given
+     * @param url URL of page to load
      */
     @RobocopTarget
     public Tab loadUrl(String url) {
         return loadUrl(url, LOADURL_NONE);
     }
 
     /**
      * Loads a tab with the given URL.
      *
-     * @param url   URL of page to load, or search term used if searchEngine is given
+     * @param url   URL of page to load
      * @param flags flags used to load tab
      *
      * @return      the Tab if a new one was created; null otherwise
      */
     @RobocopTarget
     public Tab loadUrl(String url, int flags) {
         return loadUrl(url, null, -1, null, flags);
     }
@@ -932,25 +958,26 @@ public class Tabs implements BundleEvent
         boolean background = delayLoad || (flags & LOADURL_BACKGROUND) != 0;
 
         boolean isPrivate = (flags & LOADURL_PRIVATE) != 0;
         boolean userEntered = (flags & LOADURL_USER_ENTERED) != 0;
         boolean desktopMode = (flags & LOADURL_DESKTOP) != 0;
         boolean external = (flags & LOADURL_EXTERNAL) != 0;
         final boolean isFirstShownAfterActivityUnhidden = (flags & LOADURL_FIRST_AFTER_ACTIVITY_UNHIDDEN) != 0;
         final boolean customTab = (flags & LOADURL_CUSTOMTAB) != 0;
+        final TabType type = customTab ? TabType.CUSTOMTAB : TabType.BROWSING;
 
         data.putString("url", url);
         data.putString("engine", searchEngine);
         data.putInt("parentId", parentId);
         data.putBoolean("userEntered", userEntered);
         data.putBoolean("isPrivate", isPrivate);
         data.putBoolean("pinned", (flags & LOADURL_PINNED) != 0);
         data.putBoolean("desktopMode", desktopMode);
-        data.putBoolean("customTab", customTab);
+        data.putString("tabType", type.name());
 
         final boolean needsNewTab;
         final String applicationId = (intent == null) ? null :
                 intent.getStringExtra(Browser.EXTRA_APPLICATION_ID);
         if (applicationId == null) {
             needsNewTab = (flags & LOADURL_NEW_TAB) != 0;
         } else {
             // If you modify this code, be careful that intent != null.
@@ -989,17 +1016,17 @@ public class Tabs implements BundleEvent
             // The URL is updated for the tab once Gecko responds with the
             // Tab:Added data. We can preliminarily set the tab's URL as
             // long as it's a valid URI.
             String tabUrl = (url != null && Uri.parse(url).getScheme() != null) ? url : null;
 
             // Add the new tab to the end of the tab order.
             final int tabIndex = -1;
 
-            tabToSelect = addTab(tabId, tabUrl, external, parentId, url, isPrivate, tabIndex);
+            tabToSelect = addTab(tabId, tabUrl, external, parentId, url, isPrivate, tabIndex, type);
             tabToSelect.setDesktopMode(desktopMode);
             tabToSelect.setApplicationId(applicationId);
             if (isFirstShownAfterActivityUnhidden) {
                 // We just opened Firefox so we want to show
                 // the toolbar but not animate it to avoid jank.
                 tabToSelect.setShouldShowToolbarWithoutAnimationOnFirstSelection(true);
             }
         }
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
@@ -33,16 +33,18 @@ import android.widget.ImageButton;
 import android.widget.ProgressBar;
 
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.GeckoMenuInflater;
+import org.mozilla.gecko.Telemetry;
+import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.util.ColorUtil;
 import org.mozilla.gecko.widget.GeckoPopupMenu;
 
 import java.util.List;
 
 import static android.support.customtabs.CustomTabsIntent.EXTRA_TOOLBAR_COLOR;
 
 public class CustomTabsActivity extends GeckoApp implements Tabs.OnTabsChangedListener {
@@ -64,16 +66,17 @@ public class CustomTabsActivity extends 
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         if (savedInstanceState != null) {
             toolbarColor = savedInstanceState.getInt(SAVED_TOOLBAR_COLOR, DEFAULT_ACTION_BAR_COLOR);
         } else {
+            Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "customtab");
             toolbarColor = getIntent().getIntExtra(EXTRA_TOOLBAR_COLOR, DEFAULT_ACTION_BAR_COLOR);
         }
 
         // Translucent color does not make sense for toolbar color. Ensure it is 0xFF.
         toolbarColor = 0xFF000000 | toolbarColor;
 
         setThemeFromToolbarColor();
 
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/GeckoCustomTabsService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/GeckoCustomTabsService.java
@@ -10,16 +10,18 @@ import android.net.Uri;
 import android.os.Bundle;
 import android.support.customtabs.CustomTabsService;
 import android.support.customtabs.CustomTabsSessionToken;
 import android.util.Log;
 
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.GeckoService;
 import org.mozilla.gecko.GeckoThread;
+import org.mozilla.gecko.Telemetry;
+import org.mozilla.gecko.TelemetryContract;
 
 import java.util.List;
 
 /**
  * Custom tabs service external, third-party apps connect to.
  */
 public class GeckoCustomTabsService extends CustomTabsService {
     private static final String LOGTAG = "GeckoCustomTabsService";
@@ -30,16 +32,19 @@ public class GeckoCustomTabsService exte
     protected boolean updateVisuals(CustomTabsSessionToken sessionToken, Bundle bundle) {
         Log.v(LOGTAG, "updateVisuals()");
 
         return false;
     }
 
     @Override
     protected boolean warmup(long flags) {
+
+        Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.SERVICE, "customtab-warmup");
+
         if (DEBUG) {
             Log.v(LOGTAG, "warming up...");
         }
 
         if (GeckoThread.isRunning()) {
             return true;
         }
 
--- a/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunPager.java
+++ b/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunPager.java
@@ -4,38 +4,39 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.firstrun;
 
 import android.content.Context;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.FragmentPagerAdapter;
-import android.support.v4.view.ViewPager;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
 import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 
+import com.booking.rtlviewpager.RtlViewPager;
+
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.home.HomePager.Decor;
 import org.mozilla.gecko.home.TabMenuStrip;
 import org.mozilla.gecko.restrictions.Restrictions;
 
 import java.util.List;
 
 /**
  * ViewPager containing for our first run pages.
  *
  * @see FirstrunPanel for the first run pages that are used in this pager.
  */
-public class FirstrunPager extends ViewPager {
+public class FirstrunPager extends RtlViewPager {
 
     private Context context;
     protected FirstrunPanel.PagerNavigation pagerNavigation;
     private Decor mDecor;
 
     public FirstrunPager(Context context) {
         this(context, null);
     }
@@ -43,17 +44,17 @@ public class FirstrunPager extends ViewP
     public FirstrunPager(Context context, AttributeSet attrs) {
         super(context, attrs);
         this.context = context;
     }
 
     @Override
     public void addView(View child, int index, ViewGroup.LayoutParams params) {
         if (child instanceof Decor) {
-            ((ViewPager.LayoutParams) params).isDecor = true;
+            ((RtlViewPager.LayoutParams) params).isDecor = true;
             mDecor = (Decor) child;
             mDecor.setOnTitleClickListener(new TabMenuStrip.OnTitleClickListener() {
                 @Override
                 public void onTitleClicked(int index) {
                     setCurrentItem(index, true);
                 }
             });
         }
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomePager.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomePager.java
@@ -23,23 +23,24 @@ import org.mozilla.gecko.util.ThreadUtil
 import android.content.Context;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.LoaderManager;
 import android.support.v4.app.LoaderManager.LoaderCallbacks;
 import android.support.v4.content.Loader;
-import android.support.v4.view.ViewPager;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 
-public class HomePager extends ViewPager implements HomeScreen {
+import com.booking.rtlviewpager.RtlViewPager;
+
+public class HomePager extends RtlViewPager implements HomeScreen {
 
     @Override
     public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
         return super.requestFocus(direction, previouslyFocusedRect);
     }
 
     private static final int LOADER_ID_CONFIG = 0;
 
@@ -167,25 +168,25 @@ public class HomePager extends ViewPager
         //  We can call HomePager.requestFocus to steal focus from the URL bar and drop the soft
         //  keyboard. However, if there are no focusable views (e.g. an empty reading list), the
         //  URL bar will be refocused. Therefore, we make the HomePager container focusable to
         //  ensure there is always a focusable view. This would ordinarily be done via an XML
         //  attribute, but it is not working properly.
         setFocusableInTouchMode(true);
 
         mOriginalBackground = getBackground();
-        setOnPageChangeListener(new PageChangeListener());
+        addOnPageChangeListener(new PageChangeListener());
 
         mLoadState = LoadState.UNLOADED;
     }
 
     @Override
     public void addView(View child, int index, ViewGroup.LayoutParams params) {
         if (child instanceof Decor) {
-            ((ViewPager.LayoutParams) params).isDecor = true;
+            ((RtlViewPager.LayoutParams) params).isDecor = true;
             mDecor = (Decor) child;
             mTabStrip = child;
 
             mDecor.setOnTitleClickListener(new TabMenuStrip.OnTitleClickListener() {
                 @Override
                 public void onTitleClicked(int index) {
                     setCurrentItem(index, true);
                 }
@@ -514,17 +515,17 @@ public class HomePager extends ViewPager
         }
 
         @Override
         public void onLoaderReset(Loader<HomeConfig.State> loader) {
             mLoadState = LoadState.UNLOADED;
         }
     }
 
-    private class PageChangeListener implements ViewPager.OnPageChangeListener {
+    private class PageChangeListener implements RtlViewPager.OnPageChangeListener {
         @Override
         public void onPageSelected(int position) {
             notifyPanelSelected(position);
 
             if (mHomeBanner != null) {
                 mHomeBanner.setActive(position == mDefaultPageIndex);
             }
 
--- a/mobile/android/base/java/org/mozilla/gecko/home/TwoLinePageRow.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/TwoLinePageRow.java
@@ -200,22 +200,23 @@ public class TwoLinePageRow extends Line
     /**
      * Replaces the page URL with "Switch to tab" if there is already a tab open with that URL.
      * Only looks for tabs that are either private or non-private, depending on the current
      * selected tab.
      */
     protected void updateDisplayedUrl() {
         final Tab selectedTab = Tabs.getInstance().getSelectedTab();
         final boolean isPrivate = (selectedTab != null) && (selectedTab.isPrivate());
+        final Tab.TabType type = selectedTab != null ? selectedTab.getType() : Tab.TabType.BROWSING;
 
         // We always want to display the underlying page url, however for readermode pages
         // we navigate to the about:reader equivalent, hence we need to use that url when finding
         // existing tabs
         final String navigationUrl = mHasReaderCacheItem ? ReaderModeUtils.getAboutReaderForUrl(mPageUrl) : mPageUrl;
-        Tab tab = Tabs.getInstance().getFirstTabForUrl(navigationUrl, isPrivate);
+        Tab tab = Tabs.getInstance().getFirstTabForUrl(navigationUrl, isPrivate, type);
 
 
         if (!mShowIcons || tab == null) {
             setUrl(mPageUrl);
             setSwitchToTabIcon(NO_ICON);
         } else {
             setUrl(R.string.switch_to_tab);
             setSwitchToTabIcon(R.drawable.ic_url_bar_tab);
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/CirclePageIndicator.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/CirclePageIndicator.java
@@ -381,23 +381,23 @@ public class CirclePageIndicator
         return true;
     }
 
     public void setViewPager(ViewPager view) {
         if (mViewPager == view) {
             return;
         }
         if (mViewPager != null) {
-            mViewPager.setOnPageChangeListener(null);
+            mViewPager.removeOnPageChangeListener(this);
         }
         if (view.getAdapter() == null) {
             throw new IllegalStateException("ViewPager does not have adapter instance.");
         }
         mViewPager = view;
-        mViewPager.setOnPageChangeListener(this);
+        mViewPager.addOnPageChangeListener(this);
         invalidate();
     }
 
     public void setViewPager(ViewPager view, int initialPosition) {
         setViewPager(view);
         setCurrentItem(initialPosition);
     }
 
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabStrip.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabStrip.java
@@ -19,38 +19,43 @@ import android.view.ViewTreeObserver;
 
 import org.mozilla.gecko.BrowserApp.TabStripInterface;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.widget.themed.ThemedImageButton;
 import org.mozilla.gecko.widget.themed.ThemedLinearLayout;
 
+import static org.mozilla.gecko.Tab.TabType;
+
 public class TabStrip extends ThemedLinearLayout
                       implements TabStripInterface {
     private static final String LOGTAG = "GeckoTabStrip";
 
     private final TabStripView tabStripView;
     private final ThemedImageButton addTabButton;
 
     private final TabsListener tabsListener;
     private OnTabAddedOrRemovedListener tabChangedListener;
 
     // True when the tab strip isn't visible to the user due to something being drawn over it.
     private boolean tabStripIsCovered;
     private boolean tabsNeedUpdating;
+    private final TabType type;
 
     public TabStrip(Context context) {
         this(context, null);
     }
 
     public TabStrip(Context context, AttributeSet attrs) {
         super(context, attrs);
         setOrientation(HORIZONTAL);
 
+        type = TabType.BROWSING;
+
         LayoutInflater.from(context).inflate(R.layout.tab_strip_inner, this);
         tabStripView = (TabStripView) findViewById(R.id.tab_strip);
 
         addTabButton = (ThemedImageButton) findViewById(R.id.tablet_add_tab);
         addTabButton.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 final Tabs tabs = Tabs.getInstance();
@@ -108,16 +113,20 @@ public class TabStrip extends ThemedLine
 
     public void setOnTabChangedListener(OnTabAddedOrRemovedListener listener) {
         tabChangedListener = listener;
     }
 
     private class TabsListener implements Tabs.OnTabsChangedListener {
         @Override
         public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
+            if (msg != Tabs.TabEvents.RESTORED && tab.getType() != type) {
+                return;
+            }
+
             switch (msg) {
                 case RESTORED:
                     tabStripView.restoreTabs();
                     break;
 
                 case ADDED:
                     final int tabIndex = Integer.parseInt(data);
                     tabStripView.addTab(tab, tabIndex);
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayout.java
@@ -1,48 +1,51 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
 /* 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.tabs;
 
-import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.widget.RecyclerViewClickSupport;
 
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.Button;
 
 import java.util.ArrayList;
 
+import static org.mozilla.gecko.Tab.TabType;
+
 public abstract class TabsLayout extends RecyclerView
         implements TabsPanel.TabsLayout,
         Tabs.OnTabsChangedListener,
         RecyclerViewClickSupport.OnItemClickListener,
         TabsTouchHelperCallback.DismissListener,
         TabsTouchHelperCallback.DragListener {
 
     private static final String LOGTAG = "Gecko" + TabsLayout.class.getSimpleName();
 
     private final boolean isPrivate;
+    private final TabType type;
     private TabsPanel tabsPanel;
     private final TabsLayoutAdapter tabsAdapter;
 
     public TabsLayout(Context context, AttributeSet attrs, int itemViewLayoutResId) {
         super(context, attrs);
 
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabsLayout);
         isPrivate = (a.getInt(R.styleable.TabsLayout_tabs, 0x0) == 1);
+        type = TabType.BROWSING;
         a.recycle();
 
         tabsAdapter = new TabsLayoutAdapter(context, itemViewLayoutResId, isPrivate,
                 /* close on click listener */
                 new Button.OnClickListener() {
                     @Override
                     public void onClick(View v) {
                         // The view here is the close button, which has a reference
@@ -91,16 +94,20 @@ public abstract class TabsLayout extends
     }
 
     protected void autoHidePanel() {
         tabsPanel.autoHidePanel();
     }
 
     @Override
     public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
+        if (msg != Tabs.TabEvents.RESTORED && tab.getType() != type) {
+            return;
+        }
+
         switch (msg) {
             case ADDED:
                 final int tabIndex = Integer.parseInt(data);
                 tabsAdapter.notifyTabInserted(tab, tabIndex);
                 if (addAtIndexRequiresScroll(tabIndex)) {
                     // (The SELECTED tab is updated *after* this call to ADDED, so don't just call
                     // updateSelectedPosition().)
                     scrollToPosition(tabIndex);
@@ -169,17 +176,17 @@ public abstract class TabsLayout extends
 
     private void refreshTabsData() {
         // Store a different copy of the tabs, so that we don't have to worry about
         // accidentally updating it on the wrong thread.
         final ArrayList<Tab> tabData = new ArrayList<>();
         final Iterable<Tab> allTabs = Tabs.getInstance().getTabsInOrder();
 
         for (final Tab tab : allTabs) {
-            if (tab.isPrivate() == isPrivate) {
+            if (tab.isPrivate() == isPrivate && tab.getType() == type) {
                 tabData.add(tab);
             }
         }
 
         tabsAdapter.setTabs(tabData);
         updateSelectedPosition();
     }
 
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -993,16 +993,18 @@ gbjar.extra_jars += [CONFIG['ANDROID_REC
 gbjar.extra_jars += [CONFIG['ANDROID_CUSTOMTABS_AAR_LIB']]
 gbjar.extra_jars += [CONFIG['ANDROID_PALETTE_V7_AAR_LIB']]
 
 gbjar.javac_flags += ['-Xlint:all,-deprecation,-fallthrough', '-J-Xmx512m', '-J-Xms128m']
 
 # gecko-thirdparty is a good place to put small independent libraries
 gtjar = add_java_jar('gecko-thirdparty')
 gtjar.sources += [ thirdparty_source_dir + f for f in [
+    'com/booking/rtlviewpager/PagerAdapterWrapper.java',
+    'com/booking/rtlviewpager/RtlViewPager.java',
     'com/jakewharton/disklrucache/DiskLruCache.java',
     'com/jakewharton/disklrucache/StrictLineReader.java',
     'com/jakewharton/disklrucache/Util.java',
     'com/squareup/leakcanary/LeakCanary.java',
     'com/squareup/leakcanary/RefWatcher.java',
     'com/squareup/picasso/Action.java',
     'com/squareup/picasso/AssetBitmapHunter.java',
     'com/squareup/picasso/BitmapHunter.java',
--- a/mobile/android/base/resources/layout/activity_stream_main_toppanel.xml
+++ b/mobile/android/base/resources/layout/activity_stream_main_toppanel.xml
@@ -1,17 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical">
 
-    <android.support.v4.view.ViewPager
+    <com.booking.rtlviewpager.RtlViewPager
         android:layout_marginTop="10dp"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:id="@+id/topsites_pager"
         android:contentDescription="@string/activity_stream_topsites" />
 
     <org.mozilla.gecko.home.activitystream.topsites.CirclePageIndicator
         android:id="@+id/topsites_indicator"
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -1865,17 +1865,17 @@ var BrowserApp = {
           selected: ("selected" in data) ? data.selected : !delayLoad,
           parentId: ("parentId" in data) ? data.parentId : -1,
           flags: flags,
           tabID: data.tabID,
           isPrivate: (data.isPrivate === true),
           pinned: (data.pinned === true),
           delayLoad: (delayLoad === true),
           desktopMode: (data.desktopMode === true),
-          customTab: ("customTab" in data) ? data.customTab : false
+          tabType: ("tabType" in data) ? data.tabType : "BROWSING"
         };
 
         params.userRequested = url;
 
         if (data.engine) {
           let engine = Services.search.getEngineByName(data.engine);
           if (engine) {
             let submission = engine.getSubmission(url);
@@ -3388,18 +3388,18 @@ nsBrowserAccess.prototype = {
     // along with other OPEN_ values that create a new tab.
     let newTab = (aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW ||
                   aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB ||
                   aWhere == Ci.nsIBrowserDOMWindow.OPEN_SWITCHTAB);
     let isPrivate = false;
 
     if (aOpener != null) {
       let parent = BrowserApp.getTabForWindow(aOpener.top);
-      if ((parent != null) && ("isCustomTab" in parent)) {
-        newTab = newTab && !parent.isCustomTab;
+      if (parent != null) {
+        newTab = newTab && parent.tabType != "CUSTOMTAB";
       }
     }
 
     if (newTab) {
       let parentId = -1;
       if (!isExternal && aOpener) {
         let parent = BrowserApp.getTabForWindow(aOpener.top);
         if (parent) {
@@ -3558,16 +3558,19 @@ Tab.prototype = {
 
     // When the tab is stubbed from Java, there's a window between the stub
     // creation and the tab creation in Gecko where the stub could be removed
     // or the selected tab can change (which is easiest to hit during startup).
     // To prevent these races, we need to differentiate between tab stubs from
     // Java and new tabs from Gecko.
     let stub = false;
 
+    // The authoritative list of possible tab types is the TabType enum in Tab.java.
+    this.type = "tabType" in aParams ? aParams.tabType : "BROWSING";
+
     if (!aParams.zombifying) {
       if ("tabID" in aParams) {
         this.id = aParams.tabID;
         stub = true;
       } else {
         let jenv = JNI.GetForThread();
         let jTabs = JNI.LoadClass(jenv, "org.mozilla.gecko.Tabs", {
           static_methods: [
@@ -3580,16 +3583,17 @@ Tab.prototype = {
 
       this.desktopMode = ("desktopMode" in aParams) ? aParams.desktopMode : false;
       this._parentId = ("parentId" in aParams && typeof aParams.parentId == "number")
                       ? aParams.parentId : -1;
 
       let message = {
         type: "Tab:Added",
         tabID: this.id,
+        tabType: this.type,
         uri: truncate(uri, MAX_URI_LENGTH),
         parentId: this.parentId,
         tabIndex: ("tabIndex" in aParams) ? aParams.tabIndex : -1,
         external: ("external" in aParams) ? aParams.external : false,
         selected: ("selected" in aParams || aParams.cancelEditMode === true)
                   ? aParams.selected !== false || aParams.cancelEditMode === true : true,
         cancelEditMode: aParams.cancelEditMode === true,
         title: truncate(title, MAX_TITLE_LENGTH),
@@ -3659,17 +3663,16 @@ Tab.prototype = {
       let flags = "flags" in aParams ? aParams.flags : Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
       let postData = ("postData" in aParams && aParams.postData) ? aParams.postData.value : null;
       let referrerURI = "referrerURI" in aParams ? aParams.referrerURI : null;
       let charset = "charset" in aParams ? aParams.charset : null;
 
       // The search term the user entered to load the current URL
       this.userRequested = "userRequested" in aParams ? aParams.userRequested : "";
       this.isSearch = "isSearch" in aParams ? aParams.isSearch : false;
-      this.isCustomTab = "customTab" in aParams ? aParams.customTab : false;
 
       try {
         this.browser.loadURIWithFlags(aURL, flags, referrerURI, charset, postData);
       } catch(e) {
         let message = {
           type: "Content:LoadError",
           tabID: this.id
         };
--- a/mobile/android/components/SessionStore.js
+++ b/mobile/android/components/SessionStore.js
@@ -1111,16 +1111,17 @@ SessionStore.prototype = {
     let tab = aWindow.BrowserApp.getTabForBrowser(aBrowser);
     tabData.entries = aHistory.entries;
     tabData.index = aHistory.index;
     tabData.attributes = { image: aBrowser.mIconURL };
     tabData.desktopMode = tab.desktopMode;
     tabData.isPrivate = aBrowser.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing;
     tabData.tabId = tab.id;
     tabData.parentId = tab.parentId;
+    tabData.type = tab.type;
 
     aBrowser.__SS_data = tabData;
   },
 
   _collectWindowData: function ss__collectWindowData(aWindow) {
     // Ignore windows not tracked by SessionStore
     if (!aWindow.__SSID || !this._windows[aWindow.__SSID]) {
       return;
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testFilterOpenTab.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testFilterOpenTab.java
@@ -3,26 +3,27 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.tests;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Callable;
 
-import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.PrivateTab;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.TabsProvider;
 
 import android.content.ContentProvider;
 import android.content.Context;
 import android.database.Cursor;
 
+import static org.mozilla.gecko.Tab.TabType;
+
 /**
  * Tests that local tabs are filtered prior to upload.
  * - create a set of tabs and persists them through TabsAccessor.
  * - verifies that tabs are filtered by querying.
  */
 public class testFilterOpenTab extends ContentProviderTest {
     private static final String[] TABS_PROJECTION_COLUMNS = new String[] {
                                                                 BrowserContract.Tabs.TITLE,
@@ -50,21 +51,21 @@ public class testFilterOpenTab extends C
         return mProvider.query(BrowserContract.Tabs.CONTENT_URI,
                                TABS_PROJECTION_COLUMNS,
                                LOCAL_TABS_SELECTION,
                                null,
                                null);
     }
 
     private Tab createTab(int id, String url, boolean external, int parentId, String title) {
-        return new Tab((Context) getActivity(), id, url, external, parentId, title);
+        return new Tab((Context) getActivity(), id, url, external, parentId, title, TabType.BROWSING);
     }
 
     private Tab createPrivateTab(int id, String url, boolean external, int parentId, String title) {
-        return new PrivateTab((Context) getActivity(), id, url, external, parentId, title);
+        return new PrivateTab((Context) getActivity(), id, url, external, parentId, title, TabType.BROWSING);
     }
 
     @Override
     public void setUp() throws Exception {
         super.setUp(sTabProviderCallable, BrowserContract.TABS_AUTHORITY, "tabs.db");
         mTests.add(new TestInsertLocalTabs());
     }
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/thirdparty/com/booking/rtlviewpager/PagerAdapterWrapper.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2015 Diego Gómez Olvera
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.booking.rtlviewpager;
+
+import android.database.DataSetObservable;
+import android.database.DataSetObserver;
+import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import android.support.v4.view.PagerAdapter;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * PagerAdapter decorator.
+ */
+class PagerAdapterWrapper extends PagerAdapter {
+
+    @NonNull
+    private final PagerAdapter adapter;
+    private final DataSetObservable dataSetObservable = new DataSetObservable();
+
+
+    protected PagerAdapterWrapper(@NonNull PagerAdapter adapter) {
+        this.adapter = adapter;
+        this.adapter.registerDataSetObserver(new DataSetObserver() {
+            @Override
+            public void onChanged() {
+                PagerAdapterWrapper.super.notifyDataSetChanged();
+                dataSetObservable.notifyChanged();
+            }
+
+            @Override
+            public void onInvalidated() {
+                dataSetObservable.notifyInvalidated();
+            }
+        });
+    }
+
+    @NonNull
+    public PagerAdapter getInnerAdapter() {
+        return adapter;
+    }
+
+    @Override
+    public int getCount() {
+        return adapter.getCount();
+    }
+
+    @Override
+    public boolean isViewFromObject(View view, Object object) {
+        return adapter.isViewFromObject(view, object);
+    }
+
+    @Override
+    public CharSequence getPageTitle(int position) {
+        return adapter.getPageTitle(position);
+    }
+
+    @Override
+    public float getPageWidth(int position) {
+        return adapter.getPageWidth(position);
+    }
+
+    @Override
+    public int getItemPosition(Object object) {
+        return adapter.getItemPosition(object);
+    }
+
+    @Override
+    public Object instantiateItem(ViewGroup container, int position) {
+        return adapter.instantiateItem(container, position);
+    }
+
+    @Override
+    public void destroyItem(ViewGroup container, int position, Object object) {
+        adapter.destroyItem(container, position, object);
+    }
+
+    @Override
+    public void setPrimaryItem(ViewGroup container, int position, Object object) {
+        adapter.setPrimaryItem(container, position, object);
+    }
+
+    @Override
+    public void notifyDataSetChanged() {
+        adapter.notifyDataSetChanged();
+    }
+
+    @Override
+    public void registerDataSetObserver(DataSetObserver observer) {
+        dataSetObservable.registerObserver(observer);
+    }
+
+    @Override
+    public void unregisterDataSetObserver(DataSetObserver observer) {
+        dataSetObservable.unregisterObserver(observer);
+    }
+
+    @Override
+    public Parcelable saveState() {
+        return adapter.saveState();
+    }
+
+    @Override
+    public void restoreState(Parcelable state, ClassLoader loader) {
+        adapter.restoreState(state, loader);
+    }
+
+    @Override
+    public void startUpdate(ViewGroup container) {
+        adapter.startUpdate(container);
+    }
+
+    @Override
+    public void finishUpdate(ViewGroup container) {
+        adapter.finishUpdate(container);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/thirdparty/com/booking/rtlviewpager/RtlViewPager.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright 2015 Diego Gómez Olvera
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.booking.rtlviewpager;
+
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.text.TextUtilsCompat;
+import android.support.v4.util.ArrayMap;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+
+import java.util.Map;
+
+/**
+ * ViewPager that reverses the items order in RTL locales.
+ */
+public class RtlViewPager extends ViewPager {
+
+    @NonNull
+    private final Map<OnPageChangeListener, ReverseOnPageChangeListener> reverseOnPageChangeListeners;
+
+    @Nullable
+    private DataSetObserver dataSetObserver;
+
+    private boolean suppressOnPageChangeListeners;
+
+    public RtlViewPager(Context context) {
+        super(context);
+        reverseOnPageChangeListeners = new ArrayMap<>(1);
+    }
+
+    public RtlViewPager(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        reverseOnPageChangeListeners = new ArrayMap<>(1);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        registerRtlDataSetObserver(super.getAdapter());
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        unregisterRtlDataSetObserver();
+        super.onDetachedFromWindow();
+    }
+
+    private void registerRtlDataSetObserver(PagerAdapter adapter) {
+        if (adapter instanceof ReverseAdapter && dataSetObserver == null) {
+            dataSetObserver = new RevalidateIndicesOnContentChange((ReverseAdapter) adapter);
+            adapter.registerDataSetObserver(dataSetObserver);
+            ((ReverseAdapter) adapter).revalidateIndices();
+        }
+    }
+
+    private void unregisterRtlDataSetObserver() {
+        final PagerAdapter adapter = super.getAdapter();
+
+        if (adapter instanceof ReverseAdapter && dataSetObserver != null) {
+            adapter.unregisterDataSetObserver(dataSetObserver);
+            dataSetObserver = null;
+        }
+    }
+
+    @Override
+    public void setCurrentItem(int item, boolean smoothScroll) {
+        super.setCurrentItem(convert(item), smoothScroll);
+    }
+
+    @Override
+    public void setCurrentItem(int item) {
+        super.setCurrentItem(convert(item));
+    }
+
+    @Override
+    public int getCurrentItem() {
+        return convert(super.getCurrentItem());
+    }
+
+    private int convert(int position) {
+        if (position >= 0 && isRtl()) {
+            return getAdapter() == null ? 0 : getAdapter().getCount() - position - 1;
+        } else {
+            return position;
+        }
+    }
+
+    @Nullable
+    @Override
+    public PagerAdapter getAdapter() {
+        final PagerAdapter adapter = super.getAdapter();
+        return adapter instanceof ReverseAdapter ? ((ReverseAdapter) adapter).getInnerAdapter() : adapter;
+    }
+
+    @Override
+    public void fakeDragBy(float xOffset) {
+        super.fakeDragBy(isRtl() ? xOffset : -xOffset);
+    }
+
+    @Override
+    public void setAdapter(@Nullable PagerAdapter adapter) {
+        unregisterRtlDataSetObserver();
+
+        final boolean rtlReady = adapter != null && isRtl();
+        if (rtlReady) {
+            adapter = new ReverseAdapter(adapter);
+            registerRtlDataSetObserver(adapter);
+        }
+        super.setAdapter(adapter);
+        if (rtlReady && adapter.getCount() > 0) {
+            setCurrentItemWithoutNotification(0);
+        }
+    }
+
+    private void setCurrentItemWithoutNotification(int index) {
+        suppressOnPageChangeListeners = true;
+        setCurrentItem(index, false);
+        suppressOnPageChangeListeners = false;
+    }
+
+    protected boolean isRtl() {
+        return TextUtilsCompat.getLayoutDirectionFromLocale(
+                getContext().getResources().getConfiguration().locale) == ViewCompat.LAYOUT_DIRECTION_RTL;
+    }
+
+    @Override
+    public void addOnPageChangeListener(@NonNull OnPageChangeListener listener) {
+        if (isRtl()) {
+            final ReverseOnPageChangeListener reverseListener = new ReverseOnPageChangeListener(listener);
+            reverseOnPageChangeListeners.put(listener, reverseListener);
+            listener = reverseListener;
+        }
+        super.addOnPageChangeListener(listener);
+    }
+
+    @Override
+    public void removeOnPageChangeListener(@NonNull OnPageChangeListener listener) {
+        if (isRtl()) {
+            listener = reverseOnPageChangeListeners.remove(listener);
+        }
+        super.removeOnPageChangeListener(listener);
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        return new SavedState(super.onSaveInstanceState(), getCurrentItem(), isRtl());
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+        super.onRestoreInstanceState(ss.superState);
+        if (ss.isRTL != isRtl()) setCurrentItem(ss.position, false);
+    }
+
+    private class ReverseAdapter extends PagerAdapterWrapper {
+
+        private int lastCount;
+
+        public ReverseAdapter(@NonNull PagerAdapter adapter) {
+            super(adapter);
+            lastCount = adapter.getCount();
+        }
+
+        @Override
+        public CharSequence getPageTitle(int position) {
+            return super.getPageTitle(reverse(position));
+        }
+
+        @Override
+        public float getPageWidth(int position) {
+            return super.getPageWidth(reverse(position));
+        }
+
+        @Override
+        public int getItemPosition(Object object) {
+            final int itemPosition = super.getItemPosition(object);
+            return itemPosition < 0 ? itemPosition : reverse(itemPosition);
+        }
+
+        @Override
+        public Object instantiateItem(ViewGroup container, int position) {
+            return super.instantiateItem(container, reverse(position));
+        }
+
+        @Override
+        public void destroyItem(ViewGroup container, int position, Object object) {
+            super.destroyItem(container, reverse(position), object);
+        }
+
+        @Override
+        public void setPrimaryItem(ViewGroup container, int position, Object object) {
+            super.setPrimaryItem(container, lastCount - position - 1, object);
+        }
+
+        private int reverse(int position) {
+            return getCount() - position - 1;
+        }
+
+        private void revalidateIndices() {
+            final int newCount = getCount();
+            if (newCount != lastCount) {
+                setCurrentItemWithoutNotification(Math.max(0, lastCount - 1));
+                lastCount = newCount;
+            }
+        }
+    }
+
+    private static class RevalidateIndicesOnContentChange extends DataSetObserver {
+        @NonNull
+        private final ReverseAdapter adapter;
+
+        private RevalidateIndicesOnContentChange(@NonNull ReverseAdapter adapter) {
+            this.adapter = adapter;
+        }
+
+        @Override
+        public void onChanged() {
+            super.onChanged();
+            adapter.revalidateIndices();
+        }
+    }
+
+    private class ReverseOnPageChangeListener implements OnPageChangeListener {
+
+        @NonNull
+        private final OnPageChangeListener original;
+
+        private int pagerPosition;
+
+        private ReverseOnPageChangeListener(@NonNull OnPageChangeListener original) {
+            this.original = original;
+            pagerPosition = -1;
+        }
+
+        @Override
+        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+            if (!suppressOnPageChangeListeners) {
+                if (positionOffset == 0f && positionOffsetPixels == 0) {
+                    pagerPosition = reverse(position);
+                } else {
+                    pagerPosition = reverse(position + 1);
+                }
+                original.onPageScrolled(pagerPosition, positionOffset > 0 ? 1f - positionOffset : positionOffset, positionOffsetPixels);
+            }
+        }
+
+        @Override
+        public void onPageSelected(int position) {
+            if (!suppressOnPageChangeListeners) {
+                original.onPageSelected(reverse(position));
+            }
+        }
+
+        @Override
+        public void onPageScrollStateChanged(int state) {
+            if (!suppressOnPageChangeListeners) {
+                original.onPageScrollStateChanged(state);
+            }
+        }
+
+        private int reverse(int position) {
+            final PagerAdapter adapter = getAdapter();
+            return adapter == null ? position : adapter.getCount() - position - 1;
+        }
+    }
+
+    public static class SavedState implements Parcelable {
+
+        Parcelable superState;
+        int position;
+        boolean isRTL;
+
+        public SavedState(Parcelable superState, int position, boolean isRTL) {
+            super();
+            this.superState = superState;
+            this.position = position;
+            this.isRTL = isRTL;
+        }
+
+        SavedState(Parcel in, ClassLoader loader) {
+            if (loader == null) {
+                loader = getClass().getClassLoader();
+            }
+            superState = in.readParcelable(loader);
+            position = in.readInt();
+            isRTL = in.readByte() != 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeParcelable(superState, flags);
+            out.writeInt(position);
+            out.writeByte(isRTL ? (byte) 1 : (byte) 0);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public static final ClassLoaderCreator<SavedState> CREATOR = new
+                ClassLoaderCreator<SavedState>() {
+            @Override
+            public SavedState createFromParcel(Parcel source, ClassLoader loader) {
+                return new SavedState(source, loader);
+            }
+
+            @Override
+            public SavedState createFromParcel(Parcel source) {
+                return new SavedState(source, null);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+}
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5203,18 +5203,21 @@ pref("browser.safebrowsing.provider.goog
 pref("browser.safebrowsing.provider.google.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
 pref("browser.safebrowsing.provider.google.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
 
 
 // Prefs for v4.
 pref("browser.safebrowsing.provider.google4.pver", "4");
 pref("browser.safebrowsing.provider.google4.lists", "goog-badbinurl-proto,goog-downloadwhite-proto,goog-phish-proto,googpub-phish-proto,goog-malware-proto,goog-unwanted-proto");
 pref("browser.safebrowsing.provider.google4.updateURL", "https://safebrowsing.googleapis.com/v4/threatListUpdates:fetch?$ct=application/x-protobuf&key=%GOOGLE_API_KEY%");
-// Leave it empty until we roll out v4 hash completion feature. See Bug 1323856.
+#ifdef NIGHTLY_BUILD
+pref("browser.safebrowsing.provider.google4.gethashURL", "https://safebrowsing.googleapis.com/v4/fullHashes:find?$ct=application/x-protobuf&key=%GOOGLE_API_KEY%");
+#else
 pref("browser.safebrowsing.provider.google4.gethashURL", "");
+#endif // NIGHTLY_BUILD
 pref("browser.safebrowsing.provider.google4.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
 pref("browser.safebrowsing.provider.google4.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
 pref("browser.safebrowsing.provider.google4.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
 
 pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%&url=");
 
 // The table and global pref for blocking plugin content
 pref("browser.safebrowsing.blockedURIs.enabled", true);
--- a/netwerk/protocol/http/Http2Session.cpp
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -953,16 +953,19 @@ Http2Session::SendHello()
     CreatePriorityNode(kBackgroundGroupID, 0, 0, "background");
     mNextStreamID += 2;
     MOZ_ASSERT(mNextStreamID == kSpeculativeGroupID);
     CreatePriorityNode(kSpeculativeGroupID, kBackgroundGroupID, 0, "speculative");
     mNextStreamID += 2;
     MOZ_ASSERT(mNextStreamID == kFollowerGroupID);
     CreatePriorityNode(kFollowerGroupID, kLeaderGroupID, 0, "follower");
     mNextStreamID += 2;
+    // Hey, you! YES YOU! If you add/remove any groups here, you almost
+    // certainly need to change the lookup of the stream/ID hash in
+    // Http2Session::OnTransportStatus. Yeah, that's right. YOU!
   }
 
   FlushOutputQueue();
 }
 
 void
 Http2Session::CreatePriorityNode(uint32_t streamID, uint32_t dependsOn, uint8_t weight,
                                  const char *label)
@@ -2302,17 +2305,17 @@ Http2Session::OnTransportStatus(nsITrans
   switch (aStatus) {
     // These should appear only once, deliver to the first
     // transaction on the session.
   case NS_NET_STATUS_RESOLVING_HOST:
   case NS_NET_STATUS_RESOLVED_HOST:
   case NS_NET_STATUS_CONNECTING_TO:
   case NS_NET_STATUS_CONNECTED_TO:
   {
-    Http2Stream *target = mStreamIDHash.Get(1);
+    Http2Stream *target = mStreamIDHash.Get(mUseH2Deps ? 0xD : 0x3);
     nsAHttpTransaction *transaction = target ? target->Transaction() : nullptr;
     if (transaction)
       transaction->OnTransportStatus(aTransport, aStatus, aProgress);
     break;
   }
 
   default:
     // The other transport events are ignored here because there is no good
--- a/netwerk/protocol/http/Http2Session.h
+++ b/netwerk/protocol/http/Http2Session.h
@@ -165,16 +165,19 @@ public:
     kFrameTypeBytes + kFrameStreamIDBytes;
 
   enum {
     kLeaderGroupID =     0x3,
     kOtherGroupID =       0x5,
     kBackgroundGroupID =  0x7,
     kSpeculativeGroupID = 0x9,
     kFollowerGroupID =    0xB
+    // Hey, you! YES YOU! If you add/remove any groups here, you almost
+    // certainly need to change the lookup of the stream/ID hash in
+    // Http2Session::OnTransportStatus. Yeah, that's right. YOU!
   };
 
   static nsresult RecvHeaders(Http2Session *);
   static nsresult RecvPriority(Http2Session *);
   static nsresult RecvRstStream(Http2Session *);
   static nsresult RecvSettings(Http2Session *);
   static nsresult RecvPushPromise(Http2Session *);
   static nsresult RecvPing(Http2Session *);
--- a/security/manager/.eslintrc.js
+++ b/security/manager/.eslintrc.js
@@ -1,13 +1,13 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../toolkit/.eslintrc.js"
+    "plugin:mozilla/recommended"
   ],
   "rules": {
     // Enforce return statements in callbacks of array methods.
     "array-callback-return": "error",
 
     // Braces only needed for multi-line arrow function blocks
     "arrow-body-style": ["error", "as-needed"],
 
--- a/security/manager/ssl/nsNSSCallbacks.cpp
+++ b/security/manager/ssl/nsNSSCallbacks.cpp
@@ -1062,19 +1062,20 @@ AccumulateCipherSuite(Telemetry::Histogr
 // In the case of session resumption, the AuthCertificate hook has been bypassed
 // (because we've previously successfully connected to our peer). That being the
 // case, we unfortunately don't know if the peer's server certificate verified
 // as extended validation or not. To address this, we attempt to build a
 // verified EV certificate chain here using as much of the original context as
 // possible (e.g. stapled OCSP responses, SCTs, the hostname, the first party
 // domain, etc.). Note that because we are on the socket thread, this must not
 // cause any network requests, hence the use of FLAG_LOCAL_ONLY.
+// Similarly, we need to determine the certificate's CT status.
 static void
-DetermineEVStatusAndSetNewCert(RefPtr<nsSSLStatus> sslStatus, PRFileDesc* fd,
-                               nsNSSSocketInfo* infoObject)
+DetermineEVAndCTStatusAndSetNewCert(RefPtr<nsSSLStatus> sslStatus,
+                                    PRFileDesc* fd, nsNSSSocketInfo* infoObject)
 {
   MOZ_ASSERT(sslStatus);
   MOZ_ASSERT(fd);
   MOZ_ASSERT(infoObject);
 
   if (!sslStatus || !fd || !infoObject) {
     return;
   }
@@ -1110,41 +1111,52 @@ DetermineEVStatusAndSetNewCert(RefPtr<ns
   int flags = mozilla::psm::CertVerifier::FLAG_LOCAL_ONLY |
               mozilla::psm::CertVerifier::FLAG_MUST_BE_EV;
   if (!infoObject->SharedState().IsOCSPStaplingEnabled() ||
       !infoObject->SharedState().IsOCSPMustStapleEnabled()) {
     flags |= CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
   }
 
   SECOidTag evOidPolicy;
+  CertificateTransparencyInfo certificateTransparencyInfo;
   UniqueCERTCertList unusedBuiltChain;
   const bool saveIntermediates = false;
   mozilla::pkix::Result rv = certVerifier->VerifySSLServerCert(
     cert,
     stapledOCSPResponse,
     sctsFromTLSExtension,
     mozilla::pkix::Now(),
     infoObject,
     infoObject->GetHostNameRaw(),
     unusedBuiltChain,
     saveIntermediates,
     flags,
     infoObject->GetOriginAttributes(),
-    &evOidPolicy);
+    &evOidPolicy,
+    nullptr, // OCSP stapling telemetry
+    nullptr, // key size telemetry
+    nullptr, // SHA-1 telemetry
+    nullptr, // pinning telemetry
+    &certificateTransparencyInfo);
 
   RefPtr<nsNSSCertificate> nssc(nsNSSCertificate::Create(cert.get()));
   if (rv == Success && evOidPolicy != SEC_OID_UNKNOWN) {
+    sslStatus->SetCertificateTransparencyInfo(certificateTransparencyInfo);
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
             ("HandshakeCallback using NEW cert %p (is EV)", nssc.get()));
     sslStatus->SetServerCert(nssc, EVStatus::EV);
   } else {
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
             ("HandshakeCallback using NEW cert %p (is not EV)", nssc.get()));
     sslStatus->SetServerCert(nssc, EVStatus::NotEV);
   }
+
+  if (rv == Success) {
+    sslStatus->SetCertificateTransparencyInfo(certificateTransparencyInfo);
+  }
 }
 
 void HandshakeCallback(PRFileDesc* fd, void* client_data) {
   nsNSSShutDownPreventionLock locker;
   SECStatus rv;
 
   nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*) fd->higher->secret;
 
@@ -1285,17 +1297,17 @@ void HandshakeCallback(PRFileDesc* fd, v
                                                 infoObject->GetPort());
     }
   }
 
   if (status->HasServerCert()) {
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("HandshakeCallback KEEPING existing cert\n"));
   } else {
-    DetermineEVStatusAndSetNewCert(status, fd, infoObject);
+    DetermineEVAndCTStatusAndSetNewCert(status, fd, infoObject);
   }
 
   bool domainMismatch;
   bool untrusted;
   bool notValidAtThisTime;
   // These all return NS_OK, so don't even bother checking the return values.
   Unused << status->GetIsDomainMismatch(&domainMismatch);
   Unused << status->GetIsUntrusted(&untrusted);
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -192,17 +192,16 @@ nsNSSComponent::~nsNSSComponent()
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   // All cleanup code requiring services needs to happen in xpcom_shutdown
 
   ShutdownNSS();
   SharedSSLState::GlobalCleanup();
   RememberCertErrorsTable::Cleanup();
   --mInstanceCount;
-  nsNSSShutDownList::shutdown();
 
   MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSComponent::dtor finished\n"));
 }
 
 NS_IMETHODIMP
 nsNSSComponent::PIPBundleFormatStringFromName(const char* name,
                                               const char16_t** params,
                                               uint32_t numParams,
@@ -1804,17 +1803,17 @@ nsNSSComponent::ShutdownNSS()
     ShutdownSmartCardThreads();
 #endif
     SSL_ClearSessionCache();
     // TLSServerSocket may be run with the session cache enabled. This ensures
     // those resources are cleaned up.
     Unused << SSL_ShutdownServerSessionIDCache();
 
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("evaporating psm resources"));
-    if (NS_FAILED(nsNSSShutDownList::evaporateAllNSSResources())) {
+    if (NS_FAILED(nsNSSShutDownList::evaporateAllNSSResourcesAndShutDown())) {
       MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("failed to evaporate resources"));
       return;
     }
     UnloadLoadableRoots();
     if (SECSuccess != ::NSS_Shutdown()) {
       MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("NSS SHUTDOWN FAILURE"));
     } else {
       MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("NSS shutdown =====>> OK <<====="));
--- a/security/manager/ssl/nsNSSShutDown.cpp
+++ b/security/manager/ssl/nsNSSShutDown.cpp
@@ -45,16 +45,18 @@ nsNSSShutDownList::nsNSSShutDownList()
   : mObjects(&gSetOps, sizeof(ObjectHashEntry))
   , mPK11LogoutCancelObjects(&gSetOps, sizeof(ObjectHashEntry))
 {
 }
 
 nsNSSShutDownList::~nsNSSShutDownList()
 {
   MOZ_ASSERT(this == singleton);
+  MOZ_ASSERT(sInShutdown,
+             "evaporateAllNSSResourcesAndShutDown() should have been called");
   singleton = nullptr;
 }
 
 void nsNSSShutDownList::remember(nsNSSShutDownObject *o)
 {
   StaticMutexAutoLock lock(sListLock);
   if (!nsNSSShutDownList::construct(lock)) {
     return;
@@ -121,17 +123,17 @@ nsresult nsNSSShutDownList::doPK11Logout
     if (pklco) {
       pklco->logout();
     }
   }
 
   return NS_OK;
 }
 
-nsresult nsNSSShutDownList::evaporateAllNSSResources()
+nsresult nsNSSShutDownList::evaporateAllNSSResourcesAndShutDown()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   if (!NS_IsMainThread()) {
     return NS_ERROR_NOT_SAME_THREAD;
   }
 
   StaticMutexAutoLock lock(sListLock);
   // Other threads can acquire an nsNSSShutDownPreventionLock and cause this
@@ -145,16 +147,18 @@ nsresult nsNSSShutDownList::evaporateAll
   // and destroyed on the main thread, and if we similarly enforce that this
   // function is only called on the main thread, what we can do is check that
   // the singleton hasn't already gone away and then we don't actually have to
   // hold sListLock while calling restrictActivityToCurrentThread.
   if (!singleton) {
     return NS_OK;
   }
 
+  sInShutdown = true;
+
   {
     StaticMutexAutoUnlock unlock(sListLock);
     PRStatus rv = singleton->mActivityState.restrictActivityToCurrentThread();
     if (rv != PR_SUCCESS) {
       MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
               ("failed to restrict activity to current thread"));
       return NS_ERROR_FAILURE;
     }
@@ -178,16 +182,18 @@ nsresult nsNSSShutDownList::evaporateAll
     iter.Remove();
   }
 
   if (!singleton) {
     return NS_ERROR_FAILURE;
   }
 
   singleton->mActivityState.releaseCurrentThreadActivityRestriction();
+  delete singleton;
+
   return NS_OK;
 }
 
 void nsNSSShutDownList::enterActivityState()
 {
   StaticMutexAutoLock lock(sListLock);
   if (nsNSSShutDownList::construct(lock)) {
     singleton->mActivityState.enter();
@@ -206,27 +212,16 @@ bool nsNSSShutDownList::construct(const 
 {
   if (!singleton && !sInShutdown && XRE_IsParentProcess()) {
     singleton = new nsNSSShutDownList();
   }
 
   return !!singleton;
 }
 
-void nsNSSShutDownList::shutdown()
-{
-  MOZ_RELEASE_ASSERT(NS_IsMainThread());
-  StaticMutexAutoLock lock(sListLock);
-  sInShutdown = true;
-
-  if (singleton) {
-    delete singleton;
-  }
-}
-
 nsNSSActivityState::nsNSSActivityState()
 :mNSSActivityStateLock("nsNSSActivityState.mNSSActivityStateLock"), 
  mNSSActivityChanged(mNSSActivityStateLock,
                      "nsNSSActivityState.mNSSActivityStateLock"),
  mNSSActivityCounter(0),
  mNSSRestrictedThread(nullptr)
 {
 }
@@ -281,8 +276,14 @@ nsNSSShutDownPreventionLock::nsNSSShutDo
 {
   nsNSSShutDownList::enterActivityState();
 }
 
 nsNSSShutDownPreventionLock::~nsNSSShutDownPreventionLock()
 {
   nsNSSShutDownList::leaveActivityState();
 }
+
+bool
+nsNSSShutDownObject::isAlreadyShutDown() const
+{
+  return mAlreadyShutDown || sInShutdown;
+}
--- a/security/manager/ssl/nsNSSShutDown.h
+++ b/security/manager/ssl/nsNSSShutDown.h
@@ -59,29 +59,28 @@ public:
   ~nsNSSShutDownPreventionLock();
 };
 
 // Singleton, used by nsNSSComponent to track the list of PSM objects,
 // which hold NSS resources and support the "early cleanup mechanism".
 class nsNSSShutDownList
 {
 public:
-  static void shutdown();
-
   // track instances that support early cleanup
   static void remember(nsNSSShutDownObject *o);
   static void forget(nsNSSShutDownObject *o);
 
   // track instances that would like notification when
   // a PK11 logout operation is performed.
   static void remember(nsOnPK11LogoutCancelObject *o);
   static void forget(nsOnPK11LogoutCancelObject *o);
 
-  // Do the "early cleanup", if possible.
-  static nsresult evaporateAllNSSResources();
+  // Release all tracked NSS resources and prevent nsNSSShutDownObjects from
+  // using NSS functions.
+  static nsresult evaporateAllNSSResourcesAndShutDown();
 
   // PSM has been asked to log out of a token.
   // Notify all registered instances that want to react to that event.
   static nsresult doPK11Logout();
 
   // Signal entering/leaving a scope where shutting down NSS is prohibited.
   static void enterActivityState();
   static void leaveActivityState();
@@ -223,17 +222,17 @@ public:
           break;
         default:
           MOZ_CRASH("shutdown() called from an unknown source");
       }
       mAlreadyShutDown = true;
     }
   }
 
-  bool isAlreadyShutDown() const { return mAlreadyShutDown; }
+  bool isAlreadyShutDown() const;
 
 protected:
   virtual void virtualDestroyNSSReference() = 0;
 private:
   volatile bool mAlreadyShutDown;
 };
 
 class nsOnPK11LogoutCancelObject
--- a/security/manager/ssl/tests/mochitest/browser/.eslintrc.js
+++ b/security/manager/ssl/tests/mochitest/browser/.eslintrc.js
@@ -1,5 +1,5 @@
 "use strict";
 
 module.exports = { // eslint-disable-line no-undef
-  "extends": "../../../../../../testing/mochitest/browser.eslintrc.js"
+  "extends": "plugin:mozilla/browser-test"
 };
--- a/security/manager/ssl/tests/mochitest/mixedcontent/.eslintrc.js
+++ b/security/manager/ssl/tests/mochitest/mixedcontent/.eslintrc.js
@@ -1,9 +1,9 @@
 "use strict";
 
 module.exports = { // eslint-disable-line no-undef
-  "extends": "../../../../../../testing/mochitest/mochitest.eslintrc.js",
+  "extends": "plugin:mozilla/mochitest-test",
   "rules": {
 	// Boilerplate runTest and afterNavigationtest calls use opening braces on newline.
     "brace-style": "off"
   }
 };
--- a/security/manager/ssl/tests/mochitest/stricttransportsecurity/.eslintrc.js
+++ b/security/manager/ssl/tests/mochitest/stricttransportsecurity/.eslintrc.js
@@ -1,9 +1,9 @@
 "use strict";
 
 module.exports = { // eslint-disable-line no-undef
   // mochitest-chrome tests also exist in this directory, but don't appear to
   // use anything not also available to plain mochitests. Since plain mochitests
   // are the majority, we take the safer option and only extend the
   // mochitest-plain eslintrc file.
-  "extends": "../../../../../../testing/mochitest/mochitest.eslintrc.js"
+  "extends": "plugin:mozilla/mochitest-test"
 };
--- a/security/manager/ssl/tests/unit/.eslintrc.js
+++ b/security/manager/ssl/tests/unit/.eslintrc.js
@@ -1,5 +1,5 @@
 "use strict";
 
 module.exports = { // eslint-disable-line no-undef
-  "extends": "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+  "extends": "plugin:mozilla/xpcshell-test"
 };
--- a/services/cloudsync/tests/mochitest/.eslintrc.js
+++ b/services/cloudsync/tests/mochitest/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/chrome-test"
   ]
 };
--- a/services/cloudsync/tests/xpcshell/.eslintrc.js
+++ b/services/cloudsync/tests/xpcshell/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/services/common/tests/unit/.eslintrc.js
+++ b/services/common/tests/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/services/crypto/component/tests/unit/.eslintrc.js
+++ b/services/crypto/component/tests/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/services/crypto/tests/unit/.eslintrc.js
+++ b/services/crypto/tests/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/services/fxaccounts/tests/browser/.eslintrc.js
+++ b/services/fxaccounts/tests/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/chrome-test"
   ]
 };
--- a/services/fxaccounts/tests/mochitest/.eslintrc.js
+++ b/services/fxaccounts/tests/mochitest/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/mochitest.eslintrc.js"
+    "plugin:mozilla/mochitest-test"
   ]
 };
--- a/services/fxaccounts/tests/xpcshell/.eslintrc.js
+++ b/services/fxaccounts/tests/xpcshell/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/services/sync/modules/bookmark_validator.js
+++ b/services/sync/modules/bookmark_validator.js
@@ -217,16 +217,20 @@ XPCOMUtils.defineLazyGetter(this, "SYNCE
   PlacesUtils.bookmarks.menuGuid,
   PlacesUtils.bookmarks.toolbarGuid,
   PlacesUtils.bookmarks.unfiledGuid,
   PlacesUtils.bookmarks.mobileGuid,
 ]);
 
 class BookmarkValidator {
 
+  async canValidate() {
+    return !await PlacesSyncUtils.bookmarks.havePendingChanges();
+  }
+
   _followQueries(recordMap) {
     for (let [guid, entry] of recordMap) {
       if (entry.type !== "query" && (!entry.bmkUri || !entry.bmkUri.startsWith(QUERY_PROTOCOL))) {
         continue;
       }
       // Might be worth trying to parse the place: query instead so that this
       // works "automatically" with things like aboutsync.
       let queryNodeParent = PlacesUtils.getFolderContents(entry, false, true);
--- a/services/sync/modules/collection_validator.js
+++ b/services/sync/modules/collection_validator.js
@@ -77,16 +77,25 @@ class CollectionValidator {
     return items;
   }
 
   // Should return a promise that resolves to an array of client items.
   getClientItems() {
     return Promise.reject("Must implement");
   }
 
+  /**
+   * Can we guarantee validation will fail with a reason that isn't actually a
+   * problem? For example, if we know there are pending changes left over from
+   * the last sync, this should resolve to false. By default resolves to true.
+   */
+  async canValidate() {
+    return true;
+  }
+
   // Turn the client item into something that can be compared with the server item,
   // and is also safe to mutate.
   normalizeClientItem(item) {
     return Cu.cloneInto(item, {});
   }
 
   // Turn the server item into something that can be easily compared with the client
   // items.
--- a/services/sync/modules/doctor.js
+++ b/services/sync/modules/doctor.js
@@ -153,16 +153,21 @@ this.Doctor = {
                         `than the maximum allowed (${maxRecords}).`);
         continue;
       }
       let validator = engine.getValidator();
       if (!validator) {
         continue;
       }
 
+      if (!await validator.canValidate()) {
+        log.debug(`Skipping validation for ${engineName} because validator.canValidate() is false`);
+        continue;
+      }
+
       // Let's do it!
       Services.console.logStringMessage(
         `Sync is about to run a consistency check of ${engine.name}. This may be slow, and ` +
         `can be controlled using the pref "services.sync.${engine.name}.validation.enabled".\n` +
         `If you encounter any problems because of this, please file a bug.`);
 
       // Make a new flowID just incase we end up starting a repair.
       let flowID = Utils.makeGUID();
--- a/services/sync/tests/tps/.eslintrc.js
+++ b/services/sync/tests/tps/.eslintrc.js
@@ -1,13 +1,13 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/mochitest.eslintrc.js"
+    "plugin:mozilla/mochitest-test"
   ],
 
   globals: {
     // Globals specific to mozmill
     "assert": false,
     "controller": false,
     "findElement": false,
     "mozmill": false,
--- a/services/sync/tests/unit/.eslintrc.js
+++ b/services/sync/tests/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/services/sync/tests/unit/test_doctor.js
+++ b/services/sync/tests/unit/test_doctor.js
@@ -60,16 +60,19 @@ add_task(async function test_validation_
 add_task(async function test_repairs_start() {
   let repairStarted = false;
   let problems = {
     missingChildren: ["a", "b", "c"],
   }
   let validator = {
     validate(engine) {
       return problems;
+    },
+    canValidate() {
+      return Promise.resolve(true);
     }
   }
   let engine = {
     name: "test-engine",
     getValidator() {
       return validator;
     }
   }
@@ -134,8 +137,43 @@ add_task(async function test_repairs_adv
   // should not have done another repair yet - it's too soon.
   equal(repairCalls, 1);
   // advance our pretend clock by the advance period (eg, day)
   now += REPAIR_ADVANCE_PERIOD;
   await doctor.consult();
   // should have done another repair
   equal(repairCalls, 2);
 });
+
+add_task(async function test_repairs_skip_if_cant_vaidate() {
+  let validator = {
+    canValidate() {
+      return Promise.resolve(false);
+    },
+    validate() {
+      ok(false, "Shouldn't validate");
+    }
+  }
+  let engine = {
+    name: "test-engine",
+    getValidator() {
+      return validator;
+    }
+  }
+  let requestor = {
+    startRepairs(validationInfo, flowID) {
+      assert.ok(false, "Never should start repairs");
+    }
+  }
+  let doctor = mockDoctor({
+    _getEnginesToValidate(recentlySyncedEngines) {
+      deepEqual(recentlySyncedEngines, [engine]);
+      return {
+        "test-engine": { engine, maxRecords: -1 }
+      };
+    },
+    _getRepairRequestor(engineName) {
+      equal(engineName, engine.name);
+      return requestor;
+    },
+  });
+  await doctor.consult([engine]);
+});
--- a/services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm
+++ b/services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm
@@ -5,36 +5,142 @@
 "use strict";
 
 this.EXPORTED_SYMBOLS = [
   "Authentication",
 ];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/FxAccounts.jsm");
 Cu.import("resource://gre/modules/FxAccountsClient.jsm");
 Cu.import("resource://gre/modules/FxAccountsConfig.jsm");
 Cu.import("resource://services-common/async.js");
 Cu.import("resource://services-sync/main.js");
 Cu.import("resource://tps/logger.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
 
+Cu.importGlobalProperties(["fetch"]);
 
 /**
  * Helper object for Firefox Accounts authentication
  */
 var Authentication = {
 
   /**
    * Check if an user has been logged in
    */
   get isLoggedIn() {
     return !!this.getSignedInUser();
   },
 
+  _getRestmailUsername(user) {
+    const restmailSuffix = "@restmail.net";
+    if (user.toLowerCase().endsWith(restmailSuffix)) {
+      return user.slice(0, -restmailSuffix.length);
+    }
+    return null;
+  },
+
+  async shortWaitForVerification(ms) {
+    let userData = this.getSignedInUser();
+    await Promise.race([
+      fxAccounts.whenVerified(userData),
+      new Promise(resolve => {
+        setTimeout(() => {
+          Logger.logInfo(`Warning: no verification after ${ms}ms.`);
+          resolve();
+        }, ms);
+      })
+    ]);
+    userData = this.getSignedInUser();
+    return userData && userData.verified;
+  },
+
+  async _openVerificationPage(uri) {
+    let mainWindow = Services.wm.getMostRecentWindow("navigator:browser");
+    let newtab = mainWindow.getBrowser().addTab(uri);
+    let win = mainWindow.getBrowser().getBrowserForTab(newtab);
+    await new Promise(resolve => {
+      win.addEventListener("loadend", resolve, { once: true });
+    });
+    let didVerify = await this.shortWaitForVerification(10000);
+    mainWindow.getBrowser().removeTab(newtab);
+    return didVerify;
+  },
+
+  async _completeVerification(user) {
+    let username = this._getRestmailUsername(user);
+    if (!username) {
+      Logger.logInfo(`Username "${user}" isn't a restmail username so can't complete verification`);
+      return false;
+    }
+    Logger.logInfo("Fetching mail (from restmail) for user " + username);
+    let restmailURI = `https://www.restmail.net/mail/${encodeURIComponent(username)}`;
+    let triedAlready = new Set();
+    const tries = 10;
+    const normalWait = 2000;
+    for (let i = 0; i < tries; ++i) {
+      if (await this.shortWaitForVerification(normalWait)) {
+        return true;
+      }
+      let resp = await fetch(restmailURI);
+      let messages = await resp.json();
+      // Sort so that the most recent emails are first.
+      messages.sort((a, b) => new Date(b.receivedAt) - new Date(a.receivedAt));
+      for (let m of messages) {
+        // We look for a link that has a x-link that we haven't yet tried.
+        if (!m.headers["x-link"] || triedAlready.has(m.headers["x-link"])) {
+          continue;
+        }
+        let confirmLink = m.headers["x-link"];
+        triedAlready.add(confirmLink);
+        Logger.logInfo("Trying confirmation link " + confirmLink);
+        try {
+          if (await this._openVerificationPage(confirmLink)) {
+            return true;
+          }
+        } catch (e) {
+          Logger.logInfo("Warning: Failed to follow confirmation link: " + Log.exceptionStr(e));
+        }
+      }
+      if (i === 0) {
+        // first time through after failing we'll do this.
+        await fxAccounts.resendVerificationEmail();
+      }
+    }
+    // One last try.
+    return this.shortWaitForVerification(normalWait);
+  },
+
+  async deleteEmail(user) {
+    let username = this._getRestmailUsername(user);
+    if (!username) {
+      Logger.logInfo("Not a restmail username, can't delete");
+      return false;
+    }
+    Logger.logInfo("Deleting mail (from restmail) for user " + username);
+    let restmailURI = `https://www.restmail.net/mail/${encodeURIComponent(username)}`;
+    try {
+      // Clean up after ourselves.
+      let deleteResult = await fetch(restmailURI, { method: "DELETE" });
+      if (!deleteResult.ok) {
+        Logger.logInfo(`Warning: Got non-success status ${deleteResult.status} when deleting emails`);
+        return false;
+      }
+    } catch (e) {
+      Logger.logInfo("Warning: Failed to delete old emails: " + Log.exceptionStr(e));
+      return false;
+    }
+    return true;
+  },
+
   /**
    * Wrapper to retrieve the currently signed in user
    *
    * @returns Information about the currently signed in user
    */
   getSignedInUser: function getSignedInUser() {
     let cb = Async.makeSpinningCallback();
 
@@ -72,16 +178,18 @@ var Authentication = {
 
     // Required here since we don't go through the real login page
     Async.promiseSpinningly(FxAccountsConfig.ensureConfigured());
 
     let client = new FxAccountsClient();
     client.signIn(account["username"], account["password"], true).then(credentials => {
       return fxAccounts.setSignedInUser(credentials);
     }).then(() => {
+      return this._completeVerification(account["username"])
+    }).then(() => {
       cb(null, true);
     }, error => {
       cb(error, false);
     });
 
     try {
       cb.wait();
 
@@ -107,15 +215,16 @@ var Authentication = {
       if (!user) {
         throw new Error("Failed to get signed in user!");
       }
       let fxc = new FxAccountsClient();
       let { sessionToken, deviceId } = user;
       if (deviceId) {
         Logger.logInfo("Destroying device " + deviceId);
         Async.promiseSpinningly(fxAccounts.deleteDeviceRegistration(sessionToken, deviceId));
+        Async.promiseSpinningly(fxAccounts.signOut(true));
       } else {
         Logger.logError("No device found.");
         Async.promiseSpinningly(fxc.signOut(sessionToken, { service: "sync" }));
       }
     }
   }
 };
--- a/services/sync/tps/extensions/tps/resource/tps.jsm
+++ b/services/sync/tps/extensions/tps/resource/tps.jsm
@@ -582,16 +582,17 @@ var TPS = {
       if (Authentication.isLoggedIn) {
         // signout and wait for Sync to completely reset itself.
         Logger.logInfo("signing out");
         let waiter = this.createEventWaiter("weave:service:start-over:finish");
         Authentication.signOut();
         waiter();
         Logger.logInfo("signout complete");
       }
+      Authentication.deleteEmail(this.config.fx_account.username);
     } catch (e) {
       Logger.logError("Failed to sign out: " + Log.exceptionStr(e));
     }
   },
 
   /**
    * Use Sync's bookmark validation code to see if we've corrupted the tree.
    */
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -920,17 +920,16 @@ dependencies = [
 
 [[package]]
 name = "geckoservo"
 version = "0.0.1"
 dependencies = [
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "env_logger 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "selectors 0.18.0",
  "servo_url 0.0.1",
  "style 0.0.1",
  "style_traits 0.0.1",
  "stylo_tests 0.0.1",
--- a/servo/components/script/dom/bindings/structuredclone.rs
+++ b/servo/components/script/dom/bindings/structuredclone.rs
@@ -1,24 +1,173 @@
 /* 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 module implements structured cloning, as defined by [HTML]
 //! (https://html.spec.whatwg.org/multipage/#safe-passing-of-structured-data).
 
+use dom::bindings::conversions::root_from_handleobject;
 use dom::bindings::error::{Error, Fallible};
+use dom::bindings::js::Root;
+use dom::bindings::reflector::DomObject;
+use dom::blob::{Blob, BlobImpl};
 use dom::globalscope::GlobalScope;
-use js::jsapi::{HandleValue, MutableHandleValue};
-use js::jsapi::{JSContext, JS_ReadStructuredClone, JS_STRUCTURED_CLONE_VERSION};
-use js::jsapi::{JS_ClearPendingException, JS_WriteStructuredClone};
+use js::jsapi::{Handle, HandleObject, HandleValue, MutableHandleValue, JSAutoCompartment, JSContext};
+use js::jsapi::{JSStructuredCloneCallbacks, JSStructuredCloneReader, JSStructuredCloneWriter};
+use js::jsapi::{JS_ClearPendingException, JSObject, JS_ReadStructuredClone};
+use js::jsapi::{JS_ReadBytes, JS_WriteBytes};
+use js::jsapi::{JS_ReadUint32Pair, JS_WriteUint32Pair};
+use js::jsapi::{JS_STRUCTURED_CLONE_VERSION, JS_WriteStructuredClone};
+use js::jsapi::{MutableHandleObject, TransferableOwnership};
 use libc::size_t;
+use std::os::raw;
 use std::ptr;
 use std::slice;
 
+// TODO: Should we add Min and Max const to https://github.com/servo/rust-mozjs/blob/master/src/consts.rs?
+// TODO: Determine for sure which value Min and Max should have.
+// NOTE: Current values found at https://dxr.mozilla.org/mozilla-central/
+// rev/ff04d410e74b69acfab17ef7e73e7397602d5a68/js/public/StructuredClone.h#323
+#[repr(u32)]
+enum StructuredCloneTags {
+    /// To support additional types, add new tags with values incremented from the last one before Max.
+    Min = 0xFFFF8000,
+    DomBlob = 0xFFFF8001,
+    Max = 0xFFFFFFFF,
+}
+
+#[cfg(target_pointer_width = "64")]
+unsafe fn write_length(w: *mut JSStructuredCloneWriter,
+                       length: usize) {
+  let high: u32 = (length >> 32) as u32;
+  let low: u32 = length as u32;
+  assert!(JS_WriteUint32Pair(w, high, low));
+}
+
+#[cfg(target_pointer_width = "32")]
+unsafe fn write_length(w: *mut JSStructuredCloneWriter,
+                       length: usize) {
+  assert!(JS_WriteUint32Pair(w, length as u32, 0));
+}
+
+#[cfg(target_pointer_width = "64")]
+unsafe fn read_length(r: *mut JSStructuredCloneReader)
+                      -> usize {
+  let mut high: u32 = 0;
+  let mut low: u32 = 0;
+  assert!(JS_ReadUint32Pair(r, &mut high as *mut u32, &mut low as *mut u32));
+  return (low << high) as usize;
+}
+
+#[cfg(target_pointer_width = "32")]
+unsafe fn read_length(r: *mut JSStructuredCloneReader)
+                      -> usize {
+  let mut length: u32 = 0;
+  let mut zero: u32 = 0;
+  assert!(JS_ReadUint32Pair(r, &mut length as *mut u32, &mut zero as *mut u32));
+  return length as usize;
+}
+
+unsafe fn read_blob(cx: *mut JSContext,
+                    r: *mut JSStructuredCloneReader)
+                    -> *mut JSObject {
+    let blob_length = read_length(r);
+    let type_str_length = read_length(r);
+    let mut blob_buffer = vec![0u8; blob_length];
+    assert!(JS_ReadBytes(r, blob_buffer.as_mut_ptr() as *mut raw::c_void, blob_length));
+    let mut type_str_buffer = vec![0u8; type_str_length];
+    assert!(JS_ReadBytes(r, type_str_buffer.as_mut_ptr() as *mut raw::c_void, type_str_length));
+    let type_str = String::from_utf8_unchecked(type_str_buffer);
+    let target_global = GlobalScope::from_context(cx);
+    let blob = Blob::new(&target_global, BlobImpl::new_from_bytes(blob_buffer), type_str);
+    return blob.reflector().get_jsobject().get()
+}
+
+unsafe fn write_blob(blob: Root<Blob>,
+                     w: *mut JSStructuredCloneWriter)
+                     -> Result<(), ()> {
+    let blob_vec = try!(blob.get_bytes());
+    let blob_length = blob_vec.len();
+    let type_string_bytes = blob.get_type_string().as_bytes().to_vec();
+    let type_string_length = type_string_bytes.len();
+    assert!(JS_WriteUint32Pair(w, StructuredCloneTags::DomBlob as u32, 0));
+    write_length(w, blob_length);
+    write_length(w, type_string_length);
+    assert!(JS_WriteBytes(w, blob_vec.as_ptr() as *const raw::c_void, blob_length));
+    assert!(JS_WriteBytes(w, type_string_bytes.as_ptr() as *const raw::c_void, type_string_length));
+    return Ok(())
+}
+
+unsafe extern "C" fn read_callback(cx: *mut JSContext,
+                                   r: *mut JSStructuredCloneReader,
+                                   tag: u32,
+                                   _data: u32,
+                                   _closure: *mut raw::c_void)
+                                   -> *mut JSObject {
+    assert!(tag < StructuredCloneTags::Max as u32, "tag should be lower than StructuredCloneTags::Max");
+    assert!(tag > StructuredCloneTags::Min as u32, "tag should be higher than StructuredCloneTags::Min");
+    if tag == StructuredCloneTags::DomBlob as u32 {
+        return read_blob(cx, r)
+    }
+    return ptr::null_mut()
+}
+
+unsafe extern "C" fn write_callback(_cx: *mut JSContext,
+                                    w: *mut JSStructuredCloneWriter,
+                                    obj: HandleObject,
+                                    _closure: *mut raw::c_void)
+                                    -> bool {
+    if let Ok(blob) = root_from_handleobject::<Blob>(obj) {
+        return write_blob(blob, w).is_ok()
+    }
+    return false
+}
+
+unsafe extern "C" fn read_transfer_callback(_cx: *mut JSContext,
+                                            _r: *mut JSStructuredCloneReader,
+                                            _tag: u32,
+                                            _content: *mut raw::c_void,
+                                            _extra_data: u64,
+                                            _closure: *mut raw::c_void,
+                                            _return_object: MutableHandleObject)
+                                            -> bool {
+    false
+}
+
+unsafe extern "C" fn write_transfer_callback(_cx: *mut JSContext,
+                                             _obj: Handle<*mut JSObject>,
+                                             _closure: *mut raw::c_void,
+                                             _tag: *mut u32,
+                                             _ownership: *mut TransferableOwnership,
+                                             _content:  *mut *mut raw::c_void,
+                                             _extra_data: *mut u64)
+                                             -> bool {
+    false
+}
+
+unsafe extern "C" fn free_transfer_callback(_tag: u32,
+                                            _ownership: TransferableOwnership,
+                                            _content: *mut raw::c_void,
+                                            _extra_data: u64,
+                                            _closure: *mut raw::c_void) {
+}
+
+unsafe extern "C" fn report_error_callback(_cx: *mut JSContext, _errorid: u32) {
+}
+
+static STRUCTURED_CLONE_CALLBACKS: JSStructuredCloneCallbacks = JSStructuredCloneCallbacks {
+    read: Some(read_callback),
+    write: Some(write_callback),
+    reportError: Some(report_error_callback),
+    readTransfer: Some(read_transfer_callback),
+    writeTransfer: Some(write_transfer_callback),
+    freeTransfer: Some(free_transfer_callback),
+};
+
 /// A buffer for a structured clone.
 pub enum StructuredCloneData {
     /// A non-serializable (default) variant
     Struct(*mut u64, size_t),
     /// A variant that can be serialized
     Vector(Vec<u8>)
 }
 
@@ -27,17 +176,17 @@ impl StructuredCloneData {
     pub fn write(cx: *mut JSContext, message: HandleValue) -> Fallible<StructuredCloneData> {
         let mut data = ptr::null_mut();
         let mut nbytes = 0;
         let result = unsafe {
             JS_WriteStructuredClone(cx,
                                     message,
                                     &mut data,
                                     &mut nbytes,
-                                    ptr::null(),
+                                    &STRUCTURED_CLONE_CALLBACKS,
                                     ptr::null_mut(),
                                     HandleValue::undefined())
         };
         if !result {
             unsafe {
                 JS_ClearPendingException(cx);
             }
             return Err(Error::DataClone);
@@ -55,24 +204,30 @@ impl StructuredCloneData {
             }
             StructuredCloneData::Vector(msg) => msg
         }
     }
 
     /// Reads a structured clone.
     ///
     /// Panics if `JS_ReadStructuredClone` fails.
-    fn read_clone(global: &GlobalScope, data: *mut u64, nbytes: size_t, rval: MutableHandleValue) {
+    fn read_clone(global: &GlobalScope,
+                  data: *mut u64,
+                  nbytes: size_t,
+                  rval: MutableHandleValue) {
+        let cx = global.get_cx();
+        let globalhandle = global.reflector().get_jsobject();
+        let _ac = JSAutoCompartment::new(cx, globalhandle.get());
         unsafe {
-            assert!(JS_ReadStructuredClone(global.get_cx(),
+            assert!(JS_ReadStructuredClone(cx,
                                            data,
                                            nbytes,
                                            JS_STRUCTURED_CLONE_VERSION,
                                            rval,
-                                           ptr::null(),
+                                           &STRUCTURED_CLONE_CALLBACKS,
                                            ptr::null_mut()));
         }
     }
 
     /// Thunk for the actual `read_clone` method. Resolves proper variant for read_clone.
     pub fn read(self, global: &GlobalScope, rval: MutableHandleValue) {
         match self {
             StructuredCloneData::Vector(mut vec_msg) => {
--- a/servo/components/script/dom/blob.rs
+++ b/servo/components/script/dom/blob.rs
@@ -158,16 +158,21 @@ impl Blob {
                 parent.get_bytes().map(|v| {
                     let range = rel_pos.to_abs_range(v.len());
                     v.index(range).to_vec()
                 })
             }
         }
     }
 
+    /// Get a copy of the type_string
+    pub fn get_type_string(&self) -> String {
+        self.type_string.clone()
+    }
+
     /// Get a FileID representing the Blob content,
     /// used by URL.createObjectURL
     pub fn get_blob_url_id(&self) -> Uuid {
         let opt_sliced_parent = match *self.blob_impl.borrow() {
             BlobImpl::Sliced(ref parent, ref rel_pos) => {
                 Some((parent.promote(/* set_valid is */ false), rel_pos.clone(), parent.Size()))
             }
             _ => None
--- a/servo/components/style/gecko/non_ts_pseudo_class_list.rs
+++ b/servo/components/style/gecko/non_ts_pseudo_class_list.rs
@@ -62,12 +62,17 @@ macro_rules! apply_non_ts_list {
                 ("read-write", ReadWrite, _, IN_READ_WRITE_STATE, _),
                 ("read-only", ReadOnly, _, IN_READ_WRITE_STATE, _),
 
                 ("-moz-browser-frame", MozBrowserFrame, mozBrowserFrame, _, PSEUDO_CLASS_INTERNAL),
                 ("-moz-table-border-nonzero", MozTableBorderNonzero, mozTableBorderNonzero, _, PSEUDO_CLASS_INTERNAL),
             ],
             string: [
                 ("-moz-system-metric", MozSystemMetric, mozSystemMetric, _, PSEUDO_CLASS_INTERNAL),
+                ("-moz-locale-dir", MozLocaleDir, mozLocaleDir, _, PSEUDO_CLASS_INTERNAL),
+                ("-moz-empty-except-children-with-localname", MozEmptyExceptChildrenWithLocalname,
+                 mozEmptyExceptChildrenWithLocalname, _, PSEUDO_CLASS_INTERNAL),
+                ("dir", Dir, dir, _, _),
+                ("lang", Lang, lang, _, _),
             ]
         }
     }
 }
--- a/servo/components/style/gecko/selector_parser.rs
+++ b/servo/components/style/gecko/selector_parser.rs
@@ -146,34 +146,41 @@ macro_rules! pseudo_class_name {
         #[derive(Clone, Debug, PartialEq, Eq, Hash)]
         pub enum NonTSPseudoClass {
             $(
                 #[doc = $css]
                 $name,
             )*
             $(
                 #[doc = $s_css]
-                $s_name(Box<str>),
+                $s_name(Box<[u16]>),
             )*
             /// The non-standard `:-moz-any` pseudo-class.
             MozAny(Vec<ComplexSelector<SelectorImpl>>),
         }
     }
 }
 apply_non_ts_list!(pseudo_class_name);
 
 impl ToCss for NonTSPseudoClass {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        use cssparser::CssStringWriter;
+        use fmt::Write;
         macro_rules! pseudo_class_serialize {
             (bare: [$(($css:expr, $name:ident, $gecko_type:tt, $state:tt, $flags:tt),)*],
              string: [$(($s_css:expr, $s_name:ident, $s_gecko_type:tt, $s_state:tt, $s_flags:tt),)*]) => {
                 match *self {
                     $(NonTSPseudoClass::$name => concat!(":", $css),)*
                     $(NonTSPseudoClass::$s_name(ref s) => {
-                        return dest.write_str(&format!(":{}({})", $s_css, s))
+                        write!(dest, ":{}(", $s_css)?;
+                        {
+                            let mut css = CssStringWriter::new(dest);
+                            css.write_str(&String::from_utf16(&s).unwrap())?;
+                        }
+                        return dest.write_str(")")
                     }, )*
                     NonTSPseudoClass::MozAny(ref selectors) => {
                         dest.write_str(":-moz-any(")?;
                         let mut iter = selectors.iter();
                         let first = iter.next().expect(":-moz-any must have at least 1 selector");
                         first.to_css(dest)?;
                         for selector in iter {
                             dest.write_str(", ")?;
@@ -329,18 +336,21 @@ impl<'a> ::selectors::Parser for Selecto
                                             name: Cow<str>,
                                             parser: &mut Parser)
                                             -> Result<NonTSPseudoClass, ()> {
         macro_rules! pseudo_class_string_parse {
             (bare: [$(($css:expr, $name:ident, $gecko_type:tt, $state:tt, $flags:tt),)*],
              string: [$(($s_css:expr, $s_name:ident, $s_gecko_type:tt, $s_state:tt, $s_flags:tt),)*]) => {
                 match_ignore_ascii_case! { &name,
                     $($s_css => {
-                        let name = String::from(parser.expect_ident_or_string()?).into_boxed_str();
-                        NonTSPseudoClass::$s_name(name)
+                        let name = parser.expect_ident_or_string()?;
+                        // convert to null terminated utf16 string
+                        // since that's what Gecko deals with
+                        let utf16: Vec<u16> = name.encode_utf16().chain(Some(0u16)).collect();
+                        NonTSPseudoClass::$s_name(utf16.into_boxed_slice())
                     }, )*
                     "-moz-any" => {
                         let selectors = parser.parse_comma_separated(|input| {
                             ComplexSelector::parse(self, input)
                         })?;
                         // Selectors inside `:-moz-any` may not include combinators.
                         if selectors.iter().any(|s| s.next.is_some()) {
                             return Err(())
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -30,16 +30,17 @@ use gecko_bindings::bindings::{Gecko_IsL
 use gecko_bindings::bindings::{Gecko_IsUnvisitedLink, Gecko_IsVisitedLink, Gecko_Namespace};
 use gecko_bindings::bindings::{Gecko_SetNodeFlags, Gecko_UnsetNodeFlags};
 use gecko_bindings::bindings::Gecko_ClassOrClassList;
 use gecko_bindings::bindings::Gecko_ElementHasCSSAnimations;
 use gecko_bindings::bindings::Gecko_GetAnimationRule;
 use gecko_bindings::bindings::Gecko_GetHTMLPresentationAttrDeclarationBlock;
 use gecko_bindings::bindings::Gecko_GetStyleAttrDeclarationBlock;
 use gecko_bindings::bindings::Gecko_GetStyleContext;
+use gecko_bindings::bindings::Gecko_MatchStringArgPseudo;
 use gecko_bindings::bindings::Gecko_UpdateAnimations;
 use gecko_bindings::structs;
 use gecko_bindings::structs::{RawGeckoElement, RawGeckoNode};
 use gecko_bindings::structs::{nsIAtom, nsIContent, nsStyleContext};
 use gecko_bindings::structs::EffectCompositor_CascadeLevel as CascadeLevel;
 use gecko_bindings::structs::NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO;
 use gecko_bindings::structs::NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE;
 use gecko_bindings::sugar::ownership::HasArcFFI;
@@ -697,20 +698,36 @@ impl<'le> ::selectors::Element for Gecko
             NonTSPseudoClass::ReadOnly => {
                 !self.get_state().contains(pseudo_class.state_flag())
             }
 
             NonTSPseudoClass::MozTableBorderNonzero |
             NonTSPseudoClass::MozBrowserFrame => unsafe {
                 Gecko_MatchesElement(pseudo_class.to_gecko_pseudoclasstype().unwrap(), self.0)
             },
-            NonTSPseudoClass::MozSystemMetric(_) => false,
             NonTSPseudoClass::MozAny(ref sels) => {
                 sels.iter().any(|s| matches_complex_selector(s, self, None, relations, flags))
             }
+            NonTSPseudoClass::MozSystemMetric(ref s) |
+            NonTSPseudoClass::MozLocaleDir(ref s) |
+            NonTSPseudoClass::MozEmptyExceptChildrenWithLocalname(ref s) |
+            NonTSPseudoClass::Dir(ref s) |
+            NonTSPseudoClass::Lang(ref s) => {
+                use selectors::matching::HAS_SLOW_SELECTOR;
+                unsafe {
+                    let mut set_slow_selector = false;
+                    let matches = Gecko_MatchStringArgPseudo(self.0,
+                                       pseudo_class.to_gecko_pseudoclasstype().unwrap(),
+                                       s.as_ptr(), &mut set_slow_selector);
+                    if set_slow_selector {
+                        *flags |= HAS_SLOW_SELECTOR;
+                    }
+                    matches
+                }
+            }
         }
     }
 
     fn get_id(&self) -> Option<Atom> {
         let ptr = unsafe {
             bindings::Gecko_AtomAttrValue(self.0,
                                           atom!("id").as_ptr())
         };
--- a/servo/components/style/gecko_bindings/bindings.rs
+++ b/servo/components/style/gecko_bindings/bindings.rs
@@ -777,16 +777,27 @@ extern "C" {
                                                  len: usize);
 }
 extern "C" {
     pub fn Gecko_EnsureStyleTransitionArrayLength(array:
                                                       *mut ::std::os::raw::c_void,
                                                   len: usize);
 }
 extern "C" {
+    pub fn Gecko_ClearWillChange(display: *mut nsStyleDisplay, length: usize);
+}
+extern "C" {
+    pub fn Gecko_AppendWillChange(display: *mut nsStyleDisplay,
+                                  atom: *mut nsIAtom);
+}
+extern "C" {
+    pub fn Gecko_CopyWillChangeFrom(dest: *mut nsStyleDisplay,
+                                    src: *mut nsStyleDisplay);
+}
+extern "C" {
     pub fn Gecko_AnimationAppendKeyframe(keyframes:
                                              RawGeckoKeyframeListBorrowedMut,
                                          offset: f32,
                                          timingFunction:
                                              *const nsTimingFunction)
      -> *mut Keyframe;
 }
 extern "C" {
@@ -999,16 +1010,22 @@ extern "C" {
 }
 extern "C" {
     pub fn Gecko_GetLookAndFeelSystemColor(color_id: i32,
                                            pres_context:
                                                RawGeckoPresContextBorrowed)
      -> nscolor;
 }
 extern "C" {
+    pub fn Gecko_MatchStringArgPseudo(element: RawGeckoElementBorrowed,
+                                      type_: CSSPseudoClassType,
+                                      ident: *const u16,
+                                      set_slow_selector: *mut bool) -> bool;
+}
+extern "C" {
     pub fn Gecko_Construct_Default_nsStyleFont(ptr: *mut nsStyleFont,
                                                pres_context:
                                                    RawGeckoPresContextBorrowed);
 }
 extern "C" {
     pub fn Gecko_CopyConstruct_nsStyleFont(ptr: *mut nsStyleFont,
                                            other: *const nsStyleFont);
 }
--- a/servo/components/style/properties/data.py
+++ b/servo/components/style/properties/data.py
@@ -91,17 +91,18 @@ def arg_to_bool(arg):
     return arg == "True"
 
 
 class Longhand(object):
     def __init__(self, style_struct, name, spec=None, animatable=None, derived_from=None, keyword=None,
                  predefined_type=None, custom_cascade=False, experimental=False, internal=False,
                  need_clone=False, need_index=False, gecko_ffi_name=None, depend_on_viewport_size=False,
                  allowed_in_keyframe_block=True, complex_color=False, cast_type='u8',
-                 has_uncacheable_values=False, logical=False, alias=None, extra_prefixes=None, boxed=False):
+                 has_uncacheable_values=False, logical=False, alias=None, extra_prefixes=None, boxed=False,
+                 creates_stacking_context=False, fixpos_cb=False, abspos_cb=False):
         self.name = name
         if not spec:
             raise TypeError("Spec should be specified for %s" % name)
         self.spec = spec
         self.keyword = keyword
         self.predefined_type = predefined_type
         self.ident = to_rust_ident(name)
         self.camel_case = to_camel_case(self.ident)
@@ -115,16 +116,19 @@ class Longhand(object):
         self.depend_on_viewport_size = depend_on_viewport_size
         self.derived_from = (derived_from or "").split()
         self.complex_color = complex_color
         self.cast_type = cast_type
         self.logical = arg_to_bool(logical)
         self.alias = alias.split() if alias else []
         self.extra_prefixes = extra_prefixes.split() if extra_prefixes else []
         self.boxed = arg_to_bool(boxed)
+        self.creates_stacking_context = arg_to_bool(creates_stacking_context)
+        self.fixpos_cb = arg_to_bool(fixpos_cb)
+        self.abspos_cb = arg_to_bool(abspos_cb)
 
         # https://drafts.csswg.org/css-animations/#keyframes
         # > The <declaration-list> inside of <keyframe-block> accepts any CSS property
         # > except those defined in this specification,
         # > but does accept the `animation-play-state` property and interprets it specially.
         self.allowed_in_keyframe_block = allowed_in_keyframe_block \
             and allowed_in_keyframe_block != "False"
 
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -1418,17 +1418,17 @@ fn static_assert() {
                           animation-name animation-delay animation-duration
                           animation-direction animation-fill-mode animation-play-state
                           animation-iteration-count animation-timing-function
                           transition-duration transition-delay
                           transition-timing-function transition-property
                           page-break-before page-break-after
                           scroll-snap-points-x scroll-snap-points-y transform
                           scroll-snap-type-y scroll-snap-coordinate
-                          perspective-origin transform-origin -moz-binding""" %>
+                          perspective-origin transform-origin -moz-binding will-change""" %>
 <%self:impl_trait style_struct_name="Box" skip_longhands="${skip_box_longhands}">
 
     // We manually-implement the |display| property until we get general
     // infrastructure for preffing certain values.
     <% display_keyword = Keyword("display", "inline block inline-block table inline-table table-row-group " +
                                             "table-header-group table-footer-group table-row table-column-group " +
                                             "table-column table-cell table-caption list-item flex none " +
                                             "inline-flex grid inline-grid ruby ruby-base ruby-base-container " +
@@ -1913,16 +1913,102 @@ fn static_assert() {
             horizontal: LengthOrPercentage::from_gecko_style_coord(&self.gecko.mTransformOrigin[0])
                 .expect("clone for LengthOrPercentage failed"),
             vertical: LengthOrPercentage::from_gecko_style_coord(&self.gecko.mTransformOrigin[1])
                 .expect("clone for LengthOrPercentage failed"),
             depth: Au::from_gecko_style_coord(&self.gecko.mTransformOrigin[2])
                 .expect("clone for Length failed"),
         }
     }
+
+    pub fn set_will_change(&mut self, v: longhands::will_change::computed_value::T) {
+        use gecko_bindings::bindings::{Gecko_AppendWillChange, Gecko_ClearWillChange};
+        use gecko_bindings::structs::NS_STYLE_WILL_CHANGE_OPACITY;
+        use gecko_bindings::structs::NS_STYLE_WILL_CHANGE_SCROLL;
+        use gecko_bindings::structs::NS_STYLE_WILL_CHANGE_TRANSFORM;
+        use properties::PropertyId;
+        use properties::longhands::will_change::computed_value::T;
+
+        fn will_change_bitfield_from_prop_flags(prop: &LonghandId) -> u8 {
+            use properties::{ABSPOS_CB, CREATES_STACKING_CONTEXT, FIXPOS_CB};
+            use gecko_bindings::structs::NS_STYLE_WILL_CHANGE_ABSPOS_CB;
+            use gecko_bindings::structs::NS_STYLE_WILL_CHANGE_FIXPOS_CB;
+            use gecko_bindings::structs::NS_STYLE_WILL_CHANGE_STACKING_CONTEXT;
+            let servo_flags = prop.flags();
+            let mut bitfield = 0;
+
+            if servo_flags.contains(CREATES_STACKING_CONTEXT) {
+                bitfield |= NS_STYLE_WILL_CHANGE_STACKING_CONTEXT;
+            }
+            if servo_flags.contains(FIXPOS_CB) {
+                bitfield |= NS_STYLE_WILL_CHANGE_FIXPOS_CB;
+            }
+            if servo_flags.contains(ABSPOS_CB) {
+                bitfield |= NS_STYLE_WILL_CHANGE_ABSPOS_CB;
+            }
+
+            bitfield as u8
+        }
+
+        self.gecko.mWillChangeBitField = 0;
+
+        match v {
+            T::AnimateableFeatures(features) => {
+                unsafe {
+                    Gecko_ClearWillChange(&mut self.gecko, features.len());
+                }
+
+                for feature in features.iter() {
+                    if feature == &atom!("scroll-position") {
+                        self.gecko.mWillChangeBitField |= NS_STYLE_WILL_CHANGE_SCROLL as u8;
+                    } else if feature == &atom!("opacity") {
+                        self.gecko.mWillChangeBitField |= NS_STYLE_WILL_CHANGE_OPACITY as u8;
+                    } else if feature == &atom!("transform") {
+                        self.gecko.mWillChangeBitField |= NS_STYLE_WILL_CHANGE_TRANSFORM as u8;
+                    }
+
+                    unsafe {
+                        Gecko_AppendWillChange(&mut self.gecko, feature.as_ptr());
+                    }
+
+                    if let Ok(prop_id) = PropertyId::parse(feature.to_string().into()) {
+                        match prop_id.as_shorthand() {
+                            Ok(shorthand) => {
+                                for longhand in shorthand.longhands() {
+                                    self.gecko.mWillChangeBitField |=
+                                        will_change_bitfield_from_prop_flags(longhand);
+                                }
+                            },
+                            Err(longhand_or_custom) => {
+                                if let PropertyDeclarationId::Longhand(longhand)
+                                    = longhand_or_custom {
+                                    self.gecko.mWillChangeBitField |=
+                                        will_change_bitfield_from_prop_flags(&longhand);
+                                }
+                            },
+                        }
+                    }
+                }
+            },
+            T::Auto => {
+                unsafe {
+                    Gecko_ClearWillChange(&mut self.gecko, 0);
+                }
+            },
+        };
+    }
+
+    pub fn copy_will_change_from(&mut self, other: &Self) {
+        use gecko_bindings::bindings::Gecko_CopyWillChangeFrom;
+
+        self.gecko.mWillChangeBitField = other.gecko.mWillChangeBitField;
+        unsafe {
+            Gecko_CopyWillChangeFrom(&mut self.gecko, &other.gecko as *const _ as *mut _);
+        }
+    }
 </%self:impl_trait>
 
 <%def name="simple_image_array_property(name, shorthand, field_name)">
     <%
         image_layers_field = "mImage" if shorthand == "background" else "mMask"
     %>
     pub fn copy_${shorthand}_${name}_from(&mut self, other: &Self) {
         use gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType;
--- a/servo/components/style/properties/longhand/box.mako.rs
+++ b/servo/components/style/properties/longhand/box.mako.rs
@@ -103,16 +103,18 @@
                          products="gecko", animatable=False, internal=True,
                          spec="Internal (not web-exposed)")}
 
 <%helpers:single_keyword_computed name="position"
                                   values="static absolute relative fixed"
                                   need_clone="True"
                                   extra_gecko_values="sticky"
                                   animatable="False"
+                                  creates_stacking_context="True"
+                                  abspos_cb="True"
                                   spec="https://drafts.csswg.org/css-position/#position-property">
     impl SpecifiedValue {
         pub fn is_absolutely_positioned_style(&self) -> bool {
             matches!(*self, SpecifiedValue::absolute | SpecifiedValue::fixed)
         }
     }
 
     use values::HasViewportPercentage;
@@ -1111,16 +1113,18 @@
                           animatable=True,
                           allow_empty=True,
                           delegate_animate=True)}
 
 
 
 <%helpers:longhand name="transform" products="gecko servo" extra_prefixes="webkit"
                    animatable="True"
+                   creates_stacking_context="True"
+                   fixpos_cb="True"
                    spec="https://drafts.csswg.org/css-transforms/#propdef-transform">
     use app_units::Au;
     use style_traits::ToCss;
     use values::CSSFloat;
     use values::HasViewportPercentage;
 
     use std::fmt;
 
@@ -1667,16 +1671,17 @@
 </%helpers:longhand>
 
 // Compositing and Blending Level 1
 // http://www.w3.org/TR/compositing-1/
 ${helpers.single_keyword("isolation",
                          "auto isolate",
                          products="gecko",
                          spec="https://drafts.fxtf.org/compositing/#isolation",
+                         creates_stacking_context=True,
                          animatable=False)}
 
 // TODO add support for logical values recto and verso
 ${helpers.single_keyword("page-break-after",
                          "auto always avoid left right",
                          products="gecko",
                          spec="https://drafts.csswg.org/css2/page.html#propdef-page-break-after",
                          animatable=False)}
@@ -1705,16 +1710,18 @@
 
 ${helpers.predefined_type("perspective",
                           "LengthOrNone",
                           "Either::Second(None_)",
                           gecko_ffi_name="mChildPerspective",
                           spec="https://drafts.csswg.org/css-transforms/#perspective",
                           extra_prefixes="moz webkit",
                           boxed=True,
+                          creates_stacking_context=True,
+                          fixpos_cb=True,
                           animatable=True)}
 
 // FIXME: This prop should be animatable
 <%helpers:longhand name="perspective-origin" boxed="True" animatable="False" extra_prefixes="moz webkit"
                    spec="https://drafts.csswg.org/css-transforms/#perspective-origin-property">
     use std::fmt;
     use style_traits::ToCss;
     use values::HasViewportPercentage;
@@ -1814,16 +1821,18 @@
                          animatable=False)}
 
 // `auto` keyword is not supported in gecko yet.
 ${helpers.single_keyword("transform-style",
                          "auto flat preserve-3d" if product == "servo" else
                          "flat preserve-3d",
                          spec="https://drafts.csswg.org/css-transforms/#transform-style-property",
                          extra_prefixes="moz webkit",
+                         creates_stacking_context=True,
+                         fixpos_cb=True,
                          animatable=False)}
 
 <%helpers:longhand name="transform-origin" animatable="True" extra_prefixes="moz webkit" boxed="True"
                    spec="https://drafts.csswg.org/css-transforms/#transform-origin-property">
     use app_units::Au;
     use std::fmt;
     use style_traits::ToCss;
     use values::HasViewportPercentage;
@@ -1968,8 +1977,72 @@
 
 ${helpers.single_keyword("-moz-orient",
                           "inline block horizontal vertical",
                           products="gecko",
                           gecko_ffi_name="mOrient",
                           gecko_enum_prefix="StyleOrient",
                           spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-orient)",
                           animatable=False)}
+
+<%helpers:longhand name="will-change" products="gecko" animatable="False"
+                   spec="https://drafts.csswg.org/css-will-change/#will-change">
+    use cssparser::serialize_identifier;
+    use std::fmt;
+    use style_traits::ToCss;
+    use values::HasViewportPercentage;
+    use values::computed::ComputedValueAsSpecified;
+
+    impl ComputedValueAsSpecified for SpecifiedValue {}
+    no_viewport_percentage!(SpecifiedValue);
+
+    pub mod computed_value {
+        pub use super::SpecifiedValue as T;
+    }
+
+    #[derive(Debug, Clone, PartialEq)]
+    #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+    pub enum SpecifiedValue {
+        Auto,
+        AnimateableFeatures(Vec<Atom>),
+    }
+
+    impl ToCss for SpecifiedValue {
+        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+            match *self {
+                SpecifiedValue::Auto => dest.write_str("auto"),
+                SpecifiedValue::AnimateableFeatures(ref features) => {
+                    let (first, rest) = features.split_first().unwrap();
+                    // handle head element
+                    serialize_identifier(&*first.to_string(), dest)?;
+                    // handle tail, precede each with a delimiter
+                    for feature in rest {
+                        dest.write_str(", ")?;
+                        serialize_identifier(&*feature.to_string(), dest)?;
+                    }
+                    Ok(())
+                }
+            }
+        }
+    }
+
+    #[inline]
+    pub fn get_initial_value() -> computed_value::T {
+        computed_value::T::Auto
+    }
+
+    /// auto | <animateable-feature>#
+    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+        if input.try(|input| input.expect_ident_matching("auto")).is_ok() {
+            Ok(computed_value::T::Auto)
+        } else {
+            input.parse_comma_separated(|i| {
+                let ident = i.expect_ident()?;
+                match_ignore_ascii_case! { &ident,
+                    "will-change" | "none" | "all" | "auto" |
+                    "initial" | "inherit" | "unset" | "default" => return Err(()),
+                    _ => {},
+                }
+                Ok((Atom::from(ident)))
+            }).map(SpecifiedValue::AnimateableFeatures)
+        }
+    }
+</%helpers:longhand>
--- a/servo/components/style/properties/longhand/effects.mako.rs
+++ b/servo/components/style/properties/longhand/effects.mako.rs
@@ -6,16 +6,17 @@
 
 // Box-shadow, etc.
 <% data.new_style_struct("Effects", inherited=False) %>
 
 ${helpers.predefined_type("opacity",
                           "Opacity",
                           "1.0",
                           animatable=True,
+                          creates_stacking_context=True,
                           spec="https://drafts.csswg.org/css-color/#opacity")}
 
 <%helpers:vector_longhand name="box-shadow" allow_empty="True"
                           animatable="True" extra_prefixes="webkit"
                           spec="https://drafts.csswg.org/css-backgrounds/#box-shadow">
     use cssparser;
     use std::fmt;
     use style_traits::ToCss;
@@ -81,16 +82,18 @@
                           "ClipRectOrAuto",
                           "computed::ClipRectOrAuto::auto()",
                           animatable=False,
                           boxed="True",
                           spec="https://drafts.fxtf.org/css-masking/#clip-property")}
 
 // FIXME: This prop should be animatable
 <%helpers:longhand name="filter" animatable="False" extra_prefixes="webkit"
+                    creates_stacking_context="True"
+                    fixpos_cb="True"
                    spec="https://drafts.fxtf.org/filters/#propdef-filter">
     //pub use self::computed_value::T as SpecifiedValue;
     use cssparser;
     use std::fmt;
     use style_traits::{self, ToCss};
     use values::{CSSFloat, HasViewportPercentage};
     use values::specified::{Angle, CSSColor, Length, Shadow};
     use values::specified::url::SpecifiedUrl;
@@ -511,9 +514,10 @@ pub fn parse_origin(context: &ParserCont
     }
 }
 
 ${helpers.single_keyword("mix-blend-mode",
                          """normal multiply screen overlay darken lighten color-dodge
                             color-burn hard-light soft-light difference exclusion hue
                             saturation color luminosity""", gecko_constant_prefix="NS_STYLE_BLEND",
                          animatable=False,
+                         creates_stacking_context=True,
                          spec="https://drafts.fxtf.org/compositing/#propdef-mix-blend-mode")}
--- a/servo/components/style/properties/longhand/position.mako.rs
+++ b/servo/components/style/properties/longhand/position.mako.rs
@@ -21,16 +21,17 @@
                               "computed::LengthOrPercentageOrAuto::Auto",
                               spec="https://drafts.csswg.org/css-logical-props/#propdef-offset-%s" % side,
                               animatable=True, logical=True)}
 % endfor
 
 ${helpers.predefined_type("z-index", "IntegerOrAuto",
                           "Either::Second(Auto)",
                           spec="https://www.w3.org/TR/CSS2/visuren.html#z-index",
+                          creates_stacking_context=True,
                           animatable="True")}
 
 // CSS Flexible Box Layout Module Level 1
 // http://www.w3.org/TR/css3-flexbox/
 
 // Flex container properties
 ${helpers.single_keyword("flex-direction", "row row-reverse column column-reverse",
                          spec="https://drafts.csswg.org/css-flexbox/#flex-direction-property",
--- a/servo/components/style/properties/longhand/svg.mako.rs
+++ b/servo/components/style/properties/longhand/svg.mako.rs
@@ -54,16 +54,17 @@
 
 // CSS Masking Module Level 1
 // https://drafts.fxtf.org/css-masking
 ${helpers.single_keyword("mask-type", "luminance alpha",
                          products="gecko", animatable=False,
                          spec="https://drafts.fxtf.org/css-masking/#propdef-mask-type")}
 
 <%helpers:longhand name="clip-path" animatable="False" products="gecko" boxed="True"
+                   creates_stacking_context="True"
                    spec="https://drafts.fxtf.org/css-masking/#propdef-clip-path">
     use std::fmt;
     use style_traits::ToCss;
     use values::HasViewportPercentage;
     use values::specified::basic_shape::{ShapeSource, GeometryBox};
 
     pub mod computed_value {
         use app_units::Au;
@@ -184,17 +185,18 @@
                          "add subtract intersect exclude",
                          vector=True,
                          products="gecko",
                          extra_prefixes="webkit",
                          animatable=False,
                          spec="https://drafts.fxtf.org/css-masking/#propdef-mask-composite")}
 
 <%helpers:vector_longhand name="mask-image" products="gecko" animatable="False" extra_prefixes="webkit"
-                          has_uncacheable_values="${product == 'gecko'}",
+                          has_uncacheable_values="${product == 'gecko'}"
+                          creates_stacking_context="True"
                           spec="https://drafts.fxtf.org/css-masking/#propdef-mask-image">
     use std::fmt;
     use style_traits::ToCss;
     use std::sync::Arc;
     use values::specified::Image;
     use values::specified::url::SpecifiedUrl;
     use values::HasViewportPercentage;
 
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -415,16 +415,30 @@ impl Parse for CSSWideKeyword {
             "initial" => Ok(CSSWideKeyword::Initial),
             "inherit" => Ok(CSSWideKeyword::Inherit),
             "unset" => Ok(CSSWideKeyword::Unset),
             _ => Err(())
         }
     }
 }
 
+bitflags! {
+    /// A set of flags for properties.
+    pub flags PropertyFlags: u8 {
+        /// This property requires a stacking context.
+        const CREATES_STACKING_CONTEXT = 0x01,
+        /// This property has values that can establish a containing block for
+        /// fixed positioned and absolutely positioned elements.
+        const FIXPOS_CB = 0x02,
+        /// This property has values that can establish a containing block for
+        /// absolutely positioned elements.
+        const ABSPOS_CB = 0x04,
+    }
+}
+
 /// An identifier for a given longhand property.
 #[derive(Clone, Copy, Eq, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum LonghandId {
     % for i, property in enumerate(data.longhands):
         /// ${property.name}
         ${property.camel_case} = ${i},
     % endfor
@@ -453,16 +467,35 @@ impl LonghandId {
                             </%def>
                         </%helpers:logical_setter_helper>
                     }
                 % endif
             % endfor
             _ => *self
         }
     }
+
+    /// Returns PropertyFlags for given property.
+    pub fn flags(&self) -> PropertyFlags {
+        match *self {
+            % for property in data.longhands:
+                LonghandId::${property.camel_case} =>
+                    %if property.creates_stacking_context:
+                        CREATES_STACKING_CONTEXT |
+                    %endif
+                    %if property.fixpos_cb:
+                        FIXPOS_CB |
+                    %endif
+                    %if property.abspos_cb:
+                        ABSPOS_CB |
+                    %endif
+                    PropertyFlags::empty(),
+            % endfor
+        }
+    }
 }
 
 /// An identifier for a given shorthand property.
 #[derive(Clone, Copy, Eq, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum ShorthandId {
     % for property in data.shorthands:
         /// ${property.name}
--- a/servo/ports/geckolib/Cargo.toml
+++ b/servo/ports/geckolib/Cargo.toml
@@ -12,17 +12,16 @@ crate-type = ["staticlib", "rlib"]
 [features]
 bindgen = ["style/use_bindgen"]
 testing = ["style/testing"]
 
 [dependencies]
 atomic_refcell = "0.1"
 cssparser = "0.12"
 env_logger = {version = "0.4", default-features = false} # disable `regex` to reduce code size
-lazy_static = "0.2"
 libc = "0.2"
 log = {version = "0.3.5", features = ["release_max_level_info"]}
 parking_lot = "0.3"
 selectors = {path = "../../components/selectors"}
 servo_url = {path = "../../components/url"}
 style = {path = "../../components/style", features = ["gecko"]}
 style_traits = {path = "../../components/style_traits"}
 
--- a/servo/ports/geckolib/lib.rs
+++ b/servo/ports/geckolib/lib.rs
@@ -2,17 +2,16 @@
  * 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/. */
 
 #![deny(warnings)]
 
 extern crate atomic_refcell;
 extern crate cssparser;
 extern crate env_logger;
-#[macro_use] extern crate lazy_static;
 extern crate libc;
 #[macro_use] extern crate log;
 extern crate parking_lot;
 extern crate selectors;
 extern crate servo_url;
 #[macro_use] extern crate style;
 extern crate style_traits;
 
new file mode 100644
--- /dev/null
+++ b/servo/tests/unit/style/parsing/box_.rs
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use cssparser::Parser;
+use media_queries::CSSErrorReporterTest;
+use parsing::parse;
+use style::parser::ParserContext;
+use style::stylesheets::Origin;
+use style_traits::ToCss;
+
+#[test]
+fn test_will_change() {
+    use style::properties::longhands::will_change;
+
+    assert_roundtrip_with_context!(will_change::parse, "auto");
+    assert_roundtrip_with_context!(will_change::parse, "scroll-position");
+    assert_roundtrip_with_context!(will_change::parse, "contents");
+    assert_roundtrip_with_context!(will_change::parse, "transition");
+    assert_roundtrip_with_context!(will_change::parse, "opacity, transform");
+
+    assert!(parse(will_change::parse, "will-change").is_err());
+    assert!(parse(will_change::parse, "all").is_err());
+    assert!(parse(will_change::parse, "none").is_err());
+    assert!(parse(will_change::parse, "contents, auto").is_err());
+    assert!(parse(will_change::parse, "contents, inherit, initial").is_err());
+    assert!(parse(will_change::parse, "transform scroll-position").is_err());
+}
--- a/servo/tests/unit/style/parsing/mod.rs
+++ b/servo/tests/unit/style/parsing/mod.rs
@@ -80,16 +80,17 @@ macro_rules! parse_longhand {
         $name::parse(&context, &mut Parser::new($s)).unwrap()
     }};
 }
 
 mod animation;
 mod background;
 mod basic_shape;
 mod border;
+mod box_;
 mod column;
 mod effects;
 mod font;
 mod image;
 mod inherited_box;
 mod inherited_text;
 mod length;
 mod mask;
--- a/storage/.eslintrc.js
+++ b/storage/.eslintrc.js
@@ -1,11 +1,11 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../toolkit/.eslintrc.js"
+    "plugin:mozilla/recommended"
   ],
 
   rules: {
     "no-undef": "error"
   }
 };
--- a/storage/test/unit/.eslintrc.js
+++ b/storage/test/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/taskcluster/ci/test/test-platforms.yml
+++ b/taskcluster/ci/test/test-platforms.yml
@@ -20,61 +20,51 @@ linux32/debug:
     test-sets:
         - linux32-tests
         - external-media-tests
 linux32/opt:
     build-platform: linux/opt
     test-sets:
         -  linux32-tests
         -  linux32-opt-tests
-        - external-media-tests
-        - external-media-tests-slow
 linux32-nightly/opt:
     build-platform: linux-nightly/opt
     test-sets:
         -  linux32-tests
         -  linux32-opt-tests
-        - external-media-tests
-        - external-media-tests-slow
         - awsy
 
 linux64/debug:
     build-platform: linux64/debug
     test-sets:
         - common-tests
-        - external-media-tests
         - web-platform-tests
 linux64/opt:
     build-platform: linux64/opt
     test-sets:
         - common-tests
-        - external-media-tests
-        - external-media-tests-slow
         - web-platform-tests
         - opt-only-tests
         - desktop-screenshot-capture
         - talos
 linux64-nightly/opt:
     build-platform: linux64-nightly/opt
     test-sets:
         - common-tests
-        - external-media-tests
         - web-platform-tests
         - opt-only-tests
         - desktop-screenshot-capture
         - talos
         - awsy
 
 # TODO: use 'pgo' and 'asan' labels here, instead of -pgo/opt
 linux64-pgo/opt:
     build-platform: linux64-pgo/opt
     test-sets:
         - common-tests
-        - external-media-tests
-        - external-media-tests-slow
         - web-platform-tests
         - talos
 
 linux64-asan/opt:
     build-platform: linux64-asan/opt
     test-sets:
         - common-tests
 
--- a/taskcluster/ci/test/tests.yml
+++ b/taskcluster/ci/test/tests.yml
@@ -123,17 +123,16 @@ external-media-tests-base:
 external-media-tests-twitch:
     description: "External Media Test Twitch run"
     suite: external-media-tests/twitch
     treeherder-symbol: tc-VP(b-t)
     unittest-try-name: media-twitch-tests
     e10s: false
     instance-size:
         by-test-platform:
-            linux.*: xlarge
             default: default
     tier: 2
     max-run-time: 5400
     docker-image: {"in-tree": "desktop1604-test"}
     run-on-projects:
         by-test-platform:
             windows.*: ['mozilla-central', 'try']
             default: ['all']
--- a/testing/mozharness/scripts/release/generate-checksums.py
+++ b/testing/mozharness/scripts/release/generate-checksums.py
@@ -288,16 +288,17 @@ class ChecksumsGenerator(BaseScript, Vir
         tools_dir = path.join(dirs["abs_work_dir"], "tools")
         self.copyfile(os.path.join(tools_dir, 'scripts', 'release', 'KEY'),
                       'KEY')
         files = ['KEY']
 
         for fmt in self.config["formats"]:
             files.append(self._get_sums_filename(fmt))
             files.append("{}.asc".format(self._get_sums_filename(fmt)))
+            files.append(self._get_summary_filename(fmt))
 
         bucket = self._get_bucket()
         for f in files:
             dest = posixpath.join(self.file_prefix, f)
             self.info("Uploading {} to {}".format(f, dest))
             key = bucket.new_key(dest)
             key.set_contents_from_filename(f, headers={'Content-Type': 'text/plain'})
 
--- a/toolkit/.eslintrc.js
+++ b/toolkit/.eslintrc.js
@@ -1,288 +1,13 @@
 "use strict";
 
 module.exports = {
-  // When adding items to this file please check for effects on all of toolkit
-  // and browser
-  "rules": {
-    // Braces only needed for multi-line arrow function blocks
-    // "arrow-body-style": ["error", "as-needed"],
-
-    // Require spacing around =>
-    "arrow-spacing": "error",
-
-    // Always require spacing around a single line block
-    "block-spacing": "error",
-
-    // No newline before open brace for a block
-    "brace-style": ["error", "1tbs", { "allowSingleLine": true }],
-
-    // No space before always a space after a comma
-    "comma-spacing": ["error", {"before": false, "after": true}],
-
-    // Commas at the end of the line not the start
-    // "comma-style": "error",
-
-    // Warn about cyclomatic complexity in functions.
-    // XXX Bug 1326071 - This should be reduced down - probably to 20.
-    "complexity": ["error", {"max": 48}],
-
-    // Don't require spaces around computed properties
-    "computed-property-spacing": ["error", "never"],
-
-    // Functions must always return something or nothing
-    "consistent-return": "error",
-
-    // Require braces around blocks that start a new line
-    // Note that this rule is likely to be overridden on a per-directory basis
-    // very frequently.
-    // "curly": ["error", "multi-line"],
-
-    // Always require a trailing EOL
-    "eol-last": "error",
-
-    // No spaces between function name and parentheses
-    "func-call-spacing": "error",
-
-    // Require function* name()
-    // "generator-star-spacing": ["error", {"before": false, "after": true}],
-
-    // Two space indent
-    // "indent": ["error", 2, { "SwitchCase": 1 }],
-
-    // Space after colon not before in property declarations
-    // "key-spacing": ["error", { "beforeColon": false, "afterColon": true, "mode": "minimum" }],
-
-    // Require spaces before and after keywords
-    "keyword-spacing": "error",
-
-    // Unix linebreaks
-    "linebreak-style": ["error", "unix"],
-
-    // Don't enforce the maximum depth that blocks can be nested. The complexity
-    // rule is a better rule to check this.
-    "max-depth": "off",
-
-    // Always require parenthesis for new calls
-    // "new-parens": "error",
-
-    // Use [] instead of Array()
-    // "no-array-constructor": "error",
-
-    // Disallow assignment operators in conditional statements
-    "no-cond-assign": "error",
-
-    // Disallow the use of debugger
-    "no-debugger": "error",
-
-    // Disallow deleting variables
-    "no-delete-var": "error",
-
-    // No duplicate arguments in function declarations
-    "no-dupe-args": "error",
-
-    // No duplicate keys in object declarations
-    "no-dupe-keys": "error",
-
-    // No duplicate cases in switch statements
-    "no-duplicate-case": "error",
-
-    // Disallow unnecessary calls to .bind()
-    "no-extra-bind": "error",
-
-    // No labels
-    "no-labels": "error",
-
-    // Disallow unnecessary nested blocks
-    "no-lone-blocks": "error",
-
-    // If an if block ends with a return no need for an else block
-    "no-else-return": "error",
-
-    // No empty statements
-    "no-empty": ["error", {"allowEmptyCatch": true}],
-
-    // No empty character classes in regex
-    "no-empty-character-class": "error",
-
-    // Disallow empty destructuring
-    "no-empty-pattern": "error",
-
-    // No assiging to exception variable
-    "no-ex-assign": "error",
-
-    // No using !! where casting to boolean is already happening
-    "no-extra-boolean-cast": "error",
-
-    // No double semicolon
-    "no-extra-semi": "error",
-
-    // No overwriting defined functions
-    "no-func-assign": "error",
-
-    // No invalid regular expresions
-    "no-invalid-regexp": "error",
-
-    // No odd whitespace characters
-    "no-irregular-whitespace": "error",
-
-    // Disallow the use of the __iterator__ property
-    "no-iterator": "error",
-
-    // No single if block inside an else block
-    "no-lonely-if": "error",
-
-    // No mixing spaces and tabs in indent
-    "no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
-
-    // No unnecessary spacing
-    "no-multi-spaces": ["error", { exceptions: { "AssignmentExpression": true, "VariableDeclarator": true, "ArrayExpression": true, "ObjectExpression": true } }],
-
-    // No reassigning native JS objects
-    "no-native-reassign": "error",
-
-    // Nested ternary statements are confusing
-    "no-nested-ternary": "error",
+  extends: [
+    "plugin:mozilla/recommended"
+  ],
 
-    // Use {} instead of new Object()
-    "no-new-object": "error",
-
-    // No Math() or JSON()
-    "no-obj-calls": "error",
-
-    // No octal literals
-    "no-octal": "error",
-
-    // No redeclaring variables
-    "no-redeclare": "error",
-
-    // Disallow multiple spaces in regular expressions
-    "no-regex-spaces": "error",
-
-    // Disallow assignments where both sides are exactly the same
-    "no-self-assign": "error",
-
-    // No unnecessary comparisons
-    "no-self-compare": "error",
-
-    // No declaring variables from an outer scope
-    // "no-shadow": "error",
-
-    // No declaring variables that hide things like arguments
-    "no-shadow-restricted-names": "error",
-
-    // Disallow sparse arrays
-    "no-sparse-arrays": "error",
-
-    // No trailing whitespace
-    "no-trailing-spaces": "error",
-
-    // No using undeclared variables
-    "no-undef": "error",
-
-    // Error on newline where a semicolon is needed
-    "no-unexpected-multiline": "error",
-
-    // No unreachable statements
-    "no-unreachable": "error",
-
-    // Disallow control flow statements in finally blocks
-    "no-unsafe-finally": "error",
-
-    // No declaring variables that are never used
-    "no-unused-vars": ["error", {
-      "vars": "local",
-      "varsIgnorePattern": "^Cc|Ci|Cu|Cr|EXPORTED_SYMBOLS",
-      "args": "none",
-    }],
-
-    // No using variables before defined
-    // "no-use-before-define": ["error", "nofunc"],
-
-    // Disallow unnecessary .call() and .apply()
-    "no-useless-call": "error",
-
-    // Disallow redundant return statements
-    "no-useless-return": "error",
-
-    // No using with
-    "no-with": "error",
-
-    // Require object-literal shorthand with ES6 method syntax
-    "object-shorthand": ["error", "always", { "avoidQuotes": true }],
-
-    // Require double-quotes everywhere, except where quotes are escaped
-    // or template literals are used.
-    "quotes": ["error", "double", { "avoidEscape": true, "allowTemplateLiterals": true }],
-
-    // No spacing inside rest or spread expressions
-    "rest-spread-spacing": "error",
-
-    // Always require semicolon at end of statement
-    // "semi": ["error", "always"],
-
-    // Require space before blocks
-    "space-before-blocks": "error",
-
-    // Never use spaces before function parentheses
-    "space-before-function-paren": ["error", "never"],
-
-    // No space padding in parentheses
-    // "space-in-parens": ["error", "never"],
-
-    // Require spaces around operators
-    "space-infix-ops": ["error", { "int32Hint": true }],
-
-    // ++ and -- should not need spacing
-    "space-unary-ops": ["error", {
-      "words": true,
-      "nonwords": false,
-      "overrides": {
-        "typeof": false // We tend to use typeof as a function call
-      }
-    }],
-
-    // Requires or disallows a whitespace (space or tab) beginning a comment
-    "spaced-comment": "error",
-
-    // No comparisons to NaN
-    "use-isnan": "error",
-
-    // Only check typeof against valid results
-    "valid-typeof": "error",
-
-    // Don't concatenate string literals together (unless they span multiple
-    // lines)
-    "no-useless-concat": "error",
-  },
-  "env": {
-    "es6": true,
-    "browser": true,
-  },
-  "globals": {
-    "BroadcastChannel": false,
-    // Specific to Firefox (Chrome code only).
-    "ChromeWindow": false,
-    "ChromeWorker": false,
-    "ChromeUtils": false,
-    "Components": false,
-    "dump": true,
-    // Specific to Firefox
-    // https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/InternalError
-    "InternalError": true,
-    "KeyEvent": false,
-    "openDialog": false,
-    "MenuBoxObject": false,
-    // Specific to Firefox (Chrome code only).
-    "MozSelfSupport": false,
-    "SimpleGestureEvent": false,
-    "sizeToContent": false,
-    "SharedArrayBuffer": false,
-    // Note: StopIteration will likely be removed as part of removing legacy
-    // generators, see bug 968038.
-    "StopIteration": false,
-    // Specific to Firefox
-    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/uneval
-    "uneval": false,
-    "XULElement": false,
+  rules: {
+    // XXX Bug 1326071 - This should be reduced down - probably to 20 or to
+    // be removed & synced with the mozilla/recommended value.
+    "complexity": ["error", {"max": 48}],
   }
 };
--- a/toolkit/components/aboutmemory/tests/.eslintrc.js
+++ b/toolkit/components/aboutmemory/tests/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/chrome-test"
   ]
 };
--- a/toolkit/components/aboutmemory/tests/xpcshell/.eslintrc.js
+++ b/toolkit/components/aboutmemory/tests/xpcshell/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/aboutperformance/tests/browser/.eslintrc.js
+++ b/toolkit/components/aboutperformance/tests/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/toolkit/components/addoncompat/tests/browser/.eslintrc.js
+++ b/toolkit/components/addoncompat/tests/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/toolkit/components/alerts/test/.eslintrc.js
+++ b/toolkit/components/alerts/test/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/mochitest.eslintrc.js"
+    "plugin:mozilla/mochitest-test"
   ]
 };
--- a/toolkit/components/asyncshutdown/tests/xpcshell/.eslintrc.js
+++ b/toolkit/components/asyncshutdown/tests/xpcshell/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/autocomplete/tests/unit/.eslintrc.js
+++ b/toolkit/components/autocomplete/tests/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/captivedetect/test/unit/.eslintrc.js
+++ b/toolkit/components/captivedetect/test/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/commandlines/test/unit/.eslintrc.js
+++ b/toolkit/components/commandlines/test/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/commandlines/test/unit_unix/.eslintrc.js
+++ b/toolkit/components/commandlines/test/unit_unix/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/commandlines/test/unit_win/.eslintrc.js
+++ b/toolkit/components/commandlines/test/unit_win/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/contentprefs/tests/mochitest/.eslintrc.js
+++ b/toolkit/components/contentprefs/tests/mochitest/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/mochitest.eslintrc.js"
+    "plugin:mozilla/mochitest-test"
   ]
 };
--- a/toolkit/components/contentprefs/tests/unit/.eslintrc.js
+++ b/toolkit/components/contentprefs/tests/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/contentprefs/tests/unit_cps2/.eslintrc.js
+++ b/toolkit/components/contentprefs/tests/unit_cps2/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/contextualidentity/tests/unit/.eslintrc.js
+++ b/toolkit/components/contextualidentity/tests/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/crashes/tests/xpcshell/.eslintrc.js
+++ b/toolkit/components/crashes/tests/xpcshell/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/crashmonitor/test/unit/.eslintrc.js
+++ b/toolkit/components/crashmonitor/test/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/ctypes/tests/chrome/.eslintrc.js
+++ b/toolkit/components/ctypes/tests/chrome/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/chrome-test"
   ]
 };
--- a/toolkit/components/ctypes/tests/unit/.eslintrc.js
+++ b/toolkit/components/ctypes/tests/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/downloads/test/unit/.eslintrc.js
+++ b/toolkit/components/downloads/test/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/extensions/test/browser/.eslintrc.js
+++ b/toolkit/components/extensions/test/browser/.eslintrc.js
@@ -1,19 +1,19 @@
 "use strict";
 
-module.exports = { // eslint-disable-line no-undef
-  "extends": "../../../../../testing/mochitest/mochitest.eslintrc.js",
+module.exports = {
+  "extends": "plugin:mozilla/mochitest-test",
 
   "env": {
     "webextensions": true,
   },
 
   "globals": {
     "BrowserTestUtils": true,
     "ExtensionTestUtils": false,
     "XPCOMUtils": true,
   },
 
   "rules": {
-    "no-shadow": 0,
+    "no-shadow": "off",
   },
 };
--- a/toolkit/components/extensions/test/mochitest/.eslintrc.js
+++ b/toolkit/components/extensions/test/mochitest/.eslintrc.js
@@ -1,12 +1,12 @@
 "use strict";
 
 module.exports = { // eslint-disable-line no-undef
-  "extends": "../../../../../testing/mochitest/mochitest.eslintrc.js",
+  "extends": "plugin:mozilla/mochitest-test",
 
   "env": {
     "browser": true,
     "webextensions": true,
   },
 
   "globals": {
     "onmessage": true,
--- a/toolkit/components/extensions/test/xpcshell/.eslintrc.js
+++ b/toolkit/components/extensions/test/xpcshell/.eslintrc.js
@@ -1,12 +1,12 @@
 "use strict";
 
-module.exports = { // eslint-disable-line no-undef
-  "extends": "../../../../../testing/xpcshell/xpcshell.eslintrc.js",
+module.exports = {
+  "extends": "plugin:mozilla/xpcshell-test",
 
   "globals": {
     "browser": false,
   },
 
   "env": {
     "browser": true,
   }
--- a/toolkit/components/feeds/test/.eslintrc.js
+++ b/toolkit/components/feeds/test/.eslintrc.js
@@ -1,8 +1,8 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/chrome.eslintrc.js",
-    "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/chrome-test",
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/filepicker/test/unit/.eslintrc.js
+++ b/toolkit/components/filepicker/test/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/filewatcher/tests/xpcshell/.eslintrc.js
+++ b/toolkit/components/filewatcher/tests/xpcshell/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/formautofill/test/.eslintrc.js
+++ b/toolkit/components/formautofill/test/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/toolkit/components/formautofill/test/browser/.eslintrc.js
+++ b/toolkit/components/formautofill/test/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/toolkit/components/formautofill/test/chrome/.eslintrc.js
+++ b/toolkit/components/formautofill/test/chrome/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/chrome-test"
   ]
 };
--- a/toolkit/components/formautofill/test/xpcshell/.eslintrc.js
+++ b/toolkit/components/formautofill/test/xpcshell/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/jsdownloads/test/browser/.eslintrc.js
+++ b/toolkit/components/jsdownloads/test/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/toolkit/components/jsdownloads/test/data/.eslintrc.js
+++ b/toolkit/components/jsdownloads/test/data/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/jsdownloads/test/unit/.eslintrc.js
+++ b/toolkit/components/jsdownloads/test/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/lz4/tests/xpcshell/.eslintrc.js
+++ b/toolkit/components/lz4/tests/xpcshell/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/mediasniffer/test/unit/.eslintrc.js
+++ b/toolkit/components/mediasniffer/test/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/mozintl/test/.eslintrc.js
+++ b/toolkit/components/mozintl/test/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/mozprotocol/tests/.eslintrc.js
+++ b/toolkit/components/mozprotocol/tests/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/chrome-test"
   ],
 };
--- a/toolkit/components/osfile/tests/mochi/.eslintrc.js
+++ b/toolkit/components/osfile/tests/mochi/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/chrome-test"
   ]
 };
--- a/toolkit/components/osfile/tests/xpcshell/.eslintrc.js
+++ b/toolkit/components/osfile/tests/xpcshell/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/passwordmgr/test/.eslintrc.js
+++ b/toolkit/components/passwordmgr/test/.eslintrc.js
@@ -1,13 +1,13 @@
 "use strict";
 
 module.exports = { // eslint-disable-line no-undef
   "extends": [
-    "../../../../testing/mochitest/mochitest.eslintrc.js",
-    "../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/mochitest-test",
+    "plugin:mozilla/chrome-test"
   ],
   "rules": {
     "brace-style": "off",
     "no-undef": "off",
     "no-unused-vars": "off",
   },
 };
--- a/toolkit/components/passwordmgr/test/browser/.eslintrc.js
+++ b/toolkit/components/passwordmgr/test/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/toolkit/components/passwordmgr/test/unit/.eslintrc.js
+++ b/toolkit/components/passwordmgr/test/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/perf/.eslintrc.js
+++ b/toolkit/components/perf/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/chrome-test"
   ]
 };
--- a/toolkit/components/perfmonitoring/tests/browser/.eslintrc.js
+++ b/toolkit/components/perfmonitoring/tests/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/toolkit/components/places/PlacesSyncUtils.jsm
+++ b/toolkit/components/places/PlacesSyncUtils.jsm
@@ -294,16 +294,26 @@ const BookmarkSyncUtils = PlacesSyncUtil
       return undefined;
     }
     let orderedChildrenGuids = childSyncIds.map(BookmarkSyncUtils.syncIdToGuid);
     return PlacesUtils.bookmarks.reorder(parentGuid, orderedChildrenGuids,
                                          { source: SOURCE_SYNC });
   }),
 
   /**
+   * Resolves to true if there are known sync changes.
+   */
+  havePendingChanges() {
+    // This could be optimized to use a more efficient query -- We don't need
+    // grab all the records if all we care about is whether or not any exist.
+    return PlacesUtils.withConnectionWrapper("BookmarkSyncUtils: havePendingChanges",
+      db => pullSyncChanges(db, true).then(changes => Object.keys(changes).length > 0));
+  },
+
+  /**
    * Returns a changeset containing local bookmark changes since the last sync.
    * Updates the sync status of all "NEW" bookmarks to "NORMAL", so that Sync
    * can recover correctly after an interrupted sync.
    *
    * @return {Promise} resolved once all items have been fetched.
    * @resolves to an object containing records for changed bookmarks, keyed by
    *           the sync ID.
    * @see pullSyncChanges for the implementation, and markChangesAsSyncing for
@@ -1682,21 +1692,23 @@ function addRowToChangeRecords(row, chan
 
 /**
  * Queries the database for synced bookmarks and tombstones, updates the sync
  * status of all "NEW" bookmarks to "NORMAL", and returns a changeset for the
  * Sync bookmarks engine.
  *
  * @param db
  *        The Sqlite.jsm connection handle.
+ * @param preventUpdate {boolean}
+ *        Should we skip updating the records we pull.
  * @return {Promise} resolved once all items have been fetched.
  * @resolves to an object containing records for changed bookmarks, keyed by
  *           the sync ID.
  */
-var pullSyncChanges = Task.async(function* (db) {
+var pullSyncChanges = Task.async(function* (db, preventUpdate = false) {
   let changeRecords = {};
 
   yield db.executeCached(`
     WITH RECURSIVE
     syncedItems(id, guid, modified, syncChangeCounter, syncStatus) AS (
       SELECT b.id, b.guid, b.lastModified, b.syncChangeCounter, b.syncStatus
        FROM moz_bookmarks b
        WHERE b.guid IN ('menu________', 'toolbar_____', 'unfiled_____',
@@ -1711,17 +1723,19 @@ var pullSyncChanges = Task.async(functio
     WHERE syncChangeCounter >= 1
     UNION ALL
     SELECT guid, dateRemoved AS modified, 1 AS syncChangeCounter,
            :deletedSyncStatus, 1 AS tombstone
     FROM moz_bookmarks_deleted`,
     { deletedSyncStatus: PlacesUtils.bookmarks.SYNC_STATUS.NORMAL },
     row => addRowToChangeRecords(row, changeRecords));
 
-  yield markChangesAsSyncing(db, changeRecords);
+  if (!preventUpdate) {
+    yield markChangesAsSyncing(db, changeRecords);
+  }
 
   return changeRecords;
 });
 
 var touchSyncBookmark = Task.async(function* (db, bookmarkItem) {
   if (BookmarkSyncLog.level <= Log.Level.Trace) {
     BookmarkSyncLog.trace(
       `touch: Reviving item "${bookmarkItem.guid}" and marking parent ` +
--- a/toolkit/components/places/tests/.eslintrc.js
+++ b/toolkit/components/places/tests/.eslintrc.js
@@ -1,9 +1,9 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/mochitest.eslintrc.js",
-    "../../../../testing/mochitest/chrome.eslintrc.js",
-    "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/mochitest-test",
+    "plugin:mozilla/chrome-test",
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/places/tests/bookmarks/.eslintrc.js
+++ b/toolkit/components/places/tests/bookmarks/.eslintrc.js
@@ -1,10 +1,10 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ],
   "parserOptions": {
     "ecmaVersion": 8,
   },
 };
--- a/toolkit/components/places/tests/browser/.eslintrc.js
+++ b/toolkit/components/places/tests/browser/.eslintrc.js
@@ -1,8 +1,8 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js",
-    "../../../../../testing/mochitest/mochitest.eslintrc.js"
+    "plugin:mozilla/browser-test",
+    "plugin:mozilla/mochitest-test"
   ]
 };
--- a/toolkit/components/places/tests/chrome/.eslintrc.js
+++ b/toolkit/components/places/tests/chrome/.eslintrc.js
@@ -1,8 +1,8 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/chrome.eslintrc.js",
-    "../../../../../testing/mochitest/mochitest.eslintrc.js"
+    "plugin:mozilla/chrome-test",
+    "plugin:mozilla/mochitest-test"
   ]
 };
--- a/toolkit/components/places/tests/expiration/.eslintrc.js
+++ b/toolkit/components/places/tests/expiration/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/places/tests/favicons/.eslintrc.js
+++ b/toolkit/components/places/tests/favicons/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/places/tests/history/.eslintrc.js
+++ b/toolkit/components/places/tests/history/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/places/tests/migration/.eslintrc.js
+++ b/toolkit/components/places/tests/migration/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/places/tests/queries/.eslintrc.js
+++ b/toolkit/components/places/tests/queries/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/places/tests/unifiedcomplete/.eslintrc.js
+++ b/toolkit/components/places/tests/unifiedcomplete/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/places/tests/unit/.eslintrc.js
+++ b/toolkit/components/places/tests/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/printing/tests/.eslintrc.js
+++ b/toolkit/components/printing/tests/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/chrome-test"
   ],
 };
--- a/toolkit/components/promiseworker/tests/xpcshell/.eslintrc.js
+++ b/toolkit/components/promiseworker/tests/xpcshell/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/prompts/test/.eslintrc.js
+++ b/toolkit/components/prompts/test/.eslintrc.js
@@ -1,11 +1,11 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/mochitest.eslintrc.js"
+    "plugin:mozilla/mochitest-test"
   ],
   "rules": {
     // ownerGlobal doesn't exist in content privileged windows.
     "mozilla/use-ownerGlobal": "off",
   }
 };
--- a/toolkit/components/reader/test/.eslintrc.js
+++ b/toolkit/components/reader/test/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/toolkit/components/remotebrowserutils/tests/browser/.eslintrc.js
+++ b/toolkit/components/remotebrowserutils/tests/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/toolkit/components/satchel/test/.eslintrc.js
+++ b/toolkit/components/satchel/test/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/mochitest.eslintrc.js"
+    "plugin:mozilla/mochitest-test"
   ]
 };
--- a/toolkit/components/satchel/test/browser/.eslintrc.js
+++ b/toolkit/components/satchel/test/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/toolkit/components/satchel/test/unit/.eslintrc.js
+++ b/toolkit/components/satchel/test/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/search/tests/xpcshell/.eslintrc.js
+++ b/toolkit/components/search/tests/xpcshell/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/social/test/xpcshell/.eslintrc.js
+++ b/toolkit/components/social/test/xpcshell/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/startup/tests/browser/.eslintrc.js
+++ b/toolkit/components/startup/tests/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/toolkit/components/startup/tests/unit/.eslintrc.js
+++ b/toolkit/components/startup/tests/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/telemetry/tests/browser/.eslintrc.js
+++ b/toolkit/components/telemetry/tests/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/toolkit/components/telemetry/tests/unit/.eslintrc.js
+++ b/toolkit/components/telemetry/tests/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/terminator/tests/xpcshell/.eslintrc.js
+++ b/toolkit/components/terminator/tests/xpcshell/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/thumbnails/test/.eslintrc.js
+++ b/toolkit/components/thumbnails/test/.eslintrc.js
@@ -1,8 +1,8 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/browser.eslintrc.js",
-    "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/browser-test",
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/timermanager/tests/unit/.eslintrc.js
+++ b/toolkit/components/timermanager/tests/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/tooltiptext/tests/.eslintrc.js
+++ b/toolkit/components/tooltiptext/tests/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/toolkit/components/url-classifier/tests/mochitest/.eslintrc.js
+++ b/toolkit/components/url-classifier/tests/mochitest/.eslintrc.js
@@ -1,8 +1,8 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/mochitest.eslintrc.js",
-    "../../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/mochitest-test",
+    "plugin:mozilla/chrome-test"
   ]
 };
--- a/toolkit/components/url-classifier/tests/unit/.eslintrc.js
+++ b/toolkit/components/url-classifier/tests/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/urlformatter/tests/unit/.eslintrc.js
+++ b/toolkit/components/urlformatter/tests/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/components/viewsource/test/.eslintrc.js
+++ b/toolkit/components/viewsource/test/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/chrome-test"
   ]
 };
--- a/toolkit/components/viewsource/test/browser/.eslintrc.js
+++ b/toolkit/components/viewsource/test/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/toolkit/components/windowcreator/test/.eslintrc.js
+++ b/toolkit/components/windowcreator/test/.eslintrc.js
@@ -1,9 +1,9 @@
 "use strict";
 
 module.exports = { // eslint-disable-line no-undef
   "extends": [
-    "../../../../testing/mochitest/browser.eslintrc.js",
-    "../../../../testing/mochitest/chrome.eslintrc.js",
-    "../../../../testing/mochitest/mochitest.eslintrc.js",
+    "plugin:mozilla/browser-test",
+    "plugin:mozilla/chrome-test",
+    "plugin:mozilla/mochitest-test",
   ]
 };
--- a/toolkit/components/windowcreator/tests/unit/.eslintrc.js
+++ b/toolkit/components/windowcreator/tests/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = { // eslint-disable-line no-undef
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js",
+    "plugin:mozilla/xpcshell-test",
   ]
 };
--- a/toolkit/components/windowwatcher/test/.eslintrc.js
+++ b/toolkit/components/windowwatcher/test/.eslintrc.js
@@ -1,9 +1,9 @@
 "use strict";
 
 module.exports = { // eslint-disable-line no-undef
   "extends": [
-    "../../../../testing/mochitest/browser.eslintrc.js",
-    "../../../../testing/mochitest/chrome.eslintrc.js",
-    "../../../../testing/mochitest/mochitest.eslintrc.js",
+    "plugin:mozilla/browser-test",
+    "plugin:mozilla/chrome-test",
+    "plugin:mozilla/mochitest-test",
   ]
 };
--- a/toolkit/components/workerloader/tests/.eslintrc.js
+++ b/toolkit/components/workerloader/tests/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/chrome-test"
   ]
 };
--- a/toolkit/components/xulstore/tests/chrome/.eslintrc.js
+++ b/toolkit/components/xulstore/tests/chrome/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/chrome-test"
   ]
 };
--- a/toolkit/components/xulstore/tests/xpcshell/.eslintrc.js
+++ b/toolkit/components/xulstore/tests/xpcshell/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/content/tests/browser/.eslintrc.js
+++ b/toolkit/content/tests/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/toolkit/content/tests/chrome/.eslintrc.js
+++ b/toolkit/content/tests/chrome/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/chrome-test"
   ]
 };
--- a/toolkit/content/tests/mochitest/.eslintrc.js
+++ b/toolkit/content/tests/mochitest/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/mochitest.eslintrc.js"
+    "plugin:mozilla/mochitest-test"
   ]
 };
--- a/toolkit/content/tests/reftests/reftest.list
+++ b/toolkit/content/tests/reftests/reftest.list
@@ -1,2 +1,3 @@
 random-if(cocoaWidget) == bug-442419-progressmeter-max.xul bug-442419-progressmeter-max-ref.xul # fails most of the time on Mac because progress meter animates
 != textbox-multiline-default-value.xul textbox-multiline-empty.xul
+== videocontrols-dynamically-add-cc.html videocontrols-dynamically-add-cc-ref.html
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/reftests/videocontrols-dynamically-add-cc-ref.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+  html, body {
+    margin: 0;
+    padding: 0;
+  }
+
+  video {
+    width: 320px;
+    height: 240px;
+  }
+
+  #mask {
+    position: absolute;
+    z-index: 3;
+    width: 320px;
+    height: 200px;
+    background-color: green;
+    top: 0;
+    left: 0;
+  }
+</style>
+<script>
+  function addCCToVid(videoElem) {
+    videoElem.addTextTrack("subtitles", "English", "en");
+  }
+</script>
+</head>
+<body>
+  <video id="vid" controls></video>
+  <div id="mask"></div>
+  <script>
+    var vid = document.getElementById("vid");
+    addCCToVid(vid);
+  </script>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/reftests/videocontrols-dynamically-add-cc.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<style>
+  html, body {
+    margin: 0;
+    padding: 0;
+  }
+
+  video {
+    width: 320px;
+    height:240px;
+  }
+
+  #mask {
+    position: absolute;
+    z-index: 3;
+    width: 320px;
+    height: 200px;
+    background-color: green;
+    top: 0;
+    left: 0;
+  }
+</style>
+<script>
+  function addCCToVid(videoElem) {
+    videoElem.addTextTrack("subtitles", "English", "en");
+  }
+</script>
+</head>
+<body>
+  <video id="vid" controls></video>
+  <div id="mask"></div>
+  <script>
+    function doTest() {
+      var vid = document.getElementById("vid");
+
+      // Videocontrols binding's "addtrack" handler synchronously fires
+      // "adjustControlSize()" first, and then the layout is ready for
+      // the reftest snapshot.
+      vid.textTracks.addEventListener("addtrack", function() {
+        document.documentElement.removeAttribute("class");
+      });
+
+      addCCToVid(vid);
+    }
+
+    window.addEventListener("MozReftestInvalidate", doTest);
+  </script>
+</body>
+</html>
+
--- a/toolkit/content/tests/unit/.eslintrc.js
+++ b/toolkit/content/tests/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/content/tests/widgets/.eslintrc.js
+++ b/toolkit/content/tests/widgets/.eslintrc.js
@@ -1,8 +1,8 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/mochitest.eslintrc.js",
-    "../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/mochitest-test",
+    "plugin:mozilla/chrome-test"
   ]
 };
--- a/toolkit/content/widgets/datetimebox.xml
+++ b/toolkit/content/widgets/datetimebox.xml
@@ -481,17 +481,17 @@
         /* eslint-disable no-multi-spaces */
         this.mHourPlaceHolder = ]]>"&time.hour.placeholder;"<![CDATA[;
         this.mMinutePlaceHolder = ]]>"&time.minute.placeholder;"<![CDATA[;
         this.mSecondPlaceHolder = ]]>"&time.second.placeholder;"<![CDATA[;
         this.mMillisecPlaceHolder = ]]>"&time.millisecond.placeholder;"<![CDATA[;
         this.mDayPeriodPlaceHolder = ]]>"&time.dayperiod.placeholder;"<![CDATA[;
         /* eslint-enable no-multi-spaces */
 
-        this.mHour12 = true;
+        this.mHour12 = this.is12HourTime(this.mLocales);
         this.mSeparatorText = ":";
         this.mMillisecSeparatorText = ".";
         this.mMaxLength = 2;
         this.mMillisecMaxLength = 3;
         this.mDefaultStep = 60 * 1000; // in milliseconds
 
         this.mMinHour = this.mHour12 ? 1 : 0;
         this.mMaxHour = this.mHour12 ? 12 : 23;
@@ -664,16 +664,29 @@
 
           [ amString, pmString ] = keys.map(key => result.values[key]);
 
           return { amString, pmString };
         ]]>
         </body>
       </method>
 
+      <method name="is12HourTime">
+        <parameter name="aLocales"/>
+          <body>
+          <![CDATA[
+            let options = (new Intl.DateTimeFormat(aLocales, {
+              hour: "numeric"
+            })).resolvedOptions();
+
+            return options.hour12;
+          ]]>
+          </body>
+      </method>
+
       <method name="setFieldsFromInputValue">
         <body>
         <![CDATA[
           let { hour, minute, second, millisecond } =
             this.getInputElementValues();
 
           if (this.isEmpty(hour) && this.isEmpty(minute)) {
             this.clearInputFields(true);
@@ -836,18 +849,19 @@
         <![CDATA[
           let value;
 
           // Use current time if field is empty.
           if (this.isEmpty(aTargetField.value)) {
             let now = new Date();
 
             if (aTargetField == this.mHourField) {
+              value = now.getHours();
               if (this.mHour12) {
-                value = now.getHours() % this.mMaxHour || this.mMaxHour;
+                value = (value % this.mMaxHour) || this.mMaxHour;
               }
             } else if (aTargetField == this.mMinuteField) {
               value = now.getMinutes();
             } else if (aTargetField == this.mSecondField) {
               value = now.getSeconds();
             } else if (aTargetField == this.mMillisecField) {
               value = now.getMilliseconds();
             } else {
@@ -983,22 +997,27 @@
         <![CDATA[
           let value = Number(aValue);
           if (isNaN(value)) {
             this.log("NaN on setFieldValue!");
             return;
           }
 
           if (aField.maxLength == this.mMaxLength) { // For hour, minute and second
-            if (aField == this.mHourField && this.mHour12) {
-              if (aValue == "00") {
+            if (aField == this.mHourField) {
+              if (this.mHour12) {
+                // Try to change to 12hr format if user input is 0 or greater
+                // than 12.
+                if (value == 0 && aValue.length == aField.maxLength) {
+                  value = this.mMaxHour;
+                } else {
+                  value = (value > this.mMaxHour) ? value % this.mMaxHour : value;
+                }
+              } else if (value > this.mMaxHour) {
                 value = this.mMaxHour;
-              } else {
-                // Change to 12hr format if user input is greater than 12.
-                value = (value > this.mMaxHour) ? value % this.mMaxHour : value;
               }
             }
             // prepend zero
             if (value < 10) {
               value = "0" + value;
             }
           } else if (aField.maxLength == this.mMillisecMaxLength) {
             // prepend zeroes
--- a/toolkit/content/widgets/videocontrols.xml
+++ b/toolkit/content/widgets/videocontrols.xml
@@ -1001,29 +1001,29 @@
         this.bufferBar.max = duration;
         this.bufferBar.value = endTime;
       },
 
       _controlsHiddenByTimeout : false,
       _showControlsTimeout : 0,
       SHOW_CONTROLS_TIMEOUT_MS: 500,
       _showControlsFn() {
-        if (this.Utils.video.matches("video:hover")) {
-          this.Utils.startFadeIn(this.Utils.controlBar, false);
-          this.Utils._showControlsTimeout = 0;
-          this.Utils._controlsHiddenByTimeout = false;
+        if (Utils.video.matches("video:hover")) {
+          Utils.startFadeIn(Utils.controlBar, false);
+          Utils._showControlsTimeout = 0;
+          Utils._controlsHiddenByTimeout = false;
         }
       },
 
       _hideControlsTimeout : 0,
       _hideControlsFn() {
-        if (!this.Utils.scrubber.isDragging) {
-          this.Utils.startFade(this.Utils.controlBar, false);
-          this.Utils._hideControlsTimeout = 0;
-          this.Utils._controlsHiddenByTimeout = true;
+        if (!Utils.scrubber.isDragging) {
+          Utils.startFade(Utils.controlBar, false);
+          Utils._hideControlsTimeout = 0;
+          Utils._controlsHiddenByTimeout = true;
         }
       },
       HIDE_CONTROLS_TIMEOUT_MS : 2000,
       onMouseMove(event) {
         // Pause playing video when the mouse is dragging over the control bar.
         if (this.scrubber.startToDrag) {
           this.scrubber.isDragging = true;
           this.pauseVideoDuringDragging();
@@ -1098,17 +1098,17 @@
           // Keep the controls visible if the click-to-play is visible.
           if (!this.clickToPlay.hidden) {
             return;
           }
 
           this.startFadeOut(this.controlBar, false);
           this.textTrackList.setAttribute("hidden", "true");
           clearTimeout(this._showControlsTimeout);
-          this.Utils._controlsHiddenByTimeout = false;
+          Utils._controlsHiddenByTimeout = false;
         }
       },
 
       startFadeIn(element, immediate) {
         this.startFade(element, true, immediate);
       },
 
       startFadeOut(element, immediate) {
@@ -1262,17 +1262,17 @@
           this.fullscreenButton.setAttribute("fullscreened", "true");
         } else {
           this.fullscreenButton.removeAttribute("fullscreened");
         }
       },
 
       onFullscreenChange() {
         if (this.isVideoInFullScreen()) {
-          this.Utils._hideControlsTimeout = setTimeout(this._hideControlsFn, this.HIDE_CONTROLS_TIMEOUT_MS);
+          Utils._hideControlsTimeout = setTimeout(this._hideControlsFn, this.HIDE_CONTROLS_TIMEOUT_MS);
         }
         this.setFullscreenButtonState();
       },
 
       clickToPlayClickHandler(e) {
         if (e.button != 0) {
           return;
         }
@@ -1494,18 +1494,16 @@
 
       setClosedCaptionButtonState() {
         this.adjustControlSize();
 
         if (!this.isClosedCaptionAvailable) {
           return;
         }
 
-        this.closedCaptionButton.removeAttribute("hidden");
-
         if (this.isClosedCaptionOn()) {
           this.closedCaptionButton.setAttribute("enabled", "true");
         } else {
           this.closedCaptionButton.removeAttribute("enabled");
         }
 
         let ttItems = this.textTrackList.childNodes;
 
@@ -1705,25 +1703,19 @@
           this.fullscreenButton,
           this.closedCaptionButton,
           this.positionDurationBox,
           this.scrubberStack,
           this.durationSpan,
           this.volumeStack
         ];
 
-        if (this.controlBar.hasAttribute("fullscreen-unavailable")) {
-          this.fullscreenButton.isWanted = false;
-        }
-        if (!this.isClosedCaptionAvailable) {
-          this.closedCaptionButton.isWanted = false;
-        }
-        if (this.muteButton.hasAttribute("noAudio")) {
-          this.volumeStack.isWanted = false;
-        }
+        this.fullscreenButton.isWanted = !this.controlBar.hasAttribute("fullscreen-unavailable");
+        this.closedCaptionButton.isWanted = this.isClosedCaptionAvailable;
+        this.volumeStack.isWanted = !this.muteButton.hasAttribute("noAudio");
 
         let widthUsed = minControlBarPaddingWidth;
         let preventAppendControl = false;
 
         for (let control of prioritizedControls) {
           if (!control.isWanted) {
             control.hideByAdjustment = true;
             continue;
@@ -1835,16 +1827,19 @@
             mozSystemGroup: true
           });
         }
 
         var self = this;
         this.controlListeners = [];
 
         // Helper function to add an event listener to the given element
+        // Due to this helper function, "Utils" is made available to the event
+        // listener functions. Hence declare it as a global for ESLint.
+        /* global Utils */
         function addListener(elem, eventName, func) {
           let boundFunc = func.bind(self);
           self.controlListeners.push({ item: elem, event: eventName, func: boundFunc });
           elem.addEventListener(eventName, boundFunc, { mozSystemGroup: true });
         }
 
         addListener(this.muteButton, "click", this.toggleMute);
         addListener(this.closedCaptionButton, "click", this.toggleClosedCaption);
--- a/toolkit/crashreporter/test/browser/.eslintrc.js
+++ b/toolkit/crashreporter/test/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/toolkit/crashreporter/test/unit/.eslintrc.js
+++ b/toolkit/crashreporter/test/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/crashreporter/test/unit_ipc/.eslintrc.js
+++ b/toolkit/crashreporter/test/unit_ipc/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/forgetaboutsite/test/browser/.eslintrc.js
+++ b/toolkit/forgetaboutsite/test/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/toolkit/forgetaboutsite/test/unit/.eslintrc.js
+++ b/toolkit/forgetaboutsite/test/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/library/gtest/rust/Cargo.lock
+++ b/toolkit/library/gtest/rust/Cargo.lock
@@ -297,17 +297,16 @@ dependencies = [
 
 [[package]]
 name = "geckoservo"
 version = "0.0.1"
 dependencies = [
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "env_logger 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "selectors 0.18.0",
  "servo_url 0.0.1",
  "style 0.0.1",
  "style_traits 0.0.1",
 ]
--- a/toolkit/library/rust/Cargo.lock
+++ b/toolkit/library/rust/Cargo.lock
@@ -295,17 +295,16 @@ dependencies = [
 
 [[package]]
 name = "geckoservo"
 version = "0.0.1"
 dependencies = [
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "env_logger 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "selectors 0.18.0",
  "servo_url 0.0.1",
  "style 0.0.1",
  "style_traits 0.0.1",
 ]
--- a/toolkit/modules/SelectContentHelper.jsm
+++ b/toolkit/modules/SelectContentHelper.jsm
@@ -137,23 +137,23 @@ this.SelectContentHelper.prototype = {
   },
 
   // Determine user agent background-color and color.
   // This is used to skip applying the custom color if it matches
   // the user agent values.
   _calculateUAColors() {
     let dummyOption = this.element.ownerDocument.createElement("option");
     dummyOption.style.setProperty("color", "-moz-comboboxtext", "important");
-    dummyOption.style.setProperty("backgroundColor", "-moz-combobox", "important");
+    dummyOption.style.setProperty("background-color", "-moz-combobox", "important");
     let optionCS = this.element.ownerGlobal.getComputedStyle(dummyOption);
     this._uaBackgroundColor = optionCS.backgroundColor;
     this._uaColor = optionCS.color;
     let dummySelect = this.element.ownerDocument.createElement("select");
     dummySelect.style.setProperty("color", "-moz-fieldtext", "important");
-    dummySelect.style.setProperty("backgroundColor", "-moz-field", "important");
+    dummySelect.style.setProperty("background-color", "-moz-field", "important");
     let selectCS = this.element.ownerGlobal.getComputedStyle(dummySelect);
     this._uaSelectBackgroundColor = selectCS.backgroundColor;
     this._uaSelectColor = selectCS.color;
   },
 
   get uaBackgroundColor() {
     if (!this._uaBackgroundColor) {
       this._calculateUAColors();
--- a/toolkit/modules/SelectParentHelper.jsm
+++ b/toolkit/modules/SelectParentHelper.jsm
@@ -50,23 +50,17 @@ this.SelectParentHelper = {
     let ruleBody = "";
 
     // Some webpages set the <select> backgroundColor to transparent,
     // but they don't intend to change the popup to transparent.
     if (customStylingEnabled &&
         selectBackgroundColor != uaSelectBackgroundColor &&
         selectBackgroundColor != "rgba(0, 0, 0, 0)" &&
         selectBackgroundColor != selectColor) {
-      let rgba = selectBackgroundColor.match(/rgba\((\d+), (\d+), (\d+),/);
-      if (rgba) {
-        let [, r, g, b] = rgba;
-        ruleBody = `background-color: rgb(${r}, ${g}, ${b});`;
-      } else {
-        ruleBody = `background-color: ${selectBackgroundColor};`;
-      }
+      ruleBody = `background-image: linear-gradient(${selectBackgroundColor}, ${selectBackgroundColor});`;
       usedSelectBackgroundColor = selectBackgroundColor;
     } else {
       usedSelectBackgroundColor = uaSelectBackgroundColor;
     }
 
     if (customStylingEnabled &&
         selectColor != uaSelectColor &&
         selectColor != selectBackgroundColor &&
--- a/toolkit/modules/subprocess/test/xpcshell/.eslintrc.js
+++ b/toolkit/modules/subprocess/test/xpcshell/.eslintrc.js
@@ -1,5 +1,5 @@
 "use strict";
 
-module.exports = { // eslint-disable-line no-undef
-  "extends": "../../../../../testing/xpcshell/xpcshell.eslintrc.js",
+module.exports = {
+  "extends": "plugin:mozilla/xpcshell-test",
 };
--- a/toolkit/modules/tests/browser/.eslintrc.js
+++ b/toolkit/modules/tests/browser/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/toolkit/modules/tests/chrome/.eslintrc.js
+++ b/toolkit/modules/tests/chrome/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/chrome-test"
   ]
 };
--- a/toolkit/modules/tests/modules/.eslintrc.js
+++ b/toolkit/modules/tests/modules/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/modules/tests/xpcshell/.eslintrc.js
+++ b/toolkit/modules/tests/xpcshell/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/mozapps/downloads/tests/chrome/.eslintrc.js
+++ b/toolkit/mozapps/downloads/tests/chrome/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/chrome-test"
   ]
 };
--- a/toolkit/mozapps/downloads/tests/unit/.eslintrc.js
+++ b/toolkit/mozapps/downloads/tests/unit/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/mozapps/extensions/.eslintrc.js
+++ b/toolkit/mozapps/extensions/.eslintrc.js
@@ -1,15 +1,15 @@
 "use strict";
 
 module.exports = { // eslint-disable-line no-undef
   "rules": {
     // Warn about cyclomatic complexity in functions.
     // XXX Bug 1326071 - This should be reduced down - probably to 20 or to
-    // be removed & synced with the toolkit/.eslintrc.js value.
+    // be removed & synced with the mozilla/recommended value.
     "complexity": ["error", {"max": 60}],
 
     // No using undeclared variables
     "no-undef": "error",
 
     "no-unused-vars": ["error", {"args": "none", "varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$"}],
   }
 };
--- a/toolkit/mozapps/extensions/test/browser/.eslintrc.js
+++ b/toolkit/mozapps/extensions/test/browser/.eslintrc.js
@@ -1,11 +1,11 @@
 "use strict";
 
 module.exports = { // eslint-disable-line no-undef
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ],
 
   "rules": {
     "no-unused-vars": ["error", {"args": "none", "varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS|end_test)$"}],
   }
 };
--- a/toolkit/mozapps/extensions/test/mochitest/.eslintrc.js
+++ b/toolkit/mozapps/extensions/test/mochitest/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = { // eslint-disable-line no-undef
   "extends": [
-    "../../../../../testing/mochitest/mochitest.eslintrc.js"
+    "plugin:mozilla/mochitest-test"
   ]
 };
--- a/toolkit/mozapps/extensions/test/xpcshell/.eslintrc.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/.eslintrc.js
@@ -1,10 +1,10 @@
 "use strict";
 
 module.exports = { // eslint-disable-line no-undef
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ],
   "rules": {
     "no-unused-vars": ["error", {"args": "none", "varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS|end_test)$"}],
   }
 };
--- a/toolkit/mozapps/extensions/test/xpinstall/.eslintrc.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
-module.exports = { // eslint-disable-line no-undef
+module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/toolkit/mozapps/update/tests/chrome/.eslintrc.js
+++ b/toolkit/mozapps/update/tests/chrome/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/chrome-test"
   ]
 };
--- a/toolkit/mozapps/update/tests/unit_aus_update/.eslintrc.js
+++ b/toolkit/mozapps/update/tests/unit_aus_update/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/mozapps/update/tests/unit_base_updater/.eslintrc.js
+++ b/toolkit/mozapps/update/tests/unit_base_updater/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/mozapps/update/tests/unit_service_updater/.eslintrc.js
+++ b/toolkit/mozapps/update/tests/unit_service_updater/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+    "plugin:mozilla/xpcshell-test"
   ]
 };
--- a/toolkit/profile/test/.eslintrc.js
+++ b/toolkit/profile/test/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/chrome-test"
   ]
 };
--- a/toolkit/themes/osx/mochitests/.eslintrc.js
+++ b/toolkit/themes/osx/mochitests/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/chrome-test"
   ]
 };
--- a/toolkit/xre/test/.eslintrc.js
+++ b/toolkit/xre/test/.eslintrc.js
@@ -1,8 +1,8 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../testing/mochitest/mochitest.eslintrc.js",
-    "../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/mochitest-test",
+    "plugin:mozilla/browser-test"
   ]
 };
rename from testing/mochitest/browser.eslintrc.js
rename to tools/lint/eslint/eslint-plugin-mozilla/lib/configs/browser-test.js
--- a/testing/mochitest/browser.eslintrc.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/browser-test.js
@@ -1,19 +1,21 @@
 // Parent config file for all browser-chrome files.
+"use strict";
+
 module.exports = {
   "rules": {
     "mozilla/import-headjs-globals": "warn",
-    "mozilla/mark-test-function-used": "warn",
+    "mozilla/mark-test-function-used": "warn"
   },
 
   "env": {
     "browser": true,
     "mozilla/browser-window": true,
-    "mozilla/simpletest": true,
+    "mozilla/simpletest": true
     //"node": true
   },
 
   "plugins": [
     "mozilla"
   ],
 
   // All globals made available in the test environment.
@@ -49,11 +51,11 @@ module.exports = {
     "SpecialPowers": false,
     "TestUtils": false,
     "thisTestLeaksUncaughtRejectionsAndShouldBeFixed": false,
     "todo": false,
     "todo_is": false,
     "todo_isnot": false,
     "waitForClipboard": false,
     "waitForExplicitFinish": false,
-    "waitForFocus": false,
+    "waitForFocus": false
   }
 };
rename from testing/mochitest/chrome.eslintrc.js
rename to tools/lint/eslint/eslint-plugin-mozilla/lib/configs/chrome-test.js
--- a/testing/mochitest/chrome.eslintrc.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/chrome-test.js
@@ -1,19 +1,21 @@
 // Parent config file for all mochitest files.
+"use strict";
+
 module.exports = {
   rules: {
     "mozilla/import-headjs-globals": "warn",
-    "mozilla/mark-test-function-used": "warn",
+    "mozilla/mark-test-function-used": "warn"
   },
 
   "env": {
     "browser": true,
     "mozilla/browser-window": true,
-    "mozilla/simpletest": true,
+    "mozilla/simpletest": true
   },
 
   "plugins": [
     "mozilla"
   ],
 
   // All globals made available in the test environment.
   "globals": {
@@ -42,11 +44,11 @@ module.exports = {
     "registerCleanupFunction": false,
     "requestLongerTimeout": false,
     "SpecialPowers": false,
     "todo": false,
     "todo_is": false,
     "todo_isnot": false,
     "waitForClipboard": false,
     "waitForExplicitFinish": false,
-    "waitForFocus": false,
+    "waitForFocus": false
   }
 };
rename from testing/mochitest/mochitest.eslintrc.js
rename to tools/lint/eslint/eslint-plugin-mozilla/lib/configs/mochitest-test.js
--- a/testing/mochitest/mochitest.eslintrc.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/mochitest-test.js
@@ -1,19 +1,21 @@
 // Parent config file for all mochitest files.
+"use strict";
+
 module.exports = {
   "rules": {
     "mozilla/import-headjs-globals": "warn",
     "mozilla/mark-test-function-used": "warn",
-    "no-shadow": "error",
+    "no-shadow": "error"
   },
 
   "env": {
     "browser": true,
-    "mozilla/simpletest": true,
+    "mozilla/simpletest": true
   },
 
   "plugins": [
     "mozilla"
   ],
 
   // All globals made available in the test environment.
   "globals": {
@@ -39,11 +41,11 @@ module.exports = {
     "registerCleanupFunction": false,
     "requestLongerTimeout": false,
     "SpecialPowers": false,
     "todo": false,
     "todo_is": false,
     "todo_isnot": false,
     "waitForClipboard": false,
     "waitForExplicitFinish": false,
-    "waitForFocus": false,
+    "waitForFocus": false
   }
 };
copy from toolkit/.eslintrc.js
copy to tools/lint/eslint/eslint-plugin-mozilla/lib/configs/recommended.js
--- a/toolkit/.eslintrc.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/recommended.js
@@ -1,14 +1,34 @@
 "use strict";
 
 module.exports = {
+  // When adding items to this file please check for effects on sub-directories.
+  "plugins": [
+    "mozilla"
+  ],
+  "env": {
+    "browser": true,
+    "es6": true
+  },
+  "parserOptions": {
+    "ecmaVersion": 8
+  },
   // When adding items to this file please check for effects on all of toolkit
   // and browser
   "rules": {
+    "mozilla/avoid-removeChild": "error",
+    "mozilla/avoid-nsISupportsString-preferences": "error",
+    "mozilla/import-globals": "error",
+    "mozilla/no-import-into-var-and-global": "error",
+    "mozilla/no-useless-parameters": "error",
+    "mozilla/no-useless-removeEventListener": "error",
+    "mozilla/use-default-preference-values": "error",
+    "mozilla/use-ownerGlobal": "error",
+
     // Braces only needed for multi-line arrow function blocks
     // "arrow-body-style": ["error", "as-needed"],
 
     // Require spacing around =>
     "arrow-spacing": "error",
 
     // Always require spacing around a single line block
     "block-spacing": "error",
@@ -18,18 +38,18 @@ module.exports = {
 
     // No space before always a space after a comma
     "comma-spacing": ["error", {"before": false, "after": true}],
 
     // Commas at the end of the line not the start
     // "comma-style": "error",
 
     // Warn about cyclomatic complexity in functions.
-    // XXX Bug 1326071 - This should be reduced down - probably to 20.
-    "complexity": ["error", {"max": 48}],
+    // XXX Get this down to 20?
+    "complexity": ["error", {"max": 35}],
 
     // Don't require spaces around computed properties
     "computed-property-spacing": ["error", "never"],
 
     // Functions must always return something or nothing
     "consistent-return": "error",
 
     // Require braces around blocks that start a new line
@@ -45,17 +65,18 @@ module.exports = {
 
     // Require function* name()
     // "generator-star-spacing": ["error", {"before": false, "after": true}],
 
     // Two space indent
     // "indent": ["error", 2, { "SwitchCase": 1 }],
 
     // Space after colon not before in property declarations
-    // "key-spacing": ["error", { "beforeColon": false, "afterColon": true, "mode": "minimum" }],
+    // "key-spacing": ["error", { "beforeColon": false, "afterColon": true,
+    //                            "mode": "minimum" }],
 
     // Require spaces before and after keywords
     "keyword-spacing": "error",
 
     // Unix linebreaks
     "linebreak-style": ["error", "unix"],
 
     // Don't enforce the maximum depth that blocks can be nested. The complexity
@@ -130,17 +151,22 @@ module.exports = {
 
     // No single if block inside an else block
     "no-lonely-if": "error",
 
     // No mixing spaces and tabs in indent
     "no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
 
     // No unnecessary spacing
-    "no-multi-spaces": ["error", { exceptions: { "AssignmentExpression": true, "VariableDeclarator": true, "ArrayExpression": true, "ObjectExpression": true } }],
+    "no-multi-spaces": ["error", { exceptions: {
+      "AssignmentExpression": true,
+      "VariableDeclarator": true,
+      "ArrayExpression": true,
+      "ObjectExpression": true
+    } }],
 
     // No reassigning native JS objects
     "no-native-reassign": "error",
 
     // Nested ternary statements are confusing
     "no-nested-ternary": "error",
 
     // Use {} instead of new Object()
@@ -183,21 +209,24 @@ module.exports = {
     "no-unexpected-multiline": "error",
 
     // No unreachable statements
     "no-unreachable": "error",
 
     // Disallow control flow statements in finally blocks
     "no-unsafe-finally": "error",
 
+    // No (!foo in bar) or (!object instanceof Class)
+    "no-unsafe-negation": "error",
+
     // No declaring variables that are never used
     "no-unused-vars": ["error", {
       "vars": "local",
       "varsIgnorePattern": "^Cc|Ci|Cu|Cr|EXPORTED_SYMBOLS",
-      "args": "none",
+      "args": "none"
     }],
 
     // No using variables before defined
     // "no-use-before-define": ["error", "nofunc"],
 
     // Disallow unnecessary .call() and .apply()
     "no-useless-call": "error",
 
@@ -207,17 +236,20 @@ module.exports = {
     // No using with
     "no-with": "error",
 
     // Require object-literal shorthand with ES6 method syntax
     "object-shorthand": ["error", "always", { "avoidQuotes": true }],
 
     // Require double-quotes everywhere, except where quotes are escaped
     // or template literals are used.
-    "quotes": ["error", "double", { "avoidEscape": true, "allowTemplateLiterals": true }],
+    "quotes": ["error", "double", {
+      "avoidEscape": true,
+      "allowTemplateLiterals": true
+    }],
 
     // No spacing inside rest or spread expressions
     "rest-spread-spacing": "error",
 
     // Always require semicolon at end of statement
     // "semi": ["error", "always"],
 
     // Require space before blocks
@@ -247,42 +279,40 @@ module.exports = {
     // No comparisons to NaN
     "use-isnan": "error",
 
     // Only check typeof against valid results
     "valid-typeof": "error",
 
     // Don't concatenate string literals together (unless they span multiple
     // lines)
-    "no-useless-concat": "error",
-  },
-  "env": {
-    "es6": true,
-    "browser": true,
+    "no-useless-concat": "error"
   },
   "globals": {
     "BroadcastChannel": false,
     // Specific to Firefox (Chrome code only).
     "ChromeWindow": false,
     "ChromeWorker": false,
     "ChromeUtils": false,
     "Components": false,
     "dump": true,
     // Specific to Firefox
+    // eslint-disable-next-line max-len
     // https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/InternalError
     "InternalError": true,
     "KeyEvent": false,
     "openDialog": false,
     "MenuBoxObject": false,
     // Specific to Firefox (Chrome code only).
     "MozSelfSupport": false,
     "SimpleGestureEvent": false,
     "sizeToContent": false,
     "SharedArrayBuffer": false,
     // Note: StopIteration will likely be removed as part of removing legacy
     // generators, see bug 968038.
     "StopIteration": false,
     // Specific to Firefox
+    // eslint-disable-next-line max-len
     // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/uneval
     "uneval": false,
-    "XULElement": false,
+    "XULElement": false
   }
 };
rename from testing/xpcshell/xpcshell.eslintrc.js
rename to tools/lint/eslint/eslint-plugin-mozilla/lib/configs/xpcshell-test.js
--- a/testing/xpcshell/xpcshell.eslintrc.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/xpcshell-test.js
@@ -1,14 +1,16 @@
 // Parent config file for all xpcshell files.
+"use strict";
+
 module.exports = {
   rules: {
     "mozilla/import-headjs-globals": "warn",
     "mozilla/mark-test-function-used": "warn",
-    "no-shadow": "error",
+    "no-shadow": "error"
   },
 
   // All globals made available in the test environment.
   "globals": {
     "_TEST_FILE": false,
     "add_task": false,
     "add_test": false,
     "Assert": false,
@@ -60,12 +62,13 @@ module.exports = {
     // Defined in XPCShellImpl.
     "sendCommand": false,
     "strictEqual": false,
     "throws": false,
     "todo": false,
     "todo_check_false": false,
     "todo_check_true": false,
     // Firefox specific function.
+    // eslint-disable-next-line max-len
     // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/uneval
-    "uneval": false,
+    "uneval": false
   }
 };
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/index.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/index.js
@@ -7,16 +7,23 @@
  */
 
 "use strict";
 
 //------------------------------------------------------------------------------
 // Plugin Definition
 //------------------------------------------------------------------------------
 module.exports = {
+  configs: {
+    "browser-test": require("../lib/configs/browser-test"),
+    "chrome-test": require("../lib/configs/chrome-test"),
+    "mochitest-test": require("../lib/configs/mochitest-test"),
+    "recommended": require("../lib/configs/recommended"),
+    "xpcshell-test": require("../lib/configs/xpcshell-test")
+  },
   environments: {
     "browser-window": require("../lib/environments/browser-window.js"),
     "chrome-worker": require("../lib/environments/chrome-worker.js"),
     "frame-script": require("../lib/environments/frame-script.js"),
     "places-overlay": require("../lib/environments/places-overlay.js"),
     "simpletest": require("../lib/environments/simpletest.js")
   },
   processors: {
@@ -42,27 +49,27 @@ module.exports = {
       require("../lib/rules/reject-importGlobalProperties"),
     "reject-some-requires": require("../lib/rules/reject-some-requires"),
     "use-default-preference-values":
       require("../lib/rules/use-default-preference-values"),
     "use-ownerGlobal": require("../lib/rules/use-ownerGlobal"),
     "var-only-at-top-level": require("../lib/rules/var-only-at-top-level")
   },
   rulesConfig: {
-    "avoid-removeChild": 0,
-    "avoid-nsISupportsString-preferences": 0,
-    "balanced-listeners": 0,
-    "import-globals": 0,
-    "import-headjs-globals": 0,
-    "mark-test-function-used": 0,
-    "no-aArgs": 0,
-    "no-cpows-in-tests": 0,
-    "no-single-arg-cu-import": 0,
-    "no-import-into-var-and-global": 0,
-    "no-useless-parameters": 0,
-    "no-useless-removeEventListener": 0,
-    "reject-importGlobalProperties": 0,
-    "reject-some-requires": 0,
-    "use-default-preference-values": 0,
-    "use-ownerGlobal": 0,
-    "var-only-at-top-level": 0
+    "avoid-removeChild": "off",
+    "avoid-nsISupportsString-preferences": "off",
+    "balanced-listeners": "off",
+    "import-globals": "off",
+    "import-headjs-globals": "off",
+    "mark-test-function-used": "off",
+    "no-aArgs": "off",
+    "no-cpows-in-tests": "off",
+    "no-single-arg-cu-import": "off",
+    "no-import-into-var-and-global": "off",
+    "no-useless-parameters": "off",
+    "no-useless-removeEventListener": "off",
+    "reject-importGlobalProperties": "off",
+    "reject-some-requires": "off",
+    "use-default-preference-values": "off",
+    "use-ownerGlobal": "off",
+    "var-only-at-top-level": "off"
   }
 };
--- a/tools/lint/eslint/eslint-plugin-mozilla/package.json
+++ b/tools/lint/eslint/eslint-plugin-mozilla/package.json
@@ -1,11 +1,11 @@
 {
   "name": "eslint-plugin-mozilla",
-  "version": "0.2.31",
+  "version": "0.2.32",
   "description": "A collection of rules that help enforce JavaScript coding standard in the Mozilla project.",
   "keywords": [
     "eslint",
     "eslintplugin",
     "eslint-plugin",
     "mozilla",
     "firefox"
   ],
--- a/tools/mach_commands.py
+++ b/tools/mach_commands.py
@@ -255,17 +255,17 @@ class FormatProvider(MachCommandBase):
 
         except urllib2.HTTPError as e:
             print("HTTP error {0}: {1}".format(e.code, e.reason))
             return 1
 
         from subprocess import Popen, PIPE
 
         if os.path.exists(".hg"):
-            diff_process = Popen(["hg", "diff", "-U0", "-r", "tip^",
+            diff_process = Popen(["hg", "diff", "-U0", "-r", ".^",
                                   "--include", "glob:**.c", "--include", "glob:**.cpp",
                                   "--include", "glob:**.h",
                                   "--exclude", "listfile:.clang-format-ignore"], stdout=PIPE)
         else:
             git_process = Popen(["git", "diff", "--no-color", "-U0", "HEAD^"], stdout=PIPE)
             try:
                 diff_process = Popen(["filterdiff", "--include=*.h", "--include=*.cpp",
                                       "--exclude-from-file=.clang-format-ignore"],