Merge m-c to inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 30 May 2017 16:09:45 -0400
changeset 409513 efea61490537e815e9c0e1738d181b1dd8be6020
parent 409512 34a50e140cf36d107dc3e77caa16aaa0859eb93a (current diff)
parent 409471 fbe0e3f31233cc7846a2168c613452a1d7147cc2 (diff)
child 409514 648eadae596481359d38051189444c64fad03192
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [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 m-c to inbound. a=merge
browser/base/content/test/siteIdentity/browser_bug435035.js
browser/base/content/test/siteIdentity/test_bug435035.html
devtools/client/inspector/computed/test/browser_computed_select-and-copy-styles.js
taskcluster/scripts/tester/test-ubuntu.sh
testing/marionette/harness/marionette_harness/tests/unit/importanotherscript.js
testing/marionette/harness/marionette_harness/tests/unit/importscript.js
testing/marionette/harness/marionette_harness/tests/unit/test_import_script.py
--- a/accessible/tests/browser/.eslintrc.js
+++ b/accessible/tests/browser/.eslintrc.js
@@ -26,17 +26,16 @@ module.exports = {
     "generator-star-spacing": "off",
     "handle-callback-err": ["error", "er"],
     "indent": ["error", 2, {"SwitchCase": 1}],
     "max-nested-callbacks": ["error", 4],
     "max-params": "off",
     "max-statements": "off",
     "new-cap": ["error", {"capIsNew": false}],
     "new-parens": "error",
-    "no-array-constructor": "error",
     "no-bitwise": "off",
     "no-caller": "error",
     "no-catch-shadow": "error",
     "no-comma-dangle": "off",
     "no-console": "off",
     "no-constant-condition": "off",
     "no-continue": "off",
     "no-control-regex": "error",
--- a/accessible/tests/mochitest/actions.js
+++ b/accessible/tests/mochitest/actions.js
@@ -56,17 +56,17 @@ function testActions(aArray)
     var actionObj = aArray[idx];
     var accOrElmOrID = actionObj.ID;
     var actionIndex = actionObj.actionIndex;
     var actionName = actionObj.actionName;
     var events = actionObj.events;
     var accOrElmOrIDOfTarget = actionObj.targetID ?
       actionObj.targetID : accOrElmOrID;
 
-    var eventSeq = new Array();
+    var eventSeq = [];
     if (events) {
       var elm = getNode(accOrElmOrIDOfTarget);
       if (events & MOUSEDOWN_EVENT)
         eventSeq.push(new checkerOfActionInvoker("mousedown", elm));
 
       if (events & MOUSEUP_EVENT)
         eventSeq.push(new checkerOfActionInvoker("mouseup", elm));
 
--- a/accessible/tests/mochitest/editabletext/editabletext.js
+++ b/accessible/tests/mochitest/editabletext/editabletext.js
@@ -9,17 +9,17 @@ function editableTextTestRun()
   }
 
   this.run = function run()
   {
     this.iterate();
   }
 
   this.index = 0;
-  this.seq = new Array();
+  this.seq = [];
 
   this.iterate = function iterate()
   {
     if (this.index < this.seq.length) {
       this.seq[this.index++].startTest(this);
       return;
     }
 
--- a/accessible/tests/mochitest/events.js
+++ b/accessible/tests/mochitest/events.js
@@ -829,17 +829,17 @@ function eventQueue(aEventType)
     this.mNextInvokerStatus = aStatus;
 
     // Uncomment it to debug invoker processing logic.
     //gLogger.log(eventQueue.invokerStatusToMsg(aStatus, aLogMsg));
   }
 
   this.mDefEventType = aEventType;
 
-  this.mInvokers = new Array();
+  this.mInvokers = [];
   this.mIndex = -1;
   this.mScenarios = null;
 
   this.mNextInvokerStatus = kInvokerNotScheduled;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // eventQueue static members and constants
@@ -1020,32 +1020,32 @@ function sequence()
       ok(false, "End of sequence: nothing to process!");
       SimpleTest.finish();
       return;
     }
 
     this.items[this.idx].startProcess();
   }
 
-  this.items = new Array();
+  this.items = [];
   this.idx = -1;
 }
 
 
 ////////////////////////////////////////////////////////////////////////////////
 // Event queue invokers
 
 /**
  * Defines a scenario of expected/unexpected events. Each invoker can have
  * one or more scenarios of events. Only one scenario must be completed.
  */
 function defineScenario(aInvoker, aEventSeq, aUnexpectedEventSeq)
 {
   if (!("scenarios" in aInvoker))
-    aInvoker.scenarios = new Array();
+    aInvoker.scenarios = [];
 
   // Create unified event sequence concatenating expected and unexpected
   // events.
   if (!aEventSeq)
     aEventSeq = [];
 
   for (var idx = 0; idx < aEventSeq.length; idx++) {
     aEventSeq[idx].unexpected |= false;
@@ -2127,17 +2127,17 @@ function listenA11yEvents(aStartToListen
     if (--gA11yEventApplicantsCount <= 0)
       Services.obs.removeObserver(gA11yEventObserver, "accessible-event");
   }
 }
 
 function addA11yEventListener(aEventType, aEventHandler)
 {
   if (!(aEventType in gA11yEventListeners))
-    gA11yEventListeners[aEventType] = new Array();
+    gA11yEventListeners[aEventType] = [];
 
   var listenersArray = gA11yEventListeners[aEventType];
   var index = listenersArray.indexOf(aEventHandler);
   if (index == -1)
     listenersArray.push(aEventHandler);
 }
 
 function removeA11yEventListener(aEventType, aEventHandler)
--- a/accessible/tests/mochitest/table.js
+++ b/accessible/tests/mochitest/table.js
@@ -369,17 +369,17 @@ function testTableSelection(aIdentifier,
   var acc = getAccessible(aIdentifier, [nsIAccessibleTable]);
   if (!acc)
     return;
 
   var rowCount = aCellsArray.length;
   var colsCount = aCellsArray[0].length;
 
   // Columns selection tests.
-  var selCols = new Array();
+  var selCols = [];
 
   // isColumnSelected test
   for (var colIdx = 0; colIdx < colsCount; colIdx++) {
     var isColSelected = true;
     for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
       if (aCellsArray[rowIdx][colIdx] == false ||
           aCellsArray[rowIdx][colIdx] == undefined) {
         isColSelected = false;
@@ -409,17 +409,17 @@ function testTableSelection(aIdentifier,
       "from getSelectedColumns.");
 
   for (var i = 0; i < actualSelColsCount; i++) {
     is (actualSelCols[i], selCols[i],
         msg + "Column at index " + selCols[i] + " should be selected.");
   }
 
   // Rows selection tests.
-  var selRows = new Array();
+  var selRows = [];
 
   // isRowSelected test
   for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
     var isRowSelected = true;
     for (var colIdx = 0; colIdx < colsCount; colIdx++) {
       if (aCellsArray[rowIdx][colIdx] == false ||
           aCellsArray[rowIdx][colIdx] == undefined) {
         isRowSelected = false;
@@ -449,17 +449,17 @@ function testTableSelection(aIdentifier,
       "from getSelectedRows.");
 
   for (var i = 0; i < actualSelrowCount; i++) {
     is (actualSelRows[i], selRows[i],
         msg + "Row at index " + selRows[i] + " should be selected.");
   }
 
   // Cells selection tests.
-  var selCells = new Array();
+  var selCells = [];
 
   // isCellSelected test
   for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
     for (var colIdx = 0; colIdx < colsCount; colIdx++) {
       if (aCellsArray[rowIdx][colIdx] & kSpanned)
         continue;
 
       var isSelected = aCellsArray[rowIdx][colIdx] == true;
--- a/accessible/tests/mochitest/test_nsIAccessibleImage.html
+++ b/accessible/tests/mochitest/test_nsIAccessibleImage.html
@@ -108,49 +108,49 @@ https://bugzilla.mozilla.org/show_bug.cg
     }
 
     function doTest()
     {
       // Test non-linked image
       testThis("nonLinkedImage", "moz.png", 89, 38);
 
       // Test linked image
-      var actionNamesArray = new Array("jump");
+      var actionNamesArray = ["jump"];
       testThis("linkedImage", "moz.png", 89, 38, 1,
                actionNamesArray);
 
       // Image with long desc
-      var actionNamesArray = new Array("showlongdesc");
+      var actionNamesArray = ["showlongdesc"];
       testThis("longdesc", "moz.png", 89, 38, 1,
                actionNamesArray);
 
       // Image with invalid url in long desc
       testThis("invalidLongdesc", "moz.png", 89, 38, 0);
 
       // Image with click and long desc
       actionNamesArray = null;
-      actionNamesArray = new Array("click", "showlongdesc");
+      actionNamesArray = ["click", "showlongdesc"];
       testThis("clickAndLongdesc", "moz.png",
                89, 38, 2, actionNamesArray);
-      
+
       // Image with click
       actionNamesArray = null;
-      actionNamesArray = new Array("click");
+      actionNamesArray = ["click"];
       testThis("click", "moz.png",
                89, 38, 1, actionNamesArray);
-      
+
       // Image with long desc
       actionNamesArray = null;
-      actionNamesArray = new Array("showlongdesc");
+      actionNamesArray = ["showlongdesc"];
       testThis("longdesc2", "moz.png",
                89, 38, 1, actionNamesArray);
 
       // Image described by HTML:a@href with whitespaces
       actionNamesArray = null;
-      actionNamesArray = new Array("showlongdesc");
+      actionNamesArray = ["showlongdesc"];
       testThis("longdesc3", "moz.png",
                89, 38, 1, actionNamesArray);
 
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -227,16 +227,20 @@ pref("general.skins.selectedSkin", "clas
 
 pref("general.smoothScroll", true);
 #ifdef UNIX_BUT_NOT_MAC
 pref("general.autoScroll", false);
 #else
 pref("general.autoScroll", true);
 #endif
 
+// UI density of the browser chrome. This mostly affects toolbarbutton
+// and urlbar spacing. The possible values are 0=normal, 1=compact, 2=touch.
+pref("browser.uidensity", 0);
+
 // At startup, check if we're the default browser and prompt user if not.
 pref("browser.shell.checkDefaultBrowser", true);
 pref("browser.shell.shortcutFavicons",true);
 pref("browser.shell.mostRecentDateSetAsDefault", "");
 pref("browser.shell.skipDefaultBrowserCheckOnFirstRun", true);
 pref("browser.shell.didSkipDefaultBrowserCheckOnFirstRun", false);
 pref("browser.shell.defaultBrowserCheckCount", 0);
 pref("browser.defaultbrowser.notificationbar", false);
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1309,16 +1309,19 @@ var gBrowserInit = {
     SidebarUI.init();
 
     // Certain kinds of automigration rely on this notification to complete
     // their tasks BEFORE the browser window is shown. SessionStore uses it to
     // restore tabs into windows AFTER important parts like gMultiProcessBrowser
     // have been initialized.
     Services.obs.notifyObservers(window, "browser-window-before-show");
 
+    gUIDensity.update();
+    gPrefService.addObserver(gUIDensity.prefDomain, gUIDensity);
+
     let isResistFingerprintingEnabled = gPrefService.getBoolPref("privacy.resistFingerprinting");
 
     // Set a sane starting width/height for all resolutions on new profiles.
     if (isResistFingerprintingEnabled) {
       // When the fingerprinting resistance is enabled, making sure that we don't
       // have a maximum window to interfere with generating rounded window dimensions.
       document.documentElement.setAttribute("sizemode", "normal");
     } else if (!document.documentElement.hasAttribute("width")) {
@@ -1765,16 +1768,18 @@ var gBrowserInit = {
     FullScreen.uninit();
 
     gSync.uninit();
 
     gExtensionsNotifications.uninit();
 
     Services.obs.removeObserver(gPluginHandler.NPAPIPluginCrashed, "plugin-crashed");
 
+    gPrefService.removeObserver(gUIDensity.prefDomain, gUIDensity);
+
     try {
       gBrowser.removeProgressListener(window.XULBrowserWindow);
       gBrowser.removeTabsProgressListener(window.TabsProgressListener);
     } catch (ex) {
     }
 
     PlacesToolbarHelper.uninit();
 
@@ -5416,16 +5421,41 @@ var gTabletModePageCounter = {
     }
   },
 };
 
 function displaySecurityInfo() {
   BrowserPageInfo(null, "securityTab");
 }
 
+// Updates the UI density (for touch and compact mode) based on the uidensity pref.
+var gUIDensity = {
+  prefDomain: "browser.uidensity",
+  observe(aSubject, aTopic, aPrefName) {
+    if (aTopic != "nsPref:changed" || aPrefName != this.prefDomain)
+      return;
+
+    this.update();
+  },
+
+  update() {
+    let doc = document.documentElement;
+    switch (gPrefService.getIntPref(this.prefDomain)) {
+    case 1:
+      doc.setAttribute("uidensity", "compact");
+      break;
+    case 2:
+      doc.setAttribute("uidensity", "touch");
+      break;
+    default:
+      doc.removeAttribute("uidensity");
+      break;
+    }
+  },
+};
 
 var gHomeButton = {
   prefDomain: "browser.startup.homepage",
   observe(aSubject, aTopic, aPrefName) {
     if (aTopic != "nsPref:changed" || aPrefName != this.prefDomain)
       return;
 
     this.updateTooltip();
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -2352,20 +2352,24 @@
 
             // if we're adding tabs, we're past interrupt mode, ditch the owner
             if (this.mCurrentTab.owner)
               this.mCurrentTab.owner = null;
 
             var t = document.createElementNS(NS_XUL, "tab");
 
             aURI = aURI || "about:blank";
+            let aURIObject = null;
+            try {
+              aURIObject = Services.io.newURI(aURI);
+            } catch (ex) { /* we'll try to fix up this URL later */ }
 
             let lazyBrowserURI;
             if (aCreateLazyBrowser && aURI != "about:blank") {
-              lazyBrowserURI = Services.io.newURI(aURI);
+              lazyBrowserURI = aURIObject;
               aURI = "about:blank";
             }
 
             var uriIsAboutBlank = aURI == "about:blank";
 
             if (!aNoInitialLabel) {
               if (isBlankPageURL(aURI)) {
                 t.setAttribute("label", this.mStringBundle.getString("tabs.emptyTabTitle"));
@@ -2501,18 +2505,24 @@
 
             // Dispatch a new tab notification.  We do this once we're
             // entirely done, so that things are in a consistent state
             // even if the event listener opens or closes tabs.
             var detail = aEventDetail || {};
             var evt = new CustomEvent("TabOpen", { bubbles: true, detail });
             t.dispatchEvent(evt);
 
-            if (!usingPreloadedContent && aOriginPrincipal) {
-              b.createAboutBlankContentViewer(aOriginPrincipal);
+            if (!usingPreloadedContent && aOriginPrincipal && aURI) {
+              let {URI_INHERITS_SECURITY_CONTEXT} = Ci.nsIProtocolHandler;
+              // Unless we know for sure we're not inheriting principals,
+              // force the about:blank viewer to have the right principal:
+              if (!aURIObject ||
+                  (Services.io.getProtocolFlags(aURIObject.scheme) & URI_INHERITS_SECURITY_CONTEXT)) {
+                b.createAboutBlankContentViewer(aOriginPrincipal);
+              }
             }
 
             // If we didn't swap docShells with a preloaded browser
             // then let's just continue loading the page normally.
             if (!usingPreloadedContent && (!uriIsAboutBlank || aDisallowInheritPrincipal)) {
               // pretend the user typed this so it'll be available till
               // the document successfully loads
               if (aURI && gInitialPages.indexOf(aURI) == -1)
--- a/browser/base/content/test/general/browser_bug537013.js
+++ b/browser/base/content/test/general/browser_bug537013.js
@@ -40,17 +40,17 @@ function test() {
   });
   texts.forEach(aText => addTabWithText(aText));
 
   // Set up the first tab
   gBrowser.selectedTab = tabs[0];
 
   setFindString(texts[0]);
   // Turn on highlight for testing bug 891638
-  gFindBar.toggleHighlight(true);
+  gFindBar.getElement("highlight").checked = true;
 
   // Make sure the second tab is correct, then set it up
   gBrowser.selectedTab = tabs[1];
   gBrowser.selectedTab.addEventListener("TabFindInitialized", continueTests1);
   // Initialize the findbar
   gFindBar;
 }
 function continueTests1() {
@@ -67,17 +67,17 @@ function continueTests1() {
   gBrowser.selectedTab = tabs[0];
   ok(!gFindBar.hidden, "First tab shows find bar!");
   // When the Find Clipboard is supported, this test not relevant.
   if (!HasFindClipboard)
     is(gFindBar._findField.value, texts[0], "First tab persists find value!");
   ok(gFindBar.getElement("highlight").checked,
      "Highlight button state persists!");
 
-  // While we're here, let's test the backout of bug 253793.
+  // While we're here, let's test bug 253793
   gBrowser.reload();
   gBrowser.addEventListener("DOMContentLoaded", continueTests2, true);
 }
 
 function continueTests2() {
   gBrowser.removeEventListener("DOMContentLoaded", continueTests2, true);
   ok(gFindBar.getElement("highlight").checked, "Highlight never reset!");
   continueTests3();
@@ -123,13 +123,13 @@ function continueTests3() {
 // Test that findbar gets restored when a tab is moved to a new window.
 function checkNewWindow() {
   ok(!newWindow.gFindBar.hidden, "New window shows find bar!");
   // Disabled the following assertion due to intermittent failure on OSX 10.6 Debug.
   if (!HasFindClipboard) {
     is(newWindow.gFindBar._findField.value, texts[1],
        "New window find bar has correct find value!");
     ok(!newWindow.gFindBar.getElement("find-next").disabled,
-       "New window findbar has disabled buttons!");
+       "New window findbar has enabled buttons!");
   }
   newWindow.close();
   finish();
 }
--- a/browser/base/content/test/siteIdentity/browser.ini
+++ b/browser/base/content/test/siteIdentity/browser.ini
@@ -1,16 +1,13 @@
 [DEFAULT]
 support-files =
   head.js
   !/image/test/mochitest/blue.png
 
-[browser_bug435035.js]
-support-files =
-  test_bug435035.html
 [browser_bug822367.js]
 tags = mcb
 support-files =
   file_bug822367_1.html
   file_bug822367_1.js
   file_bug822367_2.html
   file_bug822367_3.html
   file_bug822367_4.html
@@ -61,16 +58,20 @@ support-files =
   test_mcb_redirect_image.html
   test_mcb_double_redirect_image.html
   test_mcb_redirect.js
   test_mcb_redirect.sjs
 [browser_mixed_content_cert_override.js]
 tags = mcb
 support-files =
   test-mixedcontent-securityerrors.html
+[browser_mixed_passive_content_indicator.js]
+tags = mcb
+support-files =
+  simple_mixed_passive.html
 [browser_mixedcontent_securityflags.js]
 tags = mcb
 support-files =
   test-mixedcontent-securityerrors.html
 [browser_mixedContentFramesOnHttp.js]
 tags = mcb
 support-files =
   file_mixedContentFramesOnHttp.html
rename from browser/base/content/test/siteIdentity/browser_bug435035.js
rename to browser/base/content/test/siteIdentity/browser_mixed_passive_content_indicator.js
--- a/browser/base/content/test/siteIdentity/browser_bug435035.js
+++ b/browser/base/content/test/siteIdentity/browser_mixed_passive_content_indicator.js
@@ -1,17 +1,9 @@
-const TEST_URL = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com") + "test_bug435035.html";
+const TEST_URL = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com") + "simple_mixed_passive.html";
 
-function test() {
-  waitForExplicitFinish();
-
-  gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
-  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
+add_task(async function test_mixed_passive_content_indicator() {
+  await BrowserTestUtils.withNewTab(TEST_URL, function() {
     is(document.getElementById("identity-box").className,
        "unknownIdentity mixedDisplayContent",
        "identity box has class name for mixed content");
-
-    gBrowser.removeCurrentTab();
-    finish();
   });
-
-  gBrowser.loadURI(TEST_URL);
-}
+});
rename from browser/base/content/test/siteIdentity/test_bug435035.html
rename to browser/base/content/test/siteIdentity/simple_mixed_passive.html
--- a/browser/base/content/test/urlbar/browser_urlbar_stop_pending.js
+++ b/browser/base/content/test/urlbar/browser_urlbar_stop_pending.js
@@ -35,8 +35,104 @@ add_task(async function() {
   is(gURLBar.value, expectedURLBarChange, "Should not have changed URL bar value synchronously.");
   await pageLoadPromise;
   ok(sawChange, "The URL bar change handler should have been called by the time the page was loaded");
   obs.disconnect();
   obs = null;
   await BrowserTestUtils.removeTab(tab);
 });
 
+/**
+ * Check that if we:
+ * 1) middle-click a link to a separate page whose server doesn't respond
+ * 2) we switch to that tab and stop the request
+ *
+ * The URL bar continues to contain the URL of the page we wanted to visit.
+ */
+add_task(async function() {
+  let socket = Cc["@mozilla.org/network/server-socket;1"].createInstance(Ci.nsIServerSocket);
+  socket.init(-1, true, -1);
+  const PORT = socket.port;
+  registerCleanupFunction(() => { socket.close(); });
+
+  const TEST_PATH = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com");
+  const BASE_PAGE = TEST_PATH + "dummy_page.html";
+  const SLOW_HOST = `https://localhost:${PORT}/`;
+  info("Using URLs: " + SLOW_HOST);
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_PAGE);
+  info("opened tab");
+  await ContentTask.spawn(tab.linkedBrowser, SLOW_HOST, URL => {
+    let link = content.document.createElement("a");
+    link.href = URL;
+    link.textContent = "click me to open a slow page";
+    link.id = "clickme"
+    content.document.body.appendChild(link);
+  });
+  info("added link");
+  let newTabPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen");
+  // Middle click the link:
+  await BrowserTestUtils.synthesizeMouseAtCenter("#clickme", { button: 1 }, tab.linkedBrowser);
+  // get new tab, switch to it
+  let newTab = (await newTabPromise).target;
+  await BrowserTestUtils.switchTab(gBrowser, newTab);
+  is(gURLBar.value, SLOW_HOST, "Should have slow page in URL bar");
+  let browserStoppedPromise = BrowserTestUtils.browserStopped(newTab.linkedBrowser);
+  BrowserStop();
+  await browserStoppedPromise;
+
+  is(gURLBar.value, SLOW_HOST, "Should still have slow page in URL bar after stop");
+  await BrowserTestUtils.removeTab(newTab);
+  await BrowserTestUtils.removeTab(tab);
+});
+/**
+ * Check that if we:
+ * 1) middle-click a link to a separate page whose server doesn't respond
+ * 2) we alter the URL on that page to some other server that doesn't respond
+ * 3) we stop the request
+ *
+ * The URL bar continues to contain the second URL.
+ */
+add_task(async function() {
+  let socket = Cc["@mozilla.org/network/server-socket;1"].createInstance(Ci.nsIServerSocket);
+  socket.init(-1, true, -1);
+  const PORT1 = socket.port;
+  let socket2 = Cc["@mozilla.org/network/server-socket;1"].createInstance(Ci.nsIServerSocket);
+  socket2.init(-1, true, -1);
+  const PORT2 = socket2.port;
+  registerCleanupFunction(() => { socket.close(); socket2.close(); });
+
+  const TEST_PATH = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com");
+  const BASE_PAGE = TEST_PATH + "dummy_page.html";
+  const SLOW_HOST1 = `https://localhost:${PORT1}/`;
+  const SLOW_HOST2 = `https://localhost:${PORT2}/`;
+  info("Using URLs: " + SLOW_HOST1 + " and " + SLOW_HOST2);
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_PAGE);
+  info("opened tab");
+  await ContentTask.spawn(tab.linkedBrowser, SLOW_HOST1, URL => {
+    let link = content.document.createElement("a");
+    link.href = URL;
+    link.textContent = "click me to open a slow page";
+    link.id = "clickme"
+    content.document.body.appendChild(link);
+  });
+  info("added link");
+  let newTabPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen");
+  // Middle click the link:
+  await BrowserTestUtils.synthesizeMouseAtCenter("#clickme", { button: 1 }, tab.linkedBrowser);
+  // get new tab, switch to it
+  let newTab = (await newTabPromise).target;
+  await BrowserTestUtils.switchTab(gBrowser, newTab);
+  is(gURLBar.value, SLOW_HOST1, "Should have slow page in URL bar");
+  let browserStoppedPromise = BrowserTestUtils.browserStopped(newTab.linkedBrowser);
+  gURLBar.value = SLOW_HOST2;
+  gURLBar.handleCommand();
+  await browserStoppedPromise;
+
+  is(gURLBar.value, SLOW_HOST2, "Should have second slow page in URL bar");
+  browserStoppedPromise = BrowserTestUtils.browserStopped(newTab.linkedBrowser);
+  BrowserStop();
+  await browserStoppedPromise;
+
+  is(gURLBar.value, SLOW_HOST2, "Should still have second slow page in URL bar after stop");
+  await BrowserTestUtils.removeTab(newTab);
+  await BrowserTestUtils.removeTab(tab);
+});
+
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -417,17 +417,22 @@ function openLinkIn(url, where, params) 
 
     if (aAllowPopups) {
       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_POPUPS;
     }
     if (aIndicateErrorPageLoad) {
       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ERROR_LOAD_CHANGES_RV;
     }
 
-    if (aForceAboutBlankViewerInCurrent) {
+    let {URI_INHERITS_SECURITY_CONTEXT} = Ci.nsIProtocolHandler;
+    if (aForceAboutBlankViewerInCurrent &&
+        (!uriObj ||
+         (Services.io.getProtocolFlags(uriObj.scheme) & URI_INHERITS_SECURITY_CONTEXT))) {
+      // Unless we know for sure we're not inheriting principals,
+      // force the about:blank viewer to have the right principal:
       targetBrowser.createAboutBlankContentViewer(aPrincipal);
     }
 
     targetBrowser.loadURIWithFlags(url, {
       triggeringPrincipal: aTriggeringPrincipal,
       flags,
       referrerURI: aNoReferrer ? null : aReferrerURI,
       referrerPolicy: aReferrerPolicy,
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -222,17 +222,17 @@ var CustomizableUIInternal = {
     let showCharacterEncoding = Services.prefs.getComplexValue(
       "browser.menu.showCharacterEncoding",
       Ci.nsIPrefLocalizedString
     ).data;
     if (showCharacterEncoding == "true") {
       panelPlacements.push("characterencoding-button");
     }
 
-    if (AppConstants.NIGHTLY_BUILD) {
+    if (!AppConstants.RELEASE_OR_BETA) {
       if (Services.prefs.getBoolPref("extensions.webcompat-reporter.enabled")) {
         panelPlacements.push("webcompat-reporter-button");
       }
     }
 
     gDefaultPanelPlacements = panelPlacements;
     this._updateAreasForPhoton();
 
--- a/browser/components/customizableui/PanelMultiView.jsm
+++ b/browser/components/customizableui/PanelMultiView.jsm
@@ -212,16 +212,21 @@ this.PanelMultiView = class {
       this.__currentSubView = panel;
     return panel;
   }
   get _keyNavigationMap() {
     if (!this.__keyNavigationMap)
       this.__keyNavigationMap = new Map();
     return this.__keyNavigationMap;
   }
+  get _multiLineElementsMap() {
+    if (!this.__multiLineElementsMap)
+      this.__multiLineElementsMap = new WeakMap();
+    return this.__multiLineElementsMap;
+  }
 
   constructor(xulNode, testMode = false) {
     this.node = xulNode;
     // If `testMode` is `true`, the consumer is only interested in accessing the
     // methods of this instance. (E.g. in unit tests.)
     if (testMode)
       return;
 
@@ -239,25 +244,25 @@ this.PanelMultiView = class {
       document.getAnonymousElementByAttribute(this.node, "anonid", "mainViewContainer");
     this._subViews =
       document.getAnonymousElementByAttribute(this.node, "anonid", "subViews");
     this._viewStack =
       document.getAnonymousElementByAttribute(this.node, "anonid", "viewStack");
 
     this._panel.addEventListener("popupshowing", this);
     this._panel.addEventListener("popuphidden", this);
+    this._panel.addEventListener("popupshown", this);
     if (this.panelViews) {
       let cs = window.getComputedStyle(document.documentElement);
       // Set CSS-determined attributes now to prevent a layout flush when we do
       // it when transitioning between panels.
       this._dir = cs.direction;
       this.setMainView(this.panelViews.currentView);
       this.showMainView();
     } else {
-      this._panel.addEventListener("popupshown", this);
       this._clickCapturer.addEventListener("click", this);
 
       this._mainViewContainer.setAttribute("panelid", this._panel.id);
 
       if (this._mainView) {
         this.setMainView(this._mainView);
       }
     }
@@ -312,29 +317,30 @@ this.PanelMultiView = class {
    * @param  {panelview} view View to check, defaults to the currently active view.
    * @return {Boolean}
    */
   _canGoBack(view = this._currentSubView) {
     return !!view.getAttribute("title");
   }
 
   setMainView(aNewMainView) {
+    if (this._mainView) {
+      if (!this.panelViews)
+        this._subViews.appendChild(this._mainView);
+      this._mainView.removeAttribute("mainview");
+    }
+    this._mainViewId = aNewMainView.id;
+    aNewMainView.setAttribute("mainview", "true");
     if (this.panelViews) {
       // If the new main view is not yet in the zeroth position, make sure it's
       // inserted there.
       if (aNewMainView.parentNode != this._viewStack && this._viewStack.firstChild != aNewMainView) {
         this._viewStack.insertBefore(aNewMainView, this._viewStack.firstChild);
       }
     } else {
-      if (this._mainView) {
-        this._subViews.appendChild(this._mainView);
-        this._mainView.removeAttribute("mainview");
-      }
-      this._mainViewId = aNewMainView.id;
-      aNewMainView.setAttribute("mainview", "true");
       this._mainViewContainer.appendChild(aNewMainView);
     }
   }
 
   showMainView() {
     if (this.panelViews) {
       this.showSubView(this._mainViewId);
     } else {
@@ -416,17 +422,17 @@ this.PanelMultiView = class {
         let custWidget = CustomizableWidgets.find(widget => widget.viewId == viewNode.id);
         if (custWidget) {
           if (custWidget.onInit)
             custWidget.onInit(aAnchor);
           custWidget.onViewShowing({ target: aAnchor, detail });
         }
       }
       viewNode.setAttribute("current", true);
-      if (playTransition && this.panelViews)
+      if (this.panelViews && this._mainViewWidth)
         viewNode.style.maxWidth = viewNode.style.minWidth = this._mainViewWidth + "px";
 
       let evt = new window.CustomEvent("ViewShowing", { bubbles: true, cancelable: true, detail });
       viewNode.dispatchEvent(evt);
 
       let cancel = evt.defaultPrevented;
       if (detail.blockers.size) {
         try {
@@ -438,38 +444,42 @@ this.PanelMultiView = class {
         }
       }
 
       if (cancel) {
         return;
       }
 
       this._currentSubView = viewNode;
+      if (this.panelViews) {
+        this.node.setAttribute("viewtype", "subview");
+        if (!playTransition)
+          this.descriptionHeightWorkaround(viewNode);
+      }
 
       // Now we have to transition the panel. There are a few parts to this:
       //
       // 1) The main view content gets shifted so that the center of the anchor
       //    node is at the left-most edge of the panel.
       // 2) The subview deck slides in so that it takes up almost all of the
       //    panel.
       // 3) If the subview is taller then the main panel contents, then the panel
       //    must grow to meet that new height. Otherwise, it must shrink.
       //
       // All three of these actions make use of CSS transformations, so they
       // should all occur simultaneously.
       if (this.panelViews && playTransition) {
-        this.node.setAttribute("viewtype", "subview");
-
         // Sliding the next subview in means that the previous panelview stays
         // where it is and the active panelview slides in from the left in LTR
         // mode, right in RTL mode.
         let onTransitionEnd = () => {
           evt = new window.CustomEvent("ViewHiding", { bubbles: true, cancelable: true });
           previousViewNode.dispatchEvent(evt);
           previousViewNode.removeAttribute("current");
+          this.descriptionHeightWorkaround(viewNode);
         };
 
         // There's absolutely no need to show off our epic animation skillz when
         // the panel's not even open.
         if (this._panel.state != "open") {
           onTransitionEnd();
           return;
         }
@@ -550,17 +560,17 @@ this.PanelMultiView = class {
 
             // Myeah, panel layout auto-resizing is a funky thing. We'll wait
             // another few milliseconds to remove the width and height 'fixtures',
             // to be sure we don't flicker annoyingly.
             // NB: HACK! Bug 1363756 is there to fix this.
             window.setTimeout(() => {
               // Only remove the height when the view is larger than the main
               // view, otherwise it'll snap back to its own height.
-              if (viewRect.height > this._mainViewHeight)
+              if (viewNode == this._mainView || viewRect.height > this._mainViewHeight)
                 this._viewContainer.style.removeProperty("height");
               this._viewContainer.style.removeProperty("width");
             }, 500);
 
             // Take another breather, just like before, to wait for the 'current'
             // attribute removal to take effect. This prevents a flicker.
             // The cleanup we do doesn't affect the display anymore, so we're not
             // too fussed about the timing here.
@@ -730,42 +740,45 @@ this.PanelMultiView = class {
           this.window.addEventListener("keydown", this);
           this._panel.addEventListener("mousemove", this);
         }
         break;
       case "popupshown":
         // Now that the main view is visible, we can check the height of the
         // description elements it contains.
         this.descriptionHeightWorkaround();
-        // Now that the panel has opened, we can compute the distance from its
-        // anchor to the available margin of the screen, based on whether the
-        // panel actually opened towards the top or the bottom. We use this to
-        // limit its maximum height, which is relevant when opening a subview.
-        let maxHeight;
-        if (this._panel.alignmentPosition.startsWith("before_")) {
-          maxHeight = this._panel.getOuterScreenRect().bottom -
-                      this.window.screen.availTop;
-        } else {
-          maxHeight = this.window.screen.availTop +
-                      this.window.screen.availHeight -
-                      this._panel.getOuterScreenRect().top;
+
+        if (!this.panelViews) {
+          // Now that the panel has opened, we can compute the distance from its
+          // anchor to the available margin of the screen, based on whether the
+          // panel actually opened towards the top or the bottom. We use this to
+          // limit its maximum height, which is relevant when opening a subview.
+          let maxHeight;
+          if (this._panel.alignmentPosition.startsWith("before_")) {
+            maxHeight = this._panel.getOuterScreenRect().bottom -
+                        this.window.screen.availTop;
+          } else {
+            maxHeight = this.window.screen.availTop +
+                        this.window.screen.availHeight -
+                        this._panel.getOuterScreenRect().top;
+          }
+          // To go from the maximum height of the panel to the maximum height of
+          // the view stack, we start by subtracting the height of the arrow box.
+          // We don't need to trigger a new layout because this does not change.
+          let arrowBox = this.document.getAnonymousElementByAttribute(
+                                          this._panel, "anonid", "arrowbox");
+          maxHeight -= this._dwu.getBoundsWithoutFlushing(arrowBox).height;
+          // We subtract a fixed margin to account for variable borders. We don't
+          // try to measure this accurately so it's faster, we don't depend on
+          // the arrowpanel structure, and we don't hit rounding errors. Instead,
+          // we use a value that is much greater than the typical borders and
+          // makes sense visually.
+          const EXTRA_MARGIN_PX = 8;
+          this._viewStack.style.maxHeight = (maxHeight - EXTRA_MARGIN_PX) + "px";
         }
-        // To go from the maximum height of the panel to the maximum height of
-        // the view stack, we start by subtracting the height of the arrow box.
-        // We don't need to trigger a new layout because this does not change.
-        let arrowBox = this.document.getAnonymousElementByAttribute(
-                                        this._panel, "anonid", "arrowbox");
-        maxHeight -= this._dwu.getBoundsWithoutFlushing(arrowBox).height;
-        // We subtract a fixed margin to account for variable borders. We don't
-        // try to measure this accurately so it's faster, we don't depend on
-        // the arrowpanel structure, and we don't hit rounding errors. Instead,
-        // we use a value that is much greater than the typical borders and
-        // makes sense visually.
-        const EXTRA_MARGIN_PX = 8;
-        this._viewStack.style.maxHeight = (maxHeight - EXTRA_MARGIN_PX) + "px";
         break;
       case "popuphidden":
         this.node.removeAttribute("panelopen");
         this.showMainView();
         if (this.panelViews) {
           this.window.removeEventListener("keydown", this);
           this._panel.removeEventListener("mousemove", this);
           this._resetKeyNavigation();
@@ -911,49 +924,61 @@ this.PanelMultiView = class {
       let bounds = dwu.getBoundsWithoutFlushing(button);
       return bounds.width > 0 && bounds.height > 0;
     });
   }
 
   /**
    * If the main view or a subview contains wrapping elements, the attribute
    * "descriptionheightworkaround" should be set on the view to force all the
-   * "description" elements to a fixed height. If the attribute is set and the
-   * visibility, contents, or width of any of these elements changes, this
-   * function should be called to refresh the calculated heights.
+   * "description" or wrapping toolbarbutton elements to a fixed height.
+   * If the attribute is set and the visibility, contents, or width of any of
+   * these elements changes, this function should be called to refresh the
+   * calculated heights.
    *
-   * @note While both "label" and "description" elements may contain wrapping
-   *       text, only "description" elements are used that way in panels.
+   * This may trigger a synchronous layout.
    *
    * @param viewNode
    *        Indicates the node to scan for descendant elements. This is the main
    *        view if omitted.
    */
   descriptionHeightWorkaround(viewNode = this._mainView) {
     if (!this.node.hasAttribute("descriptionheightworkaround")) {
       // This view does not require the workaround.
       return;
     }
 
     // We batch DOM changes together in order to reduce synchronous layouts.
     // First we reset any change we may have made previously. The first time
     // this is called, and in the best case scenario, this has no effect.
     let items = [];
-    for (let element of viewNode.getElementsByTagName("description")) {
+    for (let element of viewNode.querySelectorAll(
+         "description:not([hidden]):not([value]),toolbarbutton[wrap]:not([hidden])")) {
+      // Take the label for toolbarbuttons; it only exists on those elements.
+      element = element.labelElement || element;
+
+      let bounds = this._dwu.getBoundsWithoutFlushing(element);
+      let previous = this._multiLineElementsMap.get(element);
+      // Only remove the 'height' property, which will cause a layout flush, when
+      // absolutely necessary.
+      if (!bounds.width || !bounds.height ||
+          (previous && element.textContent == previous.textContent &&
+                       bounds.width == previous.bounds.width)) {
+        continue;
+      }
+
       element.style.removeProperty("height");
       items.push({ element });
     }
+
     // We now read the computed style to store the height of any element that
     // may contain wrapping text, which will be zero if the element is hidden.
-    // This might trigger a synchronous layout, but if this function was called
-    // from a _transitionHeight callback and there are no description elements
-    // visible, then _transitionHeight will not trigger a layout again.
     for (let item of items) {
-      item.height = item.element.getBoundingClientRect().height;
+      item.bounds = item.element.getBoundingClientRect();
     }
+
     // Now we can make all the necessary DOM changes at once.
-    for (let item of items) {
-      if (item.height) {
-        item.element.style.height = item.height + "px";
-      }
+    for (let { element, bounds } of items) {
+      this._multiLineElementsMap.set(element, { bounds, textContent: element.textContent });
+      element.style.height = bounds.height + "px";
     }
   }
 }
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -498,17 +498,17 @@
 <panel id="appMenu-popup"
        class="cui-widget-panel"
        role="group"
        type="arrow"
        hidden="true"
        flip="slide"
        position="bottomcenter topright"
        noautofocus="true">
-  <photonpanelmultiview id="appMenu-multiView" mainViewId="appMenu-mainView">
+  <photonpanelmultiview id="appMenu-multiView" mainViewId="appMenu-mainView" descriptionheightworkaround="true">
     <panelview id="appMenu-mainView" class="PanelUI-subView">
       <vbox class="panel-subview-body">
         <vbox id="appMenu-addon-banners"/>
         <toolbarbutton class="panel-banner-item"
                        label-update-available="&updateAvailable.panelUI.label;"
                        label-update-manual="&updateManual.panelUI.label;"
                        label-update-restart="&updateRestart.panelUI.label2;"
                        oncommand="PanelUI._onBannerItemSelected(event)"
--- a/browser/components/customizableui/test/browser_876944_customize_mode_create_destroy.js
+++ b/browser/components/customizableui/test/browser_876944_customize_mode_create_destroy.js
@@ -27,19 +27,17 @@ add_task(async function testWrapUnwrap()
 
 // Creating and destroying a widget should correctly deal with panel placeholders
 add_task(async function testPanelPlaceholders() {
   let panel = document.getElementById(CustomizableUI.AREA_PANEL);
   // The value of expectedPlaceholders depends on the default palette layout.
   // Bug 1229236 is for these tests to be smarter so the test doesn't need to
   // change when the default placements change.
   let expectedPlaceholders = 1;
-  if (isInDevEdition()) {
-    expectedPlaceholders += 1;
-  } else if (isInNightly()) {
+  if (isInNightly()) {
     expectedPlaceholders += 2;
   }
   is(panel.querySelectorAll(".panel-customization-placeholder").length, expectedPlaceholders, "The number of placeholders should be correct.");
   CustomizableUI.createWidget({id: kTestWidget2, label: "Pretty label", tooltiptext: "Pretty tooltip", defaultArea: CustomizableUI.AREA_PANEL});
   let elem = document.getElementById(kTestWidget2);
   let wrapper = document.getElementById("wrapper-" + kTestWidget2);
   ok(elem, "There should be an item");
   ok(wrapper, "There should be a wrapper");
--- a/browser/components/customizableui/test/browser_880382_drag_wide_widgets_in_panel.js
+++ b/browser/components/customizableui/test/browser_880382_drag_wide_widgets_in_panel.js
@@ -361,24 +361,24 @@ add_task(async function editControlsToPa
                              "find-button",
                              "preferences-button",
                              "add-ons-button",
                              "edit-controls",
                              "developer-button",
                              "sync-button",
                             ];
   removeNonReleaseButtons(placementsAfterMove);
-  if (isInNightly()) {
+  if (isNotReleaseOrBeta()) {
     CustomizableUI.removeWidgetFromArea("webcompat-reporter-button");
   }
   simulateItemDrag(editControls, panel);
   assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
   let zoomControls = document.getElementById("zoom-controls");
   simulateItemDrag(editControls, zoomControls);
-  if (isInNightly()) {
+  if (isNotReleaseOrBeta()) {
     CustomizableUI.addWidgetToArea("webcompat-reporter-button", CustomizableUI.AREA_PANEL);
   }
   ok(CustomizableUI.inDefaultState, "Should still be in default state.");
 });
 
 // Dragging the edit-controls to the customization-palette and
 // back should work.
 add_task(async function() {
--- a/browser/components/customizableui/test/browser_overflow_use_subviews.js
+++ b/browser/components/customizableui/test/browser_overflow_use_subviews.js
@@ -15,16 +15,20 @@ registerCleanupFunction(async function()
  * re-anchored panels. If we ever remove the developer widget, please
  * replace this test with another subview - don't remove it.
  */
 add_task(async function check_developer_subview_in_overflow() {
   kOverflowPanel.setAttribute("animate", "false");
   gOriginalWidth = window.outerWidth;
 
   CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_NAVBAR);
+  if (isNotReleaseOrBeta()) {
+    CustomizableUI.addWidgetToArea("webcompat-reporter-button", CustomizableUI.AREA_NAVBAR);
+  }
+
 
   let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
   ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
   window.resizeTo(400, window.outerHeight);
 
   await waitForCondition(() => navbar.hasAttribute("overflowing"));
 
   let chevron = document.getElementById("nav-bar-overflow-button");
--- a/browser/components/customizableui/test/head.js
+++ b/browser/components/customizableui/test/head.js
@@ -114,39 +114,39 @@ function resetCustomization() {
   return CustomizableUI.reset();
 }
 
 function isInDevEdition() {
   return AppConstants.MOZ_DEV_EDITION;
 }
 
 function isInNightly() {
-  return AppConstants.NIGHTLY_BUILD;
+  return AppConstants.NIGHTLY_BUILD && !AppConstants.MOZ_DEV_EDITION;
+}
+
+function isNotReleaseOrBeta() {
+  return !AppConstants.RELEASE_OR_BETA;
 }
 
 function removeNonReleaseButtons(areaPanelPlacements) {
   if (isInDevEdition() && areaPanelPlacements.includes("developer-button")) {
     areaPanelPlacements.splice(areaPanelPlacements.indexOf("developer-button"), 1);
   }
-
-  if (!isInNightly() && areaPanelPlacements.includes("webcompat-reporter-button")) {
-    areaPanelPlacements.splice(areaPanelPlacements.indexOf("webcompat-reporter-button"), 1);
-  }
 }
 
 function removeNonOriginalButtons() {
   CustomizableUI.removeWidgetFromArea("sync-button");
-  if (isInNightly()) {
+  if (isNotReleaseOrBeta()) {
     CustomizableUI.removeWidgetFromArea("webcompat-reporter-button");
   }
 }
 
 function restoreNonOriginalButtons() {
   CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
-  if (isInNightly()) {
+  if (isNotReleaseOrBeta()) {
     CustomizableUI.addWidgetToArea("webcompat-reporter-button", CustomizableUI.AREA_PANEL);
   }
 }
 
 function assertAreaPlacements(areaId, expectedPlacements) {
   let actualPlacements = getAreaWidgetIds(areaId);
   placementArraysEqual(areaId, actualPlacements, expectedPlacements);
 }
--- a/browser/components/migration/.eslintrc.js
+++ b/browser/components/migration/.eslintrc.js
@@ -9,17 +9,16 @@ module.exports = {
     "block-scoped-var": "error",
     "comma-dangle": "off",
     "comma-style": ["error", "last"],
     "complexity": ["error", {"max": 21}],
     "dot-notation": "error",
     "indent": ["error", 2, {"SwitchCase": 1, "ArrayExpression": "first", "ObjectExpression": "first"}],
     "max-nested-callbacks": ["error", 3],
     "new-parens": "error",
-    "no-array-constructor": "error",
     "no-control-regex": "error",
     "no-extend-native": "error",
     "no-fallthrough": ["error", { "commentPattern": ".*[Ii]ntentional(?:ly)?\\s+fall(?:ing)?[\\s-]*through.*" }],
     "no-multi-str": "error",
     "no-return-assign": "error",
     "no-sequences": "error",
     "no-shadow": "error",
     "no-throw-literal": "error",
--- a/browser/components/places/tests/browser/browser_drag_bookmarks_on_toolbar.js
+++ b/browser/components/places/tests/browser/browser_drag_bookmarks_on_toolbar.js
@@ -160,17 +160,17 @@ var gTests = [
         function() {
           info("Dragging right");
           synthesizeDragWithDirection(element, expectedData, dragDirections.RIGHT,
             function() {
               info("Dragging up");
               synthesizeDragWithDirection(element, expectedData, dragDirections.UP,
                 function() {
                   info("Dragging down");
-                  synthesizeDragWithDirection(element, new Array(), dragDirections.DOWN,
+                  synthesizeDragWithDirection(element, [], dragDirections.DOWN,
                     function() {
                       // Cleanup.
                       PlacesUtils.bookmarks.removeItem(folderId);
                       nextTest();
                     });
                 });
             });
         });
@@ -240,9 +240,8 @@ function test() {
 
   // Uncollapse the personal toolbar if needed.
   if (wasCollapsed) {
     promiseSetToolbarVisibility(toolbar, true).then(nextTest);
   } else {
     nextTest();
   }
 }
-
--- a/browser/components/places/tests/browser/browser_views_liveupdate.js
+++ b/browser/components/places/tests/browser/browser_views_liveupdate.js
@@ -465,10 +465,10 @@ function getViewsForFolder(aFolderId) {
   switch (rootId) {
     case PlacesUtils.toolbarFolderId:
       return ["toolbar", "sidebar"]
     case PlacesUtils.bookmarksMenuFolderId:
       return ["menu", "sidebar"]
     case PlacesUtils.unfiledBookmarksFolderId:
       return ["sidebar"]
   }
-  return new Array();
+  return [];
 }
--- a/browser/extensions/formautofill/.eslintrc.js
+++ b/browser/extensions/formautofill/.eslintrc.js
@@ -50,19 +50,16 @@ module.exports = {
     "generator-star-spacing": ["error", {"before": false, "after": true}],
 
     // Two space indent
     "indent": ["error", 2, {"SwitchCase": 1}],
 
     // Always require parenthesis for new calls
     "new-parens": "error",
 
-    // Use [] instead of Array()
-    "no-array-constructor": "error",
-
     // No expressions where a statement is expected
     "no-unused-expressions": "error",
 
     // No declaring variables that are never used
     "no-unused-vars": ["error", {"args": "none", "varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$"}],
 
     // No using variables before defined
     "no-use-before-define": "error",
--- a/browser/extensions/moz.build
+++ b/browser/extensions/moz.build
@@ -15,22 +15,22 @@ DIRS += [
 
 # Only include the following system add-ons if building Aurora or Nightly
 if not CONFIG['RELEASE_OR_BETA']:
     DIRS += [
         'flyweb',
         'formautofill',
         'presentation',
         'shield-recipe-client',
+        'webcompat-reporter',
     ]
 
 # Only include mortar system add-ons if we locally enable it
 if CONFIG['MOZ_MORTAR']:
     DIRS += [
         'mortar',
     ]
 
 # Nightly-only system add-ons
 if CONFIG['NIGHTLY_BUILD']:
     DIRS += [
         'activity-stream',
-        'webcompat-reporter',
     ]
--- a/browser/locales/Makefile.in
+++ b/browser/locales/Makefile.in
@@ -99,25 +99,23 @@ libs-%:
 	@$(MAKE) -C ../../services/sync/locales AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) -C ../../extensions/spellcheck/locales AB_CD=$* XPI_NAME=locale-$*
 ifndef RELEASE_OR_BETA
 	@$(MAKE) -C ../extensions/formautofill/locale AB_CD=$* XPI_NAME=locale-$*
 endif
 	@$(MAKE) -C ../extensions/pocket/locale AB_CD=$* XPI_NAME=locale-$*
 ifndef RELEASE_OR_BETA
 	@$(MAKE) -C ../extensions/presentation/locale AB_CD=$* XPI_NAME=locale-$*
+	@$(MAKE) -C ../extensions/webcompat-reporter/locales AB_CD=$* XPI_NAME=locale-$*
 endif
 	@$(MAKE) -C ../../intl/locales AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) -C ../../devtools/client/locales AB_CD=$* XPI_NAME=locale-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)'
 	@$(MAKE) -B searchplugins AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) libs AB_CD=$* XPI_NAME=locale-$* PREF_DIR=$(PREF_DIR)
 	@$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales AB_CD=$* XPI_NAME=locale-$*
-ifdef NIGHTLY_BUILD
-	@$(MAKE) -C ../extensions/webcompat-reporter/locales AB_CD=$* XPI_NAME=locale-$*
-endif
 
 repackage-win32-installer: WIN32_INSTALLER_OUT=$(ABS_DIST)/$(PKG_INST_PATH)$(PKG_INST_BASENAME).exe
 repackage-win32-installer: $(call ESCAPE_WILDCARD,$(WIN32_INSTALLER_IN)) $(SUBMAKEFILES) libs-$(AB_CD)
 	@echo 'Repackaging $(WIN32_INSTALLER_IN) into $(WIN32_INSTALLER_OUT).'
 	$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY) export
 	$(MAKE) -C ../installer/windows CONFIG_DIR=l10ngen l10ngen/setup.exe l10ngen/7zSD.sfx
 	$(MAKE) repackage-zip \
 	  AB_CD=$(AB_CD) \
--- a/browser/modules/BrowserUITelemetry.jsm
+++ b/browser/modules/BrowserUITelemetry.jsm
@@ -76,17 +76,17 @@ XPCOMUtils.defineLazyGetter(this, "DEFAU
   let showCharacterEncoding = Services.prefs.getComplexValue(
     "browser.menu.showCharacterEncoding",
     Ci.nsIPrefLocalizedString
   ).data;
   if (showCharacterEncoding == "true") {
     result["PanelUI-contents"].push("characterencoding-button");
   }
 
-  if (AppConstants.NIGHTLY_BUILD) {
+  if (!AppConstants.RELEASE_OR_BETA) {
     if (Services.prefs.getBoolPref("extensions.webcompat-reporter.enabled")) {
       result["PanelUI-contents"].push("webcompat-reporter-button");
     }
   }
 
   return result;
 });
 
--- a/browser/themes/linux/customizableui/panelUI.css
+++ b/browser/themes/linux/customizableui/panelUI.css
@@ -95,14 +95,15 @@ menu.subviewbutton > .menu-right:-moz-lo
 
 .subviewradio > .radio-label-box {
   -moz-appearance: none;
 }
 
 /* START photon adjustments */
 
 photonpanelmultiview .subviewbutton > .toolbarbutton-text,
-photonpanelmultiview .subviewbutton > .toolbarbutton-icon {
+photonpanelmultiview .subviewbutton > .toolbarbutton-icon,
+photonpanelmultiview .panel-banner-item > .toolbarbutton-multiline-text {
   padding: 0;
   margin: 0;
 }
 
 /* END photon adjustments */
--- a/browser/themes/osx/customizableui/panelUI.css
+++ b/browser/themes/osx/customizableui/panelUI.css
@@ -20,17 +20,18 @@
 
 .subviewbutton:-moz-any([image],[targetURI],.cui-withicon, .bookmark-item) > .toolbarbutton-text {
   margin: 2px 6px !important; /* !important for overriding toolbarbutton.css */
 }
 
 /* START photon adjustments */
 
 photonpanelmultiview .subviewbutton > .toolbarbutton-text,
-photonpanelmultiview .subviewbutton:-moz-any([image],[targetURI],.cui-withicon, .bookmark-item) > .toolbarbutton-text {
+photonpanelmultiview .subviewbutton:-moz-any([image],[targetURI],.cui-withicon, .bookmark-item) > .toolbarbutton-text,
+photonpanelmultiview .panel-banner-item > .toolbarbutton-multiline-text {
   margin: 0 !important;  /* !important for overriding the rules above. */
 }
 
 /* END photon adjustments */
 
 .restoreallitem > .toolbarbutton-icon {
   display: none;
 }
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -1279,17 +1279,18 @@ photonpanelmultiview .PanelUI-subView .s
   padding: 4px 12px;
 }
 
 photonpanelmultiview .subviewbutton:focus {
   outline: 0;
 }
 
 photonpanelmultiview .subviewbutton-iconic:not(.subviewbutton-back) > .toolbarbutton-text,
-photonpanelmultiview .subviewbutton[checked="true"] > .toolbarbutton-text {
+photonpanelmultiview .subviewbutton[checked="true"] > .toolbarbutton-text,
+photonpanelmultiview .panel-banner-item > .toolbarbutton-multiline-text {
   padding-inline-start: 8px; /* See '.subviewbutton-iconic > .toolbarbutton-text' rule above. */
 }
 
 photonpanelmultiview .subviewbutton:not(.subviewbutton-iconic):not([checked="true"]) > .toolbarbutton-text {
   padding-inline-start: 24px; /* This is 16px for the icon + 8px for the padding as defined above. */
 }
 
 photonpanelmultiview .PanelUI-subView .panel-subview-footer {
@@ -1332,16 +1333,21 @@ photonpanelmultiview .PanelUI-subView .t
   min-width: auto;
   padding: 4px;
 }
 
 photonpanelmultiview .PanelUI-subView .toolbaritem-combined-buttons > .subviewbutton > .toolbarbutton-text {
   display: none;
 }
 
+photonpanelmultiview .panel-banner-item::after {
+  margin-inline-end: 14px;
+  margin-inline-start: 10px;
+}
+
 /* END photon adjustments */
 
 panelview .toolbarbutton-1,
 .widget-overflow-list > .toolbarbutton-1:not(:first-child),
 .widget-overflow-list > toolbaritem:not(:first-child) {
   margin-top: 6px;
 }
 
--- a/browser/themes/windows/customizableui/panelUI.css
+++ b/browser/themes/windows/customizableui/panelUI.css
@@ -143,14 +143,15 @@ menu.subviewbutton > .menu-right:-moz-lo
   #zoom-controls@inAnyPanel@ > toolbarbutton {
     border-radius: 0;
   }
 }
 
 /* START photon adjustments */
 
 photonpanelmultiview .subviewbutton > .toolbarbutton-text,
-photonpanelmultiview .subviewbutton > .toolbarbutton-icon {
+photonpanelmultiview .subviewbutton > .toolbarbutton-icon,
+photonpanelmultiview .panel-banner-item > .toolbarbutton-multiline-text {
   padding: 0;
   margin: 0;
 }
 
 /* END photon adjustments */
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -135,19 +135,16 @@ set_config('MOZ_USING_SCCACHE', using_sc
 option(env='SCCACHE_VERBOSE_STATS', help='Print verbose sccache stats after build')
 
 @depends(using_sccache, 'SCCACHE_VERBOSE_STATS')
 def sccache_verbose_stats(using_sccache, verbose_stats):
     return using_sccache and bool(verbose_stats)
 
 set_config('SCCACHE_VERBOSE_STATS', sccache_verbose_stats)
 
-option(env='MOZ_HAZARD', help='Build for the GC rooting hazard analysis')
-set_config('MOZ_HAZARD', depends_if('MOZ_HAZARD')(lambda _: True))
-
 @depends('--with-compiler-wrapper', ccache)
 @imports(_from='mozbuild.shellutil', _import='split', _as='shell_split')
 def compiler_wrapper(wrapper, ccache):
     if wrapper:
         raw_wrapper = wrapper[0]
         wrapper = shell_split(raw_wrapper)
         wrapper_program = find_program(wrapper[0])
         if not wrapper_program:
@@ -167,16 +164,28 @@ add_old_configure_assignment('COMPILER_W
 
 @depends_if(compiler_wrapper)
 def using_compiler_wrapper(compiler_wrapper):
     return True
 
 set_config('MOZ_USING_COMPILER_WRAPPER', using_compiler_wrapper)
 
 
+# GC rooting and hazard analysis.
+# ==============================================================
+option(env='MOZ_HAZARD', help='Build for the GC rooting hazard analysis')
+
+@depends('MOZ_HAZARD')
+def hazard_analysis(value):
+    if value:
+        return True
+
+set_config('MOZ_HAZARD', hazard_analysis)
+
+
 # Cross-compilation related things.
 # ==============================================================
 js_option('--with-toolchain-prefix', env='TOOLCHAIN_PREFIX', nargs=1,
           help='Prefix for the target toolchain')
 
 @depends('--with-toolchain-prefix', target, cross_compiling)
 def toolchain_prefix(value, target, cross_compiling):
     if value:
--- a/devtools/client/animationinspector/test/browser_animation_timeline_ui.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_ui.js
@@ -21,32 +21,38 @@ add_task(function* () {
      "The header has some time graduations");
 
   ok(el.querySelector(".animations"),
      "The animations container is in the DOM of the timeline");
   is(el.querySelectorAll(".animations .animation").length,
      timeline.animations.length,
      "The number of animations displayed matches the number of animations");
 
+  const animationEls = el.querySelectorAll(".animations .animation");
+  const evenColor =
+    el.ownerDocument.defaultView.getComputedStyle(animationEls[0]).backgroundColor;
+  const oddColor =
+    el.ownerDocument.defaultView.getComputedStyle(animationEls[1]).backgroundColor;
+  isnot(evenColor, oddColor,
+        "Background color of an even animation should be different from odd");
   for (let i = 0; i < timeline.animations.length; i++) {
     let animation = timeline.animations[i];
-    let animationEl = el.querySelectorAll(".animations .animation")[i];
+    let animationEl = animationEls[i];
 
     ok(animationEl.querySelector(".target"),
        "The animated node target element is in the DOM");
     ok(animationEl.querySelector(".time-block"),
        "The timeline element is in the DOM");
     is(animationEl.querySelector(".name").textContent,
        animation.state.name,
        "The name on the timeline is correct");
     is(animationEl.querySelectorAll("svg g").length, 1,
        "The g element should be one since this doc's all animation has only one shape");
     ok(animationEl.querySelector("svg g path"),
        "The timeline has svg and path element as summary graph");
 
-    const expectedBackgroundColor =
-      i % 2 === 0 ? "rgba(128, 128, 128, 0.03)" : "rgba(0, 0, 0, 0)";
+    const expectedBackgroundColor = i % 2 === 0 ? evenColor : oddColor;
     const backgroundColor =
-      animationEl.ownerDocument.defaultView.getComputedStyle(animationEl).backgroundColor;
+      el.ownerDocument.defaultView.getComputedStyle(animationEl).backgroundColor;
     is(backgroundColor, expectedBackgroundColor,
        "The background-color shoud be changed to alternate");
   }
 });
--- a/devtools/client/inspector/computed/computed.js
+++ b/devtools/client/inspector/computed/computed.js
@@ -325,17 +325,18 @@ CssComputedView.prototype = {
     let classes = node.classList;
 
     // Check if the node isn't a selector first since this doesn't require
     // walking the DOM
     if (classes.contains("matched") ||
         classes.contains("bestmatch") ||
         classes.contains("parentmatch")) {
       let selectorText = "";
-      for (let child of node.childNodes) {
+
+      for (let child of node.childNodes[0].childNodes) {
         if (child.nodeType === node.TEXT_NODE) {
           selectorText += child.textContent;
         }
       }
       return {
         type: VIEW_NODE_SELECTOR_TYPE,
         value: selectorText.trim()
       };
@@ -363,25 +364,25 @@ CssComputedView.prototype = {
     let value, type;
 
     // Get the property and value for a node that's a property name or value
     let isHref = classes.contains("theme-link") && !classes.contains("link");
     if (propertyView && (classes.contains("property-name") ||
                          classes.contains("property-value") ||
                          isHref)) {
       value = {
-        property: parent.querySelector(".property-name").textContent,
+        property: parent.querySelector(".property-name").firstChild.textContent,
         value: parent.querySelector(".property-value").textContent
       };
     }
     if (propertyContent && (classes.contains("other-property-value") ||
                             isHref)) {
       let view = propertyContent.previousSibling;
       value = {
-        property: view.querySelector(".property-name").textContent,
+        property: view.querySelector(".property-name").firstChild.textContent,
         value: node.textContent
       };
     }
 
     // Get the type
     if (classes.contains("property-name")) {
       type = VIEW_NODE_PROPERTY_TYPE;
     } else if (classes.contains("property-value") ||
@@ -736,50 +737,18 @@ CssComputedView.prototype = {
 
   /**
    * Copy the current selection to the clipboard
    */
   copySelection: function () {
     try {
       let win = this.styleWindow;
       let text = win.getSelection().toString().trim();
-      // isPropertyPresent is set when a property name is spotted and
-      // we assume that the next line will be a property value.
-      let isPropertyPresent = false;
-      // Tidy up block headings by moving CSS property names and their
-      // values onto the same line and inserting a colon between them.
-      let textArray = text.split(/[\r\n]+/);
-      let result = "";
 
-      // Parse text array to output string.
-      if (textArray.length > 1) {
-        for (let prop of textArray) {
-          if (CssComputedView.propertyNames.indexOf(prop) !== -1) {
-            // Property name found so setting isPropertyPresent to true
-            isPropertyPresent = true;
-            // Property name
-            result += prop;
-          } else if (isPropertyPresent === true) {
-            // Since isPropertyPresent is true so we assume that this is
-            // a property value and we append it to result preceeded by
-            // a :.
-            result += ": " + prop + ";\n";
-            isPropertyPresent = false;
-          } else {
-            // since isPropertyPresent is not set, we assume this is
-            // normal text and we append it to result without any :.
-            result += prop + "\n";
-          }
-        }
-      } else {
-        // Short text fragment.
-        result = textArray[0];
-      }
-
-      clipboardHelper.copyString(result);
+      clipboardHelper.copyString(text);
     } catch (e) {
       console.error(e);
     }
   },
 
   /**
    * Destructor for CssComputedView.
    */
@@ -1002,65 +971,79 @@ PropertyView.prototype = {
       this.mdnLinkClick(event);
       // Prevent opening the options panel
       event.preventDefault();
       event.stopPropagation();
     });
     this.shortcuts.on("Return", (name, event) => this.onMatchedToggle(event));
     this.shortcuts.on("Space", (name, event) => this.onMatchedToggle(event));
 
-    let nameContainer = doc.createElementNS(HTML_NS, "div");
+    let nameContainer = doc.createElementNS(HTML_NS, "span");
     nameContainer.className = "property-name-container";
     this.element.appendChild(nameContainer);
 
     // Build the twisty expand/collapse
     this.matchedExpander = doc.createElementNS(HTML_NS, "div");
     this.matchedExpander.className = "expander theme-twisty";
     this.matchedExpander.addEventListener("click", this.onMatchedToggle);
     nameContainer.appendChild(this.matchedExpander);
 
     // Build the style name element
-    this.nameNode = doc.createElementNS(HTML_NS, "div");
-    this.nameNode.setAttribute("class", "property-name theme-fg-color5");
+    this.nameNode = doc.createElementNS(HTML_NS, "span");
+    this.nameNode.classList.add("property-name", "theme-fg-color5");
     // Reset its tabindex attribute otherwise, if an ellipsis is applied
     // it will be reachable via TABing
     this.nameNode.setAttribute("tabindex", "");
     // Avoid english text (css properties) from being altered
     // by RTL mode
     this.nameNode.setAttribute("dir", "ltr");
     this.nameNode.textContent = this.nameNode.title = this.name;
     // Make it hand over the focus to the container
     this.onFocus = () => this.element.focus();
     this.nameNode.addEventListener("click", this.onFocus);
+
+    // Build the style name ":" separator
+    let nameSeparator = doc.createElementNS(HTML_NS, "span");
+    nameSeparator.classList.add("visually-hidden");
+    nameSeparator.textContent = ": ";
+    this.nameNode.appendChild(nameSeparator);
+
     nameContainer.appendChild(this.nameNode);
 
-    let valueContainer = doc.createElementNS(HTML_NS, "div");
+    let valueContainer = doc.createElementNS(HTML_NS, "span");
     valueContainer.className = "property-value-container";
     this.element.appendChild(valueContainer);
 
     // Build the style value element
-    this.valueNode = doc.createElementNS(HTML_NS, "div");
-    this.valueNode.setAttribute("class", "property-value theme-fg-color1");
+    this.valueNode = doc.createElementNS(HTML_NS, "span");
+    this.valueNode.classList.add("property-value", "theme-fg-color1");
     // Reset its tabindex attribute otherwise, if an ellipsis is applied
     // it will be reachable via TABing
     this.valueNode.setAttribute("tabindex", "");
     this.valueNode.setAttribute("dir", "ltr");
     // Make it hand over the focus to the container
     this.valueNode.addEventListener("click", this.onFocus);
+
+    // Build the style value ";" separator
+    let valueSeparator = doc.createElementNS(HTML_NS, "span");
+    valueSeparator.classList.add("visually-hidden");
+    valueSeparator.textContent = ";";
+
     valueContainer.appendChild(this.valueNode);
+    valueContainer.appendChild(valueSeparator);
 
     return this.element;
   },
 
   buildSelectorContainer: function () {
     let doc = this.tree.styleDocument;
     let element = doc.createElementNS(HTML_NS, "div");
     element.setAttribute("class", this.propertyContentClassName);
     this.matchedSelectorsContainer = doc.createElementNS(HTML_NS, "div");
-    this.matchedSelectorsContainer.setAttribute("class", "matchedselectors");
+    this.matchedSelectorsContainer.classList.add("matchedselectors");
     element.appendChild(this.matchedSelectorsContainer);
 
     return element;
   },
 
   /**
    * Refresh the panel's CSS property value.
    */
@@ -1160,23 +1143,28 @@ PropertyView.prototype = {
         window: this.tree.styleWindow,
         target: link
       });
       shortcuts.on("Return", () => selector.openStyleEditor());
 
       let status = createChild(p, "span", {
         dir: "ltr",
         class: "rule-text theme-fg-color3 " + selector.statusClass,
-        title: selector.statusText,
+        title: selector.statusText
+      });
+
+      createChild(status, "div", {
+        class: "fix-get-selection",
         textContent: selector.sourceText
       });
-      let valueSpan = createChild(status, "span", {
-        class: "other-property-value theme-fg-color1"
+
+      let valueDiv = createChild(status, "div", {
+        class: "fix-get-selection other-property-value theme-fg-color1"
       });
-      valueSpan.appendChild(selector.outputFragment);
+      valueDiv.appendChild(selector.outputFragment);
       promises.push(selector.ready);
     }
 
     this.matchedSelectorsContainer.innerHTML = "";
     this.matchedSelectorsContainer.appendChild(frag);
     return promise.all(promises);
   },
 
--- a/devtools/client/inspector/computed/test/browser.ini
+++ b/devtools/client/inspector/computed/test/browser.ini
@@ -32,12 +32,15 @@ support-files =
 [browser_computed_refresh-on-style-change_01.js]
 [browser_computed_search-filter.js]
 [browser_computed_search-filter_clear.js]
 [browser_computed_search-filter_context-menu.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_computed_search-filter_escape-keypress.js]
 [browser_computed_search-filter_noproperties.js]
-[browser_computed_select-and-copy-styles.js]
+[browser_computed_select-and-copy-styles-01.js]
+subsuite = clipboard
+skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
+[browser_computed_select-and-copy-styles-02.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_computed_style-editor-link.js]
rename from devtools/client/inspector/computed/test/browser_computed_select-and-copy-styles.js
rename to devtools/client/inspector/computed/test/browser_computed_select-and-copy-styles-01.js
--- a/devtools/client/inspector/computed/test/browser_computed_select-and-copy-styles.js
+++ b/devtools/client/inspector/computed/test/browser_computed_select-and-copy-styles-01.js
@@ -1,18 +1,16 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Tests that properties can be selected and copied from the computed view.
 
-const osString = Services.appinfo.OS;
-
 const TEST_URI = `
   <style type="text/css">
     span {
       font-variant-caps: small-caps;
       color: #000000;
     }
     .nomatches {
       color: #ff0000;
@@ -33,86 +31,32 @@ const TEST_URI = `
     <p>even more text</p>
   </div>
 `;
 
 add_task(function* () {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openComputedView();
   yield selectNode("span", inspector);
-  yield checkCopySelection(view);
-  yield checkSelectAll(view);
+
+  yield testCopySome(view);
+  yield testCopyAll(view);
 });
 
-function* checkCopySelection(view) {
-  info("Testing selection copy");
-
-  let contentDocument = view.styleDocument;
-  let props = contentDocument.querySelectorAll(".property-view");
-  ok(props, "captain, we have the property-view nodes");
-
-  let range = contentDocument.createRange();
-  range.setStart(props[1], 0);
-  range.setEnd(props[3], 2);
-  contentDocument.defaultView.getSelection().addRange(range);
-
-  info("Checking that cssHtmlTree.siBoundCopy() returns the correct " +
-    "clipboard value");
-
+function* testCopySome(view) {
   let expectedPattern = "font-family: helvetica,sans-serif;[\\r\\n]+" +
                         "font-size: 16px;[\\r\\n]+" +
                         "font-variant-caps: small-caps;[\\r\\n]*";
 
-  try {
-    yield waitForClipboardPromise(() => fireCopyEvent(props[0]),
-                           () => checkClipboardData(expectedPattern));
-  } catch (e) {
-    failedClipboard(expectedPattern);
-  }
+  yield copySomeTextAndCheckClipboard(view, {
+    start: {prop: 1, offset: 0},
+    end: {prop: 3, offset: 2}
+  }, expectedPattern);
 }
 
-function* checkSelectAll(view) {
-  info("Testing select-all copy");
-
-  let contentDoc = view.styleDocument;
-  let prop = contentDoc.querySelector(".property-view");
-
-  info("Checking that _onSelectAll() then copy returns the correct " +
-    "clipboard value");
-  view._contextmenu._onSelectAll();
+function* testCopyAll(view) {
   let expectedPattern = "color: rgb\\(255, 255, 0\\);[\\r\\n]+" +
                         "font-family: helvetica,sans-serif;[\\r\\n]+" +
                         "font-size: 16px;[\\r\\n]+" +
                         "font-variant-caps: small-caps;[\\r\\n]*";
 
-  try {
-    yield waitForClipboardPromise(() => fireCopyEvent(prop),
-                           () => checkClipboardData(expectedPattern));
-  } catch (e) {
-    failedClipboard(expectedPattern);
-  }
-}
-
-function checkClipboardData(expectedPattern) {
-  let actual = SpecialPowers.getClipboardData("text/unicode");
-  let expectedRegExp = new RegExp(expectedPattern, "g");
-  return expectedRegExp.test(actual);
+  yield copyAllAndCheckClipboard(view, expectedPattern);
 }
-
-function failedClipboard(expectedPattern) {
-  // Format expected text for comparison
-  let terminator = osString == "WINNT" ? "\r\n" : "\n";
-  expectedPattern = expectedPattern.replace(/\[\\r\\n\][+*]/g, terminator);
-  expectedPattern = expectedPattern.replace(/\\\(/g, "(");
-  expectedPattern = expectedPattern.replace(/\\\)/g, ")");
-
-  let actual = SpecialPowers.getClipboardData("text/unicode");
-
-  // Trim the right hand side of our strings. This is because expectedPattern
-  // accounts for windows sometimes adding a newline to our copied data.
-  expectedPattern = expectedPattern.trimRight();
-  actual = actual.trimRight();
-
-  dump("TEST-UNEXPECTED-FAIL | Clipboard text does not match expected ... " +
-    "results (escaped for accurate comparison):\n");
-  info("Actual: " + escape(actual));
-  info("Expected: " + escape(expectedPattern));
-}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/computed/test/browser_computed_select-and-copy-styles-02.js
@@ -0,0 +1,36 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that properties can be selected and copied from the computed view.
+
+const TEST_URI = `<div style="text-align:left;width:25px;">Hello world</div>`;
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let {inspector, view} = yield openComputedView();
+  yield selectNode("div", inspector);
+
+  let expectedPattern = "text-align: left;[\\r\\n]+" +
+                        "width: 25px;[\\r\\n]*";
+  yield copyAllAndCheckClipboard(view, expectedPattern);
+
+  info("Testing expand then select all copy");
+
+  expectedPattern = "text-align: left;[\\r\\n]+" +
+                    "element[\\r\\n]+" +
+                    "this.style[\\r\\n]+" +
+                    "left[\\r\\n]+" +
+                    "width: 25px;[\\r\\n]+" +
+                    "element[\\r\\n]+" +
+                    "this.style[\\r\\n]+" +
+                    "25px[\\r\\n]*";
+
+  info("Expanding computed view properties");
+  yield expandComputedViewPropertyByIndex(view, 0);
+  yield expandComputedViewPropertyByIndex(view, 1);
+
+  yield copyAllAndCheckClipboard(view, expectedPattern);
+});
--- a/devtools/client/inspector/computed/test/head.js
+++ b/devtools/client/inspector/computed/test/head.js
@@ -34,17 +34,17 @@ function fireCopyEvent(element) {
  * @return an object {nameSpan, valueSpan}
  */
 function getComputedViewProperty(view, name) {
   let prop;
   for (let property of view.styleDocument.querySelectorAll(".property-view")) {
     let nameSpan = property.querySelector(".property-name");
     let valueSpan = property.querySelector(".property-value");
 
-    if (nameSpan.textContent === name) {
+    if (nameSpan.firstChild.textContent === name) {
       prop = {nameSpan: nameSpan, valueSpan: valueSpan};
       break;
     }
   }
   return prop;
 }
 
 /**
@@ -82,17 +82,17 @@ function getComputedViewPropertyView(vie
  * @return {Promise} A promise that resolves to the property matched rules
  * container
  */
 var getComputedViewMatchedRules = Task.async(function* (view, name) {
   let expander;
   let propertyContent;
   for (let property of view.styleDocument.querySelectorAll(".property-view")) {
     let nameSpan = property.querySelector(".property-name");
-    if (nameSpan.textContent === name) {
+    if (nameSpan.firstChild.textContent === name) {
       expander = property.querySelector(".expandable");
       propertyContent = property.nextSibling;
       break;
     }
   }
 
   if (!expander.hasAttribute("open")) {
     // Need to expand the property
@@ -150,8 +150,100 @@ function expandComputedViewPropertyByInd
  * @param {Number} index
  *        The index of the link to be retrieved
  * @return {DOMNode} The link at the given index, if one exists, null otherwise
  */
 function getComputedViewLinkByIndex(view, index) {
   let links = view.styleDocument.querySelectorAll(".rule-link .link");
   return links[index];
 }
+
+/**
+ * Trigger the select all action in the computed view.
+ *
+ * @param {CssComputedView} view
+ *        The instance of the computed view panel
+ */
+function selectAllText(view) {
+  info("Selecting all the text");
+  view._contextmenu._onSelectAll();
+}
+
+/**
+ * Select all the text, copy it, and check the content in the clipboard.
+ *
+ * @param {CssComputedView} view
+ *        The instance of the computed view panel
+ * @param {String} expectedPattern
+ *        A regular expression used to check the content of the clipboard
+ */
+function* copyAllAndCheckClipboard(view, expectedPattern) {
+  selectAllText(view);
+  let contentDoc = view.styleDocument;
+  let prop = contentDoc.querySelector(".property-view");
+
+  try {
+    info("Trigger a copy event and wait for the clipboard content");
+    yield waitForClipboardPromise(() => fireCopyEvent(prop),
+                                  () => checkClipboard(expectedPattern));
+  } catch (e) {
+    failClipboardCheck(expectedPattern);
+  }
+}
+
+/**
+ * Select some text, copy it, and check the content in the clipboard.
+ *
+ * @param {CssComputedView} view
+ *        The instance of the computed view panel
+ * @param {Object} positions
+ *        The start and end positions of the text to be selected. This must be an object
+ *        like this:
+ *        { start: {prop: 1, offset: 0}, end: {prop: 3, offset: 5} }
+ * @param {String} expectedPattern
+ *        A regular expression used to check the content of the clipboard
+ */
+function* copySomeTextAndCheckClipboard(view, positions, expectedPattern) {
+  info("Testing selection copy");
+
+  let contentDocument = view.styleDocument;
+  let props = contentDocument.querySelectorAll(".property-view");
+
+  info("Create the text selection range");
+  let range = contentDocument.createRange();
+  range.setStart(props[positions.start.prop], positions.start.offset);
+  range.setEnd(props[positions.end.prop], positions.end.offset);
+  contentDocument.defaultView.getSelection().addRange(range);
+
+  try {
+    info("Trigger a copy event and wait for the clipboard content");
+    yield waitForClipboardPromise(() => fireCopyEvent(props[0]),
+                                  () => checkClipboard(expectedPattern));
+  } catch (e) {
+    failClipboardCheck(expectedPattern);
+  }
+}
+
+function checkClipboard(expectedPattern) {
+  let actual = SpecialPowers.getClipboardData("text/unicode");
+  let expectedRegExp = new RegExp(expectedPattern, "g");
+  return expectedRegExp.test(actual);
+}
+
+function failClipboardCheck(expectedPattern) {
+  // Format expected text for comparison
+  let terminator = Services.appinfo.OS == "WINNT" ? "\r\n" : "\n";
+  expectedPattern = expectedPattern.replace(/\[\\r\\n\][+*]/g, terminator);
+  expectedPattern = expectedPattern.replace(/\\\(/g, "(");
+  expectedPattern = expectedPattern.replace(/\\\)/g, ")");
+
+  let actual = SpecialPowers.getClipboardData("text/unicode");
+
+  // Trim the right hand side of our strings. This is because expectedPattern
+  // accounts for windows sometimes adding a newline to our copied data.
+  expectedPattern = expectedPattern.trimRight();
+  actual = actual.trimRight();
+
+  dump("TEST-UNEXPECTED-FAIL | Clipboard text does not match expected ... " +
+    "results (escaped for accurate comparison):\n");
+  info("Actual: " + escape(actual));
+  info("Expected: " + escape(expectedPattern));
+}
--- a/devtools/client/inspector/shared/test/head.js
+++ b/devtools/client/inspector/shared/test/head.js
@@ -198,18 +198,18 @@ var simulateColorPickerChange = Task.asy
  * @return an object {nameSpan, valueSpan}
  */
 function getComputedViewProperty(view, name) {
   let prop;
   for (let property of view.styleDocument.querySelectorAll(".property-view")) {
     let nameSpan = property.querySelector(".property-name");
     let valueSpan = property.querySelector(".property-value");
 
-    if (nameSpan.textContent === name) {
-      prop = {nameSpan: nameSpan, valueSpan: valueSpan};
+    if (nameSpan.firstChild.textContent === name) {
+      prop = {nameSpan, valueSpan};
       break;
     }
   }
   return prop;
 }
 
 /**
  * Get the text value of the property corresponding to a given name in the
@@ -217,11 +217,10 @@ function getComputedViewProperty(view, n
  *
  * @param {CssComputedView} view
  *        The instance of the computed view panel
  * @param {String} name
  *        The name of the property to retrieve
  * @return {String} The property value
  */
 function getComputedViewPropertyValue(view, name, propertyName) {
-  return getComputedViewProperty(view, name, propertyName)
-    .valueSpan.textContent;
+  return getComputedViewProperty(view, name, propertyName).valueSpan.textContent;
 }
--- a/devtools/client/shared/source-map/worker.js
+++ b/devtools/client/shared/source-map/worker.js
@@ -757,17 +757,16 @@ return /******/ (function(modules) { // 
 
 	/**
 	 * Source Map Worker
 	 * @module utils/source-map-worker
 	 */
 
 	const { networkRequest } = __webpack_require__(6);
 
-	const { parse } = __webpack_require__(10);
 	const path = __webpack_require__(17);
 	const { SourceMapConsumer, SourceMapGenerator } = __webpack_require__(18);
 	const assert = __webpack_require__(29);
 	const {
 	  originalToGeneratedId,
 	  generatedToOriginalId,
 	  isGeneratedId,
 	  isOriginalId,
@@ -784,17 +783,17 @@ return /******/ (function(modules) { // 
 	  const { url = "", sourceMapURL = "" } = source;
 	  if (path.isURL(sourceMapURL) || url == "") {
 	    // If it's already a full URL or the source doesn't have a URL,
 	    // don't resolve anything.
 	    return sourceMapURL;
 	  } else if (path.isAbsolute(sourceMapURL)) {
 	    // If it's an absolute path, it should be resolved relative to the
 	    // host of the source.
-	    const { protocol = "", host = "" } = parse(url);
+	    const { protocol = "", host = "" } = new URL(url);
 	    return `${protocol}//${host}${sourceMapURL}`;
 	  }
 	  // Otherwise, it's a relative path and should be resolved relative
 	  // to the source.
 	  return `${path.dirname(url)}/${sourceMapURL}`;
 	}
 
 	/**
@@ -804,25 +803,30 @@ return /******/ (function(modules) { // 
 	 */
 	function _setSourceMapRoot(sourceMap, absSourceMapURL, source) {
 	  // No need to do this fiddling if we won't be fetching any sources over the
 	  // wire.
 	  if (sourceMap.hasContentsOfAllSources()) {
 	    return;
 	  }
 
-	  const base = path.dirname(absSourceMapURL.indexOf("data:") === 0 && source.url ? source.url : absSourceMapURL);
-
-	  if (sourceMap.sourceRoot) {
-	    sourceMap.sourceRoot = path.join(base, sourceMap.sourceRoot);
-	  } else {
-	    sourceMap.sourceRoot = base;
+	  // If it's already a URL, just leave it alone.
+	  if (!path.isURL(sourceMap.sourceRoot)) {
+	    // In the odd case where the sourceMap is a data: URL and it does
+	    // not contain the full sources, fall back to using the source's
+	    // URL, if possible.
+	    let parsedSourceMapURL = new URL(absSourceMapURL);
+	    if (parsedSourceMapURL.protocol === "data:" && source.url) {
+	      parsedSourceMapURL = new URL(source.url);
+	    }
+
+	    parsedSourceMapURL.pathname = path.dirname(parsedSourceMapURL.pathname);
+	    sourceMap.sourceRoot = new URL(sourceMap.sourceRoot || "", parsedSourceMapURL).toString();
 	  }
-
-	  return sourceMap;
+	  return sourceMap.sourceRoot;
 	}
 
 	function _getSourceMap(generatedSourceId) {
 	  return sourceMapRequests.get(generatedSourceId);
 	}
 
 	function _fetchSourceMap(generatedSource) {
 	  const existingRequest = sourceMapRequests.get(generatedSource.id);
@@ -860,1500 +864,26 @@ return /******/ (function(modules) { // 
 	  getOriginalLocation,
 	  getOriginalSourceText,
 	  applySourceMap,
 	  clearSourceMaps,
 	  hasMappedSource
 	};
 
 /***/ },
-/* 10 */
-/***/ function(module, exports, __webpack_require__) {
-
-	// Copyright Joyent, Inc. and other Node contributors.
-	//
-	// Permission is hereby granted, free of charge, to any person obtaining a
-	// copy of this software and associated documentation files (the
-	// "Software"), to deal in the Software without restriction, including
-	// without limitation the rights to use, copy, modify, merge, publish,
-	// distribute, sublicense, and/or sell copies of the Software, and to permit
-	// persons to whom the Software is furnished to do so, subject to the
-	// following conditions:
-	//
-	// The above copyright notice and this permission notice shall be included
-	// in all copies or substantial portions of the Software.
-	//
-	// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-	// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-	// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
-	// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-	// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-	// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
-	// USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-	'use strict';
-
-	var punycode = __webpack_require__(11);
-	var util = __webpack_require__(13);
-
-	exports.parse = urlParse;
-	exports.resolve = urlResolve;
-	exports.resolveObject = urlResolveObject;
-	exports.format = urlFormat;
-
-	exports.Url = Url;
-
-	function Url() {
-	  this.protocol = null;
-	  this.slashes = null;
-	  this.auth = null;
-	  this.host = null;
-	  this.port = null;
-	  this.hostname = null;
-	  this.hash = null;
-	  this.search = null;
-	  this.query = null;
-	  this.pathname = null;
-	  this.path = null;
-	  this.href = null;
-	}
-
-	// Reference: RFC 3986, RFC 1808, RFC 2396
-
-	// define these here so at least they only have to be
-	// compiled once on the first module load.
-	var protocolPattern = /^([a-z0-9.+-]+:)/i,
-	    portPattern = /:[0-9]*$/,
-
-	    // Special case for a simple path URL
-	    simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,
-
-	    // RFC 2396: characters reserved for delimiting URLs.
-	    // We actually just auto-escape these.
-	    delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'],
-
-	    // RFC 2396: characters not allowed for various reasons.
-	    unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims),
-
-	    // Allowed by RFCs, but cause of XSS attacks.  Always escape these.
-	    autoEscape = ['\''].concat(unwise),
-	    // Characters that are never ever allowed in a hostname.
-	    // Note that any invalid chars are also handled, but these
-	    // are the ones that are *expected* to be seen, so we fast-path
-	    // them.
-	    nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape),
-	    hostEndingChars = ['/', '?', '#'],
-	    hostnameMaxLen = 255,
-	    hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/,
-	    hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/,
-	    // protocols that can allow "unsafe" and "unwise" chars.
-	    unsafeProtocol = {
-	      'javascript': true,
-	      'javascript:': true
-	    },
-	    // protocols that never have a hostname.
-	    hostlessProtocol = {
-	      'javascript': true,
-	      'javascript:': true
-	    },
-	    // protocols that always contain a // bit.
-	    slashedProtocol = {
-	      'http': true,
-	      'https': true,
-	      'ftp': true,
-	      'gopher': true,
-	      'file': true,
-	      'http:': true,
-	      'https:': true,
-	      'ftp:': true,
-	      'gopher:': true,
-	      'file:': true
-	    },
-	    querystring = __webpack_require__(14);
-
-	function urlParse(url, parseQueryString, slashesDenoteHost) {
-	  if (url && util.isObject(url) && url instanceof Url) return url;
-
-	  var u = new Url;
-	  u.parse(url, parseQueryString, slashesDenoteHost);
-	  return u;
-	}
-
-	Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) {
-	  if (!util.isString(url)) {
-	    throw new TypeError("Parameter 'url' must be a string, not " + typeof url);
-	  }
-
-	  // Copy chrome, IE, opera backslash-handling behavior.
-	  // Back slashes before the query string get converted to forward slashes
-	  // See: https://code.google.com/p/chromium/issues/detail?id=25916
-	  var queryIndex = url.indexOf('?'),
-	      splitter =
-	          (queryIndex !== -1 && queryIndex < url.indexOf('#')) ? '?' : '#',
-	      uSplit = url.split(splitter),
-	      slashRegex = /\\/g;
-	  uSplit[0] = uSplit[0].replace(slashRegex, '/');
-	  url = uSplit.join(splitter);
-
-	  var rest = url;
-
-	  // trim before proceeding.
-	  // This is to support parse stuff like "  http://foo.com  \n"
-	  rest = rest.trim();
-
-	  if (!slashesDenoteHost && url.split('#').length === 1) {
-	    // Try fast path regexp
-	    var simplePath = simplePathPattern.exec(rest);
-	    if (simplePath) {
-	      this.path = rest;
-	      this.href = rest;
-	      this.pathname = simplePath[1];
-	      if (simplePath[2]) {
-	        this.search = simplePath[2];
-	        if (parseQueryString) {
-	          this.query = querystring.parse(this.search.substr(1));
-	        } else {
-	          this.query = this.search.substr(1);
-	        }
-	      } else if (parseQueryString) {
-	        this.search = '';
-	        this.query = {};
-	      }
-	      return this;
-	    }
-	  }
-
-	  var proto = protocolPattern.exec(rest);
-	  if (proto) {
-	    proto = proto[0];
-	    var lowerProto = proto.toLowerCase();
-	    this.protocol = lowerProto;
-	    rest = rest.substr(proto.length);
-	  }
-
-	  // figure out if it's got a host
-	  // user@server is *always* interpreted as a hostname, and url
-	  // resolution will treat //foo/bar as host=foo,path=bar because that's
-	  // how the browser resolves relative URLs.
-	  if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) {
-	    var slashes = rest.substr(0, 2) === '//';
-	    if (slashes && !(proto && hostlessProtocol[proto])) {
-	      rest = rest.substr(2);
-	      this.slashes = true;
-	    }
-	  }
-
-	  if (!hostlessProtocol[proto] &&
-	      (slashes || (proto && !slashedProtocol[proto]))) {
-
-	    // there's a hostname.
-	    // the first instance of /, ?, ;, or # ends the host.
-	    //
-	    // If there is an @ in the hostname, then non-host chars *are* allowed
-	    // to the left of the last @ sign, unless some host-ending character
-	    // comes *before* the @-sign.
-	    // URLs are obnoxious.
-	    //
-	    // ex:
-	    // http://a@b@c/ => user:a@b host:c
-	    // http://a@b?@c => user:a host:c path:/?@c
-
-	    // v0.12 TODO(isaacs): This is not quite how Chrome does things.
-	    // Review our test case against browsers more comprehensively.
-
-	    // find the first instance of any hostEndingChars
-	    var hostEnd = -1;
-	    for (var i = 0; i < hostEndingChars.length; i++) {
-	      var hec = rest.indexOf(hostEndingChars[i]);
-	      if (hec !== -1 && (hostEnd === -1 || hec < hostEnd))
-	        hostEnd = hec;
-	    }
-
-	    // at this point, either we have an explicit point where the
-	    // auth portion cannot go past, or the last @ char is the decider.
-	    var auth, atSign;
-	    if (hostEnd === -1) {
-	      // atSign can be anywhere.
-	      atSign = rest.lastIndexOf('@');
-	    } else {
-	      // atSign must be in auth portion.
-	      // http://a@b/c@d => host:b auth:a path:/c@d
-	      atSign = rest.lastIndexOf('@', hostEnd);
-	    }
-
-	    // Now we have a portion which is definitely the auth.
-	    // Pull that off.
-	    if (atSign !== -1) {
-	      auth = rest.slice(0, atSign);
-	      rest = rest.slice(atSign + 1);
-	      this.auth = decodeURIComponent(auth);
-	    }
-
-	    // the host is the remaining to the left of the first non-host char
-	    hostEnd = -1;
-	    for (var i = 0; i < nonHostChars.length; i++) {
-	      var hec = rest.indexOf(nonHostChars[i]);
-	      if (hec !== -1 && (hostEnd === -1 || hec < hostEnd))
-	        hostEnd = hec;
-	    }
-	    // if we still have not hit it, then the entire thing is a host.
-	    if (hostEnd === -1)
-	      hostEnd = rest.length;
-
-	    this.host = rest.slice(0, hostEnd);
-	    rest = rest.slice(hostEnd);
-
-	    // pull out port.
-	    this.parseHost();
-
-	    // we've indicated that there is a hostname,
-	    // so even if it's empty, it has to be present.
-	    this.hostname = this.hostname || '';
-
-	    // if hostname begins with [ and ends with ]
-	    // assume that it's an IPv6 address.
-	    var ipv6Hostname = this.hostname[0] === '[' &&
-	        this.hostname[this.hostname.length - 1] === ']';
-
-	    // validate a little.
-	    if (!ipv6Hostname) {
-	      var hostparts = this.hostname.split(/\./);
-	      for (var i = 0, l = hostparts.length; i < l; i++) {
-	        var part = hostparts[i];
-	        if (!part) continue;
-	        if (!part.match(hostnamePartPattern)) {
-	          var newpart = '';
-	          for (var j = 0, k = part.length; j < k; j++) {
-	            if (part.charCodeAt(j) > 127) {
-	              // we replace non-ASCII char with a temporary placeholder
-	              // we need this to make sure size of hostname is not
-	              // broken by replacing non-ASCII by nothing
-	              newpart += 'x';
-	            } else {
-	              newpart += part[j];
-	            }
-	          }
-	          // we test again with ASCII char only
-	          if (!newpart.match(hostnamePartPattern)) {
-	            var validParts = hostparts.slice(0, i);
-	            var notHost = hostparts.slice(i + 1);
-	            var bit = part.match(hostnamePartStart);
-	            if (bit) {
-	              validParts.push(bit[1]);
-	              notHost.unshift(bit[2]);
-	            }
-	            if (notHost.length) {
-	              rest = '/' + notHost.join('.') + rest;
-	            }
-	            this.hostname = validParts.join('.');
-	            break;
-	          }
-	        }
-	      }
-	    }
-
-	    if (this.hostname.length > hostnameMaxLen) {
-	      this.hostname = '';
-	    } else {
-	      // hostnames are always lower case.
-	      this.hostname = this.hostname.toLowerCase();
-	    }
-
-	    if (!ipv6Hostname) {
-	      // IDNA Support: Returns a punycoded representation of "domain".
-	      // It only converts parts of the domain name that
-	      // have non-ASCII characters, i.e. it doesn't matter if
-	      // you call it with a domain that already is ASCII-only.
-	      this.hostname = punycode.toASCII(this.hostname);
-	    }
-
-	    var p = this.port ? ':' + this.port : '';
-	    var h = this.hostname || '';
-	    this.host = h + p;
-	    this.href += this.host;
-
-	    // strip [ and ] from the hostname
-	    // the host field still retains them, though
-	    if (ipv6Hostname) {
-	      this.hostname = this.hostname.substr(1, this.hostname.length - 2);
-	      if (rest[0] !== '/') {
-	        rest = '/' + rest;
-	      }
-	    }
-	  }
-
-	  // now rest is set to the post-host stuff.
-	  // chop off any delim chars.
-	  if (!unsafeProtocol[lowerProto]) {
-
-	    // First, make 100% sure that any "autoEscape" chars get
-	    // escaped, even if encodeURIComponent doesn't think they
-	    // need to be.
-	    for (var i = 0, l = autoEscape.length; i < l; i++) {
-	      var ae = autoEscape[i];
-	      if (rest.indexOf(ae) === -1)
-	        continue;
-	      var esc = encodeURIComponent(ae);
-	      if (esc === ae) {
-	        esc = escape(ae);
-	      }
-	      rest = rest.split(ae).join(esc);
-	    }
-	  }
-
-
-	  // chop off from the tail first.
-	  var hash = rest.indexOf('#');
-	  if (hash !== -1) {
-	    // got a fragment string.
-	    this.hash = rest.substr(hash);
-	    rest = rest.slice(0, hash);
-	  }
-	  var qm = rest.indexOf('?');
-	  if (qm !== -1) {
-	    this.search = rest.substr(qm);
-	    this.query = rest.substr(qm + 1);
-	    if (parseQueryString) {
-	      this.query = querystring.parse(this.query);
-	    }
-	    rest = rest.slice(0, qm);
-	  } else if (parseQueryString) {
-	    // no query string, but parseQueryString still requested
-	    this.search = '';
-	    this.query = {};
-	  }
-	  if (rest) this.pathname = rest;
-	  if (slashedProtocol[lowerProto] &&
-	      this.hostname && !this.pathname) {
-	    this.pathname = '/';
-	  }
-
-	  //to support http.request
-	  if (this.pathname || this.search) {
-	    var p = this.pathname || '';
-	    var s = this.search || '';
-	    this.path = p + s;
-	  }
-
-	  // finally, reconstruct the href based on what has been validated.
-	  this.href = this.format();
-	  return this;
-	};
-
-	// format a parsed object into a url string
-	function urlFormat(obj) {
-	  // ensure it's an object, and not a string url.
-	  // If it's an obj, this is a no-op.
-	  // this way, you can call url_format() on strings
-	  // to clean up potentially wonky urls.
-	  if (util.isString(obj)) obj = urlParse(obj);
-	  if (!(obj instanceof Url)) return Url.prototype.format.call(obj);
-	  return obj.format();
-	}
-
-	Url.prototype.format = function() {
-	  var auth = this.auth || '';
-	  if (auth) {
-	    auth = encodeURIComponent(auth);
-	    auth = auth.replace(/%3A/i, ':');
-	    auth += '@';
-	  }
-
-	  var protocol = this.protocol || '',
-	      pathname = this.pathname || '',
-	      hash = this.hash || '',
-	      host = false,
-	      query = '';
-
-	  if (this.host) {
-	    host = auth + this.host;
-	  } else if (this.hostname) {
-	    host = auth + (this.hostname.indexOf(':') === -1 ?
-	        this.hostname :
-	        '[' + this.hostname + ']');
-	    if (this.port) {
-	      host += ':' + this.port;
-	    }
-	  }
-
-	  if (this.query &&
-	      util.isObject(this.query) &&
-	      Object.keys(this.query).length) {
-	    query = querystring.stringify(this.query);
-	  }
-
-	  var search = this.search || (query && ('?' + query)) || '';
-
-	  if (protocol && protocol.substr(-1) !== ':') protocol += ':';
-
-	  // only the slashedProtocols get the //.  Not mailto:, xmpp:, etc.
-	  // unless they had them to begin with.
-	  if (this.slashes ||
-	      (!protocol || slashedProtocol[protocol]) && host !== false) {
-	    host = '//' + (host || '');
-	    if (pathname && pathname.charAt(0) !== '/') pathname = '/' + pathname;
-	  } else if (!host) {
-	    host = '';
-	  }
-
-	  if (hash && hash.charAt(0) !== '#') hash = '#' + hash;
-	  if (search && search.charAt(0) !== '?') search = '?' + search;
-
-	  pathname = pathname.replace(/[?#]/g, function(match) {
-	    return encodeURIComponent(match);
-	  });
-	  search = search.replace('#', '%23');
-
-	  return protocol + host + pathname + search + hash;
-	};
-
-	function urlResolve(source, relative) {
-	  return urlParse(source, false, true).resolve(relative);
-	}
-
-	Url.prototype.resolve = function(relative) {
-	  return this.resolveObject(urlParse(relative, false, true)).format();
-	};
-
-	function urlResolveObject(source, relative) {
-	  if (!source) return relative;
-	  return urlParse(source, false, true).resolveObject(relative);
-	}
-
-	Url.prototype.resolveObject = function(relative) {
-	  if (util.isString(relative)) {
-	    var rel = new Url();
-	    rel.parse(relative, false, true);
-	    relative = rel;
-	  }
-
-	  var result = new Url();
-	  var tkeys = Object.keys(this);
-	  for (var tk = 0; tk < tkeys.length; tk++) {
-	    var tkey = tkeys[tk];
-	    result[tkey] = this[tkey];
-	  }
-
-	  // hash is always overridden, no matter what.
-	  // even href="" will remove it.
-	  result.hash = relative.hash;
-
-	  // if the relative url is empty, then there's nothing left to do here.
-	  if (relative.href === '') {
-	    result.href = result.format();
-	    return result;
-	  }
-
-	  // hrefs like //foo/bar always cut to the protocol.
-	  if (relative.slashes && !relative.protocol) {
-	    // take everything except the protocol from relative
-	    var rkeys = Object.keys(relative);
-	    for (var rk = 0; rk < rkeys.length; rk++) {
-	      var rkey = rkeys[rk];
-	      if (rkey !== 'protocol')
-	        result[rkey] = relative[rkey];
-	    }
-
-	    //urlParse appends trailing / to urls like http://www.example.com
-	    if (slashedProtocol[result.protocol] &&
-	        result.hostname && !result.pathname) {
-	      result.path = result.pathname = '/';
-	    }
-
-	    result.href = result.format();
-	    return result;
-	  }
-
-	  if (relative.protocol && relative.protocol !== result.protocol) {
-	    // if it's a known url protocol, then changing
-	    // the protocol does weird things
-	    // first, if it's not file:, then we MUST have a host,
-	    // and if there was a path
-	    // to begin with, then we MUST have a path.
-	    // if it is file:, then the host is dropped,
-	    // because that's known to be hostless.
-	    // anything else is assumed to be absolute.
-	    if (!slashedProtocol[relative.protocol]) {
-	      var keys = Object.keys(relative);
-	      for (var v = 0; v < keys.length; v++) {
-	        var k = keys[v];
-	        result[k] = relative[k];
-	      }
-	      result.href = result.format();
-	      return result;
-	    }
-
-	    result.protocol = relative.protocol;
-	    if (!relative.host && !hostlessProtocol[relative.protocol]) {
-	      var relPath = (relative.pathname || '').split('/');
-	      while (relPath.length && !(relative.host = relPath.shift()));
-	      if (!relative.host) relative.host = '';
-	      if (!relative.hostname) relative.hostname = '';
-	      if (relPath[0] !== '') relPath.unshift('');
-	      if (relPath.length < 2) relPath.unshift('');
-	      result.pathname = relPath.join('/');
-	    } else {
-	      result.pathname = relative.pathname;
-	    }
-	    result.search = relative.search;
-	    result.query = relative.query;
-	    result.host = relative.host || '';
-	    result.auth = relative.auth;
-	    result.hostname = relative.hostname || relative.host;
-	    result.port = relative.port;
-	    // to support http.request
-	    if (result.pathname || result.search) {
-	      var p = result.pathname || '';
-	      var s = result.search || '';
-	      result.path = p + s;
-	    }
-	    result.slashes = result.slashes || relative.slashes;
-	    result.href = result.format();
-	    return result;
-	  }
-
-	  var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/'),
-	      isRelAbs = (
-	          relative.host ||
-	          relative.pathname && relative.pathname.charAt(0) === '/'
-	      ),
-	      mustEndAbs = (isRelAbs || isSourceAbs ||
-	                    (result.host && relative.pathname)),
-	      removeAllDots = mustEndAbs,
-	      srcPath = result.pathname && result.pathname.split('/') || [],
-	      relPath = relative.pathname && relative.pathname.split('/') || [],
-	      psychotic = result.protocol && !slashedProtocol[result.protocol];
-
-	  // if the url is a non-slashed url, then relative
-	  // links like ../.. should be able
-	  // to crawl up to the hostname, as well.  This is strange.
-	  // result.protocol has already been set by now.
-	  // Later on, put the first path part into the host field.
-	  if (psychotic) {
-	    result.hostname = '';
-	    result.port = null;
-	    if (result.host) {
-	      if (srcPath[0] === '') srcPath[0] = result.host;
-	      else srcPath.unshift(result.host);
-	    }
-	    result.host = '';
-	    if (relative.protocol) {
-	      relative.hostname = null;
-	      relative.port = null;
-	      if (relative.host) {
-	        if (relPath[0] === '') relPath[0] = relative.host;
-	        else relPath.unshift(relative.host);
-	      }
-	      relative.host = null;
-	    }
-	    mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === '');
-	  }
-
-	  if (isRelAbs) {
-	    // it's absolute.
-	    result.host = (relative.host || relative.host === '') ?
-	                  relative.host : result.host;
-	    result.hostname = (relative.hostname || relative.hostname === '') ?
-	                      relative.hostname : result.hostname;
-	    result.search = relative.search;
-	    result.query = relative.query;
-	    srcPath = relPath;
-	    // fall through to the dot-handling below.
-	  } else if (relPath.length) {
-	    // it's relative
-	    // throw away the existing file, and take the new path instead.
-	    if (!srcPath) srcPath = [];
-	    srcPath.pop();
-	    srcPath = srcPath.concat(relPath);
-	    result.search = relative.search;
-	    result.query = relative.query;
-	  } else if (!util.isNullOrUndefined(relative.search)) {
-	    // just pull out the search.
-	    // like href='?foo'.
-	    // Put this after the other two cases because it simplifies the booleans
-	    if (psychotic) {
-	      result.hostname = result.host = srcPath.shift();
-	      //occationaly the auth can get stuck only in host
-	      //this especially happens in cases like
-	      //url.resolveObject('mailto:local1@domain1', 'local2@domain2')
-	      var authInHost = result.host && result.host.indexOf('@') > 0 ?
-	                       result.host.split('@') : false;
-	      if (authInHost) {
-	        result.auth = authInHost.shift();
-	        result.host = result.hostname = authInHost.shift();
-	      }
-	    }
-	    result.search = relative.search;
-	    result.query = relative.query;
-	    //to support http.request
-	    if (!util.isNull(result.pathname) || !util.isNull(result.search)) {
-	      result.path = (result.pathname ? result.pathname : '') +
-	                    (result.search ? result.search : '');
-	    }
-	    result.href = result.format();
-	    return result;
-	  }
-
-	  if (!srcPath.length) {
-	    // no path at all.  easy.
-	    // we've already handled the other stuff above.
-	    result.pathname = null;
-	    //to support http.request
-	    if (result.search) {
-	      result.path = '/' + result.search;
-	    } else {
-	      result.path = null;
-	    }
-	    result.href = result.format();
-	    return result;
-	  }
-
-	  // if a url ENDs in . or .., then it must get a trailing slash.
-	  // however, if it ends in anything else non-slashy,
-	  // then it must NOT get a trailing slash.
-	  var last = srcPath.slice(-1)[0];
-	  var hasTrailingSlash = (
-	      (result.host || relative.host || srcPath.length > 1) &&
-	      (last === '.' || last === '..') || last === '');
-
-	  // strip single dots, resolve double dots to parent dir
-	  // if the path tries to go above the root, `up` ends up > 0
-	  var up = 0;
-	  for (var i = srcPath.length; i >= 0; i--) {
-	    last = srcPath[i];
-	    if (last === '.') {
-	      srcPath.splice(i, 1);
-	    } else if (last === '..') {
-	      srcPath.splice(i, 1);
-	      up++;
-	    } else if (up) {
-	      srcPath.splice(i, 1);
-	      up--;
-	    }
-	  }
-
-	  // if the path is allowed to go above the root, restore leading ..s
-	  if (!mustEndAbs && !removeAllDots) {
-	    for (; up--; up) {
-	      srcPath.unshift('..');
-	    }
-	  }
-
-	  if (mustEndAbs && srcPath[0] !== '' &&
-	      (!srcPath[0] || srcPath[0].charAt(0) !== '/')) {
-	    srcPath.unshift('');
-	  }
-
-	  if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) {
-	    srcPath.push('');
-	  }
-
-	  var isAbsolute = srcPath[0] === '' ||
-	      (srcPath[0] && srcPath[0].charAt(0) === '/');
-
-	  // put the host back
-	  if (psychotic) {
-	    result.hostname = result.host = isAbsolute ? '' :
-	                                    srcPath.length ? srcPath.shift() : '';
-	    //occationaly the auth can get stuck only in host
-	    //this especially happens in cases like
-	    //url.resolveObject('mailto:local1@domain1', 'local2@domain2')
-	    var authInHost = result.host && result.host.indexOf('@') > 0 ?
-	                     result.host.split('@') : false;
-	    if (authInHost) {
-	      result.auth = authInHost.shift();
-	      result.host = result.hostname = authInHost.shift();
-	    }
-	  }
-
-	  mustEndAbs = mustEndAbs || (result.host && srcPath.length);
-
-	  if (mustEndAbs && !isAbsolute) {
-	    srcPath.unshift('');
-	  }
-
-	  if (!srcPath.length) {
-	    result.pathname = null;
-	    result.path = null;
-	  } else {
-	    result.pathname = srcPath.join('/');
-	  }
-
-	  //to support request.http
-	  if (!util.isNull(result.pathname) || !util.isNull(result.search)) {
-	    result.path = (result.pathname ? result.pathname : '') +
-	                  (result.search ? result.search : '');
-	  }
-	  result.auth = relative.auth || result.auth;
-	  result.slashes = result.slashes || relative.slashes;
-	  result.href = result.format();
-	  return result;
-	};
-
-	Url.prototype.parseHost = function() {
-	  var host = this.host;
-	  var port = portPattern.exec(host);
-	  if (port) {
-	    port = port[0];
-	    if (port !== ':') {
-	      this.port = port.substr(1);
-	    }
-	    host = host.substr(0, host.length - port.length);
-	  }
-	  if (host) this.hostname = host;
-	};
-
-
-/***/ },
-/* 11 */
-/***/ function(module, exports, __webpack_require__) {
-
-	var __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(module, global) {/*! https://mths.be/punycode v1.3.2 by @mathias */
-	;(function(root) {
-
-		/** Detect free variables */
-		var freeExports = typeof exports == 'object' && exports &&
-			!exports.nodeType && exports;
-		var freeModule = typeof module == 'object' && module &&
-			!module.nodeType && module;
-		var freeGlobal = typeof global == 'object' && global;
-		if (
-			freeGlobal.global === freeGlobal ||
-			freeGlobal.window === freeGlobal ||
-			freeGlobal.self === freeGlobal
-		) {
-			root = freeGlobal;
-		}
-
-		/**
-		 * The `punycode` object.
-		 * @name punycode
-		 * @type Object
-		 */
-		var punycode,
-
-		/** Highest positive signed 32-bit float value */
-		maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1
-
-		/** Bootstring parameters */
-		base = 36,
-		tMin = 1,
-		tMax = 26,
-		skew = 38,
-		damp = 700,
-		initialBias = 72,
-		initialN = 128, // 0x80
-		delimiter = '-', // '\x2D'
-
-		/** Regular expressions */
-		regexPunycode = /^xn--/,
-		regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars
-		regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators
-
-		/** Error messages */
-		errors = {
-			'overflow': 'Overflow: input needs wider integers to process',
-			'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
-			'invalid-input': 'Invalid input'
-		},
-
-		/** Convenience shortcuts */
-		baseMinusTMin = base - tMin,
-		floor = Math.floor,
-		stringFromCharCode = String.fromCharCode,
-
-		/** Temporary variable */
-		key;
-
-		/*--------------------------------------------------------------------------*/
-
-		/**
-		 * A generic error utility function.
-		 * @private
-		 * @param {String} type The error type.
-		 * @returns {Error} Throws a `RangeError` with the applicable error message.
-		 */
-		function error(type) {
-			throw RangeError(errors[type]);
-		}
-
-		/**
-		 * A generic `Array#map` utility function.
-		 * @private
-		 * @param {Array} array The array to iterate over.
-		 * @param {Function} callback The function that gets called for every array
-		 * item.
-		 * @returns {Array} A new array of values returned by the callback function.
-		 */
-		function map(array, fn) {
-			var length = array.length;
-			var result = [];
-			while (length--) {
-				result[length] = fn(array[length]);
-			}
-			return result;
-		}
-
-		/**
-		 * A simple `Array#map`-like wrapper to work with domain name strings or email
-		 * addresses.
-		 * @private
-		 * @param {String} domain The domain name or email address.
-		 * @param {Function} callback The function that gets called for every
-		 * character.
-		 * @returns {Array} A new string of characters returned by the callback
-		 * function.
-		 */
-		function mapDomain(string, fn) {
-			var parts = string.split('@');
-			var result = '';
-			if (parts.length > 1) {
-				// In email addresses, only the domain name should be punycoded. Leave
-				// the local part (i.e. everything up to `@`) intact.
-				result = parts[0] + '@';
-				string = parts[1];
-			}
-			// Avoid `split(regex)` for IE8 compatibility. See #17.
-			string = string.replace(regexSeparators, '\x2E');
-			var labels = string.split('.');
-			var encoded = map(labels, fn).join('.');
-			return result + encoded;
-		}
-
-		/**
-		 * Creates an array containing the numeric code points of each Unicode
-		 * character in the string. While JavaScript uses UCS-2 internally,
-		 * this function will convert a pair of surrogate halves (each of which
-		 * UCS-2 exposes as separate characters) into a single code point,
-		 * matching UTF-16.
-		 * @see `punycode.ucs2.encode`
-		 * @see <https://mathiasbynens.be/notes/javascript-encoding>
-		 * @memberOf punycode.ucs2
-		 * @name decode
-		 * @param {String} string The Unicode input string (UCS-2).
-		 * @returns {Array} The new array of code points.
-		 */
-		function ucs2decode(string) {
-			var output = [],
-			    counter = 0,
-			    length = string.length,
-			    value,
-			    extra;
-			while (counter < length) {
-				value = string.charCodeAt(counter++);
-				if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
-					// high surrogate, and there is a next character
-					extra = string.charCodeAt(counter++);
-					if ((extra & 0xFC00) == 0xDC00) { // low surrogate
-						output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
-					} else {
-						// unmatched surrogate; only append this code unit, in case the next
-						// code unit is the high surrogate of a surrogate pair
-						output.push(value);
-						counter--;
-					}
-				} else {
-					output.push(value);
-				}
-			}
-			return output;
-		}
-
-		/**
-		 * Creates a string based on an array of numeric code points.
-		 * @see `punycode.ucs2.decode`
-		 * @memberOf punycode.ucs2
-		 * @name encode
-		 * @param {Array} codePoints The array of numeric code points.
-		 * @returns {String} The new Unicode string (UCS-2).
-		 */
-		function ucs2encode(array) {
-			return map(array, function(value) {
-				var output = '';
-				if (value > 0xFFFF) {
-					value -= 0x10000;
-					output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
-					value = 0xDC00 | value & 0x3FF;
-				}
-				output += stringFromCharCode(value);
-				return output;
-			}).join('');
-		}
-
-		/**
-		 * Converts a basic code point into a digit/integer.
-		 * @see `digitToBasic()`
-		 * @private
-		 * @param {Number} codePoint The basic numeric code point value.
-		 * @returns {Number} The numeric value of a basic code point (for use in
-		 * representing integers) in the range `0` to `base - 1`, or `base` if
-		 * the code point does not represent a value.
-		 */
-		function basicToDigit(codePoint) {
-			if (codePoint - 48 < 10) {
-				return codePoint - 22;
-			}
-			if (codePoint - 65 < 26) {
-				return codePoint - 65;
-			}
-			if (codePoint - 97 < 26) {
-				return codePoint - 97;
-			}
-			return base;
-		}
-
-		/**
-		 * Converts a digit/integer into a basic code point.
-		 * @see `basicToDigit()`
-		 * @private
-		 * @param {Number} digit The numeric value of a basic code point.
-		 * @returns {Number} The basic code point whose value (when used for
-		 * representing integers) is `digit`, which needs to be in the range
-		 * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
-		 * used; else, the lowercase form is used. The behavior is undefined
-		 * if `flag` is non-zero and `digit` has no uppercase form.
-		 */
-		function digitToBasic(digit, flag) {
-			//  0..25 map to ASCII a..z or A..Z
-			// 26..35 map to ASCII 0..9
-			return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
-		}
-
-		/**
-		 * Bias adaptation function as per section 3.4 of RFC 3492.
-		 * http://tools.ietf.org/html/rfc3492#section-3.4
-		 * @private
-		 */
-		function adapt(delta, numPoints, firstTime) {
-			var k = 0;
-			delta = firstTime ? floor(delta / damp) : delta >> 1;
-			delta += floor(delta / numPoints);
-			for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
-				delta = floor(delta / baseMinusTMin);
-			}
-			return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
-		}
-
-		/**
-		 * Converts a Punycode string of ASCII-only symbols to a string of Unicode
-		 * symbols.
-		 * @memberOf punycode
-		 * @param {String} input The Punycode string of ASCII-only symbols.
-		 * @returns {String} The resulting string of Unicode symbols.
-		 */
-		function decode(input) {
-			// Don't use UCS-2
-			var output = [],
-			    inputLength = input.length,
-			    out,
-			    i = 0,
-			    n = initialN,
-			    bias = initialBias,
-			    basic,
-			    j,
-			    index,
-			    oldi,
-			    w,
-			    k,
-			    digit,
-			    t,
-			    /** Cached calculation results */
-			    baseMinusT;
-
-			// Handle the basic code points: let `basic` be the number of input code
-			// points before the last delimiter, or `0` if there is none, then copy
-			// the first basic code points to the output.
-
-			basic = input.lastIndexOf(delimiter);
-			if (basic < 0) {
-				basic = 0;
-			}
-
-			for (j = 0; j < basic; ++j) {
-				// if it's not a basic code point
-				if (input.charCodeAt(j) >= 0x80) {
-					error('not-basic');
-				}
-				output.push(input.charCodeAt(j));
-			}
-
-			// Main decoding loop: start just after the last delimiter if any basic code
-			// points were copied; start at the beginning otherwise.
-
-			for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {
-
-				// `index` is the index of the next character to be consumed.
-				// Decode a generalized variable-length integer into `delta`,
-				// which gets added to `i`. The overflow checking is easier
-				// if we increase `i` as we go, then subtract off its starting
-				// value at the end to obtain `delta`.
-				for (oldi = i, w = 1, k = base; /* no condition */; k += base) {
-
-					if (index >= inputLength) {
-						error('invalid-input');
-					}
-
-					digit = basicToDigit(input.charCodeAt(index++));
-
-					if (digit >= base || digit > floor((maxInt - i) / w)) {
-						error('overflow');
-					}
-
-					i += digit * w;
-					t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
-
-					if (digit < t) {
-						break;
-					}
-
-					baseMinusT = base - t;
-					if (w > floor(maxInt / baseMinusT)) {
-						error('overflow');
-					}
-
-					w *= baseMinusT;
-
-				}
-
-				out = output.length + 1;
-				bias = adapt(i - oldi, out, oldi == 0);
-
-				// `i` was supposed to wrap around from `out` to `0`,
-				// incrementing `n` each time, so we'll fix that now:
-				if (floor(i / out) > maxInt - n) {
-					error('overflow');
-				}
-
-				n += floor(i / out);
-				i %= out;
-
-				// Insert `n` at position `i` of the output
-				output.splice(i++, 0, n);
-
-			}
-
-			return ucs2encode(output);
-		}
-
-		/**
-		 * Converts a string of Unicode symbols (e.g. a domain name label) to a
-		 * Punycode string of ASCII-only symbols.
-		 * @memberOf punycode
-		 * @param {String} input The string of Unicode symbols.
-		 * @returns {String} The resulting Punycode string of ASCII-only symbols.
-		 */
-		function encode(input) {
-			var n,
-			    delta,
-			    handledCPCount,
-			    basicLength,
-			    bias,
-			    j,
-			    m,
-			    q,
-			    k,
-			    t,
-			    currentValue,
-			    output = [],
-			    /** `inputLength` will hold the number of code points in `input`. */
-			    inputLength,
-			    /** Cached calculation results */
-			    handledCPCountPlusOne,
-			    baseMinusT,
-			    qMinusT;
-
-			// Convert the input in UCS-2 to Unicode
-			input = ucs2decode(input);
-
-			// Cache the length
-			inputLength = input.length;
-
-			// Initialize the state
-			n = initialN;
-			delta = 0;
-			bias = initialBias;
-
-			// Handle the basic code points
-			for (j = 0; j < inputLength; ++j) {
-				currentValue = input[j];
-				if (currentValue < 0x80) {
-					output.push(stringFromCharCode(currentValue));
-				}
-			}
-
-			handledCPCount = basicLength = output.length;
-
-			// `handledCPCount` is the number of code points that have been handled;
-			// `basicLength` is the number of basic code points.
-
-			// Finish the basic string - if it is not empty - with a delimiter
-			if (basicLength) {
-				output.push(delimiter);
-			}
-
-			// Main encoding loop:
-			while (handledCPCount < inputLength) {
-
-				// All non-basic code points < n have been handled already. Find the next
-				// larger one:
-				for (m = maxInt, j = 0; j < inputLength; ++j) {
-					currentValue = input[j];
-					if (currentValue >= n && currentValue < m) {
-						m = currentValue;
-					}
-				}
-
-				// Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,
-				// but guard against overflow
-				handledCPCountPlusOne = handledCPCount + 1;
-				if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
-					error('overflow');
-				}
-
-				delta += (m - n) * handledCPCountPlusOne;
-				n = m;
-
-				for (j = 0; j < inputLength; ++j) {
-					currentValue = input[j];
-
-					if (currentValue < n && ++delta > maxInt) {
-						error('overflow');
-					}
-
-					if (currentValue == n) {
-						// Represent delta as a generalized variable-length integer
-						for (q = delta, k = base; /* no condition */; k += base) {
-							t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
-							if (q < t) {
-								break;
-							}
-							qMinusT = q - t;
-							baseMinusT = base - t;
-							output.push(
-								stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
-							);
-							q = floor(qMinusT / baseMinusT);
-						}
-
-						output.push(stringFromCharCode(digitToBasic(q, 0)));
-						bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
-						delta = 0;
-						++handledCPCount;
-					}
-				}
-
-				++delta;
-				++n;
-
-			}
-			return output.join('');
-		}
-
-		/**
-		 * Converts a Punycode string representing a domain name or an email address
-		 * to Unicode. Only the Punycoded parts of the input will be converted, i.e.
-		 * it doesn't matter if you call it on a string that has already been
-		 * converted to Unicode.
-		 * @memberOf punycode
-		 * @param {String} input The Punycoded domain name or email address to
-		 * convert to Unicode.
-		 * @returns {String} The Unicode representation of the given Punycode
-		 * string.
-		 */
-		function toUnicode(input) {
-			return mapDomain(input, function(string) {
-				return regexPunycode.test(string)
-					? decode(string.slice(4).toLowerCase())
-					: string;
-			});
-		}
-
-		/**
-		 * Converts a Unicode string representing a domain name or an email address to
-		 * Punycode. Only the non-ASCII parts of the domain name will be converted,
-		 * i.e. it doesn't matter if you call it with a domain that's already in
-		 * ASCII.
-		 * @memberOf punycode
-		 * @param {String} input The domain name or email address to convert, as a
-		 * Unicode string.
-		 * @returns {String} The Punycode representation of the given domain name or
-		 * email address.
-		 */
-		function toASCII(input) {
-			return mapDomain(input, function(string) {
-				return regexNonASCII.test(string)
-					? 'xn--' + encode(string)
-					: string;
-			});
-		}
-
-		/*--------------------------------------------------------------------------*/
-
-		/** Define the public API */
-		punycode = {
-			/**
-			 * A string representing the current Punycode.js version number.
-			 * @memberOf punycode
-			 * @type String
-			 */
-			'version': '1.3.2',
-			/**
-			 * An object of methods to convert from JavaScript's internal character
-			 * representation (UCS-2) to Unicode code points, and back.
-			 * @see <https://mathiasbynens.be/notes/javascript-encoding>
-			 * @memberOf punycode
-			 * @type Object
-			 */
-			'ucs2': {
-				'decode': ucs2decode,
-				'encode': ucs2encode
-			},
-			'decode': decode,
-			'encode': encode,
-			'toASCII': toASCII,
-			'toUnicode': toUnicode
-		};
-
-		/** Expose `punycode` */
-		// Some AMD build optimizers, like r.js, check for specific condition patterns
-		// like the following:
-		if (
-			true
-		) {
-			!(__WEBPACK_AMD_DEFINE_RESULT__ = function() {
-				return punycode;
-			}.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
-		} else if (freeExports && freeModule) {
-			if (module.exports == freeExports) { // in Node.js or RingoJS v0.8.0+
-				freeModule.exports = punycode;
-			} else { // in Narwhal or RingoJS v0.7.0-
-				for (key in punycode) {
-					punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]);
-				}
-			}
-		} else { // in Rhino or a web browser
-			root.punycode = punycode;
-		}
-
-	}(this));
-
-	/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(12)(module), (function() { return this; }())))
-
-/***/ },
-/* 12 */
-/***/ function(module, exports) {
-
-	module.exports = function(module) {
-		if(!module.webpackPolyfill) {
-			module.deprecate = function() {};
-			module.paths = [];
-			// module.parent = undefined by default
-			module.children = [];
-			module.webpackPolyfill = 1;
-		}
-		return module;
-	}
-
-
-/***/ },
-/* 13 */
-/***/ function(module, exports) {
-
-	'use strict';
-
-	module.exports = {
-	  isString: function(arg) {
-	    return typeof(arg) === 'string';
-	  },
-	  isObject: function(arg) {
-	    return typeof(arg) === 'object' && arg !== null;
-	  },
-	  isNull: function(arg) {
-	    return arg === null;
-	  },
-	  isNullOrUndefined: function(arg) {
-	    return arg == null;
-	  }
-	};
-
-
-/***/ },
-/* 14 */
-/***/ function(module, exports, __webpack_require__) {
-
-	'use strict';
-
-	exports.decode = exports.parse = __webpack_require__(15);
-	exports.encode = exports.stringify = __webpack_require__(16);
-
-
-/***/ },
-/* 15 */
-/***/ function(module, exports) {
-
-	// Copyright Joyent, Inc. and other Node contributors.
-	//
-	// Permission is hereby granted, free of charge, to any person obtaining a
-	// copy of this software and associated documentation files (the
-	// "Software"), to deal in the Software without restriction, including
-	// without limitation the rights to use, copy, modify, merge, publish,
-	// distribute, sublicense, and/or sell copies of the Software, and to permit
-	// persons to whom the Software is furnished to do so, subject to the
-	// following conditions:
-	//
-	// The above copyright notice and this permission notice shall be included
-	// in all copies or substantial portions of the Software.
-	//
-	// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-	// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-	// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
-	// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-	// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-	// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
-	// USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-	'use strict';
-
-	// If obj.hasOwnProperty has been overridden, then calling
-	// obj.hasOwnProperty(prop) will break.
-	// See: https://github.com/joyent/node/issues/1707
-	function hasOwnProperty(obj, prop) {
-	  return Object.prototype.hasOwnProperty.call(obj, prop);
-	}
-
-	module.exports = function(qs, sep, eq, options) {
-	  sep = sep || '&';
-	  eq = eq || '=';
-	  var obj = {};
-
-	  if (typeof qs !== 'string' || qs.length === 0) {
-	    return obj;
-	  }
-
-	  var regexp = /\+/g;
-	  qs = qs.split(sep);
-
-	  var maxKeys = 1000;
-	  if (options && typeof options.maxKeys === 'number') {
-	    maxKeys = options.maxKeys;
-	  }
-
-	  var len = qs.length;
-	  // maxKeys <= 0 means that we should not limit keys count
-	  if (maxKeys > 0 && len > maxKeys) {
-	    len = maxKeys;
-	  }
-
-	  for (var i = 0; i < len; ++i) {
-	    var x = qs[i].replace(regexp, '%20'),
-	        idx = x.indexOf(eq),
-	        kstr, vstr, k, v;
-
-	    if (idx >= 0) {
-	      kstr = x.substr(0, idx);
-	      vstr = x.substr(idx + 1);
-	    } else {
-	      kstr = x;
-	      vstr = '';
-	    }
-
-	    k = decodeURIComponent(kstr);
-	    v = decodeURIComponent(vstr);
-
-	    if (!hasOwnProperty(obj, k)) {
-	      obj[k] = v;
-	    } else if (Array.isArray(obj[k])) {
-	      obj[k].push(v);
-	    } else {
-	      obj[k] = [obj[k], v];
-	    }
-	  }
-
-	  return obj;
-	};
-
-
-/***/ },
-/* 16 */
-/***/ function(module, exports) {
-
-	// Copyright Joyent, Inc. and other Node contributors.
-	//
-	// Permission is hereby granted, free of charge, to any person obtaining a
-	// copy of this software and associated documentation files (the
-	// "Software"), to deal in the Software without restriction, including
-	// without limitation the rights to use, copy, modify, merge, publish,
-	// distribute, sublicense, and/or sell copies of the Software, and to permit
-	// persons to whom the Software is furnished to do so, subject to the
-	// following conditions:
-	//
-	// The above copyright notice and this permission notice shall be included
-	// in all copies or substantial portions of the Software.
-	//
-	// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-	// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-	// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
-	// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-	// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-	// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
-	// USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-	'use strict';
-
-	var stringifyPrimitive = function(v) {
-	  switch (typeof v) {
-	    case 'string':
-	      return v;
-
-	    case 'boolean':
-	      return v ? 'true' : 'false';
-
-	    case 'number':
-	      return isFinite(v) ? v : '';
-
-	    default:
-	      return '';
-	  }
-	};
-
-	module.exports = function(obj, sep, eq, name) {
-	  sep = sep || '&';
-	  eq = eq || '=';
-	  if (obj === null) {
-	    obj = undefined;
-	  }
-
-	  if (typeof obj === 'object') {
-	    return Object.keys(obj).map(function(k) {
-	      var ks = encodeURIComponent(stringifyPrimitive(k)) + eq;
-	      if (Array.isArray(obj[k])) {
-	        return obj[k].map(function(v) {
-	          return ks + encodeURIComponent(stringifyPrimitive(v));
-	        }).join(sep);
-	      } else {
-	        return ks + encodeURIComponent(stringifyPrimitive(obj[k]));
-	      }
-	    }).join(sep);
-
-	  }
-
-	  if (!name) return '';
-	  return encodeURIComponent(stringifyPrimitive(name)) + eq +
-	         encodeURIComponent(stringifyPrimitive(obj));
-	};
-
-
-/***/ },
+/* 10 */,
+/* 11 */,
+/* 12 */,
+/* 13 */,
+/* 14 */,
+/* 15 */,
+/* 16 */,
 /* 17 */
 /***/ function(module, exports) {
 
-	function basename(path) {
-	  return path.split("/").pop();
-	}
-
 	function dirname(path) {
 	  const idx = path.lastIndexOf("/");
 	  return path.slice(0, idx);
 	}
 
 	function isURL(str) {
 	  try {
 	    new URL(str);
@@ -2362,22 +892,18 @@ return /******/ (function(modules) { // 
 	    return false;
 	  }
 	}
 
 	function isAbsolute(str) {
 	  return str[0] === "/";
 	}
 
-	function join(base, dir) {
-	  return `${base}/${dir}`;
-	}
-
 	module.exports = {
-	  basename, dirname, isURL, isAbsolute, join
+	  dirname, isURL, isAbsolute
 	};
 
 /***/ },
 /* 18 */
 /***/ function(module, exports, __webpack_require__) {
 
 	/*
 	 * Copyright 2009-2011 Mozilla Foundation and contributors
--- a/devtools/client/themes/computed.css
+++ b/devtools/client/themes/computed.css
@@ -128,16 +128,30 @@
 .theme-firebug .property-content {
   font-family: var(--proportional-font-family);
 }
 
 .theme-firebug .property-view {
   border-bottom: 1px solid rgba(0, 0, 0, 0.1);
 }
 
+/* Bug 1360238 - getSelection displays an extra "\n" on multiple sibling block elements.
+   We rely on this behavior to add an extra "\n" between matched selectors (Bug 1222737).
+   Therefore we use <div> elements around matched selectors and need this class
+   to keep them inline. We do that to avoid doing any formatting logic in JS.
+   Once Bug 1360238 will be fixed, we'll probably have to change the behavior
+   and remove this class. */
+.fix-get-selection {
+  display: inline;
+}
+
+.visually-hidden {
+  opacity: 0;
+}
+
 /* From skin */
 .expander {
   visibility: hidden;
 }
 
 .expandable {
   visibility: visible;
 }
--- a/devtools/client/webconsole/new-console-output/components/console-output.js
+++ b/devtools/client/webconsole/new-console-output/components/console-output.js
@@ -11,58 +11,55 @@ const {
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
 const {
   getAllMessages,
   getAllMessagesUiById,
   getAllMessagesTableDataById,
 } = require("devtools/client/webconsole/new-console-output/selectors/messages");
-const { getScrollSetting } = require("devtools/client/webconsole/new-console-output/selectors/ui");
 const MessageContainer = createFactory(require("devtools/client/webconsole/new-console-output/components/message-container").MessageContainer);
 
 const ConsoleOutput = createClass({
 
   displayName: "ConsoleOutput",
 
   propTypes: {
     messages: PropTypes.object.isRequired,
     messagesUi: PropTypes.object.isRequired,
     serviceContainer: PropTypes.shape({
       attachRefToHud: PropTypes.func.isRequired,
       openContextMenu: PropTypes.func.isRequired,
       sourceMapService: PropTypes.object,
     }),
-    autoscroll: PropTypes.bool.isRequired,
     dispatch: PropTypes.func.isRequired,
     timestampsVisible: PropTypes.bool,
     messagesTableData: PropTypes.object.isRequired,
   },
 
   componentDidMount() {
     // Do the scrolling in the nextTick since this could hit console startup performances.
     // See https://bugzilla.mozilla.org/show_bug.cgi?id=1355869
     setTimeout(() => {
       scrollToBottom(this.outputNode);
     }, 0);
     this.props.serviceContainer.attachRefToHud("outputScroller", this.outputNode);
   },
 
   componentWillUpdate(nextProps, nextState) {
-    if (!this.outputNode) {
+    const outputNode = this.outputNode;
+    if (!outputNode || !outputNode.lastChild) {
       return;
     }
 
-    const outputNode = this.outputNode;
-
     // Figure out if we are at the bottom. If so, then any new message should be scrolled
     // into view.
-    if (this.props.autoscroll && outputNode.lastChild) {
-      this.shouldScrollBottom = isScrolledToBottom(outputNode.lastChild, outputNode);
-    }
+    const lastChild = outputNode.lastChild;
+    const delta = nextProps.messages.size - this.props.messages.size;
+    this.shouldScrollBottom = delta > 0 && isScrolledToBottom(lastChild, outputNode);
   },
 
   componentDidUpdate() {
     if (this.shouldScrollBottom) {
       scrollToBottom(this.outputNode);
     }
   },
 
@@ -70,34 +67,32 @@ const ConsoleOutput = createClass({
     this.props.serviceContainer.openContextMenu(e);
     e.stopPropagation();
     e.preventDefault();
   },
 
   render() {
     let {
       dispatch,
-      autoscroll,
       messages,
       messagesUi,
       messagesTableData,
       serviceContainer,
       timestampsVisible,
     } = this.props;
 
     let messageNodes = messages.map((message) => {
       return (
         MessageContainer({
           dispatch,
           message,
           key: message.id,
           serviceContainer,
           open: messagesUi.includes(message.id),
           tableData: messagesTableData.get(message.id),
-          autoscroll,
           indent: message.indent,
           timestampsVisible,
         })
       );
     });
 
     return (
       dom.div({
@@ -123,14 +118,13 @@ function isScrolledToBottom(outputNode, 
          scrollNode.scrollHeight - lastNodeHeight / 2;
 }
 
 function mapStateToProps(state, props) {
   return {
     messages: getAllMessages(state),
     messagesUi: getAllMessagesUiById(state),
     messagesTableData: getAllMessagesTableDataById(state),
-    autoscroll: getScrollSetting(state),
     timestampsVisible: state.ui.timestampsVisible,
   };
 }
 
 module.exports = connect(mapStateToProps)(ConsoleOutput);
--- a/devtools/client/webconsole/new-console-output/components/message-container.js
+++ b/devtools/client/webconsole/new-console-output/components/message-container.js
@@ -29,17 +29,16 @@ const componentMap = new Map([
 
 const MessageContainer = createClass({
   displayName: "MessageContainer",
 
   propTypes: {
     message: PropTypes.object.isRequired,
     open: PropTypes.bool.isRequired,
     serviceContainer: PropTypes.object.isRequired,
-    autoscroll: PropTypes.bool.isRequired,
     indent: PropTypes.number.isRequired,
     tableData: PropTypes.object,
     timestampsVisible: PropTypes.bool.isRequired,
   },
 
   getDefaultProps: function () {
     return {
       open: false,
--- a/devtools/client/webconsole/new-console-output/components/message-types/console-command.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/console-command.js
@@ -12,32 +12,30 @@ const {
   PropTypes
 } = require("devtools/client/shared/vendor/react");
 const Message = createFactory(require("devtools/client/webconsole/new-console-output/components/message"));
 
 ConsoleCommand.displayName = "ConsoleCommand";
 
 ConsoleCommand.propTypes = {
   message: PropTypes.object.isRequired,
-  autoscroll: PropTypes.bool.isRequired,
   indent: PropTypes.number.isRequired,
   timestampsVisible: PropTypes.bool.isRequired,
   serviceContainer: PropTypes.object,
 };
 
 ConsoleCommand.defaultProps = {
   indent: 0,
 };
 
 /**
  * Displays input from the console.
  */
 function ConsoleCommand(props) {
   const {
-    autoscroll,
     indent,
     message,
     timestampsVisible,
     serviceContainer,
   } = props;
 
   const {
     source,
@@ -47,16 +45,15 @@ function ConsoleCommand(props) {
   } = message;
 
   return Message({
     source,
     type,
     level,
     topLevelClasses: [],
     messageBody,
-    scrollToMessage: autoscroll,
     serviceContainer,
     indent,
     timestampsVisible,
   });
 }
 
 module.exports = ConsoleCommand;
--- a/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js
@@ -71,17 +71,16 @@ function EvaluationResult(props) {
   return Message({
     source,
     type,
     level,
     indent,
     topLevelClasses,
     messageBody,
     messageId,
-    scrollToMessage: props.autoscroll,
     serviceContainer,
     exceptionDocURL,
     frame,
     timeStamp,
     parameters,
     notes,
     timestampsVisible,
   });
--- a/devtools/client/webconsole/new-console-output/reducers/ui.js
+++ b/devtools/client/webconsole/new-console-output/reducers/ui.js
@@ -2,46 +2,32 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const {
   FILTER_BAR_TOGGLE,
-  MESSAGE_ADD,
-  REMOVED_MESSAGES_CLEAR,
   TIMESTAMPS_TOGGLE
 } = require("devtools/client/webconsole/new-console-output/constants");
 const Immutable = require("devtools/client/shared/vendor/immutable");
 
 const UiState = Immutable.Record({
   filterBarVisible: false,
   filteredMessageVisible: false,
-  autoscroll: true,
   timestampsVisible: true,
 });
 
 function ui(state = new UiState(), action) {
-  // Autoscroll should be set for all action types. If the last action was not message
-  // add, then turn it off. This prevents us from scrolling after someone toggles a
-  // filter, or to the bottom of the attachment when an expandable message at the bottom
-  // of the list is expanded. It does depend on the MESSAGE_ADD action being the last in
-  // its batch, though.
-  // It also depends on REMOVED_MESSAGES_CLEAR action being sent after MESSAGE_ADD
-  // if number of messages reached the maximum limit.
-  let autoscroll = action.type == MESSAGE_ADD || action.type == REMOVED_MESSAGES_CLEAR;
-  state = state.set("autoscroll", autoscroll);
-
   switch (action.type) {
     case FILTER_BAR_TOGGLE:
       return state.set("filterBarVisible", !state.filterBarVisible);
     case TIMESTAMPS_TOGGLE:
       return state.set("timestampsVisible", action.visible);
-
   }
 
   return state;
 }
 
 module.exports = {
   UiState,
   ui,
--- a/devtools/client/webconsole/new-console-output/selectors/ui.js
+++ b/devtools/client/webconsole/new-console-output/selectors/ui.js
@@ -5,16 +5,11 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 function getAllUi(state) {
   return state.ui;
 }
 
-function getScrollSetting(state) {
-  return getAllUi(state).autoscroll;
-}
-
 module.exports = {
   getAllUi,
-  getScrollSetting,
 };
--- a/devtools/client/webconsole/new-console-output/test/components/filter-bar.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/filter-bar.test.js
@@ -1,20 +1,20 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 const expect = require("expect");
 const sinon = require("sinon");
-const { render, mount } = require("enzyme");
+const { render, mount, shallow } = require("enzyme");
 
 const { createFactory, DOM } = require("devtools/client/shared/vendor/react");
 const Provider = createFactory(require("react-redux").Provider);
 
-const FilterButton = createFactory(require("devtools/client/webconsole/new-console-output/components/filter-button"));
+const FilterButton = require("devtools/client/webconsole/new-console-output/components/filter-button");
 const FilterBar = createFactory(require("devtools/client/webconsole/new-console-output/components/filter-bar"));
 const { getAllUi } = require("devtools/client/webconsole/new-console-output/selectors/ui");
 const {
   MESSAGES_CLEAR,
   MESSAGE_LEVEL
 } = require("devtools/client/webconsole/new-console-output/constants");
 
 const { setupStore } = require("devtools/client/webconsole/new-console-output/test/helpers");
@@ -52,16 +52,19 @@ describe("FilterBar component:", () => {
 
     expect(getAllUi(store.getState()).filterBarVisible).toBe(false);
 
     const wrapper = mount(Provider({store}, FilterBar({ serviceContainer })));
     wrapper.find(".devtools-filter-icon").simulate("click");
 
     expect(getAllUi(store.getState()).filterBarVisible).toBe(true);
 
+    const secondaryBar = wrapper.find(".webconsole-filterbar-secondary");
+    expect(secondaryBar.length).toBe(1);
+
     // Buttons are displayed
     const filterBtn = props => FilterButton(
       Object.assign({}, {
         active: true,
         dispatch: store.dispatch
       }, props)
     );
 
@@ -74,17 +77,19 @@ describe("FilterBar component:", () => {
       DOM.span({
         className: "devtools-separator",
       }),
       filterBtn({ label: "CSS", filterKey: "css" }),
       filterBtn({ label: "XHR", filterKey: "netxhr", active: false }),
       filterBtn({ label: "Requests", filterKey: "net", active: false }),
     ];
 
-    expect(wrapper.containsAllMatchingElements(buttons)).toBe(true);
+    secondaryBar.children().forEach((child, index) => {
+      expect(child.html()).toEqual(shallow(buttons[index]).html());
+    });
   });
 
   it("fires MESSAGES_CLEAR action when clear button is clicked", () => {
     const store = setupStore([]);
     store.dispatch = sinon.spy();
 
     const wrapper = mount(Provider({store}, FilterBar({ serviceContainer })));
     wrapper.find(".devtools-clear-icon").simulate("click");
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -6729,17 +6729,17 @@ nsDocument::CreateRange(nsIDOMRange** aR
   *aReturn = nsIDocument::CreateRange(rv).take();
   return rv.StealNSResult();
 }
 
 already_AddRefed<nsRange>
 nsIDocument::CreateRange(ErrorResult& rv)
 {
   RefPtr<nsRange> range = new nsRange(this);
-  nsresult res = range->Set(this, 0, this, 0);
+  nsresult res = range->CollapseTo(this, 0);
   if (NS_FAILED(res)) {
     rv.Throw(res);
     return nullptr;
   }
 
   return range.forget();
 }
 
--- a/dom/base/nsRange.cpp
+++ b/dom/base/nsRange.cpp
@@ -266,50 +266,38 @@ nsRange::nsRange(nsINode* aNode)
 }
 
 /* static */
 nsresult
 nsRange::CreateRange(nsINode* aStartParent, int32_t aStartOffset,
                      nsINode* aEndParent, int32_t aEndOffset,
                      nsRange** aRange)
 {
-  nsCOMPtr<nsIDOMNode> startDomNode = do_QueryInterface(aStartParent);
-  nsCOMPtr<nsIDOMNode> endDomNode = do_QueryInterface(aEndParent);
-
-  nsresult rv = CreateRange(startDomNode, aStartOffset, endDomNode, aEndOffset,
-                            aRange);
-
-  return rv;
-
+  MOZ_ASSERT(aRange);
+  *aRange = nullptr;
+
+  RefPtr<nsRange> range = new nsRange(aStartParent);
+  nsresult rv = range->SetStartAndEnd(aStartParent, aStartOffset,
+                                      aEndParent, aEndOffset);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  range.forget(aRange);
+  return NS_OK;
 }
 
 /* static */
 nsresult
 nsRange::CreateRange(nsIDOMNode* aStartParent, int32_t aStartOffset,
                      nsIDOMNode* aEndParent, int32_t aEndOffset,
                      nsRange** aRange)
 {
-  MOZ_ASSERT(aRange);
-  *aRange = nullptr;
-
   nsCOMPtr<nsINode> startParent = do_QueryInterface(aStartParent);
-  NS_ENSURE_ARG_POINTER(startParent);
-
-  RefPtr<nsRange> range = new nsRange(startParent);
-
-  // XXX this can be optimized by inlining SetStart/End and calling
-  // DoSetRange *once*.
-  nsresult rv = range->SetStart(startParent, aStartOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = range->SetEnd(aEndParent, aEndOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  range.forget(aRange);
-  return NS_OK;
+  nsCOMPtr<nsINode> endParent = do_QueryInterface(aEndParent);
+  return CreateRange(startParent, aStartOffset, endParent, aEndOffset, aRange);
 }
 
 /* static */
 nsresult
 nsRange::CreateRange(nsIDOMNode* aStartParent, int32_t aStartOffset,
                      nsIDOMNode* aEndParent, int32_t aEndOffset,
                      nsIDOMRange** aRange)
 {
@@ -1144,16 +1132,25 @@ nsRange::GetCommonAncestorContainer(nsID
     NS_ADDREF(*aCommonParent = commonAncestor->AsDOMNode());
   } else {
     *aCommonParent = nullptr;
   }
 
   return rv.StealNSResult();
 }
 
+/* static */
+bool
+nsRange::IsValidOffset(nsINode* aNode, int32_t aOffset)
+{
+  return aNode &&
+         aOffset >= 0 &&
+         static_cast<size_t>(aOffset) <= aNode->Length();
+}
+
 nsINode*
 nsRange::IsValidBoundary(nsINode* aNode)
 {
   if (!aNode) {
     return nullptr;
   }
 
   if (aNode->IsNodeOfType(nsINode::eCONTENT)) {
@@ -1232,17 +1229,17 @@ nsRange::SetStart(nsIDOMNode* aParent, i
 /* virtual */ nsresult
 nsRange::SetStart(nsINode* aParent, int32_t aOffset)
 {
   nsINode* newRoot = IsValidBoundary(aParent);
   if (!newRoot) {
     return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
   }
 
-  if (aOffset < 0 || uint32_t(aOffset) > aParent->Length()) {
+  if (!IsValidOffset(aParent, aOffset)) {
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
 
   // Collapse if not positioned yet, if positioned in another doc or
   // if the new start is after end.
   if (!mIsPositioned || newRoot != mRoot ||
       nsContentUtils::ComparePoints(aParent, aOffset,
                                     mEndParent, mEndOffset) == 1) {
@@ -1269,17 +1266,19 @@ nsRange::SetStartBefore(nsINode& aNode, 
 {
   if (!nsContentUtils::LegacyIsCallerNativeCode() &&
       !nsContentUtils::CanCallerAccess(&aNode)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
   AutoInvalidateSelection atEndOfBlock(this);
-  aRv = SetStart(aNode.GetParentNode(), IndexOf(&aNode));
+  int32_t offset = -1;
+  nsINode* parent = GetParentAndOffsetBefore(&aNode, &offset);
+  aRv = SetStart(parent, offset);
 }
 
 NS_IMETHODIMP
 nsRange::SetStartBefore(nsIDOMNode* aSibling)
 {
   nsCOMPtr<nsINode> sibling = do_QueryInterface(aSibling);
   if (!sibling) {
     return NS_ERROR_DOM_NOT_OBJECT_ERR;
@@ -1303,17 +1302,19 @@ nsRange::SetStartAfter(nsINode& aNode, E
 {
   if (!nsContentUtils::LegacyIsCallerNativeCode() &&
       !nsContentUtils::CanCallerAccess(&aNode)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
   AutoInvalidateSelection atEndOfBlock(this);
-  aRv = SetStart(aNode.GetParentNode(), IndexOf(&aNode) + 1);
+  int32_t offset = -1;
+  nsINode* parent = GetParentAndOffsetAfter(&aNode, &offset);
+  aRv = SetStart(parent, offset);
 }
 
 NS_IMETHODIMP
 nsRange::SetStartAfter(nsIDOMNode* aSibling)
 {
   nsCOMPtr<nsINode> sibling = do_QueryInterface(aSibling);
   if (!sibling) {
     return NS_ERROR_DOM_NOT_OBJECT_ERR;
@@ -1360,17 +1361,17 @@ nsRange::SetEnd(nsIDOMNode* aParent, int
 /* virtual */ nsresult
 nsRange::SetEnd(nsINode* aParent, int32_t aOffset)
 {
   nsINode* newRoot = IsValidBoundary(aParent);
   if (!newRoot) {
     return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
   }
 
-  if (aOffset < 0 || uint32_t(aOffset) > aParent->Length()) {
+  if (!IsValidOffset(aParent, aOffset)) {
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
 
   // Collapse if not positioned yet, if positioned in another doc or
   // if the new end is before start.
   if (!mIsPositioned || newRoot != mRoot ||
       nsContentUtils::ComparePoints(mStartParent, mStartOffset,
                                     aParent, aOffset) == 1) {
@@ -1379,16 +1380,74 @@ nsRange::SetEnd(nsINode* aParent, int32_
     return NS_OK;
   }
 
   DoSetRange(mStartParent, mStartOffset, aParent, aOffset, mRoot);
 
   return NS_OK;
 }
 
+nsresult
+nsRange::SetStartAndEnd(nsINode* aStartParent, int32_t aStartOffset,
+                        nsINode* aEndParent, int32_t aEndOffset)
+{
+  if (NS_WARN_IF(!aStartParent) || NS_WARN_IF(!aEndParent)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsINode* newStartRoot = IsValidBoundary(aStartParent);
+  if (!newStartRoot) {
+    return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
+  }
+  if (!IsValidOffset(aStartParent, aStartOffset)) {
+    return NS_ERROR_DOM_INDEX_SIZE_ERR;
+  }
+
+  if (aStartParent == aEndParent) {
+    if (!IsValidOffset(aEndParent, aEndOffset)) {
+      return NS_ERROR_DOM_INDEX_SIZE_ERR;
+    }
+    // If the end offset is less than the start offset, this should be
+    // collapsed at the end offset.
+    if (aStartOffset > aEndOffset) {
+      DoSetRange(aEndParent, aEndOffset, aEndParent, aEndOffset, newStartRoot);
+    } else {
+      DoSetRange(aStartParent, aStartOffset,
+                 aEndParent, aEndOffset, newStartRoot);
+    }
+    return NS_OK;
+  }
+
+  nsINode* newEndRoot = IsValidBoundary(aEndParent);
+  if (!newEndRoot) {
+    return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
+  }
+  if (!IsValidOffset(aEndParent, aEndOffset)) {
+    return NS_ERROR_DOM_INDEX_SIZE_ERR;
+  }
+
+  // If they have different root, this should be collapsed at the end point.
+  if (newStartRoot != newEndRoot) {
+    DoSetRange(aEndParent, aEndOffset, aEndParent, aEndOffset, newEndRoot);
+    return NS_OK;
+  }
+
+  // If the end point is before the start point, this should be collapsed at
+  // the end point.
+  if (nsContentUtils::ComparePoints(aStartParent, aStartOffset,
+                                    aEndParent, aEndOffset) == 1) {
+    DoSetRange(aEndParent, aEndOffset, aEndParent, aEndOffset, newEndRoot);
+    return NS_OK;
+  }
+
+  // Otherwise, set the range as specified.
+  DoSetRange(aStartParent, aStartOffset, aEndParent, aEndOffset, newStartRoot);
+  return NS_OK;
+}
+
 void
 nsRange::SetEndBeforeJS(nsINode& aNode, ErrorResult& aErr)
 {
   AutoCalledByJSRestore calledByJSRestorer(*this);
   mCalledByJS = true;
   SetEndBefore(aNode, aErr);
 }
 
@@ -1397,17 +1456,19 @@ nsRange::SetEndBefore(nsINode& aNode, Er
 {
   if (!nsContentUtils::LegacyIsCallerNativeCode() &&
       !nsContentUtils::CanCallerAccess(&aNode)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
   AutoInvalidateSelection atEndOfBlock(this);
-  aRv = SetEnd(aNode.GetParentNode(), IndexOf(&aNode));
+  int32_t offset = -1;
+  nsINode* parent = GetParentAndOffsetBefore(&aNode, &offset);
+  aRv = SetEnd(parent, offset);
 }
 
 NS_IMETHODIMP
 nsRange::SetEndBefore(nsIDOMNode* aSibling)
 {
   nsCOMPtr<nsINode> sibling = do_QueryInterface(aSibling);
   if (!sibling) {
     return NS_ERROR_DOM_NOT_OBJECT_ERR;
@@ -1431,17 +1492,19 @@ nsRange::SetEndAfter(nsINode& aNode, Err
 {
   if (!nsContentUtils::LegacyIsCallerNativeCode() &&
       !nsContentUtils::CanCallerAccess(&aNode)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
   AutoInvalidateSelection atEndOfBlock(this);
-  aRv = SetEnd(aNode.GetParentNode(), IndexOf(&aNode) + 1);
+  int32_t offset = -1;
+  nsINode* parent = GetParentAndOffsetAfter(&aNode, &offset);
+  aRv = SetEnd(parent, offset);
 }
 
 NS_IMETHODIMP
 nsRange::SetEndAfter(nsIDOMNode* aSibling)
 {
   nsCOMPtr<nsINode> sibling = do_QueryInterface(aSibling);
   if (!sibling) {
     return NS_ERROR_DOM_NOT_OBJECT_ERR;
--- a/dom/base/nsRange.h
+++ b/dom/base/nsRange.h
@@ -55,16 +55,21 @@ public:
                               nsIDOMRange** aRange);
   static nsresult CreateRange(nsINode* aStartParent, int32_t aStartOffset,
                               nsINode* aEndParent, int32_t aEndOffset,
                               nsRange** aRange);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsRange, nsIDOMRange)
 
+  nsrefcnt GetRefCount() const
+  {
+    return mRefCnt;
+  }
+
   /**
    * The DOM Range spec requires that when a node is removed from its parent,
    * and the node's subtree contains the start or end point of a range, that
    * start or end point is moved up to where the node was removed from its
    * parent.
    * For some internal uses of Ranges it's useful to disable that behavior,
    * so that a range of children within a single parent is preserved even if
    * that parent is removed from the document tree.
@@ -144,29 +149,71 @@ public:
    */
   void SetIsGenerated(bool aIsGenerated)
   {
     mIsGenerated = aIsGenerated;
   }
 
   nsINode* GetCommonAncestor() const;
   void Reset();
+
+  /**
+   * SetStart() and SetEnd() sets start point or end point separately.
+   * However, this is expensive especially when it's a range of Selection.
+   * When you set both start and end of a range, you should use
+   * SetStartAndEnd() instead.
+   */
   nsresult SetStart(nsINode* aParent, int32_t aOffset);
   nsresult SetEnd(nsINode* aParent, int32_t aOffset);
+
   already_AddRefed<nsRange> CloneRange() const;
 
-  nsresult Set(nsINode* aStartParent, int32_t aStartOffset,
-               nsINode* aEndParent, int32_t aEndOffset)
+  /**
+   * SetStartAndEnd() works similar to call both SetStart() and SetEnd().
+   * Different from calls them separately, this does nothing if either
+   * the start point or the end point is invalid point.
+   * If the specified start point is after the end point, the range will be
+   * collapsed at the end point.  Similarly, if they are in different root,
+   * the range will be collapsed at the end point.
+   */
+  nsresult SetStartAndEnd(nsINode* aStartParent, int32_t aStartOffset,
+                          nsINode* aEndParent, int32_t aEndOffset);
+
+  /**
+   * CollapseTo() works similar to call both SetStart() and SetEnd() with
+   * same node and offset.  This just calls SetStartAndParent() to set
+   * collapsed range at aParent and aOffset.
+   */
+  nsresult CollapseTo(nsINode* aParent, int32_t aOffset)
   {
-    // If this starts being hot, we may be able to optimize this a bit,
-    // but for now just set start and end separately.
-    nsresult rv = SetStart(aStartParent, aStartOffset);
-    NS_ENSURE_SUCCESS(rv, rv);
+    return SetStartAndEnd(aParent, aOffset, aParent, aOffset);
+  }
 
-    return SetEnd(aEndParent, aEndOffset);
+  /**
+   * Retrieves node and offset for setting start or end of a range to
+   * before or after aNode.
+   */
+  static nsINode* GetParentAndOffsetAfter(nsINode* aNode, int32_t* aOffset)
+  {
+    MOZ_ASSERT(aNode);
+    MOZ_ASSERT(aOffset);
+    nsINode* parentNode = aNode->GetParentNode();
+    *aOffset = parentNode ? parentNode->IndexOf(aNode) : -1;
+    if (*aOffset >= 0) {
+      (*aOffset)++;
+    }
+    return parentNode;
+  }
+  static nsINode* GetParentAndOffsetBefore(nsINode* aNode, int32_t* aOffset)
+  {
+    MOZ_ASSERT(aNode);
+    MOZ_ASSERT(aOffset);
+    nsINode* parentNode = aNode->GetParentNode();
+    *aOffset = parentNode ? parentNode->IndexOf(aNode) : -1;
+    return parentNode;
   }
 
   NS_IMETHOD GetUsedFontFaces(nsIDOMFontFaceList** aResult);
 
   // nsIMutationObserver methods
   NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
@@ -309,16 +356,17 @@ public:
    */
   void ExcludeNonSelectableNodes(nsTArray<RefPtr<nsRange>>* aOutRanges);
 
   typedef nsTHashtable<nsPtrHashKey<nsRange> > RangeHashTable;
 protected:
   void RegisterCommonAncestor(nsINode* aNode);
   void UnregisterCommonAncestor(nsINode* aNode);
   nsINode* IsValidBoundary(nsINode* aNode);
+  static bool IsValidOffset(nsINode* aNode, int32_t aOffset);
 
   // CharacterDataChanged set aNotInsertedYet to true to disable an assertion
   // and suppress re-registering a range common ancestor node since
   // the new text node of a splitText hasn't been inserted yet.
   // CharacterDataChanged does the re-registering when needed.
   void DoSetRange(nsINode* aStartN, int32_t aStartOffset,
                   nsINode* aEndN, int32_t aEndOffset,
                   nsINode* aRoot, bool aNotInsertedYet = false);
--- a/dom/events/ContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -980,21 +980,17 @@ ContentEventHandler::SetRangeFromFlatTex
     *aNewOffset = aOffset;
   }
   if (aLastTextNode) {
     *aLastTextNode = nullptr;
   }
 
   // Special case like <br contenteditable>
   if (!mRootContent->HasChildren()) {
-    nsresult rv = aRange->SetStart(mRootContent, 0);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-    rv = aRange->SetEnd(mRootContent, 0);
+    nsresult rv = aRange->CollapseTo(mRootContent, 0);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator();
   nsresult rv = iter->Init(mRootContent);
   if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -2878,18 +2874,17 @@ ContentEventHandler::AdjustCollapsedRang
   }
 
   // But if the found node isn't a text node, we cannot modify the range.
   if (!childNode || !childNode->IsNodeOfType(nsINode::eTEXT) ||
       NS_WARN_IF(offsetInChildNode < 0)) {
     return NS_OK;
   }
 
-  nsresult rv = aRange->Set(childNode, offsetInChildNode,
-                            childNode, offsetInChildNode);
+  nsresult rv = aRange->CollapseTo(childNode, offsetInChildNode);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
 nsresult
 ContentEventHandler::GetStartFrameAndOffset(const nsRange* aRange,
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1653,16 +1653,19 @@ HTMLMediaElement::OnChannelRedirect(nsIC
   return mChannelLoader->Redirect(aChannel, aNewChannel, aFlags);
 }
 
 void HTMLMediaElement::ShutdownDecoder()
 {
   RemoveMediaElementFromURITable();
   NS_ASSERTION(mDecoder, "Must have decoder to shut down");
   mWaitingForKeyListener.DisconnectIfExists();
+  if (mMediaSource) {
+    mMediaSource->CompletePendingTransactions();
+  }
   mDecoder->Shutdown();
   mDecoder = nullptr;
 }
 
 void HTMLMediaElement::AbortExistingLoads()
 {
   // If there is no existing decoder then we don't have anything to
   // report. This prevents reporting the initial load from an
@@ -5578,16 +5581,22 @@ HTMLMediaElement::UpdateReadyStateIntern
       mediaInfo.EnableAudio();
     }
     if (hasVideoTracks) {
       mediaInfo.EnableVideo();
     }
     MetadataLoaded(&mediaInfo, nsAutoPtr<const MetadataTags>(nullptr));
   }
 
+  if (mMediaSource) {
+    // readyState has changed, assuming it's following the pending mediasource
+    // operations. Notify the Mediasource that the operations have completed.
+    mMediaSource->CompletePendingTransactions();
+  }
+
   enum NextFrameStatus nextFrameStatus = NextFrameStatus();
   if (nextFrameStatus == NEXT_FRAME_UNAVAILABLE ||
       (nextFrameStatus == NEXT_FRAME_UNAVAILABLE_BUFFERING &&
        mWaitingForKey == WAITING_FOR_KEY)) {
     if (mWaitingForKey != NOT_WAITING_FOR_KEY) {
       // http://w3c.github.io/encrypted-media/#wait-for-key
       // Continuing 7.3.4 Queue a "waitingforkey" Event
       // 4. Queue a task to fire a simple event named waitingforkey
--- a/dom/indexedDB/test/unit/xpcshell-head-parent-process.js
+++ b/dom/indexedDB/test/unit/xpcshell-head-parent-process.js
@@ -631,19 +631,19 @@ var SpecialPowers = {
 
   get Cu() {
     return Cu;
   },
 
   // Based on SpecialPowersObserver.prototype.receiveMessage
   createFiles(requests, callback) {
     let dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
-    let filePaths = new Array;
+    let filePaths = [];
     if (!this._createdFiles) {
-      this._createdFiles = new Array;
+      this._createdFiles = [];
     }
     let createdFiles = this._createdFiles;
     let promises = [];
     requests.forEach(function(request) {
       const filePerms = 0o666;
       let testFile = dirSvc.get("ProfD", Ci.nsIFile);
       if (request.name) {
         testFile.append(request.name);
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -2014,16 +2014,17 @@ MediaFormatReader::HandleDemuxedSamples(
   while (decoder.mQueuedSamples.Length()) {
     RefPtr<MediaRawData> sample = decoder.mQueuedSamples[0];
     RefPtr<TrackInfoSharedPtr> info = sample->mTrackInfo;
 
     if (info && decoder.mLastStreamSourceID != info->GetID()) {
       bool recyclable = MediaPrefs::MediaDecoderCheckRecycling()
                         && decoder.mDecoder->SupportDecoderRecycling();
       if (!recyclable
+          && decoder.mTimeThreshold.isNothing()
           && (decoder.mNextStreamSourceID.isNothing()
               || decoder.mNextStreamSourceID.ref() != info->GetID())) {
         LOG("%s stream id has changed from:%d to:%d, draining decoder.",
           TrackTypeToStr(aTrack), decoder.mLastStreamSourceID,
           info->GetID());
         decoder.RequestDrain();
         decoder.mNextStreamSourceID = Some(info->GetID());
         ScheduleUpdate(aTrack);
@@ -2189,26 +2190,16 @@ MediaFormatReader::Update(TrackType aTra
     return;
   }
 
   if (aTrack == TrackType::kVideoTrack && mSkipRequest.Exists()) {
     LOGV("Skipping in progress, nothing more to do");
     return;
   }
 
-  if (decoder.HasWaitingPromise() && decoder.HasCompletedDrain()) {
-    // This situation will occur when a change of stream ID occurred during
-    // internal seeking following a gap encountered in the data, a drain was
-    // requested and has now completed. We need to complete the draining process
-    // so that the new data can be processed.
-    // We can complete the draining operation now as we have no pending
-    // operation when a waiting promise is pending.
-    decoder.mDrainState = DrainState::None;
-  }
-
   if (UpdateReceivedNewData(aTrack)) {
     LOGV("Nothing more to do");
     return;
   }
 
   if (decoder.mSeekRequest.Exists()) {
     LOGV("Seeking hasn't completed, nothing more to do");
     return;
@@ -2384,31 +2375,33 @@ MediaFormatReader::Update(TrackType aTra
       // We can't recover from this error.
       NotifyError(aTrack, NS_ERROR_DOM_MEDIA_FATAL_ERR);
     }
     return;
   }
 
   bool needInput = NeedInput(decoder);
 
-  LOGV(
-    "Update(%s) ni=%d no=%d in:%" PRIu64 " out:%" PRIu64
-    " qs=%u decoding:%d flushing:%d desc:%s pending:%u waiting:%d sid:%u",
-    TrackTypeToStr(aTrack),
-    needInput,
-    needOutput,
-    decoder.mNumSamplesInput,
-    decoder.mNumSamplesOutput,
-    uint32_t(size_t(decoder.mSizeOfQueue)),
-    decoder.mDecodeRequest.Exists(),
-    decoder.mFlushing,
-    decoder.mDescription,
-    uint32_t(decoder.mOutput.Length()),
-    decoder.mWaitingForData,
-    decoder.mLastStreamSourceID);
+  LOGV("Update(%s) ni=%d no=%d in:%" PRIu64 " out:%" PRIu64
+       " qs=%u decoding:%d flushing:%d desc:%s pending:%u waiting:%d eos:%d "
+       "ds:%d sid:%u",
+       TrackTypeToStr(aTrack),
+       needInput,
+       needOutput,
+       decoder.mNumSamplesInput,
+       decoder.mNumSamplesOutput,
+       uint32_t(size_t(decoder.mSizeOfQueue)),
+       decoder.mDecodeRequest.Exists(),
+       decoder.mFlushing,
+       decoder.mDescription,
+       uint32_t(decoder.mOutput.Length()),
+       decoder.mWaitingForData,
+       decoder.mDemuxEOS,
+       int32_t(decoder.mDrainState),
+       decoder.mLastStreamSourceID);
 
   if ((decoder.mWaitingForData
        && (!decoder.mTimeThreshold || decoder.mTimeThreshold.ref().mWaiting))
       || (decoder.mWaitingForKey && decoder.mDecodeRequest.Exists())) {
     // Nothing more we can do at present.
     LOGV("Still waiting for data or key.");
     return;
   }
@@ -3068,61 +3061,65 @@ MediaFormatReader::GetMozDebugReaderData
 
   result += nsPrintfCString("Audio Decoder: %s\n", audioName);
   result += nsPrintfCString("Audio Frames Decoded: %" PRIu64 "\n",
                             mAudio.mNumSamplesOutputTotal);
   if (HasAudio()) {
     result += nsPrintfCString(
       "Audio State: ni=%d no=%d wp=%d demuxr=%d demuxq=%u decoder=%d tt=%.1f "
       "tths=%d in=%" PRIu64 " out=%" PRIu64
-      " qs=%u pending=%u wfd=%d wfk=%d sid=%u\n",
+      " qs=%u pending=%u wfd=%d eos=%d ds=%d wfk=%d sid=%u\n",
       NeedInput(mAudio),
       mAudio.HasPromise(),
       !mAudio.mWaitingPromise.IsEmpty(),
       mAudio.mDemuxRequest.Exists(),
       uint32_t(mAudio.mQueuedSamples.Length()),
       mAudio.mDecodeRequest.Exists(),
       mAudio.mTimeThreshold ? mAudio.mTimeThreshold.ref().Time().ToSeconds()
                             : -1.0,
       mAudio.mTimeThreshold ? mAudio.mTimeThreshold.ref().mHasSeeked : -1,
       mAudio.mNumSamplesInput,
       mAudio.mNumSamplesOutput,
       unsigned(size_t(mAudio.mSizeOfQueue)),
       unsigned(mAudio.mOutput.Length()),
       mAudio.mWaitingForData,
+      mAudio.mDemuxEOS,
+      int32_t(mAudio.mDrainState),
       mAudio.mWaitingForKey,
       mAudio.mLastStreamSourceID);
   }
   result += nsPrintfCString("Video Decoder: %s\n", videoName);
   result +=
     nsPrintfCString("Hardware Video Decoding: %s\n",
                     VideoIsHardwareAccelerated() ? "enabled" : "disabled");
   result +=
     nsPrintfCString("Video Frames Decoded: %" PRIu64 " (skipped=%" PRIu64 ")\n",
                     mVideo.mNumSamplesOutputTotal,
                     mVideo.mNumSamplesSkippedTotal);
   if (HasVideo()) {
     result += nsPrintfCString(
       "Video State: ni=%d no=%d wp=%d demuxr=%d demuxq=%u decoder=%d tt=%.1f "
       "tths=%d in=%" PRIu64 " out=%" PRIu64
-      " qs=%u pending:%u wfd=%d wfk=%d sid=%u\n",
+      " qs=%u pending:%u wfd=%d eos=%d ds=%d wfk=%d sid=%u\n",
       NeedInput(mVideo),
       mVideo.HasPromise(),
       !mVideo.mWaitingPromise.IsEmpty(),
       mVideo.mDemuxRequest.Exists(),
       uint32_t(mVideo.mQueuedSamples.Length()),
       mVideo.mDecodeRequest.Exists(),
       mVideo.mTimeThreshold ? mVideo.mTimeThreshold.ref().Time().ToSeconds()
                             : -1.0,
       mVideo.mTimeThreshold ? mVideo.mTimeThreshold.ref().mHasSeeked : -1,
       mVideo.mNumSamplesInput,
       mVideo.mNumSamplesOutput,
       unsigned(size_t(mVideo.mSizeOfQueue)),
       unsigned(mVideo.mOutput.Length()),
       mVideo.mWaitingForData,
+      mVideo.mDemuxEOS,
+      int32_t(mVideo.mDrainState),
       mVideo.mWaitingForKey,
       mVideo.mLastStreamSourceID);
   }
   aString += result;
 }
 
 void
 MediaFormatReader::SetVideoNullDecode(bool aIsNullDecode)
--- a/dom/media/mediasource/MediaSource.cpp
+++ b/dom/media/mediasource/MediaSource.cpp
@@ -7,16 +7,17 @@
 #include "MediaSource.h"
 
 #include "AsyncEventRunner.h"
 #include "DecoderTraits.h"
 #include "Benchmark.h"
 #include "DecoderDoctorDiagnostics.h"
 #include "MediaContainerType.h"
 #include "MediaResult.h"
+#include "MediaSourceDemuxer.h"
 #include "MediaSourceUtils.h"
 #include "SourceBuffer.h"
 #include "SourceBufferList.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/HTMLMediaElement.h"
@@ -250,31 +251,59 @@ MediaSource::AddSourceBuffer(const nsASt
     aRv.Throw(NS_ERROR_FAILURE); // XXX need a better error here
     return nullptr;
   }
   mSourceBuffers->Append(sourceBuffer);
   MSE_DEBUG("sourceBuffer=%p", sourceBuffer.get());
   return sourceBuffer.forget();
 }
 
-void
+RefPtr<MediaSource::ActiveCompletionPromise>
 MediaSource::SourceBufferIsActive(SourceBuffer* aSourceBuffer)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mActiveSourceBuffers->ClearSimple();
+  bool initMissing = false;
   bool found = false;
   for (uint32_t i = 0; i < mSourceBuffers->Length(); i++) {
     SourceBuffer* sourceBuffer = mSourceBuffers->IndexedGetter(i, found);
     MOZ_ALWAYS_TRUE(found);
     if (sourceBuffer == aSourceBuffer) {
       mActiveSourceBuffers->Append(aSourceBuffer);
     } else if (sourceBuffer->IsActive()) {
       mActiveSourceBuffers->AppendSimple(sourceBuffer);
+    } else {
+      // Some source buffers haven't yet received an init segment.
+      // There's nothing more we can do at this stage.
+      initMissing = true;
     }
   }
+  if (initMissing || !mDecoder) {
+    return ActiveCompletionPromise::CreateAndResolve(true, __func__);
+  }
+
+  mDecoder->NotifyInitDataArrived();
+
+  // Add our promise to the queue.
+  // It will be resolved once the HTMLMediaElement modifies its readyState.
+  MozPromiseHolder<ActiveCompletionPromise> holder;
+  RefPtr<ActiveCompletionPromise> promise = holder.Ensure(__func__);
+  mCompletionPromises.AppendElement(Move(holder));
+  return promise;
+}
+
+void
+MediaSource::CompletePendingTransactions()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MSE_DEBUG("Resolving %u promises", unsigned(mCompletionPromises.Length()));
+  for (auto& promise : mCompletionPromises) {
+    promise.Resolve(true, __func__);
+  }
+  mCompletionPromises.Clear();
 }
 
 void
 MediaSource::RemoveSourceBuffer(SourceBuffer& aSourceBuffer, ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
   SourceBuffer* sourceBuffer = &aSourceBuffer;
   MSE_API("RemoveSourceBuffer(aSourceBuffer=%p)", sourceBuffer);
@@ -428,16 +457,17 @@ MediaSource::Attach(MediaSourceDecoder* 
   SetReadyState(MediaSourceReadyState::Open);
   return true;
 }
 
 void
 MediaSource::Detach()
 {
   MOZ_ASSERT(NS_IsMainThread());
+  MOZ_RELEASE_ASSERT(mCompletionPromises.IsEmpty());
   MSE_DEBUG("mDecoder=%p owner=%p",
             mDecoder.get(), mDecoder ? mDecoder->GetOwner() : nullptr);
   if (!mDecoder) {
     MOZ_ASSERT(mReadyState == MediaSourceReadyState::Closed);
     MOZ_ASSERT(mActiveSourceBuffers->IsEmpty() && mSourceBuffers->IsEmpty());
     return;
   }
   mMediaElement = nullptr;
--- a/dom/media/mediasource/MediaSource.h
+++ b/dom/media/mediasource/MediaSource.h
@@ -7,16 +7,17 @@
 #ifndef mozilla_dom_MediaSource_h_
 #define mozilla_dom_MediaSource_h_
 
 #include "MediaSourceDecoder.h"
 #include "js/RootingAPI.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/MozPromise.h"
 #include "mozilla/dom/MediaSourceBinding.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionNoteChild.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsID.h"
 #include "nsISupports.h"
 #include "nscore.h"
 #include "TimeUnits.h"
@@ -114,16 +115,19 @@ public:
     return mLiveSeekableRange.value();
   }
 
   AbstractThread* AbstractMainThread() const
   {
     return mAbstractMainThread;
   }
 
+  // Resolve all CompletionPromise pending.
+  void CompletePendingTransactions();
+
 private:
   // SourceBuffer uses SetDuration and SourceBufferIsActive
   friend class mozilla::dom::SourceBuffer;
 
   ~MediaSource();
 
   explicit MediaSource(nsPIDOMWindowInner* aWindow);
 
@@ -131,34 +135,41 @@ private:
   void DispatchSimpleEvent(const char* aName);
   void QueueAsyncSimpleEvent(const char* aName);
 
   void DurationChange(double aNewDuration, ErrorResult& aRv);
 
   // SetDuration with no checks.
   void SetDuration(double aDuration);
 
+  typedef MozPromise<bool, MediaResult, /* IsExclusive = */ true>
+    ActiveCompletionPromise;
   // Mark SourceBuffer as active and rebuild ActiveSourceBuffers.
-  void SourceBufferIsActive(SourceBuffer* aSourceBuffer);
+  // Return a MozPromise that will be resolved once all related operations are
+  // completed, or can't progress any further.
+  // Such as, transition of readyState from HAVE_NOTHING to HAVE_METADATA.
+  RefPtr<ActiveCompletionPromise> SourceBufferIsActive(
+    SourceBuffer* aSourceBuffer);
 
   RefPtr<SourceBufferList> mSourceBuffers;
   RefPtr<SourceBufferList> mActiveSourceBuffers;
 
   RefPtr<MediaSourceDecoder> mDecoder;
   // Ensures the media element remains alive to dispatch progress and
   // durationchanged events.
   RefPtr<HTMLMediaElement> mMediaElement;
 
   RefPtr<nsIPrincipal> mPrincipal;
 
   const RefPtr<AbstractThread> mAbstractMainThread;
 
   MediaSourceReadyState mReadyState;
 
   Maybe<media::TimeInterval> mLiveSeekableRange;
+  nsTArray<MozPromiseHolder<ActiveCompletionPromise>> mCompletionPromises;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(MediaSource, MOZILLA_DOM_MEDIASOURCE_IMPLEMENTATION_IID)
 
 } // namespace dom
 
 } // namespace mozilla
 
--- a/dom/media/mediasource/MediaSourceDemuxer.cpp
+++ b/dom/media/mediasource/MediaSourceDemuxer.cpp
@@ -197,21 +197,24 @@ void
 MediaSourceDemuxer::DoDetachSourceBuffer(TrackBuffersManager* aSourceBuffer)
 {
   MOZ_ASSERT(OnTaskQueue());
   for (uint32_t i = 0; i < mSourceBuffers.Length(); i++) {
     if (mSourceBuffers[i].get() == aSourceBuffer) {
       mSourceBuffers.RemoveElementAt(i);
     }
   }
-  if (aSourceBuffer == mAudioTrack) {
-    mAudioTrack = nullptr;
-  }
-  if (aSourceBuffer == mVideoTrack) {
-    mVideoTrack = nullptr;
+  {
+    MonitorAutoLock mon(mMonitor);
+    if (aSourceBuffer == mAudioTrack) {
+      mAudioTrack = nullptr;
+    }
+    if (aSourceBuffer == mVideoTrack) {
+      mVideoTrack = nullptr;
+    }
   }
   ScanSourceBuffersForContent();
 }
 
 TrackInfo*
 MediaSourceDemuxer::GetTrackInfo(TrackType aTrack)
 {
   MonitorAutoLock mon(mMonitor);
--- a/dom/media/mediasource/SourceBuffer.cpp
+++ b/dom/media/mediasource/SourceBuffer.cpp
@@ -207,16 +207,17 @@ SourceBuffer::Abort(ErrorResult& aRv)
   mCurrentAttributes.SetAppendWindowStart(0);
   mCurrentAttributes.SetAppendWindowEnd(PositiveInfinity<double>());
 }
 
 void
 SourceBuffer::AbortBufferAppend()
 {
   if (mUpdating) {
+    mCompletionPromise.DisconnectIfExists();
     if (mPendingAppend.Exists()) {
       mPendingAppend.Disconnect();
       mTrackBuffersManager->AbortAppendData();
     }
     AbortUpdating();
   }
 }
 
@@ -428,32 +429,42 @@ void
 SourceBuffer::AppendDataCompletedWithSuccess(const SourceBufferTask::AppendBufferResult& aResult)
 {
   MOZ_ASSERT(mUpdating);
   mPendingAppend.Complete();
 
   if (aResult.first()) {
     if (!mActive) {
       mActive = true;
-      mMediaSource->SourceBufferIsActive(this);
-      mMediaSource->GetDecoder()->NotifyInitDataArrived();
+      MSE_DEBUG("Init segment received");
+      RefPtr<SourceBuffer> self = this;
+      mMediaSource->SourceBufferIsActive(this)
+        ->Then(mAbstractMainThread, __func__,
+               [self, this]() {
+                 MSE_DEBUG("Complete AppendBuffer operation");
+                 mCompletionPromise.Complete();
+                 StopUpdating();
+               })
+        ->Track(mCompletionPromise);
     }
   }
   if (mActive) {
     // Tell our parent decoder that we have received new data.
     mMediaSource->GetDecoder()->NotifyDataArrived();
     // Send progress event.
     mMediaSource->GetDecoder()->NotifyBytesDownloaded();
   }
 
   mCurrentAttributes = aResult.second();
 
   CheckEndTime();
 
-  StopUpdating();
+  if (!mCompletionPromise.Exists()) {
+    StopUpdating();
+  }
 }
 
 void
 SourceBuffer::AppendDataErrored(const MediaResult& aError)
 {
   MOZ_ASSERT(mUpdating);
   mPendingAppend.Complete();
 
--- a/dom/media/mediasource/SourceBuffer.h
+++ b/dom/media/mediasource/SourceBuffer.h
@@ -177,15 +177,17 @@ private:
 
   mozilla::Atomic<bool> mActive;
 
   MozPromiseRequestHolder<SourceBufferTask::AppendPromise> mPendingAppend;
   MozPromiseRequestHolder<SourceBufferTask::RangeRemovalPromise> mPendingRemoval;
   const MediaContainerType mType;
 
   RefPtr<TimeRanges> mBuffered;
+
+  MozPromiseRequestHolder<MediaSource::ActiveCompletionPromise> mCompletionPromise;
 };
 
 } // namespace dom
 
 } // namespace mozilla
 
 #endif /* mozilla_dom_SourceBuffer_h_ */
--- a/dom/media/mediasource/test/test_AVC3_mp4.html
+++ b/dom/media/mediasource/test/test_AVC3_mp4.html
@@ -14,21 +14,23 @@ SimpleTest.waitForExplicitFinish();
 
 runWithMSE(function(ms, el) {
 
   once(ms, 'sourceopen').then(function() {
     ok(true, "Receive a sourceopen event");
     var videosb = ms.addSourceBuffer("video/mp4");
 
     fetchAndLoad(videosb, 'avc3/init', [''], '.mp4')
-    .then(fetchAndLoad.bind(null, videosb, 'avc3/segment', range(1, 2), '.m4s'))
     .then(function() {
+      var promises = [];
+      promises.push(fetchAndLoad(videosb, 'avc3/segment', range(1, 2), '.m4s'));
+      promises.push(once(el, "loadeddata"));
+      return Promise.all(promises);
+    }).then(function() {
       is(videosb.buffered.length, 1, "continuous buffered range");
-      return once(el, "loadeddata");
-    }).then(function() {
       ok(true, "got loadeddata");
       ms.endOfStream();
       return once(ms, "sourceended");
     }).then(function() {
       ok(true, "endOfStream completed");
       // Now ensure that we can play to the end.
       el.play();
       once(el, 'ended').then(SimpleTest.finish.bind(SimpleTest));
--- a/dom/media/mediasource/test/test_AudioChange_mp4.html
+++ b/dom/media/mediasource/test/test_AudioChange_mp4.html
@@ -31,18 +31,20 @@ runWithMSE(function(ms, el) {
 
     ok(true, "Receive a sourceopen event");
     var audiosb = ms.addSourceBuffer("audio/mp4");
     el.addEventListener("error", function(e) {
       ok(false, "should not fire '" + e.type + "' event");
       SimpleTest.finish();
     });
     is(el.readyState, el.HAVE_NOTHING, "readyState is HAVE_NOTHING");
-    fetchAndLoad(audiosb, 'aac20-48000-64000-', ['init'], '.mp4')
-    .then(once.bind(null, el, 'loadedmetadata'))
+    var promises = [];
+    promises.push(fetchAndLoad(audiosb, 'aac20-48000-64000-', ['init'], '.mp4'));
+    promises.push(once(el, 'loadedmetadata'));
+    Promise.all(promises)
     .then(function() {
       ok(true, "got loadedmetadata event");
       var promises = [];
       promises.push(once(el, 'loadeddata'));
       promises.push(once(el, 'canplay'));
       promises.push(fetchAndLoad(audiosb, 'aac20-48000-64000-', ['1'], '.m4s'));
       return Promise.all(promises);
     })
--- a/dom/media/mediasource/test/test_BufferedSeek.html
+++ b/dom/media/mediasource/test/test_BufferedSeek.html
@@ -7,44 +7,29 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-var updateCount = 0;
-
 runWithMSE(function (ms, v) {
   ms.addEventListener("sourceopen", function () {
     var sb = ms.addSourceBuffer("video/webm");
 
     fetchWithXHR("seek.webm", function (arrayBuffer) {
       sb.appendBuffer(new Uint8Array(arrayBuffer));
-      sb.addEventListener("updateend", function () {
-        updateCount++;
-        /* Ensure that we endOfStream on the first update event only as endOfStream can
-           raise more if the duration of the last buffered range and the intial duration
-           differ. See bug 1065207 */
-        if (updateCount == 1) {
-          ms.endOfStream();
-        };
-      });
     });
 
     var target = 2;
 
     v.addEventListener("loadedmetadata", function () {
-      if (v.currentTime != target &&
-          v.buffered.length &&
-          target >= v.buffered.start(0) &&
-          target < v.buffered.end(0)) {
-        v.currentTime = target;
-      }
+      ok(true, "received loadedmetadata");
+      v.currentTime = target;
     });
 
     var wasSeeking = false;
 
     v.addEventListener("seeking", function () {
       wasSeeking = true;
       is(v.currentTime, target, "Video currentTime at target");
     });
--- a/dom/media/mediasource/test/test_BufferedSeek_mp4.html
+++ b/dom/media/mediasource/test/test_BufferedSeek_mp4.html
@@ -7,44 +7,29 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-var updateCount = 0;
-
 runWithMSE(function (ms, v) {
   ms.addEventListener("sourceopen", function () {
     var sb = ms.addSourceBuffer("video/mp4");
 
     fetchWithXHR("bipbop/bipbop2s.mp4", function (arrayBuffer) {
       sb.appendBuffer(new Uint8Array(arrayBuffer));
-      sb.addEventListener("updateend", function () {
-        updateCount++;
-        /* Ensure that we endOfStream on the first update event only as endOfStream can
-           raise more if the duration of the last buffered range and the intial duration
-           differ. See bug 1065207 */
-        if (updateCount == 1) {
-          ms.endOfStream();
-        };
-      });
     });
 
     var target = 1.3;
 
     v.addEventListener("loadedmetadata", function () {
-      if (v.currentTime != target &&
-          v.buffered.length &&
-          target >= v.buffered.start(0) &&
-          target < v.buffered.end(0)) {
-        v.currentTime = target;
-      }
+      ok(true, "received loadedmetadata");
+      v.currentTime = target;
     });
 
     var wasSeeking = false;
 
     v.addEventListener("seeking", function () {
       wasSeeking = true;
       is(v.currentTime, target, "Video currentTime at target");
     });
--- a/dom/media/mediasource/test/test_DurationChange.html
+++ b/dom/media/mediasource/test/test_DurationChange.html
@@ -13,17 +13,20 @@
 SimpleTest.waitForExplicitFinish();
 
 runWithMSE(function (ms, v) {
   ms.addEventListener("sourceopen", function () {
     var sb = ms.addSourceBuffer("video/webm");
 
     fetchWithXHR("seek.webm", function (arrayBuffer) {
       sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 318));
-      once(v, "loadedmetadata")
+      var promises = [];
+      promises.push(once(v, "loadedmetadata"));
+      promises.push(once(sb, "updateend"));
+      Promise.all(promises)
       .then(function() {
         is(v.duration, ms.duration, "video duration is mediasource one");
         try {
           ms.duration = 0;
         } catch (e) { ok(false, "must not throw as operation is valid"); }
         is(v.duration, 0, "reducing duration with no data buffered is valid");
         sb.appendBuffer(new Uint8Array(arrayBuffer, 318));
         // Adding more data will fire durationchange.
--- a/dom/media/mediasource/test/test_DurationUpdated.html
+++ b/dom/media/mediasource/test/test_DurationUpdated.html
@@ -20,17 +20,20 @@ runWithMSE(function (ms, v) {
 
     v.addEventListener("durationchange", function () {
       durationChangeCount++;
     });
 
     fetchWithXHR("seek.webm", function (arrayBuffer) {
       sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 318));
       // Adding the first init segment will fire a durationchange.
-      once(v, "loadedmetadata")
+      var promises = [];
+      promises.push(once(sb, "updateend"));
+      promises.push(once(v, "loadedmetadata"));
+      Promise.all(promises)
       .then(function() {
         ok(true, "got loadedmetadata");
         // Set mediasource duration to 0, so future appendBuffer
         // will update the mediasource duration.
         // Changing the duration will fire a durationchange.
         ms.duration = 0;
         sb.appendBuffer(new Uint8Array(arrayBuffer, 318));
         // Adding more data will fire durationchange.
--- a/dom/media/mediasource/test/test_DurationUpdated_mp4.html
+++ b/dom/media/mediasource/test/test_DurationUpdated_mp4.html
@@ -20,17 +20,20 @@ runWithMSE(function (ms, v) {
 
     v.addEventListener("durationchange", function () {
       durationChangeCount++;
     });
 
     fetchWithXHR("bipbop/bipbop2s.mp4", function (arrayBuffer) {
       sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 1395));
       // Adding the first init segment will fire a durationchange.
-      once(v, "loadedmetadata")
+      var promises = [];
+      promises.push(once(sb, "updateend"));
+      promises.push(once(v, "loadedmetadata"));
+      Promise.all(promises)
       .then(function() {
         ok(true, "got loadedmetadata");
         // Set mediasource duration to 0, so future appendBuffer
         // will update the mediasource duration.
         // Changing the duration will fire a durationchange.
         ms.duration = 0;
         sb.appendBuffer(new Uint8Array(arrayBuffer, 1395));
         // Adding more data will fire durationchange.
--- a/dom/media/mediasource/test/test_EndedEvent.html
+++ b/dom/media/mediasource/test/test_EndedEvent.html
@@ -15,17 +15,17 @@ SimpleTest.waitForExplicitFinish();
 runWithMSE(function(ms, el) {
   once(ms, 'sourceopen').then(function() {
     var sb = ms.addSourceBuffer("video/webm");
     fetchWithXHR("seek.webm", (buf) => sb.appendBuffer(new Uint8Array(buf)));
     sb.addEventListener("updateend", () => ms.endOfStream());
 
     // Test 'ended' is fired when seeking to the end of the media
     // once the duration is known.
-    el.onloadedmetadata = () => {
+    ms.onsourceended = () => {
       el.currentTime = el.duration;
     };
     el.addEventListener("ended", SimpleTest.finish.bind(null));
   });
 });
 
 </script>
 </pre>
--- a/dom/media/mediasource/test/test_LoadedDataFired_mp4.html
+++ b/dom/media/mediasource/test/test_LoadedDataFired_mp4.html
@@ -23,18 +23,20 @@ runWithMSE(function(ms, el) {
     ok(el.buffered.length > 0, "data is buffered");
     is(el.buffered.start(0), 0, "must fire playing when data has been loaded");
     ok(el.currentTime >= 0, "must have started playback");
   });
   once(ms, 'sourceopen').then(function() {
     ok(true, "Receive a sourceopen event");
     var videosb = ms.addSourceBuffer("video/mp4");
     is(el.readyState, el.HAVE_NOTHING, "readyState is HAVE_NOTHING");
-    fetchAndLoad(videosb, 'bipbop/bipbop_video', ['init'], '.mp4')
-    .then(once.bind(null, el, "loadedmetadata"))
+    var promises = [];
+    promises.push(fetchAndLoad(videosb, 'bipbop/bipbop_video', ['init'], '.mp4'));
+    promises.push(once(el, "loadedmetadata"));
+    Promise.all(promises)
     .then(function() {
       videosb.appendWindowStart = 2;
       videosb.appendWindowEnd = 4;
       is(el.readyState, el.HAVE_METADATA, "readyState is HAVE_METADATA");
       // Load [2.4, 3.968344). 2.4 as it's the first keyframe after 2s and
       // 3.968344 as the last frame ends after 4s.
       return fetchAndLoad(videosb, 'bipbop/bipbop_video', range(1, 8), '.m4s');
     })
--- a/dom/media/mediasource/test/test_MediaSource_memory_reporting.html
+++ b/dom/media/mediasource/test/test_MediaSource_memory_reporting.html
@@ -9,17 +9,17 @@
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
 runWithMSE(function (ms, v) {
   // Test that memory reporting works once we've played a video.
-  once(v, "stalled", () => {
+  once(v, "ended", () => {
     // Grab a memory report.
     var mgr = SpecialPowers.Cc["@mozilla.org/memory-reporter-manager;1"]
           .getService(SpecialPowers.Ci.nsIMemoryReporterManager);
 
     var amount = 0;
     var resourcesPathSeen = false;
     var handleReport = function(aProcess, aPath, aKind, aUnits, aAmount, aDesc) {
       if (aPath == "explicit/media/resources") {
@@ -36,16 +36,19 @@ runWithMSE(function (ms, v) {
     }
 
     mgr.getReports(handleReport, null, finished, null, /* anonymized = */ false);
   });
 
   // Load a webm video and play it.
   ms.addEventListener("sourceopen", () => {
     var sb = ms.addSourceBuffer("video/webm");
-    fetchAndLoad(sb, 'seek', [''], '.webm').then(() => v.play());
+    fetchAndLoad(sb, 'seek', [''], '.webm').then(function() {
+      ms.endOfStream();
+      v.play()
+    });
   });
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_PlayEvents.html
+++ b/dom/media/mediasource/test/test_PlayEvents.html
@@ -37,18 +37,20 @@ runWithMSE(function(ms, el) {
     });
 
     ok(true, "Receive a sourceopen event");
     var videosb = ms.addSourceBuffer("video/mp4");
     el.addEventListener("error", function(e) {
       ok(false, "should not fire '" + e + "' event");
     });
     is(el.readyState, el.HAVE_NOTHING, "readyState is HAVE_NOTHING");
-    fetchAndLoad(videosb, 'bipbop/bipbop_video', ['init'], '.mp4')
-    .then(once.bind(null, el, 'loadedmetadata'))
+    var promises = [];
+    promises.push(fetchAndLoad(videosb, 'bipbop/bipbop_video', ['init'], '.mp4'));
+    promises.push(once(el, 'loadedmetadata'));
+    Promise.all(promises)
     .then(function() {
        ok(true, "got loadedmetadata event");
        var promises = [];
        promises.push(once(el, 'loadeddata'));
        promises.push(once(el, 'canplay'));
        promises.push(fetchAndLoad(videosb, 'bipbop/bipbop_video', range(1, 3), '.m4s'));
        return Promise.all(promises);
     })
--- a/dom/media/mediasource/test/test_SeekNoData_mp4.html
+++ b/dom/media/mediasource/test/test_SeekNoData_mp4.html
@@ -29,19 +29,21 @@ runWithMSE(function(ms, el) {
     is(el.readyState, el.HAVE_NOTHING, "readyState is HAVE_NOTHING");
     try {
       el.currentTime = 3;
     } catch (e) {
       ok(false, "should not throw '" + e + "' exception");
     }
     is(el.currentTime, 3, "currentTime is default playback start position");
     is(el.seeking, false, "seek not started with HAVE_NOTHING");
-    fetchAndLoad(audiosb, 'bipbop/bipbop_audio', ['init'], '.mp4')
-    .then(fetchAndLoad.bind(null, videosb, 'bipbop/bipbop_video', ['init'], '.mp4'))
-    .then(once.bind(null, el, 'loadedmetadata'))
+    var promises = [];
+    promises.push(fetchAndLoad(audiosb, 'bipbop/bipbop_audio', ['init'], '.mp4'));
+    promises.push(fetchAndLoad(videosb, 'bipbop/bipbop_video', ['init'], '.mp4'));
+    promises.push(once(el, 'loadedmetadata'))
+    Promise.all(promises)
     .then(function() {
       var p = once(el, 'seeking');
       el.play();
       el.currentTime = 5;
       is(el.readyState, el.HAVE_METADATA, "readyState is HAVE_METADATA");
       is(el.seeking, true, "seek not started with HAVE_METADATA");
       is(el.currentTime, 5, "currentTime is seek position");
       return p;
--- a/dom/media/mediasource/test/test_SeekableAfterEndOfStream.html
+++ b/dom/media/mediasource/test/test_SeekableAfterEndOfStream.html
@@ -7,43 +7,35 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-var updateCount = 0;
-
 runWithMSE(function (ms, v) {
   ms.addEventListener("sourceopen", function () {
     var sb = ms.addSourceBuffer("video/webm");
 
     fetchWithXHR("seek.webm", function (arrayBuffer) {
       sb.appendBuffer(new Uint8Array(arrayBuffer));
-      sb.addEventListener("updateend", function () {
-        updateCount++;
-        /* Ensure that we endOfStream on the first update event only as endOfStream can
-           raise more if the duration of the last buffered range and the intial duration
-           differ. See bug 1065207 */
-        if (updateCount == 1) {
-          ms.endOfStream();
-        };
+      var promises = [];
+      promises.push(once(sb, "updateend"));
+      promises.push(once(v, "loadedmetadata"));
+      Promise.all(promises).then(function () {
+        ms.endOfStream();
+        once(ms, "sourceended").then(function() {
+          var target = 2;
+          ok(v.seekable.length, "Resource is seekable");
+          ok(v.seekable.length &&
+             target >= v.seekable.start(0) &&
+             target < v.seekable.end(0), "Target is within seekable range");
+          SimpleTest.finish();
+        });
       });
     });
-
-    var target = 2;
-
-    v.addEventListener("loadedmetadata", function () {
-      ok(v.seekable.length, "Resource is seekable");
-      ok(v.seekable.length &&
-          target >= v.seekable.start(0) &&
-          target < v.seekable.end(0), "Target is within seekable range");
-      SimpleTest.finish();
-    });
   });
 });
-
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_SeekableAfterEndOfStream_mp4.html
+++ b/dom/media/mediasource/test/test_SeekableAfterEndOfStream_mp4.html
@@ -7,43 +7,36 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-var updateCount = 0;
-
 runWithMSE(function (ms, v) {
   ms.addEventListener("sourceopen", function () {
     var sb = ms.addSourceBuffer("video/mp4");
 
     fetchWithXHR("bipbop/bipbop2s.mp4", function (arrayBuffer) {
       sb.appendBuffer(new Uint8Array(arrayBuffer));
-      sb.addEventListener("updateend", function () {
-        updateCount++;
-        /* Ensure that we endOfStream on the first update event only as endOfStream can
-           raise more if the duration of the last buffered range and the intial duration
-           differ. See bug 1065207 */
-        if (updateCount == 1) {
-          ms.endOfStream();
-        };
+      var promises = [];
+      promises.push(once(sb, "updateend"));
+      promises.push(once(v, "loadedmetadata"));
+      Promise.all(promises).then(function () {
+        ms.endOfStream();
+        once(ms, "sourceended").then(function() {
+          var target = 1.3;
+          ok(v.seekable.length, "Resource is seekable");
+          ok(v.seekable.length &&
+             target >= v.seekable.start(0) &&
+             target < v.seekable.end(0), "Target is within seekable range");
+          SimpleTest.finish();
+        });
       });
     });
-
-    var target = 1.3;
-
-    v.addEventListener("loadedmetadata", function () {
-      ok(v.seekable.length, "Resource is seekable");
-      ok(v.seekable.length &&
-          target >= v.seekable.start(0) &&
-          target < v.seekable.end(0), "Target is within seekable range");
-      SimpleTest.finish();
-    });
   });
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_SeekedEvent_mp4.html
+++ b/dom/media/mediasource/test/test_SeekedEvent_mp4.html
@@ -34,33 +34,36 @@ runWithMSE(function(ms, el) {
     is(el._seeked, true, "must have received seeked prior playing");
     is(el._loadeddata, true, "must have received loadeddata prior playing");
     el._playing = true;
   });
   once(ms, 'sourceopen').then(function() {
     ok(true, "Receive a sourceopen event");
     var videosb = ms.addSourceBuffer("video/mp4");
     is(el.readyState, el.HAVE_NOTHING, "readyState is HAVE_NOTHING");
-    fetchAndLoad(videosb, 'bipbop/bipbop_video', ['init'], '.mp4')
-    .then(once.bind(null, el, "loadedmetadata"))
+    var promises = [];
+    promises.push(fetchAndLoad(videosb, 'bipbop/bipbop_video', ['init'], '.mp4'));
+    promises.push(once(el, "loadedmetadata"));
+    Promise.all(promises)
     .then(function() {
       el.play();
       videosb.timestampOffset = 2;
       is(el.readyState, el.HAVE_METADATA, "readyState is HAVE_METADATA");
       // Load [2, 3.606).
       var promises = [];
       promises.push(once(el, "play"));
       promises.push(fetchAndLoad(videosb, 'bipbop/bipbop_video', ['1'], '.m4s'));
       return Promise.all(promises);
     })
     .then(function() {
       return fetchAndLoad(videosb, 'bipbop/bipbop_video', ['2'], '.m4s');
     })
     .then(function() {
-      is(el.readyState, el.HAVE_METADATA, "readyState is HAVE_METADATA");
+      // TODO: readyState should be at least HAVE_CURRENTDATA, see bug 1367993.
+      ok(el.readyState >= el.HAVE_METADATA, "readyState is HAVE_METADATA");
       el.currentTime = 2;
       var promises = [];
       promises.push(once(el, "seeked"));
       promises.push(once(el, "playing"));
       return Promise.all(promises);
     })
     .then(function() {
       ok(true, "completed seek");
--- a/dom/media/mediasource/test/test_TruncatedDuration.html
+++ b/dom/media/mediasource/test/test_TruncatedDuration.html
@@ -45,34 +45,30 @@ function do_seeked(e) {
     // test playback position was updated (bug 1130839).
     is(v.currentTime, v.duration, "current time was updated");
     is(v._sb.buffered.length, 1, "One buffered range");
     // Truncated mediasource duration will cause the video element to seek.
     v.addEventListener("seeking", do_seeking);
   });
 }
 
-function do_loaded(e) {
-  var v = e.target;
-  v.removeEventListener("loadeddata", do_loaded);
-  v.currentTime = v.duration / 2;
-  is(v.currentTime, v.duration / 2, "current time was updated");
-  ok(v.seeking, "seeking is true");
-  v.addEventListener("seeked", do_seeked);
-}
-
 runWithMSE(function (ms, v) {
   ms.addEventListener("sourceopen", function () {
     var sb = ms.addSourceBuffer("video/webm");
     v._sb = sb;
     v._ms = ms;
 
     fetchWithXHR("seek.webm", function (arrayBuffer) {
       sb.appendBuffer(new Uint8Array(arrayBuffer));
-      v.addEventListener("loadeddata", do_loaded);
+      once(sb, "updateend", function() {
+        v.currentTime = v.duration / 2;
+        is(v.currentTime, v.duration / 2, "current time was updated");
+        ok(v.seeking, "seeking is true");
+        v.addEventListener("seeked", do_seeked);
+      });
     });
   });
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/mediasource/test/test_TruncatedDuration_mp4.html
+++ b/dom/media/mediasource/test/test_TruncatedDuration_mp4.html
@@ -44,40 +44,36 @@ function do_seeked(e) {
     // test playback position was updated (bug 1130839).
     is(v.currentTime, v.duration, "current time was updated");
     is(v._sb.buffered.length, 1, "One buffered range");
     // Truncated mediasource duration will cause the video element to seek.
     v.addEventListener("seeking", do_seeking);
   });
 }
 
-function do_loaded(e) {
-  var v = e.target;
-  v.removeEventListener("loadeddata", do_loaded);
-  // mp4 metadata states 10s when we only have 1.6s worth of video.
-  v._sb.remove(v._sb.buffered.end(0), Infinity);
-  once(v._sb, "updateend", function() {
-    v._ms.duration = v._sb.buffered.end(0);
-    is(v.duration, v._ms.duration, "current time updated with mediasource duration");
-    v.currentTime = v.duration / 2;
-    is(v.currentTime, v.duration / 2, "current time was updated");
-    ok(v.seeking, "seeking is true");
-    v.addEventListener("seeked", do_seeked);
-  });
-}
-
 runWithMSE(function (ms, v) {
   ms.addEventListener("sourceopen", function () {
     var sb = ms.addSourceBuffer("video/mp4");
     v._sb = sb;
     v._ms = ms;
 
     fetchWithXHR("bipbop/bipbop2s.mp4", function (arrayBuffer) {
       sb.appendBuffer(new Uint8Array(arrayBuffer));
-      v.addEventListener("loadeddata", do_loaded);
+      once(sb, "updateend", function() {
+        // mp4 metadata states 10s when we only have 1.6s worth of video.
+        sb.remove(sb.buffered.end(0), Infinity);
+        once(sb, "updateend", function() {
+          ms.duration = sb.buffered.end(0);
+          is(v.duration, ms.duration, "current time updated with mediasource duration");
+          v.currentTime = v.duration / 2;
+          is(v.currentTime, v.duration / 2, "current time was updated");
+          ok(v.seeking, "seeking is true");
+          v.addEventListener("seeked", do_seeked);
+        });
+      });
     });
   });
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/webspeech/synth/nsSpeechTask.cpp
+++ b/dom/media/webspeech/synth/nsSpeechTask.cpp
@@ -778,15 +778,15 @@ nsSpeechTask::WindowAudioCaptureChanged(
 }
 
 void
 nsSpeechTask::SetAudioOutputVolume(float aVolume)
 {
   if (mStream && !mStream->IsDestroyed()) {
     mStream->SetAudioOutputVolume(this, aVolume);
   }
-  if (mIndirectAudio) {
+  if (mIndirectAudio && mCallback) {
     mCallback->OnVolumeChanged(aVolume);
   }
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -1440,26 +1440,25 @@ HTMLEditRules::WillInsertText(EditAction
     }
     aSelection->SetInterlinePosition(false);
     if (curNode) aSelection->Collapse(curNode, curOffset);
     // manually update the doc changed range so that AfterEdit will clean up
     // the correct portion of the document.
     if (!mDocChangeRange) {
       mDocChangeRange = new nsRange(selNode);
     }
-    rv = mDocChangeRange->SetStart(selNode, selOffset);
-    NS_ENSURE_SUCCESS(rv, rv);
 
     if (curNode) {
-      rv = mDocChangeRange->SetEnd(curNode, curOffset);
+      rv = mDocChangeRange->SetStartAndEnd(selNode, selOffset,
+                                           curNode, curOffset);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     } else {
-      rv = mDocChangeRange->SetEnd(selNode, selOffset);
+      rv = mDocChangeRange->CollapseTo(selNode, selOffset);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     }
   }
   return NS_OK;
 }
 
@@ -5245,20 +5244,21 @@ HTMLEditRules::ExpandSelectionForDeletio
   bool doEndExpansion = true;
   if (firstBRParent) {
     // Find block node containing br
     nsCOMPtr<Element> brBlock = HTMLEditor::GetBlock(*firstBRParent);
     bool nodeBefore = false, nodeAfter = false;
 
     // Create a range that represents expanded selection
     RefPtr<nsRange> range = new nsRange(selStartNode);
-    nsresult rv = range->SetStart(selStartNode, selStartOffset);
-    NS_ENSURE_SUCCESS(rv, rv);
-    rv = range->SetEnd(selEndNode, selEndOffset);
-    NS_ENSURE_SUCCESS(rv, rv);
+    nsresult rv = range->SetStartAndEnd(selStartNode, selStartOffset,
+                                        selEndNode, selEndOffset);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
 
     // Check if block is entirely inside range
     if (brBlock) {
       nsRange::CompareNodeToRange(brBlock, range, &nodeBefore, &nodeAfter);
     }
 
     // If block isn't contained, forgo grabbing the br in expanded selection
     if (nodeBefore || nodeAfter) {
@@ -5709,36 +5709,37 @@ HTMLEditRules::PromoteRange(nsRange& aRa
       }
     }
   }
 
   // Make a new adjusted range to represent the appropriate block content.
   // This is tricky.  The basic idea is to push out the range endpoints to
   // truly enclose the blocks that we will affect.
 
-  nsCOMPtr<nsIDOMNode> opStartNode;
-  nsCOMPtr<nsIDOMNode> opEndNode;
+  nsCOMPtr<nsIDOMNode> opDOMStartNode;
+  nsCOMPtr<nsIDOMNode> opDOMEndNode;
   int32_t opStartOffset, opEndOffset;
 
   GetPromotedPoint(kStart, GetAsDOMNode(startNode), startOffset,
-                   aOperationType, address_of(opStartNode), &opStartOffset);
+                   aOperationType, address_of(opDOMStartNode), &opStartOffset);
   GetPromotedPoint(kEnd, GetAsDOMNode(endNode), endOffset, aOperationType,
-                   address_of(opEndNode), &opEndOffset);
+                   address_of(opDOMEndNode), &opEndOffset);
 
   // Make sure that the new range ends up to be in the editable section.
   if (!htmlEditor->IsDescendantOfEditorRoot(
-        EditorBase::GetNodeAtRangeOffsetPoint(opStartNode, opStartOffset)) ||
+        EditorBase::GetNodeAtRangeOffsetPoint(opDOMStartNode, opStartOffset)) ||
       !htmlEditor->IsDescendantOfEditorRoot(
-        EditorBase::GetNodeAtRangeOffsetPoint(opEndNode, opEndOffset - 1))) {
+        EditorBase::GetNodeAtRangeOffsetPoint(opDOMEndNode, opEndOffset - 1))) {
     return;
   }
 
-  DebugOnly<nsresult> rv = aRange.SetStart(opStartNode, opStartOffset);
-  MOZ_ASSERT(NS_SUCCEEDED(rv));
-  rv = aRange.SetEnd(opEndNode, opEndOffset);
+  nsCOMPtr<nsINode> opStartNode = do_QueryInterface(opDOMStartNode);
+  nsCOMPtr<nsINode> opEndNode = do_QueryInterface(opDOMEndNode);
+  DebugOnly<nsresult> rv =
+    aRange.SetStartAndEnd(opStartNode, opStartOffset, opEndNode, opEndOffset);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 }
 
 class UniqueFunctor final : public BoolDomIterFunctor
 {
 public:
   explicit UniqueFunctor(nsTArray<OwningNonNull<nsINode>>& aArray)
     : mArray(aArray)
@@ -7395,20 +7396,20 @@ HTMLEditRules::PinSelectionToNewBlock(Se
     EditorBase::GetStartNodeAndOffset(aSelection,
                                       getter_AddRefs(selNode), &selOffset);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // use ranges and sRangeHelper to compare sel point to new block
   nsCOMPtr<nsINode> node = do_QueryInterface(selNode);
   NS_ENSURE_STATE(node);
   RefPtr<nsRange> range = new nsRange(node);
-  rv = range->SetStart(selNode, selOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = range->SetEnd(selNode, selOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
+  rv = range->CollapseTo(node, selOffset);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
   bool nodeBefore, nodeAfter;
   rv = nsRange::CompareNodeToRange(mNewBlock, range, &nodeBefore, &nodeAfter);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (nodeBefore && nodeAfter) {
     return NS_OK;  // selection is inside block
   } else if (nodeBefore) {
     // selection is after block.  put at end of block.
@@ -8293,20 +8294,23 @@ NS_IMETHODIMP
 HTMLEditRules::DidSplitNode(nsIDOMNode* aExistingRightNode,
                             int32_t aOffset,
                             nsIDOMNode* aNewLeftNode,
                             nsresult aResult)
 {
   if (!mListenerEnabled) {
     return NS_OK;
   }
-  nsresult rv = mUtilRange->SetStart(aNewLeftNode, 0);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = mUtilRange->SetEnd(aExistingRightNode, 0);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsINode> newLeftNode = do_QueryInterface(aNewLeftNode);
+  nsCOMPtr<nsINode> existingRightNode = do_QueryInterface(aExistingRightNode);
+  nsresult rv = mUtilRange->SetStartAndEnd(newLeftNode, 0,
+                                           existingRightNode, 0);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
   return UpdateDocChangeRange(mUtilRange);
 }
 
 NS_IMETHODIMP
 HTMLEditRules::WillJoinNodes(nsIDOMNode* aLeftNode,
                              nsIDOMNode* aRightNode,
                              nsIDOMNode* aParent)
 {
@@ -8321,21 +8325,22 @@ NS_IMETHODIMP
 HTMLEditRules::DidJoinNodes(nsIDOMNode* aLeftNode,
                             nsIDOMNode* aRightNode,
                             nsIDOMNode* aParent,
                             nsresult aResult)
 {
   if (!mListenerEnabled) {
     return NS_OK;
   }
+  nsCOMPtr<nsINode> rightNode = do_QueryInterface(aRightNode);
   // assumption that Join keeps the righthand node
-  nsresult rv = mUtilRange->SetStart(aRightNode, mJoinOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = mUtilRange->SetEnd(aRightNode, mJoinOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsresult rv = mUtilRange->CollapseTo(rightNode, mJoinOffset);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
   return UpdateDocChangeRange(mUtilRange);
 }
 
 NS_IMETHODIMP
 HTMLEditRules::WillInsertText(nsIDOMCharacterData* aTextNode,
                               int32_t aOffset,
                               const nsAString& aString)
 {
@@ -8347,21 +8352,22 @@ HTMLEditRules::DidInsertText(nsIDOMChara
                              int32_t aOffset,
                              const nsAString& aString,
                              nsresult aResult)
 {
   if (!mListenerEnabled) {
     return NS_OK;
   }
   int32_t length = aString.Length();
-  nsCOMPtr<nsIDOMNode> theNode = do_QueryInterface(aTextNode);
-  nsresult rv = mUtilRange->SetStart(theNode, aOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = mUtilRange->SetEnd(theNode, aOffset+length);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsINode> theNode = do_QueryInterface(aTextNode);
+  nsresult rv = mUtilRange->SetStartAndEnd(theNode, aOffset,
+                                           theNode, aOffset + length);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
   return UpdateDocChangeRange(mUtilRange);
 }
 
 NS_IMETHODIMP
 HTMLEditRules::WillDeleteText(nsIDOMCharacterData* aTextNode,
                               int32_t aOffset,
                               int32_t aLength)
 {
@@ -8372,49 +8378,54 @@ NS_IMETHODIMP
 HTMLEditRules::DidDeleteText(nsIDOMCharacterData* aTextNode,
                              int32_t aOffset,
                              int32_t aLength,
                              nsresult aResult)
 {
   if (!mListenerEnabled) {
     return NS_OK;
   }
-  nsCOMPtr<nsIDOMNode> theNode = do_QueryInterface(aTextNode);
-  nsresult rv = mUtilRange->SetStart(theNode, aOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = mUtilRange->SetEnd(theNode, aOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsINode> theNode = do_QueryInterface(aTextNode);
+  nsresult rv = mUtilRange->CollapseTo(theNode, aOffset);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
   return UpdateDocChangeRange(mUtilRange);
 }
 
 NS_IMETHODIMP
 HTMLEditRules::WillDeleteSelection(nsISelection* aSelection)
 {
   if (!mListenerEnabled) {
     return NS_OK;
   }
   if (NS_WARN_IF(!aSelection)) {
     return NS_ERROR_INVALID_ARG;
   }
   RefPtr<Selection> selection = aSelection->AsSelection();
   // get the (collapsed) selection location
-  nsCOMPtr<nsIDOMNode> selNode;
-  int32_t selOffset;
-
+  nsCOMPtr<nsINode> startNode;
+  int32_t startOffset;
   nsresult rv =
     EditorBase::GetStartNodeAndOffset(selection,
-                                      getter_AddRefs(selNode), &selOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = mUtilRange->SetStart(selNode, selOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
+                                      getter_AddRefs(startNode), &startOffset);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  nsCOMPtr<nsINode> endNode;
+  int32_t endOffset;
   rv = EditorBase::GetEndNodeAndOffset(selection,
-                                       getter_AddRefs(selNode), &selOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = mUtilRange->SetEnd(selNode, selOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
+                                       getter_AddRefs(endNode), &endOffset);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  rv = mUtilRange->SetStartAndEnd(startNode, startOffset, endNode, endOffset);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
   return UpdateDocChangeRange(mUtilRange);
 }
 
 NS_IMETHODIMP
 HTMLEditRules::DidDeleteSelection(nsISelection *aSelection)
 {
   return NS_OK;
 }
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -3282,18 +3282,18 @@ HTMLEditor::DoContentInserted(nsIDocumen
       if (aInsertedOrAppended == eAppended) {
         // Count all the appended nodes
         nsIContent* sibling = aChild->GetNextSibling();
         while (sibling) {
           endIndex++;
           sibling = sibling->GetNextSibling();
         }
       }
-      nsresult rv = range->Set(aContainer, aIndexInContainer,
-                               aContainer, endIndex);
+      nsresult rv = range->SetStartAndEnd(aContainer, aIndexInContainer,
+                                          aContainer, endIndex);
       if (NS_SUCCEEDED(rv)) {
         mInlineSpellChecker->SpellCheckRange(range);
       }
     }
   }
 }
 
 void
--- a/editor/libeditor/HTMLStyleEditor.cpp
+++ b/editor/libeditor/HTMLStyleEditor.cpp
@@ -531,19 +531,21 @@ HTMLEditor::SplitStyleAboveRange(nsRange
 
   // second verse, same as the first...
   nsresult rv =
     SplitStyleAbovePoint(address_of(endNode), &endOffset, aProperty,
                          aAttribute);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // reset the range
-  rv = inRange->SetStart(startNode, startOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
-  return inRange->SetEnd(endNode, endOffset);
+  rv = inRange->SetStartAndEnd(startNode, startOffset, endNode, endOffset);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  return NS_OK;
 }
 
 nsresult
 HTMLEditor::SplitStyleAbovePoint(nsCOMPtr<nsINode>* aNode,
                                  int32_t* aOffset,
                                  // null here means we split all properties
                                  nsIAtom* aProperty,
                                  const nsAString* aAttribute,
@@ -878,20 +880,21 @@ HTMLEditor::PromoteRangeIfStartsOrEndsIn
   }
   NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
 
   if (HTMLEditUtils::IsNamedAnchor(parent)) {
     endNode = parent->GetParentNode();
     endOffset = endNode ? endNode->IndexOf(parent) + 1 : 0;
   }
 
-  nsresult rv = aRange.SetStart(startNode, startOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = aRange.SetEnd(endNode, endOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsresult rv = aRange.SetStartAndEnd(startNode, startOffset,
+                                      endNode, endOffset);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
   return NS_OK;
 }
 
 nsresult
 HTMLEditor::PromoteInlineRange(nsRange& aRange)
 {
   nsCOMPtr<nsINode> startNode = aRange.GetStartParent();
@@ -911,20 +914,21 @@ HTMLEditor::PromoteInlineRange(nsRange& 
          IsEditable(endNode) && IsAtEndOfNode(*endNode, endOffset)) {
     nsCOMPtr<nsINode> parent = endNode->GetParentNode();
     NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
     // We are AFTER this node
     endOffset = 1 + parent->IndexOf(endNode);
     endNode = parent;
   }
 
-  nsresult rv = aRange.SetStart(startNode, startOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = aRange.SetEnd(endNode, endOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsresult rv = aRange.SetStartAndEnd(startNode, startOffset,
+                                      endNode, endOffset);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
   return NS_OK;
 }
 
 bool
 HTMLEditor::IsAtFrontOfNode(nsINode& aNode,
                             int32_t aOffset)
 {
--- a/editor/libeditor/SelectionState.cpp
+++ b/editor/libeditor/SelectionState.cpp
@@ -681,15 +681,16 @@ RangeItem::StoreRange(nsRange* aRange)
   endNode = aRange->GetEndParent();
   endOffset = aRange->EndOffset();
 }
 
 already_AddRefed<nsRange>
 RangeItem::GetRange()
 {
   RefPtr<nsRange> range = new nsRange(startNode);
-  if (NS_FAILED(range->Set(startNode, startOffset, endNode, endOffset))) {
+  if (NS_FAILED(range->SetStartAndEnd(startNode, startOffset,
+                                      endNode, endOffset))) {
     return nullptr;
   }
   return range.forget();
 }
 
 } // namespace mozilla
--- a/editor/libeditor/WSRunObject.cpp
+++ b/editor/libeditor/WSRunObject.cpp
@@ -1291,17 +1291,17 @@ WSRunObject::DeleteChars(nsINode* aStart
           mHTMLEditor->DeleteText(*node, 0, AssertedCast<uint32_t>(aEndOffset));
         NS_ENSURE_SUCCESS(rv, rv);
       }
       break;
     } else {
       if (!range) {
         range = new nsRange(aStartNode);
         nsresult rv =
-          range->Set(aStartNode, aStartOffset, aEndNode, aEndOffset);
+          range->SetStartAndEnd(aStartNode, aStartOffset, aEndNode, aEndOffset);
         NS_ENSURE_SUCCESS(rv, rv);
       }
       bool nodeBefore, nodeAfter;
       nsresult rv =
         nsRange::CompareNodeToRange(node, range, &nodeBefore, &nodeAfter);
       NS_ENSURE_SUCCESS(rv, rv);
       if (nodeAfter) {
         break;
--- a/editor/txtsvc/nsTextServicesDocument.cpp
+++ b/editor/txtsvc/nsTextServicesDocument.cpp
@@ -401,21 +401,23 @@ nsTextServicesDocument::ExpandRangeToWor
       rngEndOffset != wordStartOffset ||
       (rngEndNode == rngStartNode && rngEndOffset == rngStartOffset)) {
     rngEndNode = wordEndNode;
     rngEndOffset = wordEndOffset;
   }
 
   // Now adjust the range so that it uses our new
   // end points.
-
-  rv = range->SetEnd(rngEndNode, rngEndOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return range->SetStart(rngStartNode, rngStartOffset);
+  nsCOMPtr<nsINode> startNode = do_QueryInterface(rngStartNode);
+  nsCOMPtr<nsINode> endNode = do_QueryInterface(rngEndNode);
+  rv = range->SetStartAndEnd(startNode, rngStartOffset, endNode, rngEndOffset);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsTextServicesDocument::SetFilter(nsITextServicesFilter *aFilter)
 {
   // Hang on to the filter so we can set it into the filtered iterator.
   mTxtSvcFilter = aFilter;
 
--- a/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
+++ b/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
@@ -1,9 +1,9 @@
-52352
+52354
 0/nm
 0th/pt
 1/n1
 1st/p
 1th/tc
 2/nm
 2nd/p
 2th/tc
@@ -22152,16 +22152,18 @@ decreasing/Y
 decree/MDS
 decreeing
 decremented
 decrements
 decrepit
 decrepitude/M
 decriminalization/M
 decry/GDS
+decrypt/GDS
+decryptable
 decryption
 dedicate/AGDS
 dedication/SM
 dedicator/SM
 dedicatory
 deduce/GDS
 deducible
 deduct/GVD
--- a/extensions/spellcheck/src/mozInlineSpellChecker.cpp
+++ b/extensions/spellcheck/src/mozInlineSpellChecker.cpp
@@ -159,26 +159,33 @@ mozInlineSpellStatus::InitForEditorChang
   mRange = new nsRange(prevNode);
 
   // ...we need to put the start and end in the correct order
   int16_t cmpResult;
   rv = mAnchorRange->ComparePoint(aPreviousNode, aPreviousOffset, &cmpResult);
   NS_ENSURE_SUCCESS(rv, rv);
   if (cmpResult < 0) {
     // previous anchor node is before the current anchor
-    rv = mRange->SetStart(aPreviousNode, aPreviousOffset);
-    NS_ENSURE_SUCCESS(rv, rv);
-    rv = mRange->SetEnd(aAnchorNode, aAnchorOffset);
+    nsCOMPtr<nsINode> previousNode = do_QueryInterface(aPreviousNode);
+    nsCOMPtr<nsINode> anchorNode = do_QueryInterface(aAnchorNode);
+    rv = mRange->SetStartAndEnd(previousNode, aPreviousOffset,
+                                anchorNode, aAnchorOffset);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
   } else {
     // previous anchor node is after (or the same as) the current anchor
-    rv = mRange->SetStart(aAnchorNode, aAnchorOffset);
-    NS_ENSURE_SUCCESS(rv, rv);
-    rv = mRange->SetEnd(aPreviousNode, aPreviousOffset);
+    nsCOMPtr<nsINode> previousNode = do_QueryInterface(aPreviousNode);
+    nsCOMPtr<nsINode> anchorNode = do_QueryInterface(aAnchorNode);
+    rv = mRange->SetStartAndEnd(anchorNode, aAnchorOffset,
+                                previousNode, aPreviousOffset);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
   }
-  NS_ENSURE_SUCCESS(rv, rv);
 
   // On insert save this range: DoSpellCheck optimizes things in this range.
   // Otherwise, just leave this nullptr.
   if (aAction == EditAction::insertText)
     mCreatedRange = mRange;
 
   // if we were given a range, we need to expand our range to encompass it
   if (aStartNode && aEndNode) {
@@ -449,27 +456,29 @@ mozInlineSpellStatus::GetDocument(nsIDOM
 // mozInlineSpellStatus::PositionToCollapsedRange
 //
 //    Converts a given DOM position to a collapsed range covering that
 //    position. We use ranges to store DOM positions becuase they stay
 //    updated as the DOM is changed.
 
 nsresult
 mozInlineSpellStatus::PositionToCollapsedRange(nsIDOMDocument* aDocument,
-    nsIDOMNode* aNode, int32_t aOffset, nsIDOMRange** aRange)
+                                               nsIDOMNode* aNode,
+                                               int32_t aOffset,
+                                               nsRange** aRange)
 {
   *aRange = nullptr;
-  nsCOMPtr<nsIDOMRange> range;
-  nsresult rv = aDocument->CreateRange(getter_AddRefs(range));
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsINode> documentNode = do_QueryInterface(aDocument);
+  RefPtr<nsRange> range = new nsRange(documentNode);
 
-  rv = range->SetStart(aNode, aOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = range->SetEnd(aNode, aOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
+  nsresult rv = range->CollapseTo(node, aOffset);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
   range.swap(*aRange);
   return NS_OK;
 }
 
 // mozInlineSpellResume
 
 class mozInlineSpellResume : public Runnable
@@ -1162,19 +1171,18 @@ mozInlineSpellChecker::MakeSpellCheckRan
   nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
   NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER);
 
   nsCOMPtr<nsIDOMDocument> doc;
   rv = editor->GetDocument(getter_AddRefs(doc));
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
 
-  nsCOMPtr<nsIDOMRange> range;
-  rv = doc->CreateRange(getter_AddRefs(range));
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsINode> documentNode = do_QueryInterface(doc);
+  RefPtr<nsRange> range = new nsRange(documentNode);
 
   // possibly use full range of the editor
   nsCOMPtr<nsIDOMElement> rootElem;
   if (! aStartNode || ! aEndNode) {
     rv = editor->GetRootElement(getter_AddRefs(rootElem));
     NS_ENSURE_SUCCESS(rv, rv);
 
     aStartNode = rootElem;
@@ -1196,25 +1204,33 @@ mozInlineSpellChecker::MakeSpellCheckRan
     aEndOffset = childCount;
   }
 
   // sometimes we are are requested to check an empty range (possibly an empty
   // document). This will result in assertions later.
   if (aStartNode == aEndNode && aStartOffset == aEndOffset)
     return NS_OK;
 
-  rv = range->SetStart(aStartNode, aStartOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
-  if (aEndOffset)
-    rv = range->SetEnd(aEndNode, aEndOffset);
-  else
-    rv = range->SetEndAfter(aEndNode);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsINode> startNode = do_QueryInterface(aStartNode);
+  nsCOMPtr<nsINode> endNode = do_QueryInterface(aEndNode);
+  if (aEndOffset) {
+    rv = range->SetStartAndEnd(startNode, aStartOffset, endNode, aEndOffset);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  } else {
+    int32_t endOffset = -1;
+    endNode = nsRange::GetParentAndOffsetAfter(endNode, &endOffset);
+    rv = range->SetStartAndEnd(startNode, aStartOffset, endNode, endOffset);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
 
-  *aRange = static_cast<nsRange*>(range.forget().take());
+  range.swap(*aRange);
   return NS_OK;
 }
 
 nsresult
 mozInlineSpellChecker::SpellCheckBetweenNodes(nsIDOMNode *aStartNode,
                                               int32_t aStartOffset,
                                               nsIDOMNode *aEndNode,
                                               int32_t aEndOffset)
--- a/extensions/spellcheck/src/mozInlineSpellChecker.h
+++ b/extensions/spellcheck/src/mozInlineSpellChecker.h
@@ -80,25 +80,25 @@ public:
   // Contains the range computed for the current word. Can be nullptr.
   RefPtr<nsRange> mNoCheckRange;
 
   // Indicates the position of the cursor for the event (so we can compute
   // mNoCheckRange). It can be nullptr if we don't care about the cursor position
   // (such as for the intial check of everything).
   //
   // For mOp == eOpNavigation, this is the NEW position of the cursor
-  nsCOMPtr<nsIDOMRange> mAnchorRange;
+  RefPtr<nsRange> mAnchorRange;
 
   // -----
   // The following members are only for navigation events and are only
   // stored for FinishNavigationEvent to initialize the other members.
   // -----
 
   // this is the OLD position of the cursor
-  nsCOMPtr<nsIDOMRange> mOldNavigationAnchorRange;
+  RefPtr<nsRange> mOldNavigationAnchorRange;
 
   // Set when we should force checking the current word. See
   // mozInlineSpellChecker::HandleNavigationEvent for a description of why we
   // have this.
   bool mForceNavigationWordCheck;
 
   // Contains the offset passed in to HandleNavigationEvent
   int32_t mNewNavigationPositionOffset;
@@ -106,17 +106,17 @@ public:
 protected:
   nsresult FinishNavigationEvent(mozInlineSpellWordUtil& aWordUtil);
 
   nsresult FillNoCheckRangeFromAnchor(mozInlineSpellWordUtil& aWordUtil);
 
   nsresult GetDocument(nsIDOMDocument** aDocument);
   nsresult PositionToCollapsedRange(nsIDOMDocument* aDocument,
                                     nsIDOMNode* aNode, int32_t aOffset,
-                                    nsIDOMRange** aRange);
+                                    nsRange** aRange);
 };
 
 class mozInlineSpellChecker final : public nsIInlineSpellChecker,
                                     public nsIEditActionListener,
                                     public nsIDOMEventListener,
                                     public nsSupportsWeakReference
 {
 private:
--- a/extensions/spellcheck/src/mozInlineSpellWordUtil.cpp
+++ b/extensions/spellcheck/src/mozInlineSpellWordUtil.cpp
@@ -330,19 +330,21 @@ nsresult
 mozInlineSpellWordUtil::MakeRange(NodeOffset aBegin, NodeOffset aEnd,
                                   nsRange** aRange)
 {
   NS_ENSURE_ARG_POINTER(aBegin.mNode);
   if (!mDOMDocument)
     return NS_ERROR_NOT_INITIALIZED;
 
   RefPtr<nsRange> range = new nsRange(aBegin.mNode);
-  nsresult rv = range->Set(aBegin.mNode, aBegin.mOffset,
-                           aEnd.mNode, aEnd.mOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsresult rv = range->SetStartAndEnd(aBegin.mNode, aBegin.mOffset,
+                                      aEnd.mNode, aEnd.mOffset);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
   range.forget(aRange);
 
   return NS_OK;
 }
 
 /*********** DOM text extraction ************/
 
 // IsDOMWordSeparator
--- a/image/test/reftest/jpeg/reftest.list
+++ b/image/test/reftest/jpeg/reftest.list
@@ -46,9 +46,9 @@ random-if(Android) == jpg-srgb-icc.jpg j
 # \r\n
 # <contents of blue.jpg> (no newline)
 # --BOUNDARYOMG--\r\n
 # 
 # (The boundary is arbitrary, and just has to be defined as something that
 # won't be in the text of the contents themselves. --$(boundary)\r\n means
 # "Here is the beginning of a boundary," and --$(boundary)-- means "All done
 # sending you parts.")
-random-if(stylo||styloVsGecko) HTTP == webcam-simulacrum.mjpg blue.jpg # bug 1368589 for stylo
+HTTP == webcam-simulacrum.mjpg blue.jpg
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -9643,22 +9643,18 @@ PresShell::Observe(nsISupports* aSubject
         WalkFramesThroughPlaceholders(mPresContext, rootFrame,
                                       ReframeImageBoxes, &changeList);
         // Mark ourselves as not safe to flush while we're doing frame
         // construction.
         {
           nsAutoScriptBlocker scriptBlocker;
           ++mChangeNestCount;
           RestyleManager* restyleManager = mPresContext->RestyleManager();
-          if (restyleManager->IsServo()) {
-            MOZ_CRASH("stylo: PresShell::Observe(\"chrome-flush-skin-caches\") "
-                      "not implemented for Servo-backed style system");
-          }
-          restyleManager->AsGecko()->ProcessRestyledFrames(changeList);
-          restyleManager->AsGecko()->FlushOverflowChangedTracker();
+          restyleManager->ProcessRestyledFrames(changeList);
+          restyleManager->FlushOverflowChangedTracker();
           --mChangeNestCount;
         }
       }
     }
     return NS_OK;
   }
 #endif
 
--- a/layout/forms/nsTextControlFrame.cpp
+++ b/layout/forms/nsTextControlFrame.cpp
@@ -750,22 +750,22 @@ nsTextControlFrame::SetSelectionInternal
   // Note that we use a new range to avoid having to do
   // isIncreasing checks to avoid possible errors.
 
   RefPtr<nsRange> range = new nsRange(mContent);
   // Be careful to use internal nsRange methods which do not check to make sure
   // we have access to the node.
   nsCOMPtr<nsINode> start = do_QueryInterface(aStartNode);
   nsCOMPtr<nsINode> end = do_QueryInterface(aEndNode);
-  // XXXbz nsRange::Set takes int32_t (and ranges generally work on int32_t),
-  // but we're passing uint32_t.  The good news is that at this point our
-  // endpoints should really be within our length, so not really that big.  And
-  // if they _are_ that big, Set() will simply error out, which is not too bad
-  // for a case we don't expect to happen.
-  nsresult rv = range->Set(start, aStartOffset, end, aEndOffset);
+  // XXXbz nsRange::SetStartAndEnd takes int32_t (and ranges generally work on
+  // int32_t), but we're passing uint32_t.  The good news is that at this point
+  // our endpoints should really be within our length, so not really that big.
+  // And if they _are_ that big, SetStartAndEnd() will simply error out, which
+  // is not too bad for a case we don't expect to happen.
+  nsresult rv = range->SetStartAndEnd(start, aStartOffset, end, aEndOffset);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Get the selection, clear it and add the new range to it!
   nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
   NS_ASSERTION(txtCtrl, "Content not a text control element");
   nsISelectionController* selCon = txtCtrl->GetSelectionController();
   NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
 
--- a/layout/generic/nsSelection.cpp
+++ b/layout/generic/nsSelection.cpp
@@ -1790,18 +1790,17 @@ nsFrameSelection::TakeFocus(nsIContent* 
 
     if (aMultipleSelection) {
       // Remove existing collapsed ranges as there's no point in having 
       // non-anchor/focus collapsed ranges.
       mDomSelections[index]->RemoveCollapsedRanges();
 
       RefPtr<nsRange> newRange = new nsRange(aNewFocus);
 
-      newRange->SetStart(aNewFocus, aContentOffset);
-      newRange->SetEnd(aNewFocus, aContentOffset);
+      newRange->CollapseTo(aNewFocus, aContentOffset);
       mDomSelections[index]->AddRange(newRange);
       mBatching = batching;
       mChangesDuringBatching = changes;
     } else {
       bool oldDesiredPosSet = mDesiredPosSet; //need to keep old desired position if it was set.
       mDomSelections[index]->Collapse(aNewFocus, aContentOffset);
       mDesiredPosSet = oldDesiredPosSet; //now reset desired pos back.
       mBatching = batching;
@@ -3363,21 +3362,22 @@ Selection::GetTableSelectionType(nsIDOMR
 nsresult
 nsFrameSelection::CreateAndAddRange(nsINode *aParentNode, int32_t aOffset)
 {
   if (!aParentNode) return NS_ERROR_NULL_POINTER;
 
   RefPtr<nsRange> range = new nsRange(aParentNode);
 
   // Set range around child at given offset
-  nsresult result = range->SetStart(aParentNode, aOffset);
-  if (NS_FAILED(result)) return result;
-  result = range->SetEnd(aParentNode, aOffset+1);
-  if (NS_FAILED(result)) return result;
-  
+  nsresult rv = range->SetStartAndEnd(aParentNode, aOffset,
+                                      aParentNode, aOffset + 1);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
   if (!mDomSelections[index])
     return NS_ERROR_NULL_POINTER;
 
   return mDomSelections[index]->AddRange(range);
 }
 
 // End of Table Selection
@@ -3795,42 +3795,39 @@ Selection::SubtractRange(RangeData* aRan
   // cmp < 0, and cmp2 < 0
   // If it right overlaps the new range then cmp > 0 and cmp2 > 0
   // If it fully contains the new range, then cmp < 0 and cmp2 > 0
 
   if (cmp2 > 0) {
     // We need to add a new RangeData to the output, running from
     // the end of aSubtract to the end of range
     RefPtr<nsRange> postOverlap = new nsRange(aSubtract->GetEndParent());
-
-    rv =
-      postOverlap->SetStart(aSubtract->GetEndParent(), aSubtract->EndOffset());
-    NS_ENSURE_SUCCESS(rv, rv);
-    rv =
-     postOverlap->SetEnd(range->GetEndParent(), range->EndOffset());
-    NS_ENSURE_SUCCESS(rv, rv);
+    rv = postOverlap->SetStartAndEnd(
+                        aSubtract->GetEndParent(), aSubtract->EndOffset(),
+                        range->GetEndParent(), range->EndOffset());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
     if (!postOverlap->Collapsed()) {
       if (!aOutput->InsertElementAt(0, RangeData(postOverlap)))
         return NS_ERROR_OUT_OF_MEMORY;
       (*aOutput)[0].mTextRangeStyle = aRange->mTextRangeStyle;
     }
   }
 
   if (cmp < 0) {
     // We need to add a new RangeData to the output, running from
     // the start of the range to the start of aSubtract
     RefPtr<nsRange> preOverlap = new nsRange(range->GetStartParent());
-
-    nsresult rv =
-     preOverlap->SetStart(range->GetStartParent(), range->StartOffset());
-    NS_ENSURE_SUCCESS(rv, rv);
-    rv =
-     preOverlap->SetEnd(aSubtract->GetStartParent(), aSubtract->StartOffset());
-    NS_ENSURE_SUCCESS(rv, rv);
-    
+    rv = preOverlap->SetStartAndEnd(
+                       range->GetStartParent(), range->StartOffset(),
+                       aSubtract->GetStartParent(), aSubtract->StartOffset());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
     if (!preOverlap->Collapsed()) {
       if (!aOutput->InsertElementAt(0, RangeData(preOverlap)))
         return NS_ERROR_OUT_OF_MEMORY;
       (*aOutput)[0].mTextRangeStyle = aRange->mTextRangeStyle;
     }
   }
 
   return NS_OK;
@@ -5248,16 +5245,19 @@ Selection::Collapse(nsINode& aParentNode
   nsresult result;
 
   RefPtr<nsPresContext> presContext = GetPresContext();
   if (!presContext || presContext->Document() != parentNode->OwnerDoc()) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
+  // Cache current range is if there is because it may be reusable.
+  RefPtr<nsRange> oldRange = !mRanges.IsEmpty() ? mRanges[0].mRange : nullptr;
+
   // Delete all of the current ranges
   Clear(presContext);
 
   // Turn off signal for table selection
   frameSelection->ClearTableCellSelection();
 
   // Hack to display the caret on the right line (bug 1237236).
   if (frameSelection->GetHint() != CARET_ASSOCIATE_AFTER &&
@@ -5271,23 +5271,25 @@ Selection::Collapse(nsINode& aParentNode
            f->GetContentEnd() == int32_t(aOffset)) ||
           (parentNode == f->GetContent()->GetParentNode() &&
            parentNode->IndexOf(f->GetContent()) + 1 == int32_t(aOffset))) {
         frameSelection->SetHint(CARET_ASSOCIATE_AFTER);
       }
     }
   }
 
-  RefPtr<nsRange> range = new nsRange(parentNode);
-  result = range->SetEnd(parentNode, aOffset);
-  if (NS_FAILED(result)) {
-    aRv.Throw(result);
-    return;
-  }
-  result = range->SetStart(parentNode, aOffset);
+  RefPtr<nsRange> range;
+  // If the old range isn't referred by anybody other than this method,
+  // we should reuse it for reducing the recreation cost.
+  if (oldRange && oldRange->GetRefCount() == 1) {
+    range = oldRange;
+  } else {
+    range = new nsRange(parentNode);
+  }
+  result = range->CollapseTo(parentNode, aOffset);
   if (NS_FAILED(result)) {
     aRv.Throw(result);
     return;
   }
 
 #ifdef DEBUG_SELECTION
   nsCOMPtr<nsIContent> content = do_QueryInterface(parentNode);
   nsCOMPtr<nsIDocument> doc = do_QueryInterface(parentNode);
@@ -5680,21 +5682,18 @@ Selection::Extend(nsINode& aParentNode, 
   RefPtr<nsRange> difRange = new nsRange(&aParentNode);
   if ((result1 == 0 && result3 < 0) || (result1 <= 0 && result2 < 0)){//a1,2  a,1,2
     //select from 1 to 2 unless they are collapsed
     range->SetEnd(aParentNode, aOffset, aRv);
     if (aRv.Failed()) {
       return;
     }
     SetDirection(eDirNext);
-    res = difRange->SetEnd(range->GetEndParent(), range->EndOffset());
-    nsresult tmp = difRange->SetStart(focusNode, focusOffset);
-    if (NS_FAILED(tmp)) {
-      res = tmp;
-    }
+    res = difRange->SetStartAndEnd(focusNode, focusOffset,
+                                   range->GetEndParent(), range->EndOffset());
     if (NS_FAILED(res)) {
       aRv.Throw(res);
       return;
     }
     SelectFrames(presContext, difRange , true);
     res = SetAnchorFocusToRange(range);
     if (NS_FAILED(res)) {
       aRv.Throw(res);
@@ -5712,21 +5711,18 @@ Selection::Extend(nsINode& aParentNode, 
     res = SetAnchorFocusToRange(range);
     if (NS_FAILED(res)) {
       aRv.Throw(res);
       return;
     }
   }
   else if (result3 <= 0 && result2 >= 0) {//a,2,1 or a2,1 or a,21 or a21
     //deselect from 2 to 1
-    res = difRange->SetEnd(focusNode, focusOffset);
-    difRange->SetStart(aParentNode, aOffset, aRv);
-    if (aRv.Failed()) {
-      return;
-    }
+    res = difRange->SetStartAndEnd(&aParentNode, aOffset,
+                                   focusNode, focusOffset);
     if (NS_FAILED(res)) {
       aRv.Throw(res);
       return;
     }
 
     range->SetEnd(aParentNode, aOffset, aRv);
     if (aRv.Failed()) {
       return;
@@ -5780,21 +5776,18 @@ Selection::Extend(nsINode& aParentNode, 
         return;
       }
     }
     //select from a to 2
     SelectFrames(presContext, range , true);
   }
   else if (result2 <= 0 && result3 >= 0) {//1,2,a or 12,a or 1,2a or 12a
     //deselect from 1 to 2
-    difRange->SetEnd(aParentNode, aOffset, aRv);
-    res = difRange->SetStart(focusNode, focusOffset);
-    if (aRv.Failed()) {
-      return;
-    }
+    res = difRange->SetStartAndEnd(focusNode, focusOffset,
+                                   &aParentNode, aOffset);
     if (NS_FAILED(res)) {
       aRv.Throw(res);
       return;
     }
     SetDirection(eDirPrevious);
     range->SetStart(aParentNode, aOffset, aRv);
     if (aRv.Failed()) {
       return;
@@ -5815,22 +5808,19 @@ Selection::Extend(nsINode& aParentNode, 
     }
     SetDirection(eDirPrevious);
     range->SetStart(aParentNode, aOffset, aRv);
     if (aRv.Failed()) {
       return;
     }
     //deselect from a to 1
     if (focusNode != anchorNode || focusOffset!= anchorOffset) {//if collapsed diff dont do anything
-      res = difRange->SetStart(anchorNode, anchorOffset);
-      nsresult tmp = difRange->SetEnd(focusNode, focusOffset);
-      if (NS_FAILED(tmp)) {
-        res = tmp;
-      }
-      tmp = SetAnchorFocusToRange(range);
+      res = difRange->SetStartAndEnd(anchorNode, anchorOffset,
+                                     focusNode, focusOffset);
+      nsresult tmp = SetAnchorFocusToRange(range);
       if (NS_FAILED(tmp)) {
         res = tmp;
       }
       if (NS_FAILED(res)) {
         aRv.Throw(res);
         return;
       }
       SelectFrames(presContext, difRange, false);
@@ -5848,21 +5838,19 @@ Selection::Extend(nsINode& aParentNode, 
   }
   else if (result2 >= 0 && result1 >= 0) {//2,1,a or 21,a or 2,1a or 21a
     //select from 2 to 1
     range->SetStart(aParentNode, aOffset, aRv);
     if (aRv.Failed()) {
       return;
     }
     SetDirection(eDirPrevious);
-    res = difRange->SetEnd(focusNode, focusOffset);
-    nsresult tmp = difRange->SetStart(range->GetStartParent(), range->StartOffset());
-    if (NS_FAILED(tmp)) {
-      res = tmp;
-    }
+    res = difRange->SetStartAndEnd(
+                      range->GetStartParent(), range->StartOffset(),
+                      focusNode, focusOffset);
     if (NS_FAILED(res)) {
       aRv.Throw(res);
       return;
     }
 
     SelectFrames(presContext, difRange, true);
     res = SetAnchorFocusToRange(range);
     if (NS_FAILED(res)) {
@@ -6478,33 +6466,26 @@ Selection::NotifySelectionListeners()
         GetEditor()) {
       RefPtr<Element> newEditingHost = GetCommonEditingHostForAllRanges();
       nsFocusManager* fm = nsFocusManager::GetFocusManager();
       nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
       nsIContent* focusedContent =
         fm->GetFocusedDescendant(window, false, getter_AddRefs(focusedWindow));
       nsCOMPtr<Element> focusedElement = do_QueryInterface(focusedContent);
       // When all selected ranges are in an editing host, it should take focus.
+      // But otherwise, we shouldn't move focus since Chromium doesn't move
+      // focus but only selection range is updated.
       if (newEditingHost && newEditingHost != focusedElement) {
         MOZ_ASSERT(!newEditingHost->IsInNativeAnonymousSubtree());
         nsCOMPtr<nsIDOMElement> domElementToFocus =
           do_QueryInterface(newEditingHost->AsDOMNode());
         // Note that don't steal focus from focused window if the window doesn't
         // have focus.
         fm->SetFocus(domElementToFocus, nsIFocusManager::FLAG_NOSWITCHFRAME);
       }
-      // Otherwise, if current focused element is an editing host, it should
-      // be blurred if there is no common editing host of the selected ranges.
-      else if (!newEditingHost && focusedElement &&
-               focusedElement == focusedElement->GetEditingHost()) {
-        IgnoredErrorResult err;
-        focusedElement->Blur(err);
-        NS_WARNING_ASSERTION(!err.Failed(),
-                             "Failed to blur focused element");
-      }
     }
   }
 
   RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
   if (frameSelection->GetBatching()) {
     frameSelection->SetDirty();
     return NS_OK;
   }
--- a/layout/printing/nsPrintEngine.cpp
+++ b/layout/printing/nsPrintEngine.cpp
@@ -2446,23 +2446,27 @@ CloneRangeToSelection(nsRange* aRange, n
   int32_t endOffset = aRange->EndOffset();
   NS_ENSURE_TRUE_VOID(startContainer && endContainer);
 
   nsCOMPtr<nsIDOMNode> newStart = GetEqualNodeInCloneTree(startContainer, aDoc);
   nsCOMPtr<nsIDOMNode> newEnd = GetEqualNodeInCloneTree(endContainer, aDoc);
   NS_ENSURE_TRUE_VOID(newStart && newEnd);
 
   nsCOMPtr<nsINode> newStartNode = do_QueryInterface(newStart);
-  NS_ENSURE_TRUE_VOID(newStartNode);
+  nsCOMPtr<nsINode> newEndNode = do_QueryInterface(newEnd);
+  if (NS_WARN_IF(!newStartNode) || NS_WARN_IF(!newEndNode)) {
+    return;
+  }
 
   RefPtr<nsRange> range = new nsRange(newStartNode);
-  nsresult rv = range->SetStart(newStartNode, startOffset);
-  NS_ENSURE_SUCCESS_VOID(rv);
-  rv = range->SetEnd(newEnd, endOffset);
-  NS_ENSURE_SUCCESS_VOID(rv);
+  nsresult rv =
+    range->SetStartAndEnd(newStartNode, startOffset, newEndNode, endOffset);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
 
   aSelection->AddRange(range);
 }
 
 static nsresult CloneSelection(nsIDocument* aOrigDoc, nsIDocument* aDoc)
 {
   nsIPresShell* origShell = aOrigDoc->GetShell();
   nsIPresShell* shell = aDoc->GetShell();
--- a/layout/reftests/svg/reftest.list
+++ b/layout/reftests/svg/reftest.list
@@ -90,19 +90,19 @@ default-preferences
 == conditions-02.svg pass.svg
 == conditions-03.svg pass.svg
 == conditions-04.svg pass.svg
 == conditions-05.svg about:blank
 == conditions-07.svg pass.svg
 fuzzy-if(skiaContent,1,320) == conditions-08.svg conditions-08-ref.svg
 == conditions-09.svg conditions-09-ref.svg
 
-fails-if(styloVsGecko||stylo) == currentColor-01.svg pass.svg
-fails-if(styloVsGecko||stylo) == currentColor-02.svg pass.svg
-fails-if(styloVsGecko||stylo) == currentColor-03.svg pass.svg
+== currentColor-01.svg pass.svg
+== currentColor-02.svg pass.svg
+== currentColor-03.svg pass.svg
 
 == data-uri-with-filter-01.xhtml data-uri-with-filter-01-ref.svg
 == data-uri-with-gradient-01.xhtml data-uri-with-gradient-01-ref.svg
 == data-uri-with-pattern-01.xhtml pass.svg
 
 == dynamic-attr-removal-1.svg pass.svg
 == dynamic-attr-removal-2.svg pass.svg
 == dynamic-attr-change-1.svg pass.svg
@@ -502,17 +502,17 @@ fuzzy-if(skiaContent,1,300) == tspan-xy-
 fuzzy-if(skiaContent,1,300) == tspan-xy-04.svg tspan-xy-ref.svg
 fuzzy-if(skiaContent,1,300) == tspan-xy-05.svg tspan-xy-ref.svg
 fuzzy-if(skiaContent,1,300) == tspan-xy-06.svg tspan-xy-ref.svg
 fuzzy-if(skiaContent,1,100) == tspan-xy-anchor-middle-01.svg tspan-xy-anchor-middle-ref.svg
 fuzzy-if(skiaContent,1,100) == tspan-xy-anchor-end-01.svg tspan-xy-anchor-end-ref.svg
 
 == use-01.svg pass.svg
 == use-01-extref.svg pass.svg
-fails-if(styloVsGecko) == use-02-extref.svg use-02-extref-ref.svg
+== use-02-extref.svg use-02-extref-ref.svg
 == use-extref-dataURI-01.svg pass.svg
 == use-children.svg pass.svg
 
 # test case for Fragment URLs
 # https://drafts.csswg.org/css-values/#local-urls
 == use-localRef-marker-01.svg use-localRef-marker-01-ref.svg
 == use-localRef-clipPath-01.svg use-localRef-clipPath-01-ref.svg
 == use-localRef-filter-01.svg use-localRef-filter-01-ref.svg
--- a/layout/reftests/svg/smil/style/reftest.list
+++ b/layout/reftests/svg/smil/style/reftest.list
@@ -2,23 +2,23 @@
 
 # XXXdholbert TODO: Test color animation with "color-interpolation: linearRGB"
 # (when it's implemented)
 
 # 'color' property, from/to/by with named colors & hex values
 fails-if(styloVsGecko||stylo) == anim-css-color-1-by-ident-hex.svg         anim-css-fill-1-ref.svg
 fails-if(styloVsGecko||stylo) == anim-css-color-1-from-by-hex-hex.svg      anim-css-fill-1-ref.svg
 fuzzy-if(skiaContent,1,550) fails-if(styloVsGecko||stylo) == anim-css-color-1-from-by-ident-hex.svg    anim-css-fill-1-ref.svg
-fuzzy-if(skiaContent,1,550) fails-if(styloVsGecko||stylo) == anim-css-color-1-from-to-hex-hex.svg      anim-css-fill-1-ref.svg
-fuzzy-if(skiaContent,1,550) fails-if(styloVsGecko||stylo) == anim-css-color-1-from-to-ident-ident.svg  anim-css-fill-1-ref.svg
+fuzzy-if(skiaContent,1,550) == anim-css-color-1-from-to-hex-hex.svg      anim-css-fill-1-ref.svg
+fuzzy-if(skiaContent,1,550) == anim-css-color-1-from-to-ident-ident.svg  anim-css-fill-1-ref.svg
 fuzzy-if(skiaContent,1,550) fails-if(styloVsGecko||stylo) == anim-css-color-1-to-ident-hex.svg         anim-css-fill-1-ref.svg
 fuzzy-if(skiaContent,1,550) fails-if(styloVsGecko||stylo) == anim-css-color-1-to-ident-ident.svg       anim-css-fill-1-ref.svg
 
 # 'color' property, paced calcMode
-fails-if(styloVsGecko||stylo) == anim-css-color-2-paced-rgb.svg            anim-css-fill-2-ref.svg
+== anim-css-color-2-paced-rgb.svg            anim-css-fill-2-ref.svg
 
 # 'color' property, animating *by* a named color
 fuzzy-if(skiaContent,1,580) fails-if(styloVsGecko||stylo) == anim-css-color-3-by-ident-ident.svg       anim-css-fill-3-ref.svg
 fuzzy-if(skiaContent,1,580) fails-if(styloVsGecko||stylo) == anim-css-color-3-from-by-ident-ident.svg  anim-css-fill-3-ref.svg
 fuzzy-if(skiaContent,1,580) fails-if(styloVsGecko||stylo) == anim-css-color-3-from-by-rgb-ident.svg    anim-css-fill-3-ref.svg
 
 # 'fill' property, from/to/by with named colors & hex values
 fuzzy-if(skiaContent,1,550) fails-if(styloVsGecko||stylo) == anim-css-fill-1-by-ident-hex.svg         anim-css-fill-1-ref.svg
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -1088,25 +1088,24 @@ ServoStyleSet::EnsureUniqueInnerOnCSSShe
   return res;
 }
 
 void
 ServoStyleSet::RebuildData()
 {
   ClearNonInheritingStyleContexts();
   Servo_StyleSet_RebuildData(mRawSet.get());
-  mStylistState = StylistState::NotDirty;
 }
 
 void
 ServoStyleSet::ClearDataAndMarkDeviceDirty()
 {
   ClearNonInheritingStyleContexts();
   Servo_StyleSet_Clear(mRawSet.get());
-  mStylistState = StylistState::FullyDirty;
+  mStylistState |= StylistState::FullyDirty;
 }
 
 already_AddRefed<ServoComputedValues>
 ServoStyleSet::ResolveServoStyle(Element* aElement)
 {
   UpdateStylistIfNeeded();
   return Servo_ResolveStyle(aElement, mRawSet.get(),
                             mAllowResolveStaleStyles).Consume();
@@ -1198,19 +1197,38 @@ ServoStyleSet::ResolveForDeclarations(
                                                aParentOrNull,
                                                aDeclarations).Consume();
 }
 
 void
 ServoStyleSet::UpdateStylist()
 {
   MOZ_ASSERT(StylistNeedsUpdate());
-  if (mStylistState == StylistState::FullyDirty) {
+  if (mStylistState & StylistState::FullyDirty) {
     RebuildData();
+
+    if (mStylistState & StylistState::StyleSheetsDirty) {
+      // Normally, whoever was in charge of posting a RebuildAllStyleDataEvent,
+      // would also be in charge of posting a restyle/change hint according to
+      // it.
+      //
+      // However, other stylesheets may have been added to the document in the
+      // same period, so when both bits are set, we need to do a full subtree
+      // update, because we can no longer reason about the state of the style
+      // data.
+      //
+      // We could not clear the invalidations when rebuilding the data and
+      // process them here... But it's not clear if that complexity is worth
+      // to handle this edge case more efficiently.
+      if (Element* root = mPresContext->Document()->GetDocumentElement()) {
+        Servo_NoteExplicitHints(root, eRestyle_Subtree, nsChangeHint(0));
+      }
+    }
   } else {
+    MOZ_ASSERT(mStylistState & StylistState::StyleSheetsDirty);
     Element* root = mPresContext->Document()->GetDocumentElement();
     Servo_StyleSet_FlushStyleSheets(mRawSet.get(), root);
   }
   mStylistState = StylistState::NotDirty;
 }
 
 void
 ServoStyleSet::PrependSheetOfType(SheetType aType,
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -42,16 +42,36 @@ class nsStyleContext;
 class nsPresContext;
 struct nsTimingFunction;
 struct RawServoRuleNode;
 struct TreeMatchContext;
 
 namespace mozilla {
 
 /**
+ * A few flags used to track which kind of stylist state we may need to
+ * update.
+ */
+enum class StylistState : uint8_t {
+  /** The stylist is not dirty, we should do nothing */
+  NotDirty = 0,
+
+  /** The style sheets have changed, so we need to update the style data. */
+  StyleSheetsDirty = 1 << 0,
+
+  /**
+   * All style data is dirty and both style sheet data and default computed
+   * values need to be recomputed.
+   */
+  FullyDirty = 1 << 1,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(StylistState)
+
+/**
  * The set of style sheets that apply to a document, backed by a Servo
  * Stylist.  A ServoStyleSet contains ServoStyleSheets.
  */
 class ServoStyleSet
 {
   friend class ServoRestyleManager;
   typedef ServoElementSnapshotTable SnapshotTable;
 
@@ -467,39 +487,21 @@ private:
    */
   void PreTraverse(dom::Element* aRoot = nullptr,
                    EffectCompositor::AnimationRestyleType =
                      EffectCompositor::AnimationRestyleType::Throttled);
   // Subset of the pre-traverse steps that involve syncing up data
   void PreTraverseSync();
 
   /**
-   * A tri-state used to track which kind of stylist state we may need to
-   * update.
-   */
-  enum class StylistState : uint8_t {
-    /** The stylist is not dirty, we should do nothing */
-    NotDirty,
-    /** The style sheets have changed, so we need to update the style data. */
-    StyleSheetsDirty,
-    /**
-     * All style data is dirty and both style sheet data and default computed
-     * values need to be recomputed.
-     */
-    FullyDirty,
-  };
-
-  /**
    * Note that the stylist needs a style flush due to style sheet changes.
    */
   void SetStylistStyleSheetsDirty()
   {
-    if (mStylistState == StylistState::NotDirty) {
-      mStylistState = StylistState::StyleSheetsDirty;
-    }
+    mStylistState |= StylistState::StyleSheetsDirty;
   }
 
   bool StylistNeedsUpdate() const
   {
     return mStylistState != StylistState::NotDirty;
   }
 
   /**
--- a/layout/style/test/stylo-failures.md
+++ b/layout/style/test/stylo-failures.md
@@ -50,17 +50,16 @@ to mochitest command.
     * test_animations_event_order.html [2]
 * Unimplemented \@font-face descriptors:
   * test_font_face_parser.html `font-language-override`: bug 1355364 [8]
 * keyword values should be preserved in \@font-face bug 1355368
   * test_font_face_parser.html `font-weight` [4]
   * test_font_loading_api.html `weight` [1]
 * @namespace support:
   * test_namespace_rule.html: bug 1355715 [6]
-* test_dont_use_document_colors.html: support of disabling document color bug 1355716 [21]
 * test_font_feature_values_parsing.html: \@font-feature-values support bug 1355721 [107]
 * Grid support bug 1341802
   * test_grid_computed_values.html [4]
   * test_grid_container_shorthands.html [65]
   * test_grid_item_shorthands.html [23]
   * test_grid_shorthand_serialization.html [28]
   * test_inherit_computation.html `grid` [2]
   * test_initial_computation.html `grid` [4]
--- a/mfbt/Assertions.h
+++ b/mfbt/Assertions.h
@@ -442,24 +442,24 @@ struct AssertionConditionType
     (__VA_ARGS__))
 
 #ifdef DEBUG
 #  define MOZ_ASSERT(...) MOZ_RELEASE_ASSERT(__VA_ARGS__)
 #else
 #  define MOZ_ASSERT(...) do { } while (0)
 #endif /* DEBUG */
 
-#ifdef RELEASE_OR_BETA
+#if defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION)
+#  define MOZ_DIAGNOSTIC_ASSERT MOZ_RELEASE_ASSERT
+#  define MOZ_DIAGNOSTIC_ASSERT_ENABLED 1
+#else // RELEASE_OR_BETA
 #  define MOZ_DIAGNOSTIC_ASSERT MOZ_ASSERT
 #  ifdef DEBUG
 #    define MOZ_DIAGNOSTIC_ASSERT_ENABLED 1
 #  endif
-#else
-#  define MOZ_DIAGNOSTIC_ASSERT MOZ_RELEASE_ASSERT
-#  define MOZ_DIAGNOSTIC_ASSERT_ENABLED 1
 #endif
 
 /*
  * MOZ_ASSERT_IF(cond1, cond2) is equivalent to MOZ_ASSERT(cond2) if cond1 is
  * true.
  *
  *   MOZ_ASSERT_IF(isPrime(num), num == 2 || isOdd(num));
  *
--- a/mfbt/SizePrintfMacros.h
+++ b/mfbt/SizePrintfMacros.h
@@ -13,17 +13,17 @@
  * MSVC's libc does not support C99's %z format length modifier for size_t
  * types. Instead, we use Microsoft's nonstandard %I modifier for size_t, which
  * is unsigned __int32 on 32-bit platforms and unsigned __int64 on 64-bit
  * platforms:
  *
  * http://msdn.microsoft.com/en-us/library/tcxf1dw6.aspx
  */
 
-#if defined(XP_WIN)
+#if defined(XP_WIN) && !defined(__MINGW32__)
 #  define PRIoSIZE  "Io"
 #  define PRIuSIZE  "Iu"
 #  define PRIxSIZE  "Ix"
 #  define PRIXSIZE  "IX"
 #else
 #  define PRIoSIZE  "zo"
 #  define PRIuSIZE  "zu"
 #  define PRIxSIZE  "zx"
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripView.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripView.java
@@ -26,32 +26,37 @@ import android.support.v7.widget.helper.
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.view.animation.DecelerateInterpolator;
 
 import java.util.ArrayList;
 import java.util.List;
 
+import static org.mozilla.gecko.Tab.TabType;
+
 public class TabStripView extends RecyclerView
                           implements TabsTouchHelperCallback.DragListener {
     private static final int ANIM_TIME_MS = 200;
     private static final DecelerateInterpolator ANIM_INTERPOLATOR = new DecelerateInterpolator();
 
     private final TabStripAdapter adapter;
+    private final TabType type;
     private boolean isPrivate;
 
     private final TabAnimatorListener animatorListener;
 
     private final Paint fadingEdgePaint;
     private final int fadingEdgeSize;
 
     public TabStripView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
+        type = TabType.BROWSING;
+
         fadingEdgePaint = new Paint();
         final Resources resources = getResources();
         fadingEdgeSize =
                 resources.getDimensionPixelOffset(R.dimen.tablet_tab_strip_fading_edge_size);
 
         animatorListener = new TabAnimatorListener();
 
         setChildrenDrawingOrderEnabled(true);
@@ -75,17 +80,17 @@ public class TabStripView extends Recycl
     }
 
     /* package */ void refreshTabs() {
         // Store a different copy of the tabs, so that we don't have
         // to worry about accidentally updating it on the wrong thread.
         final List<Tab> tabs = new ArrayList<>();
 
         for (final Tab tab : Tabs.getInstance().getTabsInOrder()) {
-            if (tab.isPrivate() == isPrivate) {
+            if (tab.isPrivate() == isPrivate && tab.getType() == type) {
                 tabs.add(tab);
             }
         }
 
         adapter.refresh(tabs);
         updateSelectedPosition();
     }
 
@@ -94,17 +99,17 @@ public class TabStripView extends Recycl
     }
 
     /* package */ void restoreTabs() {
         refreshTabs();
         animateRestoredTabs();
     }
 
     /* package */ void addTab(Tab tab, int position) {
-        if (tab.isPrivate() != isPrivate) {
+        if (tab.isPrivate() != isPrivate || tab.getType() != type) {
             return;
         }
 
         adapter.addTab(tab, position);
     }
 
     /* package */ void removeTab(Tab tab) {
         adapter.removeTab(tab);
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -460,16 +460,17 @@ gvjar.sources += [geckoview_thirdparty_s
     'java/com/googlecode/eyesfree/braille/selfbraille/SelfBrailleClient.java',
     'java/com/googlecode/eyesfree/braille/selfbraille/WriteData.java',
 ]]
 
 if CONFIG['MOZ_ANDROID_HLS_SUPPORT']:
     gvjar.sources += [geckoview_source_dir + 'java/org/mozilla/gecko/' + x for x in [
         'media/GeckoAudioInfo.java',
         'media/GeckoHlsAudioRenderer.java',
+        'media/GeckoHlsDemuxerWrapper.java',
         'media/GeckoHlsPlayer.java',
         'media/GeckoHlsRendererBase.java',
         'media/GeckoHlsSample.java',
         'media/GeckoHlsVideoRenderer.java',
         'media/GeckoVideoInfo.java',
         'media/Utils.java',
     ]]
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHlsDemuxerWrapper.java
@@ -0,0 +1,200 @@
+/* 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.media;
+
+import android.util.Log;
+
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.util.MimeTypes;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.mozilla.gecko.annotation.WrapForJNI;
+import org.mozilla.gecko.mozglue.JNIObject;
+
+public final class GeckoHlsDemuxerWrapper {
+    private static final String LOGTAG = "GeckoHlsDemuxerWrapper";
+    private static final boolean DEBUG = true;
+
+    // NOTE : These TRACK definitions should be synced with Gecko.
+    public enum TrackType {
+        UNDEFINED(0),
+        AUDIO(1),
+        VIDEO(2),
+        TEXT(3);
+        private int mType;
+        private TrackType(int type) {
+            mType = type;
+        }
+        public int value() {
+            return mType;
+        }
+    }
+
+    private GeckoHlsPlayer mPlayer = null;
+
+    public static class HlsDemuxerCallbacks extends JNIObject
+        implements GeckoHlsPlayer.DemuxerCallbacks {
+
+        @WrapForJNI(calledFrom = "gecko")
+        HlsDemuxerCallbacks() {}
+
+        @Override
+        @WrapForJNI
+        public native void onInitialized(boolean hasAudio, boolean hasVideo);
+
+        @Override
+        @WrapForJNI
+        public native void onError(int errorCode);
+
+        @Override // JNIObject
+        protected void disposeNative() {
+            throw new UnsupportedOperationException();
+        }
+    } // HlsDemuxerCallbacks
+
+    private static void assertTrue(boolean condition) {
+        if (DEBUG && !condition) {
+            throw new AssertionError("Expected condition to be true");
+        }
+    }
+
+    private GeckoHlsPlayer.TrackType getPlayerTrackType(int trackType) {
+        if (trackType == TrackType.AUDIO.value()) {
+            return GeckoHlsPlayer.TrackType.AUDIO;
+        } else if (trackType == TrackType.VIDEO.value()) {
+            return GeckoHlsPlayer.TrackType.VIDEO;
+        } else if (trackType == TrackType.TEXT.value()) {
+            return GeckoHlsPlayer.TrackType.TEXT;
+        }
+        return GeckoHlsPlayer.TrackType.UNDEFINED;
+    }
+
+    @WrapForJNI
+    public long getBuffered() {
+        assertTrue(mPlayer != null);
+        return mPlayer.getBufferedPosition();
+    }
+
+    @WrapForJNI(calledFrom = "gecko")
+    public static GeckoHlsDemuxerWrapper create(GeckoHlsPlayer player,
+                                                GeckoHlsPlayer.DemuxerCallbacks callback) {
+        return new GeckoHlsDemuxerWrapper(player, callback);
+    }
+
+    @WrapForJNI
+    public int getNumberOfTracks(int trackType) {
+        int tracks = mPlayer != null ? mPlayer.getNumberOfTracks(getPlayerTrackType(trackType)) : 0;
+        if (DEBUG) Log.d(LOGTAG, "[GetNumberOfTracks] type : " + trackType + ", num = " + tracks);
+        return tracks;
+    }
+
+    @WrapForJNI
+    public GeckoAudioInfo getAudioInfo(int index) {
+        assertTrue(mPlayer != null);
+
+        if (DEBUG) Log.d(LOGTAG, "[getAudioInfo] formatIndex : " + index);
+        Format fmt = mPlayer.getAudioTrackFormat(index);
+        if (fmt == null) {
+            return null;
+        }
+        /* According to https://github.com/google/ExoPlayer/blob
+         * /d979469659861f7fe1d39d153b90bdff1ab479cc/library/core/src/main
+         * /java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java#L221-L224,
+         * if the input audio format is not raw, exoplayer would assure that
+         * the sample's pcm encoding bitdepth is 16.
+         * For HLS content, it should always be 16.
+         */
+        assertTrue(!MimeTypes.AUDIO_RAW.equals(fmt.sampleMimeType));
+        // For HLS content, csd-0 is enough.
+        byte[] csd = fmt.initializationData.isEmpty() ? null : fmt.initializationData.get(0);
+        GeckoAudioInfo aInfo = new GeckoAudioInfo(fmt.sampleRate, fmt.channelCount,
+                                                  16, 0, mPlayer.getDuration(),
+                                                  fmt.sampleMimeType, csd);
+        return aInfo;
+    }
+
+    @WrapForJNI
+    public GeckoVideoInfo getVideoInfo(int index) {
+        assertTrue(mPlayer != null);
+
+        if (DEBUG) Log.d(LOGTAG, "[getVideoInfo] formatIndex : " + index);
+        Format fmt = mPlayer.getVideoTrackFormat(index);
+        if (fmt == null) {
+            return null;
+        }
+        GeckoVideoInfo vInfo = new GeckoVideoInfo(fmt.width, fmt.height,
+                                                  fmt.width, fmt.height,
+                                                  fmt.rotationDegrees, fmt.stereoMode,
+                                                  mPlayer.getDuration(), fmt.sampleMimeType,
+                                                  null, null);
+        return vInfo;
+    }
+
+    @WrapForJNI
+    public boolean seek(long seekTime) {
+        // seekTime : microseconds.
+        assertTrue(mPlayer != null);
+        if (DEBUG) Log.d(LOGTAG, "seek  : " + seekTime + " (Us)");
+        return mPlayer.seek(seekTime);
+    }
+
+    GeckoHlsDemuxerWrapper(GeckoHlsPlayer player,
+                           GeckoHlsPlayer.DemuxerCallbacks callback) {
+        if (DEBUG) Log.d(LOGTAG, "Constructing GeckoHlsDemuxerWrapper ...");
+        assertTrue(callback != null);
+        assertTrue(player != null);
+        try {
+            this.mPlayer = player;
+            this.mPlayer.addDemuxerWrapperCallbackListener(callback);
+        } catch (Exception e) {
+            Log.e(LOGTAG, "Constructing GeckoHlsDemuxerWrapper ... error", e);
+            callback.onError(GeckoHlsPlayer.DemuxerError.UNKNOWN.code());
+        }
+    }
+
+    @WrapForJNI
+    private GeckoHlsSample[] getSamples(int mediaType, int number) {
+        ConcurrentLinkedQueue<GeckoHlsSample> samples = null;
+        // getA/VSamples will always return a non-null instance.
+        if (mediaType == TrackType.VIDEO.value()) {
+            samples = mPlayer.getVideoSamples(number);
+        } else if (mediaType == TrackType.AUDIO.value()) {
+            samples = mPlayer.getAudioSamples(number);
+        }
+
+        assertTrue(samples.size() <= number);
+        return samples.toArray(new GeckoHlsSample[samples.size()]);
+    }
+
+    @WrapForJNI
+    private long getNextKeyFrameTime() {
+        assertTrue(mPlayer != null);
+        return mPlayer.getNextKeyFrameTime();
+    }
+
+    @WrapForJNI
+    private boolean isLiveStream() {
+        assertTrue(mPlayer != null);
+        return mPlayer.isLiveStream();
+    }
+
+    @WrapForJNI // Called when native object is destroyed.
+    private void destroy() {
+        if (DEBUG) Log.d(LOGTAG, "destroy!! Native object is destroyed.");
+        if (mPlayer != null) {
+            release();
+        }
+    }
+
+    private void release() {
+        assertTrue(mPlayer != null);
+        if (DEBUG) Log.d(LOGTAG, "release GeckoHlsPlayer...");
+        mPlayer.release();
+        mPlayer = null;
+    }
+}
--- a/mobile/android/tests/browser/chrome/test_hidden_select_option.html
+++ b/mobile/android/tests/browser/chrome/test_hidden_select_option.html
@@ -26,17 +26,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     // Returns whether an element should be visible according to its text content.
     function shouldBeVisible(e){
       return e.label.indexOf("visible") > 0;
     }
 
     // Returns an object for the callback method that would normally be created by Prompt.java's
     // addListResult(..) method.
     function createCallBackDummyData(select){
-      var dummyList = new Array();
+      var dummyList = [];
       let listElements = SelectHelper.getListForElement(select);
       for (var i = 0; i < listElements.length; i++) {
         dummyList.push(i);
       }
       return {list:dummyList};
     }
 
     // Wait until the page has loaded so that we can access the DOM.
@@ -95,9 +95,9 @@ https://bugzilla.mozilla.org/show_bug.cg
         <option value="9">9 - visible</option> 8
     </select>
 </p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 </pre>
 </body>
-</html>
\ No newline at end of file
+</html>
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4842,17 +4842,17 @@ pref("extensions.webextensions.identity.
 pref("extensions.webextensions.themes.enabled", false);
 pref("extensions.webextensions.themes.icons.enabled", false);
 pref("extensions.webextensions.remote", false);
 
 pref("layers.popups.compositing.enabled", false);
 
 // Report Site Issue button
 pref("extensions.webcompat-reporter.newIssueEndpoint", "https://webcompat.com/issues/new");
-#ifdef NIGHTLY_BUILD
+#ifndef RELEASE_OR_BETA
 pref("extensions.webcompat-reporter.enabled", true);
 #else
 pref("extensions.webcompat-reporter.enabled", false);
 #endif
 
 pref("network.buffer.cache.count", 24);
 pref("network.buffer.cache.size",  32768);
 
--- a/security/.eslintrc.js
+++ b/security/.eslintrc.js
@@ -27,19 +27,16 @@ module.exports = {
     "generator-star-spacing": ["error", {"before": false, "after": true}],
 
     // Always require parenthesis for new calls
     "new-parens": "error",
 
     // Disallow use of alert(), confirm(), and prompt().
     "no-alert": "error",
 
-    // Use [] instead of Array()
-    "no-array-constructor": "error",
-
     // Disallow use of arguments.caller or arguments.callee.
     "no-caller": "error",
 
     // Disallow likely erroneous `switch` scoped lexical declarations in
     // case/default clauses.
     "no-case-declarations": "error",
 
     // Disallow modifying variables of class declarations.
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -627,17 +627,16 @@ dependencies = [
  "blurz 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "devtools"
 version = "0.0.1"
 dependencies = [
  "devtools_traits 0.0.1",
- "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
  "hyper 0.10.10 (registry+https://github.com/rust-lang/crates.io-index)",
  "hyper_serde 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "serde 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2369,16 +2368,17 @@ dependencies = [
  "smallvec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "style 0.0.1",
  "style_traits 0.0.1",
  "swapper 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
  "tinyfiledialogs 2.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-segmentation 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "utf-8 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "uuid 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender_traits 0.39.0 (git+https://github.com/servo/webrender)",
  "webvr 0.0.1",
  "webvr_traits 0.0.1",
  "xml5ever 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
--- a/servo/components/devtools/Cargo.toml
+++ b/servo/components/devtools/Cargo.toml
@@ -6,17 +6,16 @@ license = "MPL-2.0"
 publish = false
 
 [lib]
 name = "devtools"
 path = "lib.rs"
 
 [dependencies]
 devtools_traits = {path = "../devtools_traits"}
-encoding = "0.2"
 hyper = "0.10"
 hyper_serde = "0.6"
 ipc-channel = "0.7"
 log = "0.3.5"
 msg = {path = "../msg"}
 serde = "0.9"
 serde_derive = "0.9"
 serde_json = "0.9"
--- a/servo/components/devtools/actors/network_event.rs
+++ b/servo/components/devtools/actors/network_event.rs
@@ -4,18 +4,16 @@
 
 //! Liberally derived from the [Firefox JS implementation]
 //! (http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/webconsole.js).
 //! Handles interaction with the remote web console on network events (HTTP requests, responses) in Servo.
 
 use actor::{Actor, ActorMessageStatus, ActorRegistry};
 use devtools_traits::HttpRequest as DevtoolsHttpRequest;
 use devtools_traits::HttpResponse as DevtoolsHttpResponse;
-use encoding::all::UTF_8;
-use encoding::types::{DecoderTrap, Encoding};
 use hyper::header::{ContentType, Cookie};
 use hyper::header::Headers;
 use hyper::http::RawStatus;
 use hyper::method::Method;
 use protocol::JsonPacketStream;
 use serde_json::{Map, Value};
 use std::borrow::Cow;
 use std::net::TcpStream;
@@ -356,17 +354,17 @@ impl NetworkEventActor {
         self.request.connect_time = request.connect_time;
         self.request.send_time = request.send_time;
         self.is_xhr = request.is_xhr;
     }
 
     pub fn add_response(&mut self, response: DevtoolsHttpResponse) {
         self.response.headers = response.headers.clone();
         self.response.status = response.status.as_ref().map(|&(s, ref st)| {
-            let status_text = UTF_8.decode(st, DecoderTrap::Replace).unwrap();
+            let status_text = String::from_utf8_lossy(st).into_owned();
             RawStatus(s, Cow::from(status_text))
         });
         self.response.body = response.body.clone();
     }
 
     pub fn event_actor(&self) -> EventActor {
         // TODO: Send the correct values for startedDateTime, isXHR, private
         EventActor {
--- a/servo/components/devtools/lib.rs
+++ b/servo/components/devtools/lib.rs
@@ -10,17 +10,16 @@
 #![crate_name = "devtools"]
 #![crate_type = "rlib"]
 
 #![allow(non_snake_case)]
 #![deny(unsafe_code)]
 #![feature(box_syntax)]
 
 extern crate devtools_traits;
-extern crate encoding;
 extern crate hyper;
 extern crate ipc_channel;
 #[macro_use]
 extern crate log;
 extern crate msg;
 extern crate serde;
 #[macro_use]
 extern crate serde_derive;
--- a/servo/components/layout/animation.rs
+++ b/servo/components/layout/animation.rs
@@ -1,36 +1,36 @@
 /* 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/. */
 
 //! CSS transitions and animations.
 
 use context::LayoutContext;
 use flow::{self, Flow};
+use fnv::FnvHashMap;
 use gfx::display_list::OpaqueNode;
 use ipc_channel::ipc::IpcSender;
 use msg::constellation_msg::PipelineId;
 use opaque_node::OpaqueNodeMethods;
 use script_traits::{AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg};
 use script_traits::UntrustedNodeAddress;
-use std::collections::HashMap;
 use std::sync::mpsc::Receiver;
 use style::animation::{Animation, update_style_for_animation};
 use style::font_metrics::ServoMetricsProvider;
 use style::selector_parser::RestyleDamage;
 use style::timer::Timer;
 
 /// Processes any new animations that were discovered after style recalculation.
 /// Also expire any old animations that have completed, inserting them into
 /// `expired_animations`.
 pub fn update_animation_state(constellation_chan: &IpcSender<ConstellationMsg>,
                               script_chan: &IpcSender<ConstellationControlMsg>,
-                              running_animations: &mut HashMap<OpaqueNode, Vec<Animation>>,
-                              expired_animations: &mut HashMap<OpaqueNode, Vec<Animation>>,
+                              running_animations: &mut FnvHashMap<OpaqueNode, Vec<Animation>>,
+                              expired_animations: &mut FnvHashMap<OpaqueNode, Vec<Animation>>,
                               mut newly_transitioning_nodes: Option<&mut Vec<UntrustedNodeAddress>>,
                               new_animations_receiver: &Receiver<Animation>,
                               pipeline_id: PipelineId,
                               timer: &Timer) {
     let mut new_running_animations = vec![];
     while let Ok(animation) = new_animations_receiver.try_recv() {
         let mut should_push = true;
         if let Animation::Keyframes(ref node, ref name, ref state) = animation {
@@ -144,17 +144,17 @@ pub fn update_animation_state(constellat
 
 /// Recalculates style for a set of animations. This does *not* run with the DOM
 /// lock held.
 // NB: This is specific for SelectorImpl, since the layout context and the
 // flows are SelectorImpl specific too. If that goes away at some point,
 // this should be made generic.
 pub fn recalc_style_for_animations(context: &LayoutContext,
                                    flow: &mut Flow,
-                                   animations: &HashMap<OpaqueNode,
+                                   animations: &FnvHashMap<OpaqueNode,
                                                         Vec<Animation>>) {
     let mut damage = RestyleDamage::empty();
     flow.mutate_fragments(&mut |fragment| {
         if let Some(ref animations) = animations.get(&fragment.node) {
             for animation in animations.iter() {
                 let old_style = fragment.style.clone();
                 update_style_for_animation(&context.style_context,
                                            animation,
--- a/servo/components/layout/display_list_builder.rs
+++ b/servo/components/layout/display_list_builder.rs
@@ -1419,20 +1419,20 @@ impl FragmentDisplayListBuilding for Fra
                         let corners = &border_style_struct.border_image_slice.offsets;
 
                         state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
                             base: base,
                             border_widths: border.to_physical(style.writing_mode),
                             details: BorderDetails::Image(ImageBorder {
                                 image: webrender_image,
                                 fill: border_style_struct.border_image_slice.fill,
-                                slice: SideOffsets2D::new(corners.top.resolve(webrender_image.height),
-                                                          corners.right.resolve(webrender_image.width),
-                                                          corners.bottom.resolve(webrender_image.height),
-                                                          corners.left.resolve(webrender_image.width)),
+                                slice: SideOffsets2D::new(corners.0.resolve(webrender_image.height),
+                                                          corners.1.resolve(webrender_image.width),
+                                                          corners.2.resolve(webrender_image.height),
+                                                          corners.3.resolve(webrender_image.width)),
                                 // TODO(gw): Support border-image-outset
                                 outset: SideOffsets2D::zero(),
                                 repeat_horizontal: convert_repeat_mode(border_style_struct.border_image_repeat.0),
                                 repeat_vertical: convert_repeat_mode(border_style_struct.border_image_repeat.1),
                             }),
                         }));
                     }
                 }
--- a/servo/components/layout/model.rs
+++ b/servo/components/layout/model.rs
@@ -9,19 +9,18 @@
 use app_units::Au;
 use euclid::{Matrix4D, SideOffsets2D, Size2D};
 use fragment::Fragment;
 use std::cmp::{max, min};
 use std::fmt;
 use style::computed_values::transform::ComputedMatrix;
 use style::logical_geometry::{LogicalMargin, WritingMode};
 use style::properties::ServoComputedValues;
-use style::values::computed::{BorderRadiusSize, LengthOrPercentageOrAuto};
+use style::values::computed::{BorderCornerRadius, LengthOrPercentageOrAuto};
 use style::values::computed::{LengthOrPercentage, LengthOrPercentageOrNone};
-use style::values::generics;
 
 /// A collapsible margin. See CSS 2.1 § 8.3.1.
 #[derive(Copy, Clone, Debug)]
 pub struct AdjoiningMargins {
     /// The value of the greatest positive margin.
     pub most_positive: Au,
 
     /// The actual value (not the absolute value) of the negative margin with the largest absolute
@@ -467,23 +466,22 @@ pub fn style_length(style_length: Length
 ///
 /// Note that percentages in `border-radius` are resolved against the relevant
 /// box dimension instead of only against the width per [1]:
 ///
 /// > Percentages: Refer to corresponding dimension of the border box.
 ///
 /// [1]: https://drafts.csswg.org/css-backgrounds-3/#border-radius
 pub fn specified_border_radius(
-    radius: BorderRadiusSize,
+    radius: BorderCornerRadius,
     containing_size: Size2D<Au>)
     -> Size2D<Au>
 {
-    let generics::BorderRadiusSize(size) = radius;
-    let w = size.width.to_used_value(containing_size.width);
-    let h = size.height.to_used_value(containing_size.height);
+    let w = radius.0.width.to_used_value(containing_size.width);
+    let h = radius.0.height.to_used_value(containing_size.height);
     Size2D::new(w, h)
 }
 
 #[inline]
 pub fn padding_from_style(style: &ServoComputedValues,
                           containing_block_inline_size: Au,
                           writing_mode: WritingMode)
                           -> LogicalMargin<Au> {
--- a/servo/components/layout_thread/lib.rs
+++ b/servo/components/layout_thread/lib.rs
@@ -39,17 +39,17 @@ extern crate servo_url;
 extern crate style;
 extern crate webrender_traits;
 
 use app_units::Au;
 use euclid::point::Point2D;
 use euclid::rect::Rect;
 use euclid::scale_factor::ScaleFactor;
 use euclid::size::Size2D;
-use fnv::FnvHasher;
+use fnv::FnvHashMap;
 use gfx::display_list::{OpaqueNode, WebRenderImageInfo};
 use gfx::font;
 use gfx::font_cache_thread::FontCacheThread;
 use gfx::font_context;
 use gfx_traits::{Epoch, node_id_from_clip_id};
 use heapsize::HeapSizeOf;
 use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
 use ipc_channel::router::ROUTER;
@@ -94,17 +94,16 @@ use selectors::Element;
 use servo_config::opts;
 use servo_config::prefs::PREFS;
 use servo_config::resource_files::read_resource_file;
 use servo_geometry::max_rect;
 use servo_url::ServoUrl;
 use std::borrow::ToOwned;
 use std::cell::{Cell, RefCell};
 use std::collections::HashMap;
-use std::hash::BuildHasherDefault;
 use std::marker::PhantomData;
 use std::mem as std_mem;
 use std::ops::{Deref, DerefMut};
 use std::process;
 use std::sync::{Arc, Mutex, MutexGuard};
 use std::sync::atomic::{AtomicUsize, Ordering};
 use std::sync::mpsc::{Receiver, Sender, channel};
 use std::thread;
@@ -198,20 +197,20 @@ pub struct LayoutThread {
 
     /// The root of the flow tree.
     root_flow: RefCell<Option<FlowRef>>,
 
     /// The document-specific shared lock used for author-origin stylesheets
     document_shared_lock: Option<SharedRwLock>,
 
     /// The list of currently-running animations.
-    running_animations: StyleArc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>,
+    running_animations: StyleArc<RwLock<FnvHashMap<OpaqueNode, Vec<Animation>>>>,
 
     /// The list of animations that have expired since the last style recalculation.
-    expired_animations: StyleArc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>,
+    expired_animations: StyleArc<RwLock<FnvHashMap<OpaqueNode, Vec<Animation>>>>,
 
     /// A counter for epoch messages
     epoch: Cell<Epoch>,
 
     /// The size of the viewport. This may be different from the size of the screen due to viewport
     /// constraints.
     viewport_size: Size2D<Au>,
 
@@ -219,19 +218,18 @@ pub struct LayoutThread {
     /// structures, while still letting the LayoutThread modify them.
     ///
     /// All the other elements of this struct are read-only.
     rw_data: Arc<Mutex<LayoutThreadData>>,
 
     /// The CSS error reporter for all CSS loaded in this layout thread
     error_reporter: CSSErrorReporter,
 
-    webrender_image_cache: Arc<RwLock<HashMap<(ServoUrl, UsePlaceholder),
-                                              WebRenderImageInfo,
-                                              BuildHasherDefault<FnvHasher>>>>,
+    webrender_image_cache: Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder),
+                                                 WebRenderImageInfo>>>,
 
     /// Webrender interface.
     webrender_api: webrender_traits::RenderApi,
 
     /// The timer object to control the timing of the animations. This should
     /// only be a test-mode timer during testing for animations.
     timer: Timer,
 
@@ -487,18 +485,18 @@ impl LayoutThread {
             parallel_traversal: parallel_traversal,
             parallel_flag: true,
             generation: Cell::new(0),
             new_animations_sender: new_animations_sender,
             new_animations_receiver: new_animations_receiver,
             outstanding_web_fonts: outstanding_web_fonts_counter,
             root_flow: RefCell::new(None),
             document_shared_lock: None,
-            running_animations: StyleArc::new(RwLock::new(HashMap::new())),
-            expired_animations: StyleArc::new(RwLock::new(HashMap::new())),
+            running_animations: StyleArc::new(RwLock::new(FnvHashMap::default())),
+            expired_animations: StyleArc::new(RwLock::new(FnvHashMap::default())),
             epoch: Cell::new(Epoch(0)),
             viewport_size: Size2D::new(Au(0), Au(0)),
             webrender_api: webrender_api_sender.create_api(),
             stylist: stylist,
             rw_data: Arc::new(Mutex::new(
                 LayoutThreadData {
                     constellation_chan: constellation_chan,
                     display_list: None,
@@ -516,17 +514,17 @@ impl LayoutThread {
                     text_index_response: TextIndexResponse(None),
                     nodes_from_point_response: vec![],
                 })),
             error_reporter: CSSErrorReporter {
                 pipelineid: id,
                 script_chan: Arc::new(Mutex::new(script_chan)),
             },
             webrender_image_cache:
-                Arc::new(RwLock::new(HashMap::with_hasher(Default::default()))),
+                Arc::new(RwLock::new(FnvHashMap::default())),
             timer:
                 if PREFS.get("layout.animations.test.enabled")
                            .as_boolean().unwrap_or(false) {
                    Timer::test_mode()
                 } else {
                     Timer::new()
                 },
             layout_threads: layout_threads,
--- a/servo/components/profile/mem.rs
+++ b/servo/components/profile/mem.rs
@@ -500,33 +500,33 @@ mod system_reporter {
         Some(value as usize)
     }
 
     #[cfg(target_os = "windows")]
     fn jemalloc_stat(_value_name: &str) -> Option<usize> {
         None
     }
 
-    // Like std::macros::try!, but for Option<>.
-    macro_rules! option_try(
-        ($e:expr) => (match $e { Some(e) => e, None => return None })
-    );
-
     #[cfg(target_os = "linux")]
     fn page_size() -> usize {
         unsafe {
             ::libc::sysconf(::libc::_SC_PAGESIZE) as usize
         }
     }
 
     #[cfg(target_os = "linux")]
     fn proc_self_statm_field(field: usize) -> Option<usize> {
         use std::fs::File;
         use std::io::Read;
 
+        // Like std::macros::try!, but for Option<>.
+        macro_rules! option_try(
+            ($e:expr) => (match $e { Some(e) => e, None => return None })
+        );
+
         let mut f = option_try!(File::open("/proc/self/statm").ok());
         let mut contents = String::new();
         option_try!(f.read_to_string(&mut contents).ok());
         let s = option_try!(contents.split_whitespace().nth(field));
         let npages = option_try!(s.parse::<usize>().ok());
         Some(npages * page_size())
     }
 
--- a/servo/components/script/Cargo.toml
+++ b/servo/components/script/Cargo.toml
@@ -84,13 +84,14 @@ servo_rand = {path = "../rand"}
 servo_url = {path = "../url"}
 smallvec = "0.3"
 style = {path = "../style"}
 style_traits = {path = "../style_traits"}
 swapper = "0.0.4"
 time = "0.1.12"
 unicode-segmentation = "1.1.0"
 url = {version = "1.2", features = ["heap_size", "query_encoding"]}
+utf-8 = "0.7"
 uuid = {version = "0.4", features = ["v4"]}
 xml5ever = {version = "0.7", features = ["unstable"]}
 webrender_traits = {git = "https://github.com/servo/webrender", features = ["ipc"]}
 webvr = {path = "../webvr"}
 webvr_traits = {path = "../webvr_traits"}
--- a/servo/components/script/body.rs
+++ b/servo/components/script/body.rs
@@ -6,18 +6,16 @@ use dom::bindings::codegen::Bindings::Fo
 use dom::bindings::error::{Error, Fallible};
 use dom::bindings::js::Root;
 use dom::bindings::reflector::DomObject;
 use dom::bindings::str::USVString;
 use dom::blob::{Blob, BlobImpl};
 use dom::formdata::FormData;
 use dom::globalscope::GlobalScope;
 use dom::promise::Promise;
-use encoding::all::UTF_8;
-use encoding::types::{DecoderTrap, Encoding};
 use js::jsapi::JSContext;
 use js::jsapi::JS_ClearPendingException;
 use js::jsapi::JS_ParseJSON;
 use js::jsapi::Value as JSValue;
 use js::jsval::UndefinedValue;
 use mime::{Mime, TopLevel, SubLevel};
 use std::cell::Ref;
 use std::rc::Rc;
@@ -105,24 +103,23 @@ fn run_package_data_algorithm<T: BodyOpe
         BodyType::Text => run_text_data_algorithm(bytes),
         BodyType::Json => run_json_data_algorithm(cx, bytes),
         BodyType::Blob => run_blob_data_algorithm(&global, bytes, mime),
         BodyType::FormData => run_form_data_algorithm(&global, bytes, mime),
     }
 }
 
 fn run_text_data_algorithm(bytes: Vec<u8>) -> Fallible<FetchedData> {
-    let text = UTF_8.decode(&bytes, DecoderTrap::Replace).unwrap();
-    Ok(FetchedData::Text(text))
+    Ok(FetchedData::Text(String::from_utf8_lossy(&bytes).into_owned()))
 }
 
 #[allow(unsafe_code)]
 fn run_json_data_algorithm(cx: *mut JSContext,
                            bytes: Vec<u8>) -> Fallible<FetchedData> {
-    let json_text = UTF_8.decode(&bytes, DecoderTrap::Replace).unwrap();
+    let json_text = String::from_utf8_lossy(&bytes);
     let json_text: Vec<u16> = json_text.encode_utf16().collect();
     rooted!(in(cx) let mut rval = UndefinedValue());
     unsafe {
         if !JS_ParseJSON(cx,
                          json_text.as_ptr(),
                          json_text.len() as u32,
                          rval.handle_mut()) {
             JS_ClearPendingException(cx);
--- a/servo/components/script/dom/blob.rs
+++ b/servo/components/script/dom/blob.rs
@@ -7,18 +7,16 @@ use dom::bindings::codegen::Bindings::Bl
 use dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
 use dom::bindings::codegen::UnionTypes::BlobOrString;
 use dom::bindings::error::{Error, Fallible};
 use dom::bindings::js::{JS, Root};
 use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
 use dom::bindings::str::DOMString;
 use dom::globalscope::GlobalScope;
 use dom_struct::dom_struct;
-use encoding::all::UTF_8;
-use encoding::types::{EncoderTrap, Encoding};
 use ipc_channel::ipc;
 use net_traits::{CoreResourceMsg, IpcSend};
 use net_traits::blob_url_store::{BlobBuf, get_blob_origin};
 use net_traits::filemanager_thread::{FileManagerThreadMsg, ReadFileProgress, RelativePos};
 use std::mem;
 use std::ops::Index;
 use std::path::PathBuf;
 use uuid::Uuid;
@@ -332,22 +330,21 @@ fn read_file(global: &GlobalScope, id: U
 /// Extract bytes from BlobParts, used by Blob and File constructor
 /// https://w3c.github.io/FileAPI/#constructorBlob
 pub fn blob_parts_to_bytes(blobparts: Vec<BlobOrString>) -> Result<Vec<u8>, ()> {
     let mut ret = vec![];
 
     for blobpart in &blobparts {
         match blobpart {
             &BlobOrString::String(ref s) => {
-                let mut bytes = UTF_8.encode(s, EncoderTrap::Replace).map_err(|_|())?;
-                ret.append(&mut bytes);
+                ret.extend(s.as_bytes());
             },
             &BlobOrString::Blob(ref b) => {
-                let mut bytes = b.get_bytes().unwrap_or(vec![]);
-                ret.append(&mut bytes);
+                let bytes = b.get_bytes().unwrap_or(vec![]);
+                ret.extend(bytes);
             },
         }
     }
 
     Ok(ret)
 }
 
 impl BlobMethods for Blob {
--- a/servo/components/script/dom/eventsource.rs
+++ b/servo/components/script/dom/eventsource.rs
@@ -11,18 +11,16 @@ use dom::bindings::js::Root;
 use dom::bindings::refcounted::Trusted;
 use dom::bindings::reflector::{DomObject, reflect_dom_object};
 use dom::bindings::str::DOMString;
 use dom::event::Event;
 use dom::eventtarget::EventTarget;
 use dom::globalscope::GlobalScope;
 use dom::messageevent::MessageEvent;
 use dom_struct::dom_struct;
-use encoding::Encoding;
-use encoding::all::UTF_8;
 use euclid::length::Length;
 use hyper::header::{Accept, qitem};
 use ipc_channel::ipc;
 use ipc_channel::router::ROUTER;
 use js::conversions::ToJSValConvertible;
 use js::jsapi::JSAutoCompartment;
 use js::jsval::UndefinedValue;
 use mime::{Mime, TopLevel, SubLevel};
@@ -34,16 +32,17 @@ use script_thread::Runnable;
 use servo_atoms::Atom;
 use servo_url::ServoUrl;
 use std::cell::Cell;
 use std::mem;
 use std::str::{Chars, FromStr};
 use std::sync::{Arc, Mutex};
 use task_source::TaskSource;
 use timers::OneshotTimerCallback;
+use utf8;
 
 header! { (LastEventId, "Last-Event-ID") => [String] }
 
 const DEFAULT_RECONNECTION_TIME: u64 = 5000;
 
 #[derive(JSTraceable, PartialEq, Copy, Clone, Debug, HeapSizeOf)]
 struct GenerationId(u32);
 
@@ -71,16 +70,18 @@ pub struct EventSource {
 enum ParserState {
     Field,
     Comment,
     Value,
     Eol
 }
 
 struct EventSourceContext {
+    incomplete_utf8: Option<utf8::Incomplete>,
+
     event_source: Trusted<EventSource>,
     gen_id: GenerationId,
     action_sender: ipc::IpcSender<FetchResponseMsg>,
 
     parser_state: ParserState,
     field: String,
     value: String,
     origin: String,
@@ -288,22 +289,51 @@ impl FetchResponseListener for EventSour
             }
             Err(_) => {
                 self.reestablish_the_connection();
             }
         }
     }
 
     fn process_response_chunk(&mut self, chunk: Vec<u8>) {
-        let mut stream = String::new();
-        UTF_8.raw_decoder().raw_feed(&chunk, &mut stream);
-        self.parse(stream.chars())
+        let mut input = &*chunk;
+        if let Some(mut incomplete) = self.incomplete_utf8.take() {
+            match incomplete.try_complete(input) {
+                None => return,
+                Some((result, remaining_input)) => {
+                    self.parse(result.unwrap_or("\u{FFFD}").chars());
+                    input = remaining_input;
+                }
+            }
+        }
+
+        while !input.is_empty() {
+            match utf8::decode(&input) {
+                Ok(s) => {
+                    self.parse(s.chars());
+                    return
+                }
+                Err(utf8::DecodeError::Invalid { valid_prefix, remaining_input, .. }) => {
+                    self.parse(valid_prefix.chars());
+                    self.parse("\u{FFFD}".chars());
+                    input = remaining_input;
+                }
+                Err(utf8::DecodeError::Incomplete { valid_prefix, incomplete_suffix }) => {
+                    self.parse(valid_prefix.chars());
+                    self.incomplete_utf8 = Some(incomplete_suffix);
+                    return
+                }
+            }
+        }
     }
 
     fn process_response_eof(&mut self, _response: Result<(), NetworkError>) {
+        if let Some(_) = self.incomplete_utf8.take() {
+            self.parse("\u{FFFD}".chars());
+        }
         self.reestablish_the_connection();
     }
 }
 
 impl PreInvoke for EventSourceContext {
     fn should_invoke(&self) -> bool {
         self.event_source.root().generation_id.get() == self.gen_id
     }
@@ -373,16 +403,18 @@ impl EventSource {
         request.headers.set(Accept(vec![qitem(mime!(Text / EventStream))]));
         // Step 11
         request.cache_mode = CacheMode::NoStore;
         // Step 12
         *ev.request.borrow_mut() = Some(request.clone());
         // Step 14
         let (action_sender, action_receiver) = ipc::channel().unwrap();
         let context = EventSourceContext {
+            incomplete_utf8: None,
+
             event_source: Trusted::new(&ev),
             gen_id: ev.generation_id.get(),
             action_sender: action_sender.clone(),
 
             parser_state: ParserState::Eol,
             field: String::new(),
             value: String::new(),
             origin: String::new(),
--- a/servo/components/script/dom/htmlimageelement.rs
+++ b/servo/components/script/dom/htmlimageelement.rs
@@ -17,73 +17,81 @@ use dom::bindings::error::Fallible;
 use dom::bindings::inheritance::Castable;
 use dom::bindings::js::{LayoutJS, MutNullableJS, Root};
 use dom::bindings::refcounted::Trusted;
 use dom::bindings::reflector::DomObject;
 use dom::bindings::str::DOMString;
 use dom::document::Document;
 use dom::element::{AttributeMutation, Element, RawLayoutElementHelpers};
 use dom::element::{reflect_cross_origin_attribute, set_cross_origin_attribute};
-use dom::event::Event;
+use dom::event::{Event, EventBubbles, EventCancelable};
 use dom::eventtarget::EventTarget;
 use dom::htmlareaelement::HTMLAreaElement;
 use dom::htmlelement::HTMLElement;
 use dom::htmlformelement::{FormControl, HTMLFormElement};
 use dom::htmlmapelement::HTMLMapElement;
 use dom::mouseevent::MouseEvent;
 use dom::node::{Node, NodeDamage, document_from_node, window_from_node};
+use dom::progressevent::ProgressEvent;
 use dom::values::UNSIGNED_LONG_MAX;
 use dom::virtualmethods::VirtualMethods;
 use dom::window::Window;
 use dom_struct::dom_struct;
 use euclid::point::Point2D;
 use html5ever::{LocalName, Prefix};
 use ipc_channel::ipc;
 use ipc_channel::router::ROUTER;
+use microtask::{Microtask, MicrotaskRunnable};
 use net_traits::{FetchResponseListener, FetchMetadata, NetworkError, FetchResponseMsg};
 use net_traits::image::base::{Image, ImageMetadata};
 use net_traits::image_cache::{CanRequestImages, ImageCache, ImageOrMetadataAvailable};
 use net_traits::image_cache::{ImageResponder, ImageResponse, ImageState, PendingImageId};
 use net_traits::image_cache::UsePlaceholder;
 use net_traits::request::{RequestInit, Type as RequestType};
 use network_listener::{NetworkListener, PreInvoke};
 use num_traits::ToPrimitive;
-use script_thread::Runnable;
+use script_thread::{Runnable, ScriptThread};
 use servo_url::ServoUrl;
 use servo_url::origin::ImmutableOrigin;
-use std::cell::Cell;
+use std::cell::{Cell, RefMut};
 use std::default::Default;
 use std::i32;
 use std::sync::{Arc, Mutex};
 use style::attr::{AttrValue, LengthOrPercentageOrAuto};
 use task_source::TaskSource;
 
-#[derive(JSTraceable, HeapSizeOf)]
+#[derive(Clone, Copy, JSTraceable, HeapSizeOf)]
 #[allow(dead_code)]
 enum State {
     Unavailable,
     PartiallyAvailable,
     CompletelyAvailable,
     Broken,
 }
+#[derive(Copy, Clone, JSTraceable, HeapSizeOf)]
+enum ImageRequestPhase {
+    Pending,
+    Current
+}
 #[derive(JSTraceable, HeapSizeOf)]
 #[must_root]
 struct ImageRequest {
     state: State,
     parsed_url: Option<ServoUrl>,
     source_url: Option<DOMString>,
     blocker: Option<LoadBlocker>,
     #[ignore_heap_size_of = "Arc"]
     image: Option<Arc<Image>>,
     metadata: Option<ImageMetadata>,
     final_url: Option<ServoUrl>,
 }
 #[dom_struct]
 pub struct HTMLImageElement {
     htmlelement: HTMLElement,
+    image_request: Cell<ImageRequestPhase>,
     current_request: DOMRefCell<ImageRequest>,
     pending_request: DOMRefCell<ImageRequest>,
     form_owner: MutNullableJS<HTMLFormElement>,
     generation: Cell<u32>,
 }
 
 impl HTMLImageElement {
     pub fn get_url(&self) -> Option<ServoUrl> {
@@ -171,28 +179,17 @@ impl FetchResponseListener for ImageCont
             FetchResponseMsg::ProcessResponseEOF(response));
     }
 }
 
 impl PreInvoke for ImageContext {}
 
 impl HTMLImageElement {
     /// Update the current image with a valid URL.
-    fn update_image_with_url(&self, img_url: ServoUrl, src: DOMString) {
-        {
-            let mut current_request = self.current_request.borrow_mut();
-            current_request.parsed_url = Some(img_url.clone());
-            current_request.source_url = Some(src);
-
-            LoadBlocker::terminate(&mut current_request.blocker);
-            let document = document_from_node(self);
-            current_request.blocker =
-                Some(LoadBlocker::new(&*document, LoadType::Image(img_url.clone())));
-        }
-
+    fn fetch_image(&self, img_url: &ServoUrl) {
         fn add_cache_listener_for_element(image_cache: Arc<ImageCache>,
                                           id: PendingImageId,
                                           elem: &HTMLImageElement) {
             let trusted_node = Trusted::new(elem);
             let (responder_sender, responder_receiver) = ipc::channel().unwrap();
 
             let window = window_from_node(elem);
             let task_source = window.networking_task_source();
@@ -230,22 +227,22 @@ impl HTMLImageElement {
             }
 
             Err(ImageState::LoadError) => {
                 self.process_image_response(ImageResponse::None);
             }
 
             Err(ImageState::NotRequested(id)) => {
                 add_cache_listener_for_element(image_cache, id, self);
-                self.request_image(img_url, id);
+                self.fetch_request(img_url, id);
             }
         }
     }
 
-    fn request_image(&self, img_url: ServoUrl, id: PendingImageId) {
+    fn fetch_request(&self, img_url: &ServoUrl, id: PendingImageId) {
         let document = document_from_node(self);
         let window = window_from_node(self);
 
         let context = Arc::new(Mutex::new(ImageContext {
             image_cache: window.image_cache(),
             status: Ok(()),
             id: id,
         }));
@@ -268,117 +265,380 @@ impl HTMLImageElement {
             .. RequestInit::default()
         };
 
         // This is a background load because the load blocker already fulfills the
         // purpose of delaying the document's load event.
         document.loader().fetch_async_background(request, action_sender);
     }
 
+    /// Step 14 of https://html.spec.whatwg.org/multipage/#update-the-image-data
     fn process_image_response(&self, image: ImageResponse) {
-        let (image, metadata, trigger_image_load, trigger_image_error) = match image {
-            ImageResponse::Loaded(image, url) | ImageResponse::PlaceholderLoaded(image, url) => {
+        // TODO: Handle multipart/x-mixed-replace
+        let (trigger_image_load, trigger_image_error) = match (image, self.image_request.get()) {
+            (ImageResponse::Loaded(image, url), ImageRequestPhase::Current) |
+            (ImageResponse::PlaceholderLoaded(image, url), ImageRequestPhase::Current) => {
+                self.current_request.borrow_mut().metadata = Some(ImageMetadata {
+                    height: image.height,
+                    width: image.width
+                });
+                self.current_request.borrow_mut().final_url = Some(url);
+                self.current_request.borrow_mut().image = Some(image);
+                self.current_request.borrow_mut().state = State::CompletelyAvailable;
+                LoadBlocker::terminate(&mut self.current_request.borrow_mut().blocker);
+                // Mark the node dirty
+                self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
+                (true, false)
+            },
+            (ImageResponse::Loaded(image, url), ImageRequestPhase::Pending) |
+            (ImageResponse::PlaceholderLoaded(image, url), ImageRequestPhase::Pending) => {
+                self.abort_request(State::Unavailable, ImageRequestPhase::Pending);
+                self.image_request.set(ImageRequestPhase::Current);
+                self.current_request.borrow_mut().metadata = Some(ImageMetadata {
+                    height: image.height,
+                    width: image.width
+                });
                 self.current_request.borrow_mut().final_url = Some(url);
-                (Some(image.clone()),
-                 Some(ImageMetadata { height: image.height, width: image.width }),
-                 true,
-                 false)
-            }
-            ImageResponse::MetadataLoaded(meta) => {
-                (None, Some(meta), false, false)
-            }
-            ImageResponse::None => (None, None, false, true)
+                self.current_request.borrow_mut().image = Some(image);
+                self.current_request.borrow_mut().state = State::CompletelyAvailable;
+                LoadBlocker::terminate(&mut self.current_request.borrow_mut().blocker);
+                self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
+                (true, false)
+            },
+            (ImageResponse::MetadataLoaded(meta), ImageRequestPhase::Current) => {
+                self.current_request.borrow_mut().state = State::PartiallyAvailable;
+                self.current_request.borrow_mut().metadata = Some(meta);
+                (false, false)
+            },
+            (ImageResponse::MetadataLoaded(_), ImageRequestPhase::Pending) => {
+                self.pending_request.borrow_mut().state = State::PartiallyAvailable;
+                (false, false)
+            },
+            (ImageResponse::None, ImageRequestPhase::Current) => {
+                self.abort_request(State::Broken, ImageRequestPhase::Current);
+                (false, true)
+            },
+            (ImageResponse::None, ImageRequestPhase::Pending) => {
+                self.abort_request(State::Broken, ImageRequestPhase::Current);
+                self.abort_request(State::Broken, ImageRequestPhase::Pending);
+                self.image_request.set(ImageRequestPhase::Current);
+                (false, true)
+            },
         };
-        self.current_request.borrow_mut().image = image;
-        self.current_request.borrow_mut().metadata = metadata;
 
-        // Mark the node dirty
-        self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
-
-        // Fire image.onload
+        // Fire image.onload and loadend
         if trigger_image_load {
+            // TODO: https://html.spec.whatwg.org/multipage/#fire-a-progress-event-or-event
             self.upcast::<EventTarget>().fire_event(atom!("load"));
+            self.upcast::<EventTarget>().fire_event(atom!("loadend"));
         }
 
         // Fire image.onerror
         if trigger_image_error {
             self.upcast::<EventTarget>().fire_event(atom!("error"));
-        }
-
-        if trigger_image_load || trigger_image_error {
-            LoadBlocker::terminate(&mut self.current_request.borrow_mut().blocker);
+            self.upcast::<EventTarget>().fire_event(atom!("loadend"));
         }
 
         // Trigger reflow
         let window = window_from_node(self);
         window.add_pending_reflow();
     }
 
-    /// Makes the local `image` member match the status of the `src` attribute and starts
-    /// prefetching the image. This method must be called after `src` is changed.
-    fn update_image(&self, value: Option<(DOMString, ServoUrl)>) {
-        // Force any in-progress request to be ignored.
-        self.generation.set(self.generation.get() + 1);
+    /// https://html.spec.whatwg.org/multipage/#abort-the-image-request
+    fn abort_request(&self, state: State, request: ImageRequestPhase) {
+        let mut request = match request {
+            ImageRequestPhase::Current => self.current_request.borrow_mut(),
+            ImageRequestPhase::Pending => self.pending_request.borrow_mut(),
+        };
+        LoadBlocker::terminate(&mut request.blocker);
+        request.state = state;
+        request.image = None;
+        request.metadata = None;
+    }
 
+    /// Step 11.4 of https://html.spec.whatwg.org/multipage/#update-the-image-data
+    fn set_current_request_url_to_selected_fire_error_and_loadend(&self, src: DOMString) {
+        struct Task {
+            img: Trusted<HTMLImageElement>,
+            src: String,
+        }
+        impl Runnable for Task {
+            fn handler(self: Box<Self>) {
+                let img = self.img.root();
+                {
+                    let mut current_request = img.current_request.borrow_mut();
+                    current_request.source_url = Some(DOMString::from_string(self.src));
+                }
+                img.upcast::<EventTarget>().fire_event(atom!("error"));
+                img.upcast::<EventTarget>().fire_event(atom!("loadend"));
+                img.abort_request(State::Broken, ImageRequestPhase::Current);
+                img.abort_request(State::Broken, ImageRequestPhase::Pending);
+            }
+        }
+
+        let task = box Task {
+            img: Trusted::new(self),
+            src: src.into()
+        };
+        let document = document_from_node(self);
+        let window = document.window();
+        let task_source = window.dom_manipulation_task_source();
+        let _ = task_source.queue(task, window.upcast());
+    }
+
+    /// Step 10 of html.spec.whatwg.org/multipage/#update-the-image-data
+    fn dispatch_loadstart_progress_event(&self) {
+        struct FireprogressEventTask {
+            img: Trusted<HTMLImageElement>,
+        }
+        impl Runnable for FireprogressEventTask {
+            fn handler(self: Box<Self>) {
+                let progressevent = ProgressEvent::new(&self.img.root().global(),
+                    atom!("loadstart"), EventBubbles::DoesNotBubble, EventCancelable::NotCancelable,
+                    false, 0, 0);
+                progressevent.upcast::<Event>().fire(self.img.root().upcast());
+            }
+        }
+        let runnable = box FireprogressEventTask {
+            img: Trusted::new(self),
+        };
         let document = document_from_node(self);
         let window = document.window();
-        match value {
-            None => {
-                self.current_request.borrow_mut().parsed_url = None;
-                self.current_request.borrow_mut().source_url = None;
-                LoadBlocker::terminate(&mut self.current_request.borrow_mut().blocker);
-                self.current_request.borrow_mut().image = None;
+        let task = window.dom_manipulation_task_source();
+        let _ = task.queue(runnable, window.upcast());
+    }
+
+    /// https://html.spec.whatwg.org/multipage/#update-the-source-set
+    fn update_source_set(&self) -> Vec<DOMString> {
+        let elem = self.upcast::<Element>();
+        // TODO: follow the algorithm
+        let src = elem.get_string_attribute(&local_name!("src"));
+        if src.is_empty() {
+            return vec![]
+        }
+        vec![src]
+    }
+
+    /// https://html.spec.whatwg.org/multipage/#select-an-image-source
+    fn select_image_source(&self) -> Option<DOMString> {
+        // TODO: select an image source from source set
+        self.update_source_set().first().cloned()
+    }
+
+    /// Step 9.2 of https://html.spec.whatwg.org/multipage/#update-the-image-data
+    fn set_current_request_url_to_none_fire_error(&self) {
+        struct SetUrlToNoneTask {
+            img: Trusted<HTMLImageElement>,
+        }
+        impl Runnable for SetUrlToNoneTask {
+            fn handler(self: Box<Self>) {
+                let img = self.img.root();
+                {
+                    let mut current_request = img.current_request.borrow_mut();
+                    current_request.source_url = None;
+                    current_request.parsed_url = None;
+                }
+                let elem = img.upcast::<Element>();
+                if elem.has_attribute(&local_name!("src")) {
+                    img.upcast::<EventTarget>().fire_event(atom!("error"));
+                }
+                img.abort_request(State::Broken, ImageRequestPhase::Current);
+                img.abort_request(State::Broken, ImageRequestPhase::Pending);
             }
-            Some((src, base_url)) => {
-                let img_url = base_url.join(&src);
-                if let Ok(img_url) = img_url {
-                    self.update_image_with_url(img_url, src);
-                } else {
-                    // https://html.spec.whatwg.org/multipage/#update-the-image-data
-                    // Step 11 (error substeps)
-                    debug!("Failed to parse URL {} with base {}", src, base_url);
-                    let mut req = self.current_request.borrow_mut();
+        }
+
+        let task = box SetUrlToNoneTask {
+            img: Trusted::new(self),
+        };
+        let document = document_from_node(self);
+        let window = document.window();
+        let task_source = window.dom_manipulation_task_source();
+        let _ = task_source.queue(task, window.upcast());
+    }
 
-                    // Substeps 1,2
-                    req.image = None;
-                    req.parsed_url = None;
-                    req.state = State::Broken;
-                    // todo: set pending request to null
-                    // (pending requests aren't being used yet)
+    /// Step 5.3.7 of https://html.spec.whatwg.org/multipage/#update-the-image-data
+    fn set_current_request_url_to_string_and_fire_load(&self, src: DOMString, url: ServoUrl) {
+        struct SetUrlToStringTask {
+            img: Trusted<HTMLImageElement>,
+            src: String,
+            url: ServoUrl
+        }
+        impl Runnable for SetUrlToStringTask {
+            fn handler(self: Box<Self>) {
+                let img = self.img.root();
+                {
+                    let mut current_request = img.current_request.borrow_mut();
+                    current_request.parsed_url = Some(self.url.clone());
+                    current_request.source_url = Some(self.src.into());
+                }
+                // TODO: restart animation, if set
+                img.upcast::<EventTarget>().fire_event(atom!("load"));
+            }
+        }
+        let runnable = box SetUrlToStringTask {
+            img: Trusted::new(self),
+            src: src.into(),
+            url: url
+        };
+        let document = document_from_node(self);
+        let window = document.window();
+        let task = window.dom_manipulation_task_source();
+        let _ = task.queue(runnable, window.upcast());
+    }
 
+    fn init_image_request(&self,
+                          request: &mut RefMut<ImageRequest>,
+                          url: &ServoUrl,
+                          src: &DOMString) {
+        request.parsed_url = Some(url.clone());
+        request.source_url = Some(src.clone());
+        request.image = None;
+        request.metadata = None;
+        let document = document_from_node(self);
+        request.blocker = Some(LoadBlocker::new(&*document, LoadType::Image(url.clone())));
+    }
 
-                    struct ImgParseErrorRunnable {
-                        img: Trusted<HTMLImageElement>,
-                        src: String,
+    /// Step 12 of html.spec.whatwg.org/multipage/#update-the-image-data
+    fn prepare_image_request(&self, url: &ServoUrl, src: &DOMString) {
+        match self.image_request.get() {
+            ImageRequestPhase::Pending => {
+                if let Some(pending_url) = self.pending_request.borrow().parsed_url.clone() {
+                    // Step 12.1
+                    if pending_url == *url {
+                        return
                     }
-                    impl Runnable for ImgParseErrorRunnable {
-                        fn handler(self: Box<Self>) {
-                            // https://html.spec.whatwg.org/multipage/#update-the-image-data
-                            // Step 11, substep 5
-                            let img = self.img.root();
-                            img.current_request.borrow_mut().source_url = Some(self.src.into());
-                            img.upcast::<EventTarget>().fire_event(atom!("error"));
-                            img.upcast::<EventTarget>().fire_event(atom!("loadend"));
+                }
+            },
+            ImageRequestPhase::Current => {
+                let mut current_request = self.current_request.borrow_mut();
+                let mut pending_request = self.pending_request.borrow_mut();
+                // step 12.4, create a new "image_request"
+                match (current_request.parsed_url.clone(), current_request.state) {
+                    (Some(parsed_url), State::PartiallyAvailable) => {
+                        // Step 12.2
+                        if parsed_url == *url {
+                            // 12.3 abort pending request
+                            pending_request.image = None;
+                            pending_request.parsed_url = None;
+                            LoadBlocker::terminate(&mut pending_request.blocker);
+                            // TODO: queue a task to restart animation, if restart-animation is set
+                            return
                         }
-                    }
-
-                    let runnable = box ImgParseErrorRunnable {
-                        img: Trusted::new(self),
-                        src: src.into(),
-                    };
-                    let task = window.dom_manipulation_task_source();
-                    let _ = task.queue(runnable, window.upcast());
+                        self.image_request.set(ImageRequestPhase::Pending);
+                        self.init_image_request(&mut pending_request, &url, &src);
+                        self.fetch_image(&url);
+                    },
+                    (_, State::Broken) | (_, State::Unavailable) => {
+                        // Step 12.5
+                        self.init_image_request(&mut current_request, &url, &src);
+                        self.fetch_image(&url);
+                    },
+                    (_, _) => {
+                        // step 12.6
+                        self.image_request.set(ImageRequestPhase::Pending);
+                        self.init_image_request(&mut pending_request, &url, &src);
+                        self.fetch_image(&url);
+                    },
                 }
             }
         }
     }
 
+    /// Step 8-12 of html.spec.whatwg.org/multipage/#update-the-image-data
+    fn update_the_image_data_sync_steps(&self) {
+        let document = document_from_node(self);
+        // Step 8
+        // TODO: take pixel density into account
+        match self.select_image_source() {
+            Some(src) => {
+                // Step 10
+                self.dispatch_loadstart_progress_event();
+                // Step 11
+                let base_url = document.base_url();
+                let parsed_url = base_url.join(&src);
+                match parsed_url {
+                    Ok(url) => {
+                         // Step 12
+                        self.prepare_image_request(&url, &src);
+                    },
+                    Err(_) => {
+                        // Step 11.1-11.5
+                        self.set_current_request_url_to_selected_fire_error_and_loadend(src);
+                    }
+                }
+            },
+            None => {
+                // Step 9
+                self.set_current_request_url_to_none_fire_error();
+            },
+        }
+    }
+
+    /// https://html.spec.whatwg.org/multipage/#update-the-image-data
+    fn update_the_image_data(&self) {
+        let document = document_from_node(self);
+        let window = document.window();
+        let elem = self.upcast::<Element>();
+        let src = elem.get_string_attribute(&local_name!("src"));
+        let base_url = document.base_url();
+
+        // https://html.spec.whatwg.org/multipage/#reacting-to-dom-mutations
+        // Always first set the current request to unavailable,
+        // ensuring img.complete is false.
+        {
+            let mut current_request = self.current_request.borrow_mut();
+            current_request.state = State::Unavailable;
+        }
+
+        if !document.is_active() {
+            // Step 1 (if the document is inactive)
+            // TODO: use GlobalScope::enqueue_microtask,
+            // to queue micro task to come back to this algorithm
+        }
+        // Step 2 abort if user-agent does not supports images
+        // NOTE: Servo only supports images, skipping this step
+
+        // step 3, 4
+        // TODO: take srcset and parent images into account
+        if !src.is_empty() {
+            // TODO: take pixel density into account
+            if let Ok(img_url) = base_url.join(&src) {
+                // step 5, check the list of available images
+                let image_cache = window.image_cache();
+                let response = image_cache.find_image_or_metadata(img_url.clone().into(),
+                                                                  UsePlaceholder::No,
+                                                                  CanRequestImages::No);
+                if let Ok(ImageOrMetadataAvailable::ImageAvailable(image, url)) = response {
+                    // Step 5.3
+                    let metadata = ImageMetadata { height: image.height, width: image.width };
+                    // Step 5.3.2 abort requests
+                    self.abort_request(State::CompletelyAvailable, ImageRequestPhase::Current);
+                    self.abort_request(State::CompletelyAvailable, ImageRequestPhase::Pending);
+                    let mut current_request = self.current_request.borrow_mut();
+                    current_request.final_url = Some(url);
+                    current_request.image = Some(image.clone());
+                    current_request.metadata = Some(metadata);
+                    self.set_current_request_url_to_string_and_fire_load(src, img_url);
+                    return
+                }
+            }
+        }
+        // step 6, await a stable state.
+        self.generation.set(self.generation.get() + 1);
+        let task = ImageElementMicrotask::StableStateUpdateImageDataTask {
+            elem: Root::from_ref(self),
+            generation: self.generation.get(),
+        };
+        ScriptThread::await_stable_state(Microtask::ImageElement(task));
+    }
+
     fn new_inherited(local_name: LocalName, prefix: Option<Prefix>, document: &Document) -> HTMLImageElement {
         HTMLImageElement {
             htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
+            image_request: Cell::new(ImageRequestPhase::Current),
             current_request: DOMRefCell::new(ImageRequest {
                 state: State::Unavailable,
                 parsed_url: None,
                 source_url: None,
                 image: None,
                 metadata: None,
                 blocker: None,
                 final_url: None,
@@ -451,16 +711,38 @@ impl HTMLImageElement {
         match self.current_request.borrow_mut().final_url {
             Some(ref url) => Some(url.origin()),
             None => None
         }
     }
 
 }
 
+#[derive(JSTraceable, HeapSizeOf)]
+pub enum ImageElementMicrotask {
+    StableStateUpdateImageDataTask {
+        elem: Root<HTMLImageElement>,
+        generation: u32,
+    }
+}
+
+impl MicrotaskRunnable for ImageElementMicrotask {
+    fn handler(&self) {
+        match self {
+            &ImageElementMicrotask::StableStateUpdateImageDataTask { ref elem, ref generation } => {
+                // Step 7 of https://html.spec.whatwg.org/multipage/#update-the-image-data,
+                // stop here if other instances of this algorithm have been scheduled
+                if elem.generation.get() == *generation {
+                    elem.update_the_image_data_sync_steps();
+                }
+            },
+        }
+    }
+}
+
 pub trait LayoutHTMLImageElementHelpers {
     #[allow(unsafe_code)]
     unsafe fn image(&self) -> Option<Arc<Image>>;
 
     #[allow(unsafe_code)]
     unsafe fn image_url(&self) -> Option<ServoUrl>;
 
     fn get_width(&self) -> LengthOrPercentageOrAuto;
@@ -577,18 +859,31 @@ impl HTMLImageElementMethods for HTMLIma
         match *metadata {
             Some(ref metadata) => metadata.height,
             None => 0,
         }
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-img-complete
     fn Complete(&self) -> bool {
-        let ref image = self.current_request.borrow().image;
-        image.is_some()
+        let elem = self.upcast::<Element>();
+        // TODO: take srcset into account
+        if !elem.has_attribute(&local_name!("src")) {
+            return true
+        }
+        let src = elem.get_string_attribute(&local_name!("src"));
+        if src.is_empty() {
+            return true
+        }
+        let request = self.current_request.borrow();
+        let request_state = request.state;
+        match request_state {
+            State::CompletelyAvailable | State::Broken => return true,
+            State::PartiallyAvailable | State::Unavailable => return false,
+        }
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-img-currentsrc
     fn CurrentSrc(&self) -> DOMString {
         let ref url = self.current_request.borrow().source_url;
         match *url {
             Some(ref url) => url.clone(),
             None => DOMString::from(""),
@@ -634,32 +929,23 @@ impl HTMLImageElementMethods for HTMLIma
 
 impl VirtualMethods for HTMLImageElement {
     fn super_type(&self) -> Option<&VirtualMethods> {
         Some(self.upcast::<HTMLElement>() as &VirtualMethods)
     }
 
     fn adopting_steps(&self, old_doc: &Document) {
         self.super_type().unwrap().adopting_steps(old_doc);
-
-        let elem = self.upcast::<Element>();
-        let document = document_from_node(self);
-        self.update_image(Some((elem.get_string_attribute(&local_name!("src")),
-                                document.base_url())));
+        self.update_the_image_data();
     }
 
     fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
         self.super_type().unwrap().attribute_mutated(attr, mutation);
         match attr.local_name() {
-            &local_name!("src") => {
-                self.update_image(mutation.new_value(attr).map(|value| {
-                    // FIXME(ajeffrey): convert directly from AttrValue to DOMString
-                    (DOMString::from(&**value), document_from_node(self).base_url())
-                }));
-            },
+            &local_name!("src") => self.update_the_image_data(),
             _ => {},
         }
     }
 
     fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
         match name {
             &local_name!("name") => AttrValue::from_atomic(value.into()),
             &local_name!("width") | &local_name!("height") => AttrValue::from_dimension(value.into()),
--- a/servo/components/script/dom/textencoder.rs
+++ b/servo/components/script/dom/textencoder.rs
@@ -6,19 +6,16 @@ use core::nonzero::NonZero;
 use dom::bindings::codegen::Bindings::TextEncoderBinding;
 use dom::bindings::codegen::Bindings::TextEncoderBinding::TextEncoderMethods;
 use dom::bindings::error::Fallible;
 use dom::bindings::js::Root;
 use dom::bindings::reflector::{Reflector, reflect_dom_object};
 use dom::bindings::str::{DOMString, USVString};
 use dom::globalscope::GlobalScope;
 use dom_struct::dom_struct;
-use encoding::EncoderTrap;
-use encoding::Encoding;
-use encoding::all::UTF_8;
 use js::jsapi::{JSContext, JSObject};
 use js::typedarray::{Uint8Array, CreateWith};
 use std::ptr;
 
 #[dom_struct]
 pub struct TextEncoder {
     reflector_: Reflector,
 }
@@ -40,22 +37,22 @@ impl TextEncoder {
     pub fn Constructor(global: &GlobalScope) -> Fallible<Root<TextEncoder>> {
         Ok(TextEncoder::new(global))
     }
 }
 
 impl TextEncoderMethods for TextEncoder {
     // https://encoding.spec.whatwg.org/#dom-textencoder-encoding
     fn Encoding(&self) -> DOMString {
-        DOMString::from(UTF_8.name())
+        DOMString::from("utf-8")
     }
 
     #[allow(unsafe_code)]
     // https://encoding.spec.whatwg.org/#dom-textencoder-encode
     unsafe fn Encode(&self, cx: *mut JSContext, input: USVString) -> NonZero<*mut JSObject> {
-        let encoded = UTF_8.encode(&input.0, EncoderTrap::Strict).unwrap();
+        let encoded = input.0.as_bytes();
 
         rooted!(in(cx) let mut js_object = ptr::null_mut());
         assert!(Uint8Array::create(cx, CreateWith::Slice(&encoded), js_object.handle_mut()).is_ok());
 
         NonZero::new(js_object.get())
     }
 }
--- a/servo/components/script/lib.rs
+++ b/servo/components/script/lib.rs
@@ -97,16 +97,17 @@ extern crate smallvec;
 extern crate style;
 extern crate style_traits;
 extern crate swapper;
 extern crate time;
 #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
 extern crate tinyfiledialogs;
 extern crate unicode_segmentation;
 extern crate url;
+extern crate utf8;
 extern crate uuid;
 extern crate webrender_traits;
 extern crate webvr_traits;
 extern crate xml5ever;
 
 mod body;
 pub mod clipboard_provider;
 mod devtools;
--- a/servo/components/script/microtask.rs
+++ b/servo/components/script/microtask.rs
@@ -6,16 +6,17 @@
 //! microtask queues. It is up to implementations of event loops to store a queue and
 //! perform checkpoints at appropriate times, as well as enqueue microtasks as required.
 
 use dom::bindings::callback::ExceptionHandling;
 use dom::bindings::cell::DOMRefCell;
 use dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback;
 use dom::bindings::js::Root;
 use dom::globalscope::GlobalScope;
+use dom::htmlimageelement::ImageElementMicrotask;
 use dom::htmlmediaelement::MediaElementMicrotask;
 use dom::mutationobserver::MutationObserver;
 use msg::constellation_msg::PipelineId;
 use std::cell::Cell;
 use std::mem;
 use std::rc::Rc;
 
 /// A collection of microtasks in FIFO order.
@@ -26,16 +27,17 @@ pub struct MicrotaskQueue {
     /// https://html.spec.whatwg.org/multipage/#performing-a-microtask-checkpoint
     performing_a_microtask_checkpoint: Cell<bool>,
 }
 
 #[derive(JSTraceable, HeapSizeOf)]
 pub enum Microtask {
     Promise(EnqueuedPromiseCallback),
     MediaElement(MediaElementMicrotask),
+    ImageElement(ImageElementMicrotask),
     NotifyMutationObservers,
 }
 
 pub trait MicrotaskRunnable {
     fn handler(&self) {}
 }
 
 /// A promise callback scheduled to run during the next microtask checkpoint (#4283).
@@ -76,17 +78,20 @@ impl MicrotaskQueue {
                 match *job {
                     Microtask::Promise(ref job) => {
                         if let Some(target) = target_provider(job.pipeline) {
                             let _ = job.callback.Call_(&*target, ExceptionHandling::Report);
                         }
                     },
                     Microtask::MediaElement(ref task) => {
                         task.handler();
-                    }
+                    },
+                    Microtask::ImageElement(ref task) => {
+                        task.handler();
+                    },
                     Microtask::NotifyMutationObservers => {
                         MutationObserver::notify_mutation_observers();
                     }
                 }
             }
         }
 
         //TODO: Step 8 - notify about rejected promises
--- a/servo/components/style/animation.rs
+++ b/servo/components/style/animation.rs
@@ -14,16 +14,17 @@ use font_metrics::FontMetricsProvider;
 use keyframes::{KeyframesStep, KeyframesStepValue};
 use properties::{self, CascadeFlags, ComputedValues, Importance};
 use properties::animated_properties::{AnimatedProperty, TransitionProperty};
 use properties::longhands::animation_direction::computed_value::single_value::T as AnimationDirection;
 use properties::longhands::animation_iteration_count::single_value::computed_value::T as AnimationIterationCount;
 use properties::longhands::animation_play_state::computed_value::single_value::T as AnimationPlayState;
 use properties::longhands::transition_timing_function::single_value::computed_value::StartEnd;
 use properties::longhands::transition_timing_function::single_value::computed_value::T as TransitionTimingFunction;
+use rule_tree::CascadeLevel;
 use std::sync::mpsc::Sender;
 use stylearc::Arc;
 use timer::Timer;
 use values::computed::Time;
 
 /// This structure represents a keyframes animation current iteration state.
 ///
 /// If the iteration count is infinite, there's no other state, otherwise we
@@ -467,17 +468,18 @@ fn compute_style_for_animation_step(cont
         KeyframesStepValue::Declarations { block: ref declarations } => {
             let guard = declarations.read_with(context.guards.author);
 
             // No !important in keyframes.
             debug_assert!(guard.declarations().iter()
                             .all(|&(_, importance)| importance == Importance::Normal));
 
             let iter = || {
-                guard.declarations().iter().rev().map(|&(ref decl, _importance)| decl)
+                guard.declarations().iter().rev()
+                     .map(|&(ref decl, _importance)| (decl, CascadeLevel::Animations))
             };
 
             // This currently ignores visited styles, which seems acceptable,
             // as existing browsers don't appear to animate visited styles.
             let computed =
                 properties::apply_declarations(context.stylist.device(),
                                                /* is_root = */ false,
                                                iter,
--- a/servo/components/style/context.rs
+++ b/servo/components/style/context.rs
@@ -2,35 +2,31 @@
  * 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/. */
 
 //! The context within which style is calculated.
 
 #[cfg(feature = "servo")] use animation::Animation;
 use animation::PropertyAnimation;
 use app_units::Au;
-use bit_vec::BitVec;
 use bloom::StyleBloom;
 use cache::LRUCache;
 use data::ElementData;
 use dom::{OpaqueNode, TNode, TElement, SendElement};
 use error_reporting::ParseErrorReporter;
 use euclid::Size2D;
 use fnv::FnvHashMap;
 use font_metrics::FontMetricsProvider;
 #[cfg(feature = "gecko")] use gecko_bindings::structs;
 #[cfg(feature = "servo")] use parking_lot::RwLock;
 #[cfg(feature = "gecko")] use properties::ComputedValues;
 use selector_parser::SnapshotMap;
 use selectors::matching::ElementSelectorFlags;
-#[cfg(feature = "servo")] use servo_config::opts;
 use shared_lock::StylesheetGuards;
-use sharing::StyleSharingCandidateCache;
-#[cfg(feature = "servo")] use std::collections::HashMap;
-#[cfg(feature = "gecko")] use std::env;
+use sharing::{ValidationData, StyleSharingCandidateCache};
 use std::fmt;
 use std::ops::Add;
 #[cfg(feature = "servo")] use std::sync::Mutex;
 #[cfg(feature = "servo")] use std::sync::mpsc::Sender;
 use stylearc::Arc;
 use stylist::Stylist;
 use thread_state;
 use time;
@@ -74,25 +70,28 @@ pub struct StyleSystemOptions {
     /// Whether the style sharing cache is disabled.
     pub disable_style_sharing_cache: bool,
     /// Whether we should dump statistics about the style system.
     pub dump_style_statistics: bool,
 }
 
 #[cfg(feature = "gecko")]
 fn get_env(name: &str) -> bool {
+    use std::env;
     match env::var(name) {
         Ok(s) => !s.is_empty(),
         Err(_) => false,
     }
 }
 
 impl Default for StyleSystemOptions {
     #[cfg(feature = "servo")]
     fn default() -> Self {
+        use servo_config::opts;
+
         StyleSystemOptions {
             disable_style_sharing_cache: opts::get().disable_share_style_cache,
             dump_style_statistics: opts::get().style_sharing_stats,
         }
     }
 
     #[cfg(feature = "gecko")]
     fn default() -> Self {
@@ -130,55 +129,56 @@ pub struct SharedStyleContext<'a> {
     /// Flags controlling how we traverse the tree.
     pub traversal_flags: TraversalFlags,
 
     /// A map with our snapshots in order to handle restyle hints.
     pub snapshot_map: &'a SnapshotMap,
 
     /// The animations that are currently running.
     #[cfg(feature = "servo")]
-    pub running_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>,
+    pub running_animations: Arc<RwLock<FnvHashMap<OpaqueNode, Vec<Animation>>>>,
 
     /// The list of animations that have expired since the last style recalculation.
     #[cfg(feature = "servo")]
-    pub expired_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>,
+    pub expired_animations: Arc<RwLock<FnvHashMap<OpaqueNode, Vec<Animation>>>>,
 
     /// Data needed to create the thread-local style context from the shared one.
     #[cfg(feature = "servo")]
     pub local_context_creation_data: Mutex<ThreadLocalStyleContextCreationInfo>,
 
 }
 
 impl<'a> SharedStyleContext<'a> {
     /// Return a suitable viewport size in order to be used for viewport units.
     pub fn viewport_size(&self) -> Size2D<Au> {
         self.stylist.device().au_viewport_size()
     }
 }
 
-/// Information about the current element being processed. We group this together
-/// into a single struct within ThreadLocalStyleContext so that we can instantiate
-/// and destroy it easily at the beginning and end of element processing.
+/// Information about the current element being processed. We group this
+/// together into a single struct within ThreadLocalStyleContext so that we can
+/// instantiate and destroy it easily at the beginning and end of element
+/// processing.
 pub struct CurrentElementInfo {
-    /// The element being processed. Currently we use an OpaqueNode since we only
-    /// use this for identity checks, but we could use SendElement if there were
-    /// a good reason to.
+    /// The element being processed. Currently we use an OpaqueNode since we
+    /// only use this for identity checks, but we could use SendElement if there
+    /// were a good reason to.
     element: OpaqueNode,
     /// Whether the element is being styled for the first time.
     is_initial_style: bool,
-    /// Lazy cache of the result of matching the current element against the
-    /// revalidation selectors.
-    pub revalidation_match_results: Option<BitVec>,
+    /// Lazy cache of the different data used for style sharing.
+    pub validation_data: ValidationData,
     /// A Vec of possibly expired animations. Used only by Servo.
     #[allow(dead_code)]
     pub possibly_expired_animations: Vec<PropertyAnimation>,
 }
 
-/// Statistics gathered during the traversal. We gather statistics on each thread
-/// and then combine them after the threads join via the Add implementation below.
+/// Statistics gathered during the traversal. We gather statistics on each
+/// thread and then combine them after the threads join via the Add
+/// implementation below.
 #[derive(Default)]
 pub struct TraversalStatistics {
     /// The total number of elements traversed.
     pub elements_traversed: u32,
     /// The number of elements where has_styles() went from false to true.
     pub elements_styled: u32,
     /// The number of elements for which we performed selector matching.
     pub elements_matched: u32,
@@ -458,17 +458,17 @@ impl<E: TElement> ThreadLocalStyleContex
     }
 
     /// Notes when the style system starts traversing an element.
     pub fn begin_element(&mut self, element: E, data: &ElementData) {
         debug_assert!(self.current_element_info.is_none());
         self.current_element_info = Some(CurrentElementInfo {
             element: element.as_node().opaque(),
             is_initial_style: !data.has_styles(),
-            revalidation_match_results: None,
+            validation_data: ValidationData::new(),
             possibly_expired_animations: Vec::new(),
         });
     }
 
     /// Notes when the style system finishes traversing an element.
     pub fn end_element(&mut self, element: E) {
         debug_assert!(self.current_element_info.is_some());
         debug_assert!(self.current_element_info.as_ref().unwrap().element ==
--- a/servo/components/style/gecko/conversions.rs
+++ b/servo/components/style/gecko/conversions.rs
@@ -357,23 +357,24 @@ pub mod basic_shape {
 
     use gecko::values::GeckoStyleCoordConvertible;
     use gecko_bindings::structs;
     use gecko_bindings::structs::{StyleBasicShape, StyleBasicShapeType, StyleFillRule};
     use gecko_bindings::structs::{nsStyleCoord, nsStyleCorners};
     use gecko_bindings::structs::StyleGeometryBox;
     use gecko_bindings::sugar::ns_style_coord::{CoordDataMut, CoordDataValue};
     use std::borrow::Borrow;
-    use values::computed::{BorderRadiusSize, LengthOrPercentage};
-    use values::computed::basic_shape::{BasicShape, BorderRadius, ShapeRadius};
+    use values::computed::basic_shape::{BasicShape, ShapeRadius};
+    use values::computed::border::{BorderCornerRadius, BorderRadius};
+    use values::computed::length::LengthOrPercentage;
     use values::computed::position;
-    use values::generics::BorderRadiusSize as GenericBorderRadiusSize;
     use values::generics::basic_shape::{BasicShape as GenericBasicShape, InsetRect, Polygon};
     use values::generics::basic_shape::{Circle, Ellipse, FillRule};
     use values::generics::basic_shape::{GeometryBox, ShapeBox};
+    use values::generics::border::BorderRadius as GenericBorderRadius;
     use values::generics::rect::Rect;
 
     // using Borrow so that we can have a non-moving .into()
     impl<T: Borrow<StyleBasicShape>> From<T> for BasicShape {
         fn from(other: T) -> Self {
             let other = other.borrow();
             match other.mType {
                 StyleBasicShapeType::Inset => {
@@ -430,38 +431,38 @@ pub mod basic_shape {
             }
         }
     }
 
     impl<T: Borrow<nsStyleCorners>> From<T> for BorderRadius {
         fn from(other: T) -> Self {
             let other = other.borrow();
             let get_corner = |index| {
-                GenericBorderRadiusSize::new(
+                BorderCornerRadius::new(
                     LengthOrPercentage::from_gecko_style_coord(&other.data_at(index))
                         .expect("<border-radius> should be a length, percentage, or calc value"),
                     LengthOrPercentage::from_gecko_style_coord(&other.data_at(index + 1))
                         .expect("<border-radius> should be a length, percentage, or calc value"))
             };
 
-            BorderRadius {
+            GenericBorderRadius {
                 top_left: get_corner(0),
                 top_right: get_corner(2),
                 bottom_right: get_corner(4),
                 bottom_left: get_corner(6),
             }
         }
     }
 
     // Can't be a From impl since we need to set an existing
     // nsStyleCorners, not create a new one
     impl BorderRadius {
         /// Set this `BorderRadius` into a given `nsStyleCoord`.
         pub fn set_corners(&self, other: &mut nsStyleCorners) {
-            let mut set_corner = |field: &BorderRadiusSize, index| {
+            let mut set_corner = |field: &BorderCornerRadius, index| {
                 field.0.width.to_gecko_style_coord(&mut other.data_at_mut(index));
                 field.0.height.to_gecko_style_coord(&mut other.data_at_mut(index + 1));
             };
             set_corner(&self.top_left, 0);
             set_corner(&self.top_right, 2);
             set_corner(&self.bottom_right, 4);
             set_corner(&self.bottom_left, 6);
         }
--- a/servo/components/style/gecko/media_queries.rs
+++ b/servo/components/style/gecko/media_queries.rs
@@ -1,19 +1,20 @@
 /* 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/. */
 
 //! Gecko's media-query device and expression representation.
 
 use app_units::Au;
 use context::QuirksMode;
-use cssparser::{CssStringWriter, Parser, Token};
+use cssparser::{CssStringWriter, Parser, RGBA, Token};
 use euclid::Size2D;
 use font_metrics::get_metrics_provider_for_product;
+use gecko::values::convert_nscolor_to_rgba;
 use gecko_bindings::bindings;
 use gecko_bindings::structs::{nsCSSKeyword, nsCSSProps_KTableEntry, nsCSSValue, nsCSSUnit, nsStringBuffer};
 use gecko_bindings::structs::{nsMediaExpression_Range, nsMediaFeature};
 use gecko_bindings::structs::{nsMediaFeature_ValueType, nsMediaFeature_RangeType, nsMediaFeature_RequirementFlags};
 use gecko_bindings::structs::RawGeckoPresContextOwned;
 use media_queries::MediaType;
 use parser::ParserContext;
 use properties::{ComputedValues, StyleBuilder};
@@ -129,16 +130,26 @@ impl Device {
             Size2D::new(Au::from_f32_px(v.size.width),
                         Au::from_f32_px(v.size.height))
         }).unwrap_or_else(|| unsafe {
             // TODO(emilio): Need to take into account scrollbars.
             Size2D::new(Au((*self.pres_context).mVisibleArea.width),
                         Au((*self.pres_context).mVisibleArea.height))
         })
     }
+
+    /// Returns whether document colors are enabled.
+    pub fn use_document_colors(&self) -> bool {
+        unsafe { (*self.pres_context).mUseDocumentColors() != 0 }
+    }
+
+    /// Returns the default background color.
+    pub fn default_background_color(&self) -> RGBA {
+        convert_nscolor_to_rgba(unsafe { (*self.pres_context).mBackgroundColor })
+    }
 }
 
 /// A expression for gecko contains a reference to the media feature, the value
 /// the media query contained, and the range to evaluate.
 #[derive(Debug, Clone)]
 pub struct Expression {
     feature: &'static nsMediaFeature,
     value: Option<MediaExpressionValue>,
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -18,17 +18,17 @@ use properties::{AnimationRules, Cascade
 use properties::{VISITED_DEPENDENT_ONLY, cascade};
 use properties::longhands::display::computed_value as display;
 use restyle_hints::{RESTYLE_CSS_ANIMATIONS, RESTYLE_CSS_TRANSITIONS, RestyleReplacements};
 use restyle_hints::{RESTYLE_STYLE_ATTRIBUTE, RESTYLE_SMIL};
 use rule_tree::{CascadeLevel, StrongRuleNode};
 use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl};
 use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, StyleRelations};
 use selectors::matching::{VisitedHandlingMode, AFFECTED_BY_PSEUDO_ELEMENTS};
-use sharing::{StyleSharingBehavior, StyleSharingResult};
+use sharing::StyleSharingBehavior;
 use stylearc::Arc;
 use stylist::{ApplicableDeclarationList, RuleInclusion};
 
 /// The way a style should be inherited.
 enum InheritMode {
     /// Inherit from the parent element, as normal CSS dictates, _or_ from the
     /// closest non-Native Anonymous element in case this is Native Anonymous
     /// Content.
@@ -850,27 +850,29 @@ pub trait MatchMethods : TElement {
         // If the style is shareable, add it to the LRU cache.
         if sharing == StyleSharingBehavior::Allow {
             // If we previously tried to match this element against the cache,
             // the revalidation match results will already be cached. Otherwise
             // we'll have None, and compute them later on-demand.
             //
             // If we do have the results, grab them here to satisfy the borrow
             // checker.
-            let revalidation_match_results = context.thread_local
-                                                    .current_element_info
-                                                    .as_mut().unwrap()
-                                                    .revalidation_match_results
-                                                    .take();
+            let validation_data =
+                context.thread_local
+                    .current_element_info
+                    .as_mut().unwrap()
+                    .validation_data
+                    .take();
+
             context.thread_local
                    .style_sharing_candidate_cache
                    .insert_if_possible(self,
                                        data.styles().primary.values(),
                                        primary_results.relations,
-                                       revalidation_match_results);
+                                       validation_data);
         }
 
         child_cascade_requirement
     }
 
     /// Performs the cascade, without matching.
     fn cascade_primary_and_pseudos(&self,
                                    context: &mut StyleContext<Self>,
@@ -1336,40 +1338,16 @@ pub trait MatchMethods : TElement {
                 replace_rule_node_for_animation(CascadeLevel::Animations,
                                                 primary_rules);
             }
         }
 
         false
     }
 
-    /// Attempts to share a style with another node. This method is unsafe
-    /// because it depends on the `style_sharing_candidate_cache` having only
-    /// live nodes in it, and we have no way to guarantee that at the type
-    /// system level yet.
-    unsafe fn share_style_if_possible(&self,
-                                      context: &mut StyleContext<Self>,
-                                      data: &mut ElementData)
-                                      -> StyleSharingResult {
-        let shared_context = &context.shared;
-        let current_element_info =
-            context.thread_local.current_element_info.as_mut().unwrap();
-        let selector_flags_map = &mut context.thread_local.selector_flags;
-        let bloom_filter = context.thread_local.bloom_filter.filter();
-
-        context.thread_local
-            .style_sharing_candidate_cache
-            .share_style_if_possible(shared_context,
-                                     current_element_info,
-                                     selector_flags_map,
-                                     bloom_filter,
-                                     *self,
-                                     data)
-    }
-
     /// Given the old and new style of this element, and whether it's a
     /// pseudo-element, compute the restyle damage used to determine which
     /// kind of layout or painting operations we'll need.
     fn compute_style_difference(&self,
                                 old_values: &ComputedValues,
                                 new_values: &Arc<ComputedValues>,
                                 pseudo: Option<&PseudoElement>)
                                 -> StyleDifference
--- a/servo/components/style/properties/data.py
+++ b/servo/components/style/properties/data.py
@@ -145,17 +145,18 @@ def arg_to_bool(arg):
 
 
 class Longhand(object):
     def __init__(self, style_struct, name, spec=None, animation_value_type=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,
-                 flags=None, allowed_in_page_rule=False, allow_quirks=False, vector=False):
+                 flags=None, allowed_in_page_rule=False, allow_quirks=False, ignored_when_colors_disabled=False,
+                 vector=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)
@@ -172,16 +173,17 @@ class Longhand(object):
         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.flags = flags.split() if flags else []
         self.allowed_in_page_rule = arg_to_bool(allowed_in_page_rule)
         self.allow_quirks = allow_quirks
+        self.ignored_when_colors_disabled = ignored_when_colors_disabled
         self.is_vector = vector
 
         # 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
@@ -553,24 +553,24 @@ fn color_to_nscolor_zero_currentcolor(co
         self.gecko.${gecko_ffi_name}.data_at_mut(${x_index})
                   .copy_from(&other.gecko.${gecko_ffi_name}.data_at(${x_index}));
         self.gecko.${gecko_ffi_name}.data_at_mut(${y_index})
                   .copy_from(&other.gecko.${gecko_ffi_name}.data_at(${y_index}));
     }
     % if need_clone:
         #[allow(non_snake_case)]
         pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
-            use values::generics::BorderRadiusSize;
+            use values::computed::border::BorderCornerRadius;
             let width = GeckoStyleCoordConvertible::from_gecko_style_coord(
                             &self.gecko.${gecko_ffi_name}.data_at(${x_index}))
                             .expect("Failed to clone ${ident}");
             let height = GeckoStyleCoordConvertible::from_gecko_style_coord(
                             &self.gecko.${gecko_ffi_name}.data_at(${y_index}))
                             .expect("Failed to clone ${ident}");
-            BorderRadiusSize::new(width, height)
+            BorderCornerRadius::new(width, height)
         }
     % endif
 </%def>
 
 <%def name="impl_css_url(ident, gecko_ffi_name, need_clone=False)">
     #[allow(non_snake_case)]
     pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
         use gecko_bindings::sugar::refptr::RefPtr;
@@ -959,17 +959,17 @@ fn static_assert() {
         unsafe {
             Gecko_CopyImageValueFrom(&mut self.gecko.mBorderImageSource,
                                      &other.gecko.mBorderImageSource);
         }
     }
 
     pub fn set_border_image_outset(&mut self, v: longhands::border_image_outset::computed_value::T) {
         % for side in SIDES:
-        v.${side.ident}.to_gecko_style_coord(&mut self.gecko.mBorderImageOutset.data_at_mut(${side.index}));
+        v.${side.index}.to_gecko_style_coord(&mut self.gecko.mBorderImageOutset.data_at_mut(${side.index}));
         % endfor
     }
 
     pub fn copy_border_image_outset_from(&mut self, other: &Self) {
         % for side in SIDES:
             self.gecko.mBorderImageOutset.data_at_mut(${side.index})
                 .copy_from(&other.gecko.mBorderImageOutset.data_at(${side.index}));
         % endfor
@@ -996,17 +996,17 @@ fn static_assert() {
         self.gecko.mBorderImageRepeatH = other.gecko.mBorderImageRepeatH;
         self.gecko.mBorderImageRepeatV = other.gecko.mBorderImageRepeatV;
     }
 
     pub fn set_border_image_width(&mut self, v: longhands::border_image_width::computed_value::T) {
         use values::generics::border::BorderImageWidthSide;
 
         % for side in SIDES:
-        match v.${side.ident} {
+        match v.${side.index} {
             BorderImageWidthSide::Auto => {
                 self.gecko.mBorderImageWidth.data_at_mut(${side.index}).set_value(CoordDataValue::Auto)
             },
             BorderImageWidthSide::Length(l) => {
                 l.to_gecko_style_coord(&mut self.gecko.mBorderImageWidth.data_at_mut(${side.index}))
             },
             BorderImageWidthSide::Number(n) => {
                 self.gecko.mBorderImageWidth.data_at_mut(${side.index}).set_value(CoordDataValue::Factor(n))
@@ -1021,17 +1021,17 @@ fn static_assert() {
                 .copy_from(&other.gecko.mBorderImageWidth.data_at(${side.index}));
         % endfor
     }
 
     pub fn set_border_image_slice(&mut self, v: longhands::border_image_slice::computed_value::T) {
         use gecko_bindings::structs::{NS_STYLE_BORDER_IMAGE_SLICE_NOFILL, NS_STYLE_BORDER_IMAGE_SLICE_FILL};
 
         % for side in SIDES:
-        v.offsets.${side.ident}.to_gecko_style_coord(&mut self.gecko.mBorderImageSlice.data_at_mut(${side.index}));
+        v.offsets.${side.index}.to_gecko_style_coord(&mut self.gecko.mBorderImageSlice.data_at_mut(${side.index}));
         % endfor
 
         let fill = if v.fill {
             NS_STYLE_BORDER_IMAGE_SLICE_FILL
         } else {
             NS_STYLE_BORDER_IMAGE_SLICE_NOFILL
         };
         self.gecko.mBorderImageFill = fill as u8;
@@ -3962,23 +3962,23 @@ fn static_assert() {
                         // set_len() can't call constructors, so the coordinates
                         // can contain any value. set_value() attempts to free
                         // allocated coordinates, so we don't want to feed it
                         // garbage values which it may misinterpret.
                         // Instead, we use leaky_set_value to blindly overwrite
                         // the garbage data without
                         // attempting to clean up.
                         shape.mCoordinates[0].leaky_set_null();
-                        inset.rect.top.to_gecko_style_coord(&mut shape.mCoordinates[0]);
+                        inset.rect.0.to_gecko_style_coord(&mut shape.mCoordinates[0]);
                         shape.mCoordinates[1].leaky_set_null();
-                        inset.rect.right.to_gecko_style_coord(&mut shape.mCoordinates[1]);
+                        inset.rect.1.to_gecko_style_coord(&mut shape.mCoordinates[1]);
                         shape.mCoordinates[2].leaky_set_null();
-                        inset.rect.bottom.to_gecko_style_coord(&mut shape.mCoordinates[2]);
+                        inset.rect.2.to_gecko_style_coord(&mut shape.mCoordinates[2]);
                         shape.mCoordinates[3].leaky_set_null();
-                        inset.rect.left.to_gecko_style_coord(&mut shape.mCoordinates[3]);
+                        inset.rect.3.to_gecko_style_coord(&mut shape.mCoordinates[3]);
 
                         set_corners_from_radius(inset.round, &mut shape.mRadius);
                     }
                     BasicShape::Circle(circ) => {
                         let mut shape = init_shape(${ident}, StyleBasicShapeType::Circle);
                         unsafe { shape.mCoordinates.set_len(1) };
                         shape.mCoordinates[0].leaky_set_null();
                         circ.radius.to_gecko_style_coord(&mut shape.mCoordinates[0]);
--- a/servo/components/style/properties/helpers.mako.rs
+++ b/servo/components/style/properties/helpers.mako.rs
@@ -896,18 +896,18 @@
                 ${parser_function}_quirky(_c, i, specified::AllowQuirks::Yes)
             % elif needs_context:
                 ${parser_function}(_c, i)
             % else:
                 ${parser_function}(i)
             % endif
             })?;
             Ok(expanded! {
-                % for side in ["top", "right", "bottom", "left"]:
-                    ${to_rust_ident(sub_property_pattern % side)}: rect.${side},
+                % for index, side in enumerate(["top", "right", "bottom", "left"]):
+                    ${to_rust_ident(sub_property_pattern % side)}: rect.${index},
                 % endfor
             })
         }
 
         impl<'a> ToCss for LonghandsToSerialize<'a> {
             fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
                 let rect = Rect::new(
                     % for side in ["top", "right", "bottom", "left"]:
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -32,22 +32,23 @@ use properties::longhands::visibility::c
 #[cfg(feature = "servo")] use servo_atoms::Atom;
 use smallvec::SmallVec;
 use std::cmp;
 #[cfg(feature = "gecko")] use std::collections::HashMap;
 use std::fmt;
 use style_traits::ToCss;
 use super::ComputedValues;
 use values::CSSFloat;
-use values::{Auto, Either, generics};
+use values::{Auto, Either};
 use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone};
-use values::computed::{BorderRadiusSize, ClipRect};
+use values::computed::{BorderCornerRadius, ClipRect};
 use values::computed::{CalcLengthOrPercentage, Context, LengthOrPercentage};
 use values::computed::{MaxLength, MozLength};
 use values::computed::ToComputedValue;
+use values::generics::border::BorderCornerRadius as GenericBorderCornerRadius;
 use values::generics::position as generic_position;
 
 
 /// A given transition property, that is either `All`, or an animatable
 /// property.
 // NB: This needs to be here because it needs all the longhands generated
 // beforehand.
 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
@@ -869,20 +870,20 @@ impl<T: Animatable + Copy> Animatable fo
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         let x = try!(self.x.add_weighted(&other.x, self_portion, other_portion));
         let y = try!(self.y.add_weighted(&other.y, self_portion, other_portion));
 
         Ok(Point2D::new(x, y))
     }
 }
 
-impl Animatable for BorderRadiusSize {
+impl Animatable for BorderCornerRadius {
     #[inline]
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
-        self.0.add_weighted(&other.0, self_portion, other_portion).map(generics::BorderRadiusSize)
+        self.0.add_weighted(&other.0, self_portion, other_portion).map(GenericBorderCornerRadius)
     }
 
     #[inline]
     fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
         self.compute_squared_distance(other).map(|sd| sd.sqrt())
     }
 
     #[inline]
--- a/servo/components/style/properties/longhand/background.mako.rs
+++ b/servo/components/style/properties/longhand/background.mako.rs
@@ -7,25 +7,27 @@
 <% data.new_style_struct("Background", inherited=False) %>
 
 ${helpers.predefined_type("background-color", "CSSColor",
     "::cssparser::Color::RGBA(::cssparser::RGBA::transparent())",
     initial_specified_value="SpecifiedValue::transparent()",
     spec="https://drafts.csswg.org/css-backgrounds/#background-color",
     animation_value_type="IntermediateColor",
     complex_color=True,
+    ignored_when_colors_disabled=True,
     allow_quirks=True)}
 
 ${helpers.predefined_type("background-image", "ImageLayer",
     initial_value="Either::First(None_)",
     initial_specified_value="Either::First(None_)",
     spec="https://drafts.csswg.org/css-backgrounds/#the-background-image",
     vector="True",
     animation_value_type="none",
-    has_uncacheable_values="True" if product == "gecko" else "False")}
+    has_uncacheable_values="True" if product == "gecko" else "False",
+    ignored_when_colors_disabled="True")}
 
 % for (axis, direction, initial) in [("x", "Horizontal", "left"), ("y", "Vertical", "top")]:
     ${helpers.predefined_type("background-position-" + axis, "position::" + direction + "Position",
                               initial_value="computed::LengthOrPercentage::zero()",
                               initial_specified_value="SpecifiedValue::initial_specified_value()",
                               spec="https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-" + axis,
                               animation_value_type="ComputedValue", vector=True, delegate_animate=True)}
 % endfor
--- a/servo/components/style/properties/longhand/border.mako.rs
+++ b/servo/components/style/properties/longhand/border.mako.rs
@@ -17,17 +17,18 @@
 %>
 % for side in ALL_SIDES:
     ${helpers.predefined_type("border-%s-color" % side[0], "CSSColor",
                               "::cssparser::Color::CurrentColor",
                               alias=maybe_moz_logical_alias(product, side, "-moz-border-%s-color"),
                               spec=maybe_logical_spec(side, "color"),
                               animation_value_type="IntermediateColor",
                               logical=side[1],
-                              allow_quirks=not side[1])}
+                              allow_quirks=not side[1],
+                              ignored_when_colors_disabled=True)}
 
     ${helpers.predefined_type("border-%s-style" % side[0], "BorderStyle",
                               "specified::BorderStyle::none",
                               need_clone=True,
                               alias=maybe_moz_logical_alias(product, side, "-moz-border-%s-style"),
                               spec=maybe_logical_spec(side, "style"),
                               animation_value_type="none", logical=side[1])}
 
@@ -41,30 +42,31 @@
 % endfor
 
 ${helpers.gecko_keyword_conversion(Keyword('border-style',
                                    "none solid double dotted dashed hidden groove ridge inset outset"),
                                    type="::values::specified::BorderStyle")}
 
 // FIXME(#4126): when gfx supports painting it, make this Size2D<LengthOrPercentage>
 % for corner in ["top-left", "top-right", "bottom-right", "bottom-left"]:
-    ${helpers.predefined_type("border-" + corner + "-radius", "BorderRadiusSize",
-                              "computed::BorderRadiusSize::zero()",
+    ${helpers.predefined_type("border-" + corner + "-radius", "BorderCornerRadius",
+                              "computed::LengthOrPercentage::zero().into()",
                               "parse", extra_prefixes="webkit",
                               spec="https://drafts.csswg.org/css-backgrounds/#border-%s-radius" % corner,
                               boxed=True,
                               animation_value_type="ComputedValue")}
 % endfor
 
 /// -moz-border-*-colors: color, string, enum, none, inherit/initial
 /// These non-spec properties are just for Gecko (Stylo) internal use.
 % for side in PHYSICAL_SIDES:
     <%helpers:longhand name="-moz-border-${side}-colors" animation_value_type="none"
                        spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-border-*-colors)"
-                       products="gecko">
+                       products="gecko"
+                       ignored_when_colors_disabled="True">
         use std::fmt;
         use style_traits::ToCss;
         use values::specified::CSSColor;
         no_viewport_percentage!(SpecifiedValue);
 
         pub mod computed_value {
             use values::computed::CSSColor;
             #[derive(Debug, Clone, PartialEq)]
--- a/servo/components/style/properties/longhand/color.mako.rs
+++ b/servo/components/style/properties/longhand/color.mako.rs
@@ -5,16 +5,17 @@
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <% data.new_style_struct("Color", inherited=True) %>
 
 <% from data import to_rust_ident %>
 
 <%helpers:longhand name="color" need_clone="True"
                    animation_value_type="IntermediateRGBA"
+                   ignored_when_colors_disabled="True"
                    spec="https://drafts.csswg.org/css-color/#color">
     use cssparser::RGBA;
     use std::fmt;
     use style_traits::ToCss;
     use values::specified::{AllowQuirks, Color, CSSColor};
 
     impl ToComputedValue for SpecifiedValue {
         type ComputedValue = computed_value::T;
--- a/servo/components/style/properties/longhand/column.mako.rs
+++ b/servo/components/style/properties/longhand/column.mako.rs
@@ -47,16 +47,17 @@
                           animation_value_type="ComputedValue", extra_prefixes="moz")}
 
 // https://drafts.csswg.org/css-multicol-1/#crc
 ${helpers.predefined_type("column-rule-color", "CSSColor",
                           "::cssparser::Color::CurrentColor",
                           initial_specified_value="specified::CSSColor::currentcolor()",
                           products="gecko", animation_value_type="IntermediateColor", extra_prefixes="moz",
                           complex_color=True, need_clone=True,
+                          ignored_when_colors_disabled=True,
                           spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-color")}
 
 ${helpers.single_keyword("column-span", "none all",
                          products="gecko", animation_value_type="discrete",
                          spec="https://drafts.csswg.org/css-multicol/#propdef-column-span")}
 
 ${helpers.single_keyword("column-rule-style",
                          "none hidden dotted dashed solid double groove ridge inset outset",
--- a/servo/components/style/properties/longhand/effects.mako.rs
+++ b/servo/components/style/properties/longhand/effects.mako.rs
@@ -12,16 +12,17 @@
                           "1.0",
                           animation_value_type="ComputedValue",
                           flags="CREATES_STACKING_CONTEXT",
                           spec="https://drafts.csswg.org/css-color/#opacity")}
 
 <%helpers:vector_longhand name="box-shadow" allow_empty="True"
                           animation_value_type="IntermediateBoxShadowList"
                           extra_prefixes="webkit"
+                          ignored_when_colors_disabled="True"
                           spec="https://drafts.csswg.org/css-backgrounds/#box-shadow">
     use std::fmt;
     use style_traits::ToCss;
 
     pub type SpecifiedValue = specified::Shadow;
 
     impl ToCss for SpecifiedValue {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
--- a/servo/components/style/properties/longhand/inherited_text.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_text.mako.rs
@@ -675,16 +675,17 @@
             }
         }
     }
     % endif
 </%helpers:single_keyword_computed>
 
 <%helpers:longhand name="text-shadow"
                    animation_value_type="IntermediateTextShadowList",
+                   ignored_when_colors_disabled="True",
                    spec="https://drafts.csswg.org/css-text-decor/#propdef-text-shadow">
     use cssparser;
     use std::fmt;
     use style_traits::ToCss;
     use values::specified::Shadow;
 
     #[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
@@ -1112,16 +1113,17 @@
     % endif
 </%helpers:longhand>
 
 ${helpers.predefined_type("text-emphasis-color", "CSSColor",
                           "::cssparser::Color::CurrentColor",
                           initial_specified_value="specified::CSSColor::currentcolor()",
                           products="gecko", animation_value_type="IntermediateColor",
                           complex_color=True, need_clone=True,
+                          ignored_when_colors_disabled=True,
                           spec="https://drafts.csswg.org/css-text-decor/#propdef-text-emphasis-color")}
 
 
 ${helpers.predefined_type(
     "-moz-tab-size", "LengthOrNumber",
     "::values::Either::Second(8.0)",
     "parse_non_negative",
     products="gecko", animation_value_type="none",
@@ -1130,24 +1132,26 @@
 
 // CSS Compatibility
 // https://compat.spec.whatwg.org
 ${helpers.predefined_type(
     "-webkit-text-fill-color", "CSSColor",
     "CSSParserColor::CurrentColor",
     products="gecko", animation_value_type="IntermediateColor",
     complex_color=True, need_clone=True,
+    ignored_when_colors_disabled=True,
     spec="https://compat.spec.whatwg.org/#the-webkit-text-fill-color")}
 
 ${helpers.predefined_type(
     "-webkit-text-stroke-color", "CSSColor",
     "CSSParserColor::CurrentColor",
     initial_specified_value="specified::CSSColor::currentcolor()",
     products="gecko", animation_value_type="IntermediateColor",
     complex_color=True, need_clone=True,
+    ignored_when_colors_disabled=True,
     spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke-color")}
 
 ${helpers.predefined_type("-webkit-text-stroke-width", "BorderWidth", "Au::from_px(0)",
                           initial_specified_value="specified::BorderWidth::from_length(specified::Length::zero())",
                           computed_type="::app_units::Au", products="gecko",
                           spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke-width",
                           animation_value_type="none")}
 
--- a/servo/components/style/properties/longhand/outline.mako.rs
+++ b/servo/components/style/properties/longhand/outline.mako.rs
@@ -8,16 +8,17 @@
 <% data.new_style_struct("Outline",
                          inherited=False,
                          additional_methods=[Method("outline_has_nonzero_width", "bool")]) %>
 
 // TODO(pcwalton): `invert`
 ${helpers.predefined_type("outline-color", "CSSColor", "computed::CSSColor::CurrentColor",
                           initial_specified_value="specified::CSSColor::currentcolor()",
                           animation_value_type="IntermediateColor", complex_color=True, need_clone=True,
+                          ignored_when_colors_disabled=True,
                           spec="https://drafts.csswg.org/css-ui/#propdef-outline-color")}
 
 <%helpers:longhand name="outline-style" need_clone="True" animation_value_type="none"
                    spec="https://drafts.csswg.org/css-ui/#propdef-outline-style">
     use values::specified::BorderStyle;
 
     pub type SpecifiedValue = Either<Auto, BorderStyle>;
 
@@ -103,19 +104,19 @@
             SpecifiedValue(ToComputedValue::from_computed_value(computed))
         }
     }
 </%helpers:longhand>
 
 // The -moz-outline-radius-* properties are non-standard and not on a standards track.
 // TODO: Should they animate?
 % for corner in ["topleft", "topright", "bottomright", "bottomleft"]:
-    ${helpers.predefined_type("-moz-outline-radius-" + corner, "BorderRadiusSize",
-        "computed::BorderRadiusSize::zero()",
-        "parse", products="gecko",
+    ${helpers.predefined_type("-moz-outline-radius-" + corner, "BorderCornerRadius",
+        "computed::LengthOrPercentage::zero().into()",
+        products="gecko",
         boxed=True,
         animation_value_type="none",
         spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-outline-radius)")}
 % endfor
 
 ${helpers.predefined_type("outline-offset", "Length", "Au(0)", products="servo gecko",
                           animation_value_type="ComputedValue",
                           spec="https://drafts.csswg.org/css-ui/#propdef-outline-offset")}
--- a/servo/components/style/properties/longhand/pointing.mako.rs
+++ b/servo/components/style/properties/longhand/pointing.mako.rs
@@ -179,9 +179,10 @@
                          spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-user-focus)")}
 
 ${helpers.predefined_type("caret-color",
                           "ColorOrAuto",
                           "Either::Second(Auto)",
                           spec="https://drafts.csswg.org/css-ui/#caret-color",
                           animation_value_type="Either<IntermediateColor, Auto>",
                           boxed=True,
+                          ignored_when_colors_disabled=True,
                           products="gecko")}
--- a/servo/components/style/properties/longhand/text.mako.rs
+++ b/servo/components/style/properties/longhand/text.mako.rs
@@ -279,16 +279,17 @@
 
 ${helpers.predefined_type(
     "text-decoration-color", "CSSColor",
     "computed::CSSColor::CurrentColor",
     initial_specified_value="specified::CSSColor::currentcolor()",
     complex_color=True,
     products="gecko",
     animation_value_type="IntermediateColor",
+    ignored_when_colors_disabled=True,
     spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-color")}
 
 <%helpers:longhand name="initial-letter"
                    animation_value_type="none"
                    products="gecko"
                    spec="https://drafts.csswg.org/css-inline/#sizing-drop-initials">
     use std::fmt;
     use style_traits::ToCss;
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -32,19 +32,20 @@ use logical_geometry::WritingMode;
 use media_queries::Device;
 use parser::{PARSING_MODE_DEFAULT, Parse, ParserContext};
 use properties::animated_properties::TransitionProperty;
 #[cfg(feature = "servo")] use servo_config::prefs::PREFS;
 use shared_lock::StylesheetGuards;
 use style_traits::{HasViewportPercentage, ToCss};
 use stylesheets::{CssRuleType, MallocSizeOf, MallocSizeOfFn, Origin, UrlExtraData};
 #[cfg(feature = "servo")] use values::Either;
+use values::specified::Color;
 use values::computed;
 use cascade_info::CascadeInfo;
-use rule_tree::StrongRuleNode;
+use rule_tree::{CascadeLevel, StrongRuleNode};
 use style_adjuster::StyleAdjuster;
 #[cfg(feature = "servo")] use values::specified::BorderStyle;
 
 pub use self::declaration_block::*;
 
 #[cfg(feature = "gecko")]
 #[macro_export]
 macro_rules! property_name {
@@ -586,16 +587,25 @@ impl LonghandId {
             LonghandId::BorderTopColor |
             LonghandId::BorderRightColor |
             LonghandId::BorderBottomColor |
             LonghandId::BorderLeftColor |
             LonghandId::OutlineColor
         )
     }
 
+    /// Returns true if the property is one that is ignored when document
+    /// colors are disabled.
+    fn is_ignored_when_document_colors_disabled(&self) -> bool {
+        matches!(*self,
+            ${" | ".join([("LonghandId::" + p.camel_case)
+                          for p in data.longhands if p.ignored_when_colors_disabled])}
+        )
+    }
+
     /// The computed value of some properties depends on the (sometimes
     /// computed) value of *other* properties.
     ///
     /// So we classify properties into "early" and "other", such that the only
     /// dependencies can be from "other" to "early".
     ///
     /// Unfortunately, it’s not easy to check that this classification is
     /// correct.
@@ -2494,29 +2504,30 @@ pub fn cascade(device: &Device,
             (true,
              device.default_computed_values(),
              device.default_computed_values())
         }
     };
 
     let iter_declarations = || {
         rule_node.self_and_ancestors().flat_map(|node| {
+            let cascade_level = node.cascade_level();
             let declarations = match node.style_source() {
-                Some(source) => source.read(node.cascade_level().guard(guards)).declarations(),
+                Some(source) => source.read(cascade_level.guard(guards)).declarations(),
                 // The root node has no style source.
                 None => &[]
             };
             let node_importance = node.importance();
             declarations
                 .iter()
                 // Yield declarations later in source order (with more precedence) first.
                 .rev()
                 .filter_map(move |&(ref declaration, declaration_importance)| {
                     if declaration_importance == node_importance {
-                        Some(declaration)
+                        Some((declaration, cascade_level))
                     } else {
                         None
                     }
                 })
         })
     };
     apply_declarations(device,
                        is_root_element,
@@ -2542,23 +2553,23 @@ pub fn apply_declarations<'a, F, I>(devi
                                     visited_style: Option<Arc<ComputedValues>>,
                                     mut cascade_info: Option<<&mut CascadeInfo>,
                                     error_reporter: &ParseErrorReporter,
                                     font_metrics_provider: &FontMetricsProvider,
                                     flags: CascadeFlags,
                                     quirks_mode: QuirksMode)
                                     -> ComputedValues
     where F: Fn() -> I,
-          I: Iterator<Item = &'a PropertyDeclaration>,
+          I: Iterator<Item = (&'a PropertyDeclaration, CascadeLevel)>,
 {
     let default_style = device.default_computed_values();
     let inherited_custom_properties = inherited_style.custom_properties();
     let mut custom_properties = None;
     let mut seen_custom = HashSet::new();
-    for declaration in iter_declarations() {
+    for (declaration, _cascade_level) in iter_declarations() {
         if let PropertyDeclaration::Custom(ref name, ref value) = *declaration {
             ::custom_properties::cascade(
                 &mut custom_properties, &inherited_custom_properties,
                 &mut seen_custom, name, value.borrow());
         }
     }
 
     let custom_properties =
@@ -2596,16 +2607,24 @@ pub fn apply_declarations<'a, F, I>(devi
         layout_parent_style: layout_parent_style,
         style: builder,
         font_metrics_provider: font_metrics_provider,
         cached_system_font: None,
         in_media_query: false,
         quirks_mode: quirks_mode,
     };
 
+    let ignore_colors = !device.use_document_colors();
+    let default_background_color_decl = if ignore_colors {
+        let color = device.default_background_color();
+        Some(PropertyDeclaration::BackgroundColor(Color::RGBA(color).into()))
+    } else {
+        None
+    };
+
     // Set computed values, overwriting earlier declarations for the same
     // property.
     //
     // NB: The cacheable boolean is not used right now, but will be once we
     // start caching computed values in the rule nodes.
     let mut cacheable = true;
     let mut seen = LonghandIdSet::new();
 
@@ -2620,30 +2639,53 @@ pub fn apply_declarations<'a, F, I>(devi
     % for category_to_cascade_now in ["early", "other"]:
         % if category_to_cascade_now == "early":
             // Pull these out so that we can
             // compute them in a specific order without
             // introducing more iterations
             let mut font_size = None;
             let mut font_family = None;
         % endif
-        for declaration in iter_declarations() {
+        for (declaration, cascade_level) in iter_declarations() {
+            let mut declaration = declaration;
             let longhand_id = match declaration.id() {
                 PropertyDeclarationId::Longhand(id) => id,
                 PropertyDeclarationId::Custom(..) => continue,
             };
 
             // Only a few properties are allowed to depend on the visited state
             // of links.  When cascading visited styles, we can save time by
             // only processing these properties.
             if flags.contains(VISITED_DEPENDENT_ONLY) &&
                !longhand_id.is_visited_dependent() {
                 continue
             }
 
+            // When document colors are disabled, skip properties that are
+            // marked as ignored in that mode, if they come from a UA or
+            // user style sheet.
+            if ignore_colors &&
+               longhand_id.is_ignored_when_document_colors_disabled() &&
+               !matches!(cascade_level,
+                         CascadeLevel::UANormal |
+                         CascadeLevel::UserNormal |
+                         CascadeLevel::UserImportant |
+                         CascadeLevel::UAImportant) {
+                if let PropertyDeclaration::BackgroundColor(ref color) = *declaration {
+                    // Treat background-color a bit differently.  If the specified
+                    // color is anything other than a fully transparent color, convert
+                    // it into the Device's default background color.
+                    if color.is_non_transparent() {
+                        declaration = default_background_color_decl.as_ref().unwrap();
+                    }
+                } else {
+                    continue
+                }
+            }
+
             if
                 % if category_to_cascade_now == "early":
                     !
                 % endif
                 longhand_id.is_early_property()
             {
                 continue
             }
--- a/servo/components/style/properties/shorthand/border.mako.rs
+++ b/servo/components/style/properties/shorthand/border.mako.rs
@@ -20,30 +20,29 @@
     use values::generics::rect::Rect;
     use values::specified::{AllowQuirks, BorderWidth};
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
         let rect = Rect::parse_with(context, input, |_, i| {
             BorderWidth::parse_quirky(context, i, AllowQuirks::Yes)
         })?;
         Ok(expanded! {
-            % for side in PHYSICAL_SIDES:
-                ${to_rust_ident('border-%s-width' % side)}: rect.${side},
-            % endfor
+            border_top_width: rect.0,
+            border_right_width: rect.1,
+            border_bottom_width: rect.2,
+            border_left_width: rect.3,
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            let rect = Rect {
-                % for side in PHYSICAL_SIDES:
-                ${side}: &self.border_${side}_width,
-                % endfor
-            };
-            rect.to_css(dest)
+            % for side in PHYSICAL_SIDES:
+            let ${side} = &self.border_${side}_width;
+            % endfor
+            Rect::new(top, right, bottom, left).to_css(dest)
         }
     }
 </%helpers:shorthand>
 
 
 pub fn parse_border(context: &ParserContext, input: &mut Parser)
                  -> Result<(specified::CSSColor,
                             specified::BorderStyle,
@@ -201,37 +200,43 @@ pub fn parse_border(context: &ParserCont
     }
 
 </%helpers:shorthand>
 
 <%helpers:shorthand name="border-radius" sub_properties="${' '.join(
     'border-%s-radius' % (corner)
      for corner in ['top-left', 'top-right', 'bottom-right', 'bottom-left']
 )}" extra_prefixes="webkit" spec="https://drafts.csswg.org/css-backgrounds/#border-radius">
-    use values::generics::serialize_radius_values;
-    use values::specified::basic_shape::BorderRadius;
+    use values::generics::rect::Rect;
+    use values::specified::border::BorderRadius;
     use parser::Parse;
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
         let radii = try!(BorderRadius::parse(context, input));
         Ok(expanded! {
             border_top_left_radius: radii.top_left,
             border_top_right_radius: radii.top_right,
             border_bottom_right_radius: radii.bottom_right,
             border_bottom_left_radius: radii.bottom_left,
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            serialize_radius_values(dest,
-                                    &self.border_top_left_radius.0,
-                                    &self.border_top_right_radius.0,
-                                    &self.border_bottom_right_radius.0,
-                                    &self.border_bottom_left_radius.0)
+            let LonghandsToSerialize {
+                border_top_left_radius: ref tl,
+                border_top_right_radius: ref tr,
+                border_bottom_right_radius: ref br,
+                border_bottom_left_radius: ref bl,
+            } = *self;
+
+            let widths = Rect::new(&tl.0.width, &tr.0.width, &br.0.width, &bl.0.width);
+            let heights = Rect::new(&tl.0.height, &tr.0.height, &br.0.height, &bl.0.height);
+
+            BorderRadius::serialize_rects(widths, heights, dest)
         }
     }
 </%helpers:shorthand>
 
 <%helpers:shorthand name="border-image" sub_properties="border-image-outset
     border-image-repeat border-image-slice border-image-source border-image-width"
     extra_prefixes="moz webkit" spec="https://drafts.csswg.org/css-backgrounds-3/#border-image">
     use properties::longhands::{border_image_outset, border_image_repeat, border_image_slice};
--- a/servo/components/style/properties/shorthand/outline.mako.rs
+++ b/servo/components/style/properties/shorthand/outline.mako.rs
@@ -62,33 +62,38 @@
     }
 </%helpers:shorthand>
 
 // The -moz-outline-radius shorthand is non-standard and not on a standards track.
 <%helpers:shorthand name="-moz-outline-radius" sub_properties="${' '.join(
     '-moz-outline-radius-%s' % corner
     for corner in ['topleft', 'topright', 'bottomright', 'bottomleft']
 )}" products="gecko" spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-outline-radius)">
-    use properties::shorthands;
-    use values::generics::serialize_radius_values;
+    use values::generics::rect::Rect;
+    use values::specified::border::BorderRadius;
+    use parser::Parse;
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
-        // Re-use border-radius parsing.
-        shorthands::border_radius::parse_value(context, input).map(|longhands| {
-            expanded! {
-                % for corner in ["top_left", "top_right", "bottom_right", "bottom_left"]:
-                _moz_outline_radius_${corner.replace("_", "")}: longhands.border_${corner}_radius,
-                % endfor
-            }
+        let radii = try!(BorderRadius::parse(context, input));
+        Ok(expanded! {
+            _moz_outline_radius_topleft: radii.top_left,
+            _moz_outline_radius_topright: radii.top_right,
+            _moz_outline_radius_bottomright: radii.bottom_right,
+            _moz_outline_radius_bottomleft: radii.bottom_left,
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            serialize_radius_values(dest,
-                &self._moz_outline_radius_topleft.0,
-                &self._moz_outline_radius_topright.0,
-                &self._moz_outline_radius_bottomright.0,
-                &self._moz_outline_radius_bottomleft.0,
-            )
+            let LonghandsToSerialize {
+                _moz_outline_radius_topleft: ref tl,
+                _moz_outline_radius_topright: ref tr,
+                _moz_outline_radius_bottomright: ref br,
+                _moz_outline_radius_bottomleft: ref bl,
+            } = *self;
+
+            let widths = Rect::new(&tl.0.width, &tr.0.width, &br.0.width, &bl.0.width);
+            let heights = Rect::new(&tl.0.height, &tr.0.height, &br.0.height, &bl.0.height);
+
+            BorderRadius::serialize_rects(widths, heights, dest)
         }
     }
 </%helpers:shorthand>
--- a/servo/components/style/rule_tree/mod.rs
+++ b/servo/components/style/rule_tree/mod.rs
@@ -56,16 +56,22 @@ pub struct RuleTree {
 #[derive(Debug, Clone)]
 pub enum StyleSource {
     /// A style rule stable pointer.
     Style(Arc<Locked<StyleRule>>),
     /// A declaration block stable pointer.
     Declarations(Arc<Locked<PropertyDeclarationBlock>>),
 }
 
+impl PartialEq for StyleSource {
+    fn eq(&self, other: &Self) -> bool {
+        self.ptr_equals(other)
+    }
+}
+
 impl StyleSource {
     #[inline]
     fn ptr_equals(&self, other: &Self) -> bool {
         use self::StyleSource::*;
         match (self, other) {
             (&Style(ref one), &Style(ref other)) => Arc::ptr_eq(one, other),
             (&Declarations(ref one), &Declarations(ref other)) => Arc::ptr_eq(one, other),
             _ => false,
--- a/servo/components/style/servo/media_queries.rs
+++ b/servo/components/style/servo/media_queries.rs
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Servo's media-query device and expression representation.
 
 use app_units::Au;
 use context::QuirksMode;
-use cssparser::Parser;
+use cssparser::{Parser, RGBA};
 use euclid::{Size2D, TypedSize2D};
 use font_metrics::ServoMetricsProvider;
 use media_queries::MediaType;
 use parser::ParserContext;
 use properties::{ComputedValues, StyleBuilder};
 use properties::longhands::font_size;
 use std::fmt;
 use std::sync::atomic::{AtomicIsize, Ordering};
@@ -91,16 +91,26 @@ impl Device {
     pub fn account_for_viewport_rule(&mut self, constraints: &ViewportConstraints) {
         self.viewport_size = constraints.size;
     }
 
     /// Return the media type of the current device.
     pub fn media_type(&self) -> MediaType {
         self.media_type.clone()
     }
+
+    /// Returns whether document colors are enabled.
+    pub fn use_document_colors(&self) -> bool {
+        true
+    }
+
+    /// Returns the default background color.
+    pub fn default_background_color(&self) -> RGBA {
+        RGBA::new(255, 255, 255, 255)
+    }
 }
 
 /// A expression kind servo understands and parses.
 ///
 /// Only `pub` for unit testing, please don't use it directly!
 #[derive(PartialEq, Clone, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum ExpressionKind {
--- a/servo/components/style/sharing/checks.rs
+++ b/servo/components/style/sharing/checks.rs
@@ -1,36 +1,33 @@
 /* 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/. */
 
 //! Different checks done during the style sharing process in order to determine
 //! quickly whether it's worth to share style, and whether two different
 //! elements can indeed share the same style.
 
-use context::{CurrentElementInfo, SelectorFlagsMap, SharedStyleContext};
+use context::{SelectorFlagsMap, SharedStyleContext};
 use dom::TElement;
 use element_state::*;
-use matching::MatchMethods;
 use selectors::bloom::BloomFilter;
-use selectors::matching::{ElementSelectorFlags, StyleRelations};
-use sharing::StyleSharingCandidate;
-use sink::ForgetfulSink;
+use selectors::matching::StyleRelations;
+use sharing::{StyleSharingCandidate, StyleSharingTarget};
 use stylearc::Arc;
 
 /// Determines, based on the results of selector matching, whether it's worth to
 /// try to share style with this element, that is, to try to insert the element
 /// in the chache.