Merge autoland to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Wed, 08 Feb 2017 15:48:05 -0800
changeset 341525 438b18daff7aa9a760f2435c1d89f188c7c4c13e
parent 341519 5e17f9181c6cb0968966280d1c1d96e725702af1 (current diff)
parent 341524 1bbeb6d96dac4e1322373d029863ddcb9895cae9 (diff)
child 341526 f4f18619855b03958d6475ea67ebe410ed1c078c
push id86727
push userkwierso@gmail.com
push dateThu, 09 Feb 2017 00:21:26 +0000
treeherdermozilla-inbound@55a4f5189115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone54.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to central, a=merge MozReview-Commit-ID: 47exp5fFJe1
dom/base/test/file_use_counter_svg_background.html
dom/base/test/file_use_counter_svg_list_style_image.html
testing/mozharness/configs/multi_locale/release_mozilla-esr_android-armv6.json
testing/mozharness/configs/multi_locale/staging_release_mozilla-esr_android-armv6.json
testing/mozharness/configs/releases/postrelease_esr38.py
testing/mozharness/configs/releases/postrelease_firefox_esr45.py
testing/mozharness/configs/releases/updates_firefox_esr45.py
testing/mozharness/configs/single_locale/mozilla-esr45.py
--- a/.hgignore
+++ b/.hgignore
@@ -129,8 +129,11 @@ GPATH
 ^testing/talos/talos/tests/devtools/damp.manifest.develop
 ^talos-venv
 
 # Ignore files created when running a reftest.
 ^lextab.py$
 
 # tup database
 ^\.tup
+
+subinclude:servo/.hgignore
+
--- a/browser/base/content/test/general/browser_bookmark_popup.js
+++ b/browser/base/content/test/general/browser_bookmark_popup.js
@@ -22,16 +22,21 @@ function* test_bookmarks_popup({isNewBoo
       if (!isNewBookmark) {
         yield PlacesUtils.bookmarks.insert({
           parentGuid: PlacesUtils.bookmarks.unfiledGuid,
           url: "about:home",
           title: "Home Page"
         });
       }
 
+      info(`BookmarkingUI.status is ${BookmarkingUI.status}`);
+      yield BrowserTestUtils.waitForCondition(
+        () => BookmarkingUI.status != BookmarkingUI.STATUS_UPDATING,
+        "BookmarkingUI should not be updating");
+
       is(bookmarkStar.hasAttribute("starred"), !isNewBookmark,
          "Page should only be starred prior to popupshown if editing bookmark");
       is(bookmarkPanel.state, "closed", "Panel should be 'closed' to start test");
       let shownPromise = promisePopupShown(bookmarkPanel);
       yield popupShowFn(browser);
       yield shownPromise;
       is(bookmarkPanel.state, "open", "Panel should be 'open' after shownPromise is resolved");
 
--- a/browser/base/content/test/general/browser_selectpopup.js
+++ b/browser/base/content/test/general/browser_selectpopup.js
@@ -89,16 +89,28 @@ const PAGECONTENT_COLORS =
   "</style>" +
   "<body><select id='one'>" +
   '  <option value="One" style="color: #fff; background-color: #f00;">{"color": "rgb(255, 255, 255)", "backgroundColor": "rgb(255, 0, 0)"}</option>' +
   '  <option value="Two" class="blue">{"color": "rgb(255, 255, 255)", "backgroundColor": "rgb(0, 0, 255)"}</option>' +
   '  <option value="Three" class="green">{"color": "rgb(128, 0, 128)", "backgroundColor": "rgb(0, 128, 0)"}</option>' +
   '  <option value="Four" class="defaultColor defaultBackground">{"color": "-moz-ComboboxText", "backgroundColor": "transparent", "unstyled": "true"}</option>' +
   '  <option value="Five" class="defaultColor">{"color": "-moz-ComboboxText", "backgroundColor": "transparent", "unstyled": "true"}</option>' +
   '  <option value="Six" class="defaultBackground">{"color": "-moz-ComboboxText", "backgroundColor": "transparent", "unstyled": "true"}</option>' +
+  '  <option value="Seven" selected="true">{"unstyled": "true"}</option>' +
+  "</select></body></html>";
+
+const PAGECONTENT_COLORS_ON_SELECT =
+  "<html><head><style>" +
+  "  #one { background-color: #7E3A3A; color: #fff }" +
+  "</style>" +
+  "<body><select id='one'>" +
+  '  <option value="One">{"color": "rgb(255, 255, 255)", "backgroundColor": "transparent"}</option>' +
+  '  <option value="Two">{"color": "rgb(255, 255, 255)", "backgroundColor": "transparent"}</option>' +
+  '  <option value="Three">{"color": "rgb(255, 255, 255)", "backgroundColor": "transparent"}</option>' +
+  '  <option value="Four" selected="true">{"end": "true"}</option>' +
   "</select></body></html>";
 
 function openSelectPopup(selectPopup, mode = "key", selector = "select", win = window) {
   let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
 
   if (mode == "click" || mode == "mousedown") {
     let mousePromise;
     if (mode == "click") {
@@ -145,16 +157,48 @@ function getChangeEvents() {
 }
 
 function getClickEvents() {
   return ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
     return content.wrappedJSObject.gClickEvents;
   });
 }
 
+function testOptionColors(index, item, menulist) {
+  let expected = JSON.parse(item.label);
+
+  for (let color of Object.keys(expected)) {
+    if (color.toLowerCase().includes("color") &&
+        !expected[color].startsWith("rgb")) {
+      // Need to convert system color to RGB color.
+      let textarea = document.createElementNS("http://www.w3.org/1999/xhtml", "textarea");
+      textarea.style.color = expected[color];
+      expected[color] = getComputedStyle(textarea).color;
+    }
+  }
+
+  // Press Down to move the selected item to the next item in the
+  // list and check the colors of this item when it's not selected.
+  EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
+
+  if (expected.end) {
+    return;
+  }
+
+  if (expected.unstyled) {
+    ok(!item.hasAttribute("customoptionstyling"),
+      `Item ${index} should not have any custom option styling`);
+  } else {
+    is(getComputedStyle(item).color, expected.color,
+       "Item " + (index) + " has correct foreground color");
+    is(getComputedStyle(item).backgroundColor, expected.backgroundColor,
+       "Item " + (index) + " has correct background color");
+  }
+}
+
 function* doSelectTests(contentType, dtd) {
   const pageUrl = "data:" + contentType + "," + escape(dtd + "\n" + PAGECONTENT);
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
 
   let menulist = document.getElementById("ContentSelectDropdown");
   let selectPopup = menulist.menupopup;
 
   yield openSelectPopup(selectPopup);
@@ -740,59 +784,70 @@ add_task(function* test_somehidden() {
        "Item " + (idx++) + " is visible");
     child = child.nextSibling;
   }
 
   yield hideSelectPopup(selectPopup, "escape");
   yield BrowserTestUtils.removeTab(tab);
 });
 
-add_task(function* test_colors_applied_to_popup() {
+// This test checks when a <select> element has styles applied to <option>s within it.
+add_task(function* test_colors_applied_to_popup_items() {
   const pageUrl = "data:text/html," + escape(PAGECONTENT_COLORS);
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
 
-  let selectPopup = document.getElementById("ContentSelectDropdown").menupopup;
+  let menulist = document.getElementById("ContentSelectDropdown");
+  let selectPopup = menulist.menupopup;
 
   let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
   yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser);
   yield popupShownPromise;
 
   // The label contains a JSON string of the expected colors for
   // `color` and `background-color`.
-  is(selectPopup.parentNode.itemCount, 6, "Correct number of items");
+  is(selectPopup.parentNode.itemCount, 7, "Correct number of items");
   let child = selectPopup.firstChild;
   let idx = 1;
 
-  ok(child.selected, "The first child should be selected");
+  ok(!child.selected, "The first child should not be selected");
   while (child) {
-    let expected = JSON.parse(child.label);
-
-    for (let color of Object.keys(expected)) {
-      if (color.toLowerCase().includes("color") &&
-          !expected[color].startsWith("rgb")) {
-        // Need to convert system color to RGB color.
-        let textarea = document.createElementNS("http://www.w3.org/1999/xhtml", "textarea");
-        textarea.style.color = expected[color];
-        expected[color] = getComputedStyle(textarea).color;
-      }
-    }
-
-    // Press Down to move the selected item to the next item in the
-    // list and check the colors of this item when it's not selected.
-    EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
-
-    if (expected.unstyled) {
-      ok(!child.hasAttribute("customoptionstyling"),
-        `Item ${idx} should not have any custom option styling`);
-    } else {
-      is(getComputedStyle(child).color, expected.color,
-         "Item " + (idx) + " has correct foreground color");
-      is(getComputedStyle(child).backgroundColor, expected.backgroundColor,
-         "Item " + (idx) + " has correct background color");
-    }
-
+    testOptionColors(idx, child, menulist);
     idx++;
     child = child.nextSibling;
   }
 
   yield hideSelectPopup(selectPopup, "escape");
   yield BrowserTestUtils.removeTab(tab);
 });
+
+// This test checks when a <select> element has styles applied to itself.
+add_task(function* test_colors_applied_to_popup() {
+  const pageUrl = "data:text/html," + escape(PAGECONTENT_COLORS_ON_SELECT);
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+  let menulist = document.getElementById("ContentSelectDropdown");
+  let selectPopup = menulist.menupopup;
+
+  let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
+  yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser);
+  yield popupShownPromise;
+
+  // The label contains a JSON string of the expected colors for
+  // `color` and `background-color`.
+  is(selectPopup.parentNode.itemCount, 4, "Correct number of items");
+  let child = selectPopup.firstChild;
+  let idx = 1;
+
+  is(getComputedStyle(selectPopup).color, "rgb(255, 255, 255)",
+    "popup has expected foreground color");
+  is(getComputedStyle(selectPopup).backgroundColor, "rgb(126, 58, 58)",
+    "popup has expected background color");
+
+  ok(!child.selected, "The first child should not be selected");
+  while (child) {
+    testOptionColors(idx, child, menulist);
+    idx++;
+    child = child.nextSibling;
+  }
+
+  yield hideSelectPopup(selectPopup, "escape");
+  yield BrowserTestUtils.removeTab(tab);
+});
--- a/browser/base/content/test/popupNotifications/browser_popupNotification_checkbox.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_checkbox.js
@@ -143,19 +143,21 @@ var tests = [
       checkPopup(popup, this.notifyObj);
       let notification = popup.childNodes[0];
       let checkbox = notification.checkbox;
       checkCheckbox(checkbox, "This is a checkbox");
       yield promiseElementVisible(checkbox);
       EventUtils.synthesizeMouseAtCenter(checkbox, {});
       dismissNotification(popup);
     },
-    onHidden(popup) {
+    *onHidden(popup) {
       let icon = document.getElementById("default-notification-icon");
+      let shown = waitForNotificationPanel();
       EventUtils.synthesizeMouseAtCenter(icon, {});
+      yield shown;
       let notification = popup.childNodes[0];
       let checkbox = notification.checkbox;
       checkCheckbox(checkbox, "This is a checkbox", true);
       checkMainAction(notification, true);
       gNotification.remove();
     }
   },
 ];
--- a/browser/base/content/test/popupNotifications/head.js
+++ b/browser/base/content/test/popupNotifications/head.js
@@ -96,18 +96,18 @@ function* runNextTest() {
     onPopupEvent("popupshown", function() {
       shownState = true;
       info("[" + nextTest.id + "] popup shown");
       Task.spawn(() => nextTest.onShown(this))
           .then(undefined, ex => Assert.ok(false, "onShown failed: " + ex));
     });
     onPopupEvent("popuphidden", function() {
       info("[" + nextTest.id + "] popup hidden");
-      nextTest.onHidden(this);
-      goNext();
+      Task.spawn(() => nextTest.onHidden(this))
+          .then(() => goNext(), ex => Assert.ok(false, "onHidden failed: " + ex));
     }, () => shownState);
     info("[" + nextTest.id + "] added listeners; panel is open: " + PopupNotifications.isPanelOpen);
   }
 
   info("[" + nextTest.id + "] running test");
   yield nextTest.run();
 }
 
--- a/browser/components/preferences/in-content/tests/browser_advanced_siteData.js
+++ b/browser/components/preferences/in-content/tests/browser_advanced_siteData.js
@@ -7,16 +7,17 @@ const { classes: Cc, interfaces: Ci, uti
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 /* import-globals-from ../../../../../testing/modules/sinon-1.16.1.js */
 Services.scriptloader.loadSubScript("resource://testing-common/sinon-1.16.1.js");
 
 const TEST_HOST = "example.com";
 const TEST_ORIGIN = "http://" + TEST_HOST;
 const TEST_BASE_URL = TEST_ORIGIN + "/browser/browser/components/preferences/in-content/tests/";
+const REMOVE_DIALOG_URL = "chrome://browser/content/preferences/siteDataRemoveSelected.xul";
 
 const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
 const { SiteDataManager } = Cu.import("resource:///modules/SiteDataManager.jsm", {});
 const { OfflineAppCacheHelper } = Cu.import("resource:///modules/offlineAppCache.jsm", {});
 
 const mockOfflineAppCacheHelper = {
   clear: null,
 
@@ -182,16 +183,32 @@ function promiseSitesUpdated() {
 }
 
 function promiseCookiesCleared() {
   return TestUtils.topicObserved("cookie-changed", (subj, data) => {
     return data === "cleared";
   });
 }
 
+function assertSitesListed(doc, origins) {
+  let frameDoc = doc.getElementById("dialogFrame").contentDocument;
+  let removeBtn = frameDoc.getElementById("removeSelected");
+  let removeAllBtn = frameDoc.getElementById("removeAll");
+  let sitesList = frameDoc.getElementById("sitesList");
+  let totalSitesNumber = sitesList.getElementsByTagName("richlistitem").length;
+  is(totalSitesNumber, origins.length, "Should list the right sites number");
+  origins.forEach(origin => {
+    let site = sitesList.querySelector(`richlistitem[data-origin="${origin}"]`);
+    let host = site.getAttribute("host");
+    ok(origin.includes(host), `Should list the site of ${origin}`);
+  });
+  is(removeBtn.disabled, false, "Should enable the removeSelected button");
+  is(removeAllBtn.disabled, false, "Should enable the removeAllBtn button");
+}
+
 registerCleanupFunction(function() {
   delete window.sinon;
   delete window.setImmediate;
   delete window.clearImmediate;
   mockOfflineAppCacheHelper.unregister();
 });
 
 add_task(function* () {
@@ -365,38 +382,28 @@ add_task(function* () {
 
   let doc = gBrowser.selectedBrowser.contentDocument;
   let frameDoc = doc.getElementById("dialogFrame").contentDocument;
   let searchBox = frameDoc.getElementById("searchBox");
   let mockOrigins = Array.from(mockSiteDataManager.sites.keys());
 
   searchBox.value = "xyz";
   searchBox.doCommand();
-  assertSitesListed(mockOrigins.filter(o => o.includes("xyz")));
+  assertSitesListed(doc, mockOrigins.filter(o => o.includes("xyz")));
 
   searchBox.value = "bar";
   searchBox.doCommand();
-  assertSitesListed(mockOrigins.filter(o => o.includes("bar")));
+  assertSitesListed(doc, mockOrigins.filter(o => o.includes("bar")));
 
   searchBox.value = "";
   searchBox.doCommand();
-  assertSitesListed(mockOrigins);
+  assertSitesListed(doc, mockOrigins);
 
   mockSiteDataManager.unregister();
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
-
-  function assertSitesListed(origins) {
-    let sitesList = frameDoc.getElementById("sitesList");
-    let totalSitesNumber = sitesList.getElementsByTagName("richlistitem").length;
-    is(totalSitesNumber, origins.length, "Should list the right sites number");
-    origins.forEach(origin => {
-      let site = sitesList.querySelector(`richlistitem[data-origin="${origin}"]`);
-      ok(site instanceof XULElement, `Should list the site of ${origin}`);
-    });
-  }
 });
 
 // Test selecting and removing all sites one by one
 add_task(function* () {
   yield SpecialPowers.pushPrefEnv({set: [["browser.storageManager.enabled", true]]});
   let fakeOrigins = [
     "https://news.foo.com/",
     "https://mails.bar.com/",
@@ -473,29 +480,33 @@ add_task(function* () {
       sites[i].click();
       removeBtn.doCommand();
     }
   }
 
   function assertAllSitesListed() {
     frameDoc = doc.getElementById("dialogFrame").contentDocument;
     let removeBtn = frameDoc.getElementById("removeSelected");
+    let removeAllBtn = frameDoc.getElementById("removeAll");
     let sitesList = frameDoc.getElementById("sitesList");
     let sites = sitesList.getElementsByTagName("richlistitem");
     is(sites.length, fakeOrigins.length, "Should list all sites");
     is(removeBtn.disabled, false, "Should enable the removeSelected button");
+    is(removeAllBtn.disabled, false, "Should enable the removeAllBtn button");
   }
 
   function assertAllSitesNotListed() {
     frameDoc = doc.getElementById("dialogFrame").contentDocument;
     let removeBtn = frameDoc.getElementById("removeSelected");
+    let removeAllBtn = frameDoc.getElementById("removeAll");
     let sitesList = frameDoc.getElementById("sitesList");
     let sites = sitesList.getElementsByTagName("richlistitem");
     is(sites.length, 0, "Should not list all sites");
     is(removeBtn.disabled, true, "Should disable the removeSelected button");
+    is(removeAllBtn.disabled, true, "Should disable the removeAllBtn button");
   }
 });
 
 // Test selecting and removing partial sites
 add_task(function* () {
   yield SpecialPowers.pushPrefEnv({set: [["browser.storageManager.enabled", true]]});
   let fakeOrigins = [
     "https://news.foo.com/",
@@ -507,63 +518,62 @@ add_task(function* () {
   ];
   fakeOrigins.forEach(origin => addPersistentStoragePerm(origin));
 
   let updatePromise = promiseSitesUpdated();
   yield openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
   yield updatePromise;
   yield openSettingsDialog();
 
-  const removeDialogURL = "chrome://browser/content/preferences/siteDataRemoveSelected.xul";
   let doc = gBrowser.selectedBrowser.contentDocument;
   let frameDoc = null;
   let saveBtn = null;
   let cancelBtn = null;
   let removeDialogOpenPromise = null;
   let settingsDialogClosePromise = null;
 
   // Test the initial state
-  assertSitesListed(fakeOrigins);
+  assertSitesListed(doc, fakeOrigins);
 
   // Test the "Cancel" button
   settingsDialogClosePromise = promiseSettingsDialogClose();
   frameDoc = doc.getElementById("dialogFrame").contentDocument;
   cancelBtn = frameDoc.getElementById("cancel");
   removeSelectedSite(fakeOrigins.slice(0, 4));
-  assertSitesListed(fakeOrigins.slice(4));
+  assertSitesListed(doc, fakeOrigins.slice(4));
   cancelBtn.doCommand();
   yield settingsDialogClosePromise;
   yield openSettingsDialog();
-  assertSitesListed(fakeOrigins);
+  assertSitesListed(doc, fakeOrigins);
 
   // Test the "Save Changes" button but canceling save
-  removeDialogOpenPromise = promiseWindowDialogOpen("cancel", removeDialogURL);
+  removeDialogOpenPromise = promiseWindowDialogOpen("cancel", REMOVE_DIALOG_URL);
   settingsDialogClosePromise = promiseSettingsDialogClose();
   frameDoc = doc.getElementById("dialogFrame").contentDocument;
   saveBtn = frameDoc.getElementById("save");
   removeSelectedSite(fakeOrigins.slice(0, 4));
-  assertSitesListed(fakeOrigins.slice(4));
+  assertSitesListed(doc, fakeOrigins.slice(4));
   saveBtn.doCommand();
   yield removeDialogOpenPromise;
   yield settingsDialogClosePromise;
   yield openSettingsDialog();
-  assertSitesListed(fakeOrigins);
+  assertSitesListed(doc, fakeOrigins);
 
   // Test the "Save Changes" button and accepting save
-  removeDialogOpenPromise = promiseWindowDialogOpen("accept", removeDialogURL);
+  removeDialogOpenPromise = promiseWindowDialogOpen("accept", REMOVE_DIALOG_URL);
   settingsDialogClosePromise = promiseSettingsDialogClose();
   frameDoc = doc.getElementById("dialogFrame").contentDocument;
   saveBtn = frameDoc.getElementById("save");
   removeSelectedSite(fakeOrigins.slice(0, 4));
-  assertSitesListed(fakeOrigins.slice(4));
+  assertSitesListed(doc, fakeOrigins.slice(4));
   saveBtn.doCommand();
   yield removeDialogOpenPromise;
   yield settingsDialogClosePromise;
   yield openSettingsDialog();
-  assertSitesListed(fakeOrigins.slice(4));
+  assertSitesListed(doc, fakeOrigins.slice(4));
 
   // Always clean up the fake origins
   fakeOrigins.forEach(origin => removePersistentStoragePerm(origin));
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
 
   function removeSelectedSite(origins) {
     frameDoc = doc.getElementById("dialogFrame").contentDocument;
     let removeBtn = frameDoc.getElementById("removeSelected");
@@ -573,22 +583,53 @@ add_task(function* () {
       if (site) {
         site.click();
         removeBtn.doCommand();
       } else {
         ok(false, `Should not select and remove inexisted site of ${origin}`);
       }
     });
   }
+});
 
-  function assertSitesListed(origins) {
-    frameDoc = doc.getElementById("dialogFrame").contentDocument;
-    let removeBtn = frameDoc.getElementById("removeSelected");
-    let sitesList = frameDoc.getElementById("sitesList");
-    let totalSitesNumber = sitesList.getElementsByTagName("richlistitem").length;
-    is(totalSitesNumber, origins.length, "Should list the right sites number");
-    origins.forEach(origin => {
-      let site = sitesList.querySelector(`richlistitem[data-origin="${origin}"]`);
-      ok(!!site, `Should list the site of ${origin}`);
-    });
-    is(removeBtn.disabled, false, "Should enable the removeSelected button");
-  }
+add_task(function* () {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.storageManager.enabled", true]]});
+  let fakeOrigins = [
+    "https://news.foo.com/",
+    "https://books.foo.com/",
+    "https://mails.bar.com/",
+    "https://account.bar.com/",
+    "https://videos.xyz.com/",
+    "https://shopping.xyz.com/"
+  ];
+  fakeOrigins.forEach(origin => addPersistentStoragePerm(origin));
+
+  let updatePromise = promiseSitesUpdated();
+  yield openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
+  yield updatePromise;
+  yield openSettingsDialog();
+
+  // Search "foo" to only list foo.com sites
+  let doc = gBrowser.selectedBrowser.contentDocument;
+  let frameDoc = doc.getElementById("dialogFrame").contentDocument;
+  let searchBox = frameDoc.getElementById("searchBox");
+  searchBox.value = "foo";
+  searchBox.doCommand();
+  assertSitesListed(doc, fakeOrigins.slice(0, 2));
+
+  // Test only removing all visible sites listed
+  updatePromise = promiseSitesUpdated();
+  let acceptRemovePromise = promiseWindowDialogOpen("accept", REMOVE_DIALOG_URL);
+  let settingsDialogClosePromise = promiseSettingsDialogClose();
+  let removeAllBtn = frameDoc.getElementById("removeAll");
+  let saveBtn = frameDoc.getElementById("save");
+  removeAllBtn.doCommand();
+  saveBtn.doCommand();
+  yield acceptRemovePromise;
+  yield settingsDialogClosePromise;
+  yield updatePromise;
+  yield openSettingsDialog();
+  assertSitesListed(doc, fakeOrigins.slice(2));
+
+  // Always clean up the fake origins
+  fakeOrigins.forEach(origin => removePersistentStoragePerm(origin));
+  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
--- a/browser/components/preferences/siteDataSettings.js
+++ b/browser/components/preferences/siteDataSettings.js
@@ -35,33 +35,35 @@ let gSiteDataSettings = {
 
     this._list = document.getElementById("sitesList");
     this._searchBox = document.getElementById("searchBox");
     SiteDataManager.getSites().then(sites => {
       this._sites = sites;
       let sortCol = document.getElementById("hostCol");
       this._sortSites(this._sites, sortCol);
       this._buildSitesList(this._sites);
-      this._updateButtonsState();
       Services.obs.notifyObservers(null, "sitedata-settings-init", null);
     });
 
     setEventListener("hostCol", "click", this.onClickTreeCol);
     setEventListener("usageCol", "click", this.onClickTreeCol);
     setEventListener("statusCol", "click", this.onClickTreeCol);
-    setEventListener("searchBox", "command", this.onCommandSearch);
     setEventListener("cancel", "command", this.close);
     setEventListener("save", "command", this.saveChanges);
-    setEventListener("removeSelected", "command", this.removeSelected);
+    setEventListener("searchBox", "command", this.onCommandSearch);
+    setEventListener("removeAll", "command", this.onClickRemoveAll);
+    setEventListener("removeSelected", "command", this.onClickRemoveSelected);
   },
 
   _updateButtonsState() {
     let items = this._list.getElementsByTagName("richlistitem");
-    let removeBtn = document.getElementById("removeSelected");
-    removeBtn.disabled = !(items.length > 0);
+    let removeSelectedBtn = document.getElementById("removeSelected");
+    let removeAllBtn = document.getElementById("removeAll");
+    removeSelectedBtn.disabled = items.length == 0;
+    removeAllBtn.disabled = removeSelectedBtn.disabled;
   },
 
   /**
    * @param sites {Array}
    * @param col {XULElement} the <treecol> being sorted on
    */
   _sortSites(sites, col) {
     let isCurrentSortCol = col.getAttribute("data-isCurrentSortCol")
@@ -131,40 +133,32 @@ let gSiteDataSettings = {
       let size = DownloadUtils.convertByteUnits(data.usage);
       let item = document.createElement("richlistitem");
       item.setAttribute("data-origin", data.uri.spec);
       item.setAttribute("host", host);
       item.setAttribute("status", prefStrBundle.getString(statusStrId));
       item.setAttribute("usage", prefStrBundle.getFormattedString("siteUsage", size));
       this._list.appendChild(item);
     }
-  },
-
-  onClickTreeCol(e) {
-    this._sortSites(this._sites, e.target);
-    this._buildSitesList(this._sites);
+    this._updateButtonsState();
   },
 
-  onCommandSearch() {
-    this._buildSitesList(this._sites);
-  },
-
-  removeSelected() {
-    let selected = this._list.selectedItem;
-    if (selected) {
-      let origin = selected.getAttribute("data-origin");
+  _removeSiteItems(items) {
+    for (let i = items.length - 1; i >= 0; --i) {
+      let item = items[i];
+      let origin = item.getAttribute("data-origin");
       for (let site of this._sites) {
         if (site.uri.spec === origin) {
           site.userAction = "remove";
           break;
         }
       }
-      this._list.removeChild(selected);
-      this._updateButtonsState();
+      item.remove();
     }
+    this._updateButtonsState();
   },
 
   saveChanges() {
     let allowed = true;
 
     // Confirm user really wants to remove site data starts
     let removals = [];
     this._sites = this._sites.filter(site => {
@@ -230,10 +224,33 @@ let gSiteDataSettings = {
     }
     // Confirm user really wants to remove site data ends
 
     this.close();
   },
 
   close() {
     window.close();
+  },
+
+  onClickTreeCol(e) {
+    this._sortSites(this._sites, e.target);
+    this._buildSitesList(this._sites);
+  },
+
+  onCommandSearch() {
+    this._buildSitesList(this._sites);
+  },
+
+  onClickRemoveSelected() {
+    let selected = this._list.selectedItem;
+    if (selected) {
+      this._removeSiteItems([selected]);
+    }
+  },
+
+  onClickRemoveAll() {
+    let siteItems = this._list.getElementsByTagName("richlistitem");
+    if (siteItems.length > 0) {
+      this._removeSiteItems(siteItems);
+    }
   }
 };
--- a/browser/components/preferences/siteDataSettings.xul
+++ b/browser/components/preferences/siteDataSettings.xul
@@ -39,16 +39,17 @@
         <treecol flex="2" width="50" label="&statusCol.label;" id="statusCol"/>
         <treecol flex="1" width="50" label="&usageCol.label;" id="usageCol"/>
       </listheader>
     </richlistbox>
   </vbox>
 
   <hbox align="start">
     <button id="removeSelected" label="&removeSelected.label;" accesskey="&removeSelected.accesskey;"/>
+    <button id="removeAll" label="&removeAll.label;" accesskey="&removeAll.accesskey;"/>
   </hbox>
 
   <vbox align="end">
     <hbox>
         <button id="cancel" label="&cancel.label;" accesskey="&cancel.accesskey;"/>
         <button id="save" label="&save.label;" accesskey="&save.accesskey;"/>
     </hbox>
   </vbox>
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -1556,20 +1556,32 @@
             list.insertBefore(button, settingsButton);
           }
 
           if (this.compact) {
             this.settingsButton.setAttribute("width", buttonWidth);
             if (rowCount == 1 && hasDummyItems) {
               // When there's only one row, make the compact settings button
               // hug the right edge of the panel.  It may not due to the panel's
-              // width not being an integral factor of the button width.  (See
+              // width not being an integral multiple of the button width.  (See
               // the "There will be an emtpy area" comment above.)  Increase the
               // width of the last dummy item by the remainder.
+              //
+              // There's one weird thing to guard against.  When layout pixels
+              // aren't an integral multiple of device pixels, the calculated
+              // remainder can end up being ~1px too big, at least on Windows,
+              // which pushes the settings button to a new row.  The remainder
+              // is integral, not a fraction, so that's not the problem.  To
+              // work around that, unscale the remainder, floor it, scale it
+              // back, and then floor that.
+              let scale = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                                .getInterface(Ci.nsIDOMWindowUtils)
+                                .screenPixelsPerCSSPixel;
               let remainder = panelWidth - (enginesPerRow * buttonWidth);
+              remainder = Math.floor(Math.floor(remainder * scale) / scale);
               let width = remainder + buttonWidth;
               let lastDummyItem = this.settingsButton.previousSibling;
               lastDummyItem.setAttribute("width", width);
             }
           }
         ]]></body>
       </method>
 
--- a/browser/components/search/test/browser.ini
+++ b/browser/components/search/test/browser.ini
@@ -22,19 +22,22 @@ support-files =
 [browser_bing.js]
 [browser_bing_behavior.js]
 [browser_contextmenu.js]
 [browser_contextSearchTabPosition.js]
 skip-if = os == "mac" # bug 967013
 [browser_ddg.js]
 [browser_ddg_behavior.js]
 [browser_google.js]
+skip-if = artifact # bug 1315953
 [browser_google_codes.js]
+skip-if = artifact # bug 1315953
 [browser_google_nocodes.js]
 [browser_google_behavior.js]
+skip-if = artifact # bug 1315953
 [browser_healthreport.js]
 [browser_hiddenOneOffs_cleanup.js]
 [browser_hiddenOneOffs_diacritics.js]
 [browser_oneOffContextMenu.js]
 [browser_oneOffContextMenu_setDefault.js]
 [browser_oneOffHeader.js]
 [browser_private_search_perwindowpb.js]
 [browser_yahoo.js]
--- a/browser/config/tooltool-manifests/macosx64/cross-releng.manifest
+++ b/browser/config/tooltool-manifests/macosx64/cross-releng.manifest
@@ -36,24 +36,24 @@
 "size": 57060,
 "visibility": "public",
 "digest": "9649ca595f4cf088d118da26201f92cc94cda7af49c7c48112ee31cd13c83b2935b3e145de9dd78060cff2480b4c2e7ff5fb24235876956fed13c87852071998",
 "algorithm": "sha512",
 "unpack": true,
 "filename": "dmg.tar.xz"
 },
 {
-"size": 188880,
-"visibility": "public",
-"digest": "1ffddd43efb03aed897ee42035d9d8d758a8d66ab6c867599ef755e1a586768fc22011ce03698af61454920b00fe8bed08c9a681e7bd324d7f8f78c026c83943",
-"algorithm": "sha512",
-"unpack": true,
-"filename": "genisoimage.tar.xz"
-},
-{
 "version": "rustc 1.14.0 (e8a012324 2016-12-16) repack",
 "size": 152573516,
 "digest": "eef2f10bf57005d11c34b9b49eb76d0f09d026295055039fea89952a3be51580abdab29366278ed4bfa393b33c5cee4d51d3af4221e9e7d7d833d0fc1347597c",
 "algorithm": "sha512",
 "filename": "rustc.tar.xz",
 "unpack": true
+},
+{
+"size": 281576,
+"visibility": "public",
+"digest": "71616564533d138fb12f08e761c2638d054814fdf9c9439638ec57b201e100445c364d73d8d7a4f0e3b784898d5fe6264e8242863fc5ac40163f1791468bbc46",
+"algorithm": "sha512",
+"filename": "hfsplus-tools.tar.xz",
+"unpack": true
 }
 ]
--- a/browser/extensions/shield-recipe-client/bootstrap.js
+++ b/browser/extensions/shield-recipe-client/bootstrap.js
@@ -2,26 +2,34 @@
  * 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 {utils: Cu} = Components;
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "LogManager",
+  "resource://shield-recipe-client/lib/LogManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecipeRunner",
+  "resource://shield-recipe-client/lib/RecipeRunner.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CleanupManager",
+  "resource://shield-recipe-client/lib/CleanupManager.jsm");
 
 const REASONS = {
   APP_STARTUP: 1,      // The application is starting up.
   APP_SHUTDOWN: 2,     // The application is shutting down.
   ADDON_ENABLE: 3,     // The add-on is being enabled.
   ADDON_DISABLE: 4,    // The add-on is being disabled. (Also sent during uninstallation)
   ADDON_INSTALL: 5,    // The add-on is being installed.
   ADDON_UNINSTALL: 6,  // The add-on is being uninstalled.
   ADDON_UPGRADE: 7,    // The add-on is being upgraded.
-  ADDON_DOWNGRADE: 8,  //The add-on is being downgraded.
+  ADDON_DOWNGRADE: 8,  // The add-on is being downgraded.
 };
 
 const PREF_BRANCH = "extensions.shield-recipe-client.";
 const DEFAULT_PREFS = {
   api_url: "https://self-repair.mozilla.org/api/v1",
   dev_mode: false,
   enabled: true,
   startup_delay_seconds: 300,
@@ -48,29 +56,25 @@ this.install = function() {
 this.startup = function() {
   setDefaultPrefs();
 
   if (!shouldRun) {
     return;
   }
 
   // Setup logging and listen for changes to logging prefs
-  Cu.import("resource://shield-recipe-client/lib/LogManager.jsm");
   LogManager.configure(Services.prefs.getIntPref(PREF_LOGGING_LEVEL));
   Preferences.observe(PREF_LOGGING_LEVEL, LogManager.configure);
+  CleanupManager.addCleanupHandler(
+    () => Preferences.ignore(PREF_LOGGING_LEVEL, LogManager.configure));
 
-  Cu.import("resource://shield-recipe-client/lib/RecipeRunner.jsm");
   RecipeRunner.init();
 };
 
 this.shutdown = function(data, reason) {
-  Preferences.ignore(PREF_LOGGING_LEVEL, LogManager.configure);
-
-  Cu.import("resource://shield-recipe-client/lib/CleanupManager.jsm");
-
   CleanupManager.cleanup();
 
   if (reason === REASONS.ADDON_DISABLE || reason === REASONS.ADDON_UNINSTALL) {
     Services.prefs.setBoolPref(PREF_SELF_SUPPORT_ENABLED, true);
   }
 
   const modules = [
     "lib/CleanupManager.jsm",
--- a/browser/extensions/shield-recipe-client/lib/CleanupManager.jsm
+++ b/browser/extensions/shield-recipe-client/lib/CleanupManager.jsm
@@ -1,16 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {utils: Cu} = Components;
-
 this.EXPORTED_SYMBOLS = ["CleanupManager"];
 
 const cleanupHandlers = new Set();
 
 this.CleanupManager = {
   addCleanupHandler(handler) {
     cleanupHandlers.add(handler);
   },
--- a/browser/extensions/shield-recipe-client/lib/EnvExpressions.jsm
+++ b/browser/extensions/shield-recipe-client/lib/EnvExpressions.jsm
@@ -1,16 +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/. */
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/TelemetryArchive.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://shield-recipe-client/lib/Sampling.jsm");
 
 const {generateUUID} = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
 
 this.EXPORTED_SYMBOLS = ["EnvExpressions"];
@@ -69,19 +70,21 @@ this.EnvExpressions = {
       id = generateUUID().toString().slice(1, -1);
       prefs.setCharPref("user_id", id);
     }
     return id;
   },
 
   eval(expr, extraContext = {}) {
     // First clone the extra context
-    const context = Object.assign({}, extraContext);
+    const context = Object.assign({normandy: {}}, extraContext);
     // jexl handles promises, so it is fine to include them in this data.
     context.telemetry = EnvExpressions.getLatestTelemetry();
-    context.normandy = context.normandy || {};
-    context.normandy.userId = EnvExpressions.getUserId();
+
+    context.normandy = Object.assign(context.normandy, {
+      userId: EnvExpressions.getUserId(),
+      distribution: Preferences.get("distribution.id", "default"),
+    });
 
     const onelineExpr = expr.replace(/[\t\n\r]/g, " ");
-
     return jexl.eval(onelineExpr, context);
   },
 };
--- a/browser/extensions/shield-recipe-client/lib/LogManager.jsm
+++ b/browser/extensions/shield-recipe-client/lib/LogManager.jsm
@@ -4,17 +4,17 @@
 
 "use strict";
 
 const {utils: Cu} = Components;
 Cu.import("resource://gre/modules/Log.jsm");
 
 this.EXPORTED_SYMBOLS = ["LogManager"];
 
-const ROOT_LOGGER_NAME = "extensions.shield-recipe-client"
+const ROOT_LOGGER_NAME = "extensions.shield-recipe-client";
 let rootLogger = null;
 
 this.LogManager = {
   /**
    * Configure the root logger for the Recipe Client. Must be called at
    * least once before using any loggers created via getLogger.
    * @param {Number} loggingLevel
    *        Logging level to use as defined in Log.jsm
--- a/browser/extensions/shield-recipe-client/lib/NormandyDriver.jsm
+++ b/browser/extensions/shield-recipe-client/lib/NormandyDriver.jsm
@@ -70,18 +70,22 @@ this.NormandyDriver = function(sandboxMa
 
     client() {
       const appinfo = {
         version: Services.appinfo.version,
         channel: Services.appinfo.defaultUpdateChannel,
         isDefaultBrowser: ShellService.isDefaultBrowser() || null,
         searchEngine: null,
         syncSetup: Preferences.isSet("services.sync.username"),
+        syncDesktopDevices: Preferences.get("services.sync.clients.devices.desktop", 0),
+        syncMobileDevices: Preferences.get("services.sync.clients.devices.mobile", 0),
+        syncTotalDevices: Preferences.get("services.sync.numClients", 0),
         plugins: {},
         doNotTrack: Preferences.get("privacy.donottrackheader.enabled", false),
+        distribution: Preferences.get("distribution.id", "default"),
       };
 
       const searchEnginePromise = new Promise(resolve => {
         Services.search.init(rv => {
           if (Components.isSuccessCode(rv)) {
             appinfo.searchEngine = Services.search.defaultEngine.identifier;
           }
           resolve();
--- a/browser/extensions/shield-recipe-client/test/browser/browser_EnvExpressions.js
+++ b/browser/extensions/shield-recipe-client/test/browser/browser_EnvExpressions.js
@@ -77,9 +77,18 @@ add_task(function* () {
   Assert.equal(val, "fake id", "userId is pulled from preferences");
 
   // test that it merges context correctly, `userId` comes from the default context, and
   // `injectedValue` comes from us. Expect both to be on the final `normandy` object.
   val = yield EnvExpressions.eval(
     "[normandy.userId, normandy.injectedValue]",
     {normandy: {injectedValue: "injected"}});
   Assert.deepEqual(val, ["fake id", "injected"], "context is correctly merged");
+
+  // distribution id defaults to "default"
+  val = yield EnvExpressions.eval("normandy.distribution");
+  Assert.equal(val, "default", "distribution has a default value");
+
+  // distribution id is in the context
+  yield SpecialPowers.pushPrefEnv({set: [["distribution.id", "funnelcake"]]});
+  val = yield EnvExpressions.eval("normandy.distribution");
+  Assert.equal(val, "funnelcake", "distribution is read from preferences");
 });
--- a/browser/extensions/shield-recipe-client/test/browser/browser_NormandyDriver.js
+++ b/browser/extensions/shield-recipe-client/test/browser/browser_NormandyDriver.js
@@ -13,8 +13,37 @@ add_task(Utils.withDriver(Assert, functi
   const uuid2 = driver.uuid();
   isnot(uuid1, uuid2, "uuids are unique");
 }));
 
 add_task(Utils.withDriver(Assert, function* userId(driver) {
   // Test that userId is a UUID
   ok(Utils.UUID_REGEX.test(driver.userId), "userId is a uuid");
 }));
+
+add_task(Utils.withDriver(Assert, function* syncDeviceCounts(driver) {
+  let client = yield driver.client();
+  is(client.syncMobileDevices, 0, "syncMobileDevices defaults to zero");
+  is(client.syncDesktopDevices, 0, "syncDesktopDevices defaults to zero");
+  is(client.syncTotalDevices, 0, "syncTotalDevices defaults to zero");
+
+  yield SpecialPowers.pushPrefEnv({
+    set: [
+      ["services.sync.numClients", 9],
+      ["services.sync.clients.devices.mobile", 5],
+      ["services.sync.clients.devices.desktop", 4],
+    ],
+  });
+
+  client = yield driver.client();
+  is(client.syncMobileDevices, 5, "syncMobileDevices is read when set");
+  is(client.syncDesktopDevices, 4, "syncDesktopDevices is read when set");
+  is(client.syncTotalDevices, 9, "syncTotalDevices is read when set");
+}));
+
+add_task(Utils.withDriver(Assert, function* distribution(driver) {
+  let client = yield driver.client();
+  is(client.distribution, "default", "distribution has a default value");
+
+  yield SpecialPowers.pushPrefEnv({set: [["distribution.id", "funnelcake"]]});
+  client = yield driver.client();
+  is(client.distribution, "funnelcake", "distribution is read from preferences");
+}));
--- a/browser/locales/en-US/chrome/browser/preferences/siteDataSettings.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/siteDataSettings.dtd
@@ -6,15 +6,17 @@
 <!ENTITY     settings.description          "The following websites asked to store site data in your disk. You can specify which websites are allowed to store site data. Default site data is temporary and could be deleted automatically.">
 <!ENTITY     hostCol.label                 "Site">
 <!ENTITY     statusCol.label               "Status">
 <!ENTITY     usageCol.label                "Storage">
 <!ENTITY     search.label                  "Search:">
 <!ENTITY     search.accesskey              "S">
 <!ENTITY     removeSelected.label          "Remove Selected">
 <!ENTITY     removeSelected.accesskey      "r">
+<!ENTITY     removeAll.label               "Remove All">
+<!ENTITY     removeAll.accesskey           "e">
 <!ENTITY     save.label                    "Save Changes">
 <!ENTITY     save.accesskey                "a">
 <!ENTITY     cancel.label                  "Cancel">
 <!ENTITY     cancel.accesskey              "C">
 <!ENTITY     removingDialog.title          "Removing Site Data">
 <!ENTITY     removingDialog.description    "Removing site data will also remove cookies. This may log you out of websites and remove offline web content. Are you sure you want to make the changes?">
 <!ENTITY     siteTree.label                "The following website cookies will be removed:">
--- a/browser/themes/windows/compacttheme.css
+++ b/browser/themes/windows/compacttheme.css
@@ -107,16 +107,30 @@
 @media (min-resolution: 1.1dppx) {
   .findbar-closebutton:-moz-lwtheme-brighttext,
   #sidebar-header > .close-icon:-moz-lwtheme-brighttext,
   .tab-close-button[selected=true] {
     list-style-image: url("chrome://global/skin/icons/close-inverted@2x.png");
   }
 }
 
+/* Override tab close icon (to disable inversion) for better contrast with
+   light theme on Windows 7 Classic theme. */
+@media not all and (min-resolution: 1.1dppx) {
+  #TabsToolbar[brighttext] .tab-close-button:-moz-lwtheme-darktext:not([selected="true"]) {
+    list-style-image: url("chrome://global/skin/icons/close.png");
+  }
+}
+
+@media (min-resolution: 1.1dppx) {
+  #TabsToolbar[brighttext] .tab-close-button:-moz-lwtheme-darktext:not([selected="true"]) {
+    list-style-image: url("chrome://global/skin/icons/close@2x.png");
+  }
+}
+
 @media (-moz-os-version: windows-win7),
        (-moz-os-version: windows-win8) {
   :root {
     --space-above-tabbar: 15px;
   }
 
   /* It'd be nice if there was an element in the scrollbox's inner content
      that collapsed to the current width of the tabs. Since there isn't we
--- a/build/macosx/cross-mozconfig.common
+++ b/build/macosx/cross-mozconfig.common
@@ -26,18 +26,19 @@ FLAGS="-target x86_64-apple-darwin10 -ml
 
 export CC="$topsrcdir/clang/bin/clang $FLAGS"
 export CXX="$topsrcdir/clang/bin/clang++ $FLAGS"
 export CPP="$topsrcdir/clang/bin/clang $FLAGS -E"
 export LLVMCONFIG=$topsrcdir/clang/bin/llvm-config
 export LDFLAGS="-Wl,-syslibroot,$CROSS_SYSROOT -Wl,-dead_strip"
 export TOOLCHAIN_PREFIX=$CROSS_CCTOOLS_PATH/bin/x86_64-apple-darwin10-
 export DSYMUTIL=$topsrcdir/clang/bin/llvm-dsymutil
-export GENISOIMAGE=$topsrcdir/genisoimage/genisoimage
+export MKFSHFS=$topsrcdir/hfsplus-tools/newfs_hfs
 export DMG_TOOL=$topsrcdir/dmg/dmg
+export HFS_TOOL=$topsrcdir/dmg/hfsplus
 
 export HOST_CC="$topsrcdir/clang/bin/clang"
 export HOST_CXX="$topsrcdir/clang/bin/clang++"
 export HOST_CPP="$topsrcdir/clang/bin/clang -E"
 export HOST_CFLAGS="-g"
 export HOST_CXXFLAGS="-g"
 export HOST_LDFLAGS="-g"
 
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -758,16 +758,18 @@ def compiler(language, host_or_target, c
     # and the flags that were part of the user input for those variables to
     # be provided.
     add_old_configure_assignment(var, depends_if(valid_compiler)(
         lambda x: list(x.wrapper) + [x.compiler] + list(x.flags)))
 
     # Set CC_TYPE/CC_VERSION/HOST_CC_TYPE/HOST_CC_VERSION to allow
     # old-configure to do some of its still existing checks.
     if language == 'C':
+        set_config(
+            '%s_TYPE' % var, delayed_getattr(valid_compiler, 'type'))
         add_old_configure_assignment(
             '%s_TYPE' % var, delayed_getattr(valid_compiler, 'type'))
         add_old_configure_assignment(
             '%s_VERSION' % var, delayed_getattr(valid_compiler, 'version'))
 
     valid_compiler = compiler_class(valid_compiler)
 
     def compiler_error():
new file mode 100755
--- /dev/null
+++ b/build/unix/build-hfsplus/build-hfsplus.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+set -x
+
+hfplus_version=540.1.linux3
+md5sum=0435afc389b919027b69616ad1b05709
+filename=diskdev_cmds-${hfplus_version}.tar.gz
+make_flags="-j$(getconf _NPROCESSORS_ONLN)"
+
+root_dir="$1"
+if [ -z "$root_dir" -o ! -d "$root_dir" ]; then
+  root_dir=$(mktemp -d)
+fi
+cd $root_dir
+
+if test -z $TMPDIR; then
+  TMPDIR=/tmp/
+fi
+
+# Install clang first
+yum install -y clang
+
+# Set an md5 check file to validate input
+echo "${md5sum} *${TMPDIR}/${filename}" > $TMPDIR/hfsplus.MD5
+
+# Most-upstream is https://opensource.apple.com/source/diskdev_cmds/
+
+# Download the source of the specified version of binutils
+wget -c -P $TMPDIR http://pkgs.fedoraproject.org/repo/pkgs/hfsplus-tools/${filename}/${md5sum}/${filename} || exit 1
+md5sum -c $TMPDIR/hfsplus.MD5 || exit 1
+mkdir hfsplus-source
+tar xzf $TMPDIR/${filename} -C hfsplus-source --strip-components=1
+
+# Build
+cd hfsplus-source
+make $make_flags || exit 1
+cd ..
+
+mkdir hfsplus-tools
+cp hfsplus-source/newfs_hfs.tproj/newfs_hfs hfsplus-tools/newfs_hfs
+## XXX fsck_hfs is unused, but is small and built from the package.
+cp hfsplus-source/fsck_hfs.tproj/fsck_hfs hfsplus-tools/fsck_hfs
+
+# Make a package of the built utils
+cd $root_dir
+tar caf $root_dir/hfsplus-tools.tar.xz hfsplus-tools
--- a/config/external/ffi/moz.build
+++ b/config/external/ffi/moz.build
@@ -80,16 +80,19 @@ else:
             ASFLAGS += ['-no-integrated-as']
     elif CONFIG['FFI_TARGET'] == 'AARCH64':
         ffi_srcs = ('sysv.S', 'ffi.c')
     elif CONFIG['FFI_TARGET'] == 'X86':
         ffi_srcs = ('ffi.c', 'sysv.S', 'win32.S')
     elif CONFIG['FFI_TARGET'] == 'X86_64':
         ffi_srcs = ('ffi64.c', 'unix64.S', 'ffi.c', 'sysv.S')
     elif CONFIG['FFI_TARGET'] == 'X86_WIN32':
+        # MinGW Build for 32 bit
+        if CONFIG['CC_TYPE'] == 'gcc':
+            DEFINES['SYMBOL_UNDERSCORE'] = True
         ffi_srcs = ('ffi.c', 'win32.S')
     elif CONFIG['FFI_TARGET'] == 'X86_WIN64':
         ffi_srcs = ('ffi.c', 'win64.S')
         ASFLAGS += ['-m64']
     elif CONFIG['FFI_TARGET'] == 'X86_DARWIN':
         DEFINES['FFI_MMAP_EXEC_WRIT'] = True
         if CONFIG['OS_TEST'] != 'x86_64':
             ffi_srcs = ('ffi.c', 'darwin.S', 'ffi64.c', 'darwin64.S',
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -953,16 +953,17 @@ rustflags_override = RUSTFLAGS='$(rustfl
 endif
 
 CARGO_BUILD = env $(rustflags_override) \
 	CARGO_TARGET_DIR=. \
 	RUSTC=$(RUSTC) \
 	MOZ_DIST=$(ABS_DIST) \
 	LIBCLANG_PATH=$(MOZ_LIBCLANG_PATH) \
 	CLANG_PATH=$(MOZ_CLANG_PATH) \
+	PKG_CONFIG_ALLOW_CROSS=1 \
 	$(CARGO) build $(cargo_build_flags)
 
 ifdef RUST_LIBRARY_FILE
 
 ifdef RUST_LIBRARY_FEATURES
 rust_features_flag := --features "$(RUST_LIBRARY_FEATURES)"
 endif
 
--- a/devtools/client/framework/connect/connect.js
+++ b/devtools/client/framework/connect/connect.js
@@ -74,35 +74,43 @@ var submit = Task.async(function* () {
 });
 
 /**
  * Connection is ready. List actors and build buttons.
  */
 var onConnectionReady = Task.async(function* ([aType, aTraits]) {
   clearTimeout(gConnectionTimeout);
 
-  let response = yield gClient.listAddons();
+  let addons = [];
+  try {
+    let response = yield gClient.listAddons();
+    if (!response.error && response.addons.length > 0) {
+      addons = response.addons;
+    }
+  } catch(e) {
+    // listAddons throws if the runtime doesn't support addons
+  }
 
   let parent = document.getElementById("addonActors");
-  if (!response.error && response.addons.length > 0) {
+  if (addons.length > 0) {
     // Add one entry for each add-on.
-    for (let addon of response.addons) {
+    for (let addon of addons) {
       if (!addon.debuggable) {
         continue;
       }
       buildAddonLink(addon, parent);
     }
   }
   else {
     // Hide the section when there are no add-ons
     parent.previousElementSibling.remove();
     parent.remove();
   }
 
-  response = yield gClient.listTabs();
+  let response = yield gClient.listTabs();
 
   parent = document.getElementById("tabActors");
 
   // Add Global Process debugging...
   let globals = Cu.cloneInto(response, {});
   delete globals.tabs;
   delete globals.selected;
   // ...only if there are appropriate actors (a 'from' property will always
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -29,18 +29,16 @@ var { DOMHelpers } = require("resource:/
 const { KeyCodes } = require("devtools/client/shared/keycodes");
 
 const { BrowserLoader } =
   Cu.import("resource://devtools/client/shared/browser-loader.js", {});
 
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties");
 
-loader.lazyRequireGetter(this, "CommandUtils",
-  "devtools/client/shared/developer-toolbar", true);
 loader.lazyRequireGetter(this, "getHighlighterUtils",
   "devtools/client/framework/toolbox-highlighter-utils", true);
 loader.lazyRequireGetter(this, "Selection",
   "devtools/client/framework/selection", true);
 loader.lazyRequireGetter(this, "InspectorFront",
   "devtools/shared/fronts/inspector", true);
 loader.lazyRequireGetter(this, "flags",
   "devtools/shared/flags");
@@ -1200,25 +1198,16 @@ Toolbox.prototype = {
     if (this.target.activeTab) {
       this.target.activeTab.reconfigure({
         "serviceWorkersTestingEnabled": serviceWorkersTestingEnabled
       });
     }
   },
 
  /**
-  * Get the toolbar spec for toolbox
-  */
-  getToolbarSpec: function () {
-    let spec = CommandUtils.getCommandbarSpec("devtools.toolbox.toolbarSpec");
-
-    return spec;
-  },
-
- /**
   * Return all toolbox buttons (command buttons, plus any others that were
   * added manually).
 
   /**
    * Update the visibility of the buttons.
    */
   updateToolboxButtonsVisibility() {
     this.toolbarButtons.forEach(button => {
--- a/devtools/client/locales/en-US/responsive.properties
+++ b/devtools/client/locales/en-US/responsive.properties
@@ -77,8 +77,60 @@ responsive.noThrottling=No throttling
 # DevicePixelRatio (DPR) dropdown when is enabled.
 responsive.devicePixelRatio=Device Pixel Ratio
 
 # LOCALIZATION NOTE (responsive.autoDPR): tooltip for the DevicePixelRatio
 # (DPR) dropdown when is disabled because a device is selected.
 # The argument (%1$S) is the selected device (e.g. iPhone 6) that set
 # automatically the DPR value.
 responsive.autoDPR=DPR automatically set by %1$S
+
+# LOCALIZATION NOTE (responsive.customDeviceName): Default value in a form to
+# add a custom device based on an arbitrary size (no association to an existing
+# device).
+responsive.customDeviceName=Custom Device
+
+# LOCALIZATION NOTE (responsive.customDeviceNameFromBase): Default value in a
+# form to add a custom device based on the properties of another.  %1$S is the
+# name of the device we're staring from, such as "Apple iPhone 6".
+responsive.customDeviceNameFromBase=%1$S (Custom)
+
+# LOCALIZATION NOTE (responsive.addDevice): Button text that reveals a form to
+# be used for adding custom devices.
+responsive.addDevice=Add Device
+
+# LOCALIZATION NOTE (responsive.deviceAdderName): Label of form field for the
+# name of a new device.  The available width is very low, so you might see
+# overlapping text if the length is much longer than 5 or so characters.
+responsive.deviceAdderName=Name
+
+# LOCALIZATION NOTE (responsive.deviceAdderSize): Label of form field for the
+# size of a new device.  The available width is very low, so you might see
+# overlapping text if the length is much longer than 5 or so characters.
+responsive.deviceAdderSize=Size
+
+# LOCALIZATION NOTE (responsive.deviceAdderPixelRatio): Label of form field for
+# the devicePixelRatio of a new device.  The available width is very low, so you
+# might see overlapping text if the length is much longer than 5 or so
+# characters.
+responsive.deviceAdderPixelRatio=DPR
+
+# LOCALIZATION NOTE (responsive.deviceAdderUserAgent): Label of form field for
+# the user agent of a new device.  The available width is very low, so you might
+# see overlapping text if the length is much longer than 5 or so characters.
+responsive.deviceAdderUserAgent=UA
+
+# LOCALIZATION NOTE (responsive.deviceAdderTouch): Label of form field for the
+# touch input support of a new device.  The available width is very low, so you
+# might see overlapping text if the length is much longer than 5 or so
+# characters.
+responsive.deviceAdderTouch=Touch
+
+# LOCALIZATION NOTE (responsive.deviceAdderSave): Button text that submits a
+# form to add a new device.
+responsive.deviceAdderSave=Save
+
+# LOCALIZATION NOTE (responsive.deviceDetails): Tooltip that appears when
+# hovering on a device in the device modal.  %1$S is the width of the device.
+# %2$S is the height of the device.  %3$S is the devicePixelRatio value of the
+# device.  %4$S is the user agent of the device.  %5$S is a boolean value
+# noting whether touch input is supported.
+responsive.deviceDetails=Size: %1$S x %2$S\nDPR: %3$S\nUA: %4$S\nTouch: %5$S
--- a/devtools/client/responsive.html/actions/devices.js
+++ b/devtools/client/responsive.html/actions/devices.js
@@ -5,21 +5,23 @@
 "use strict";
 
 const {
   ADD_DEVICE,
   ADD_DEVICE_TYPE,
   LOAD_DEVICE_LIST_START,
   LOAD_DEVICE_LIST_ERROR,
   LOAD_DEVICE_LIST_END,
+  REMOVE_DEVICE,
   UPDATE_DEVICE_DISPLAYED,
-  UPDATE_DEVICE_MODAL_OPEN,
+  UPDATE_DEVICE_MODAL,
 } = require("./index");
+const { removeDeviceAssociation } = require("./viewports");
 
-const { getDevices } = require("devtools/client/shared/devices");
+const { addDevice, getDevices, removeDevice } = require("devtools/client/shared/devices");
 
 const Services = require("Services");
 const DISPLAYED_DEVICES_PREF = "devtools.responsive.html.displayedDeviceList";
 
 /**
  * Returns an object containing the user preference of displayed devices.
  *
  * @return {Object} containing two Sets:
@@ -66,43 +68,75 @@ function updatePreferredDevices(devices)
 
 module.exports = {
 
   // This function is only exported for testing purposes
   _loadPreferredDevices: loadPreferredDevices,
 
   updatePreferredDevices: updatePreferredDevices,
 
+  addCustomDevice(device) {
+    return function* (dispatch) {
+      // Add custom device to device storage
+      yield addDevice(device, "custom");
+      dispatch({
+        type: ADD_DEVICE,
+        device,
+        deviceType: "custom",
+      });
+    };
+  },
+
   addDevice(device, deviceType) {
     return {
       type: ADD_DEVICE,
       device,
       deviceType,
     };
   },
 
   addDeviceType(deviceType) {
     return {
       type: ADD_DEVICE_TYPE,
       deviceType,
     };
   },
 
+  removeCustomDevice(device) {
+    return function* (dispatch, getState) {
+      // Check if the custom device is currently associated with any viewports
+      let { viewports } = getState();
+      for (let viewport of viewports) {
+        if (viewport.device == device.name) {
+          dispatch(removeDeviceAssociation(viewport.id));
+        }
+      }
+
+      // Remove custom device from device storage
+      yield removeDevice(device, "custom");
+      dispatch({
+        type: REMOVE_DEVICE,
+        device,
+        deviceType: "custom",
+      });
+    };
+  },
+
   updateDeviceDisplayed(device, deviceType, displayed) {
     return {
       type: UPDATE_DEVICE_DISPLAYED,
       device,
       deviceType,
       displayed,
     };
   },
 
   loadDevices() {
-    return function* (dispatch, getState) {
-      yield dispatch({ type: LOAD_DEVICE_LIST_START });
+    return function* (dispatch) {
+      dispatch({ type: LOAD_DEVICE_LIST_START });
       let preferredDevices = loadPreferredDevices();
       let devices;
 
       try {
         devices = yield getDevices();
       } catch (e) {
         console.error("Could not load device list: " + e);
         dispatch({ type: LOAD_DEVICE_LIST_ERROR });
@@ -119,20 +153,27 @@ module.exports = {
           let newDevice = Object.assign({}, device, {
             displayed: preferredDevices.added.has(device.name) ||
               (device.featured && !(preferredDevices.removed.has(device.name))),
           });
 
           dispatch(module.exports.addDevice(newDevice, type));
         }
       }
+
+      // Add an empty "custom" type if it doesn't exist in device storage
+      if (!devices.TYPES.find(type => type == "custom")) {
+        dispatch(module.exports.addDeviceType("custom"));
+      }
+
       dispatch({ type: LOAD_DEVICE_LIST_END });
     };
   },
 
-  updateDeviceModalOpen(isOpen) {
+  updateDeviceModal(isOpen, modalOpenedFromViewport = null) {
     return {
-      type: UPDATE_DEVICE_MODAL_OPEN,
+      type: UPDATE_DEVICE_MODAL,
       isOpen,
+      modalOpenedFromViewport,
     };
   },
 
 };
--- a/devtools/client/responsive.html/actions/index.js
+++ b/devtools/client/responsive.html/actions/index.js
@@ -48,30 +48,33 @@ createEnum([
   "LOAD_DEVICE_LIST_START",
 
   // Indicates that the device list loading action threw an error
   "LOAD_DEVICE_LIST_ERROR",
 
   // Indicates that the device list has been loaded successfully
   "LOAD_DEVICE_LIST_END",
 
+  // Remove a device.
+  "REMOVE_DEVICE",
+
   // Remove the viewport's device assocation.
-  "REMOVE_DEVICE",
+  "REMOVE_DEVICE_ASSOCIATION",
 
   // Resize the viewport.
   "RESIZE_VIEWPORT",
 
   // Rotate the viewport.
   "ROTATE_VIEWPORT",
 
   // Take a screenshot of the viewport.
   "TAKE_SCREENSHOT_START",
 
   // Indicates when the screenshot action ends.
   "TAKE_SCREENSHOT_END",
 
   // Update the device display state in the device selector.
   "UPDATE_DEVICE_DISPLAYED",
 
-  // Update the device modal open state.
-  "UPDATE_DEVICE_MODAL_OPEN",
+  // Update the device modal state.
+  "UPDATE_DEVICE_MODAL",
 
 ], module.exports);
--- a/devtools/client/responsive.html/actions/viewports.js
+++ b/devtools/client/responsive.html/actions/viewports.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {
   ADD_VIEWPORT,
   CHANGE_DEVICE,
   CHANGE_PIXEL_RATIO,
-  REMOVE_DEVICE,
+  REMOVE_DEVICE_ASSOCIATION,
   RESIZE_VIEWPORT,
   ROTATE_VIEWPORT
 } = require("./index");
 
 module.exports = {
 
   /**
    * Add an additional viewport to display the document.
@@ -22,21 +22,22 @@ module.exports = {
     return {
       type: ADD_VIEWPORT,
     };
   },
 
   /**
    * Change the viewport device.
    */
-  changeDevice(id, device) {
+  changeDevice(id, device, deviceType) {
     return {
       type: CHANGE_DEVICE,
       id,
       device,
+      deviceType,
     };
   },
 
   /**
    * Change the viewport pixel ratio.
    */
   changePixelRatio(id, pixelRatio = 0) {
     return {
@@ -44,19 +45,19 @@ module.exports = {
       id,
       pixelRatio,
     };
   },
 
   /**
    * Remove the viewport's device assocation.
    */
-  removeDevice(id) {
+  removeDeviceAssociation(id) {
     return {
-      type: REMOVE_DEVICE,
+      type: REMOVE_DEVICE_ASSOCIATION,
       id,
     };
   },
 
   /**
    * Resize the viewport.
    */
   resizeViewport(id, width, height) {
--- a/devtools/client/responsive.html/app.js
+++ b/devtools/client/responsive.html/app.js
@@ -6,27 +6,29 @@
 
 "use strict";
 
 const { createClass, createFactory, PropTypes, DOM: dom } =
   require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
 const {
+  addCustomDevice,
+  removeCustomDevice,
   updateDeviceDisplayed,
-  updateDeviceModalOpen,
+  updateDeviceModal,
   updatePreferredDevices,
 } = require("./actions/devices");
 const { changeNetworkThrottling } = require("./actions/network-throttling");
 const { takeScreenshot } = require("./actions/screenshot");
 const { changeTouchSimulation } = require("./actions/touch-simulation");
 const {
   changeDevice,
   changePixelRatio,
-  removeDevice,
+  removeDeviceAssociation,
   resizeViewport,
   rotateViewport,
 } = require("./actions/viewports");
 const DeviceModal = createFactory(require("./components/device-modal"));
 const GlobalToolbar = createFactory(require("./components/global-toolbar"));
 const Viewports = createFactory(require("./components/viewports"));
 const Types = require("./types");
 
@@ -39,26 +41,30 @@ let App = createClass({
     displayPixelRatio: Types.pixelRatio.value.isRequired,
     location: Types.location.isRequired,
     networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
     screenshot: PropTypes.shape(Types.screenshot).isRequired,
     touchSimulation: PropTypes.shape(Types.touchSimulation).isRequired,
     viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
   },
 
+  onAddCustomDevice(device) {
+    this.props.dispatch(addCustomDevice(device));
+  },
+
   onBrowserMounted() {
     window.postMessage({ type: "browser-mounted" }, "*");
   },
 
-  onChangeDevice(id, device) {
+  onChangeDevice(id, device, deviceType) {
     window.postMessage({
       type: "change-device",
       device,
     }, "*");
-    this.props.dispatch(changeDevice(id, device.name));
+    this.props.dispatch(changeDevice(id, device.name, deviceType));
     this.props.dispatch(changeTouchSimulation(device.touch));
     this.props.dispatch(changePixelRatio(id, device.pixelRatio));
   },
 
   onChangeNetworkThrottling(enabled, profile) {
     window.postMessage({
       type: "change-network-throtting",
       enabled,
@@ -94,22 +100,26 @@ let App = createClass({
   onDeviceListUpdate(devices) {
     updatePreferredDevices(devices);
   },
 
   onExit() {
     window.postMessage({ type: "exit" }, "*");
   },
 
-  onRemoveDevice(id) {
+  onRemoveCustomDevice(device) {
+    this.props.dispatch(removeCustomDevice(device));
+  },
+
+  onRemoveDeviceAssociation(id) {
     // TODO: Bug 1332754: Move messaging and logic into the action creator.
     window.postMessage({
-      type: "remove-device",
+      type: "remove-device-association",
     }, "*");
-    this.props.dispatch(removeDevice(id));
+    this.props.dispatch(removeDeviceAssociation(id));
     this.props.dispatch(changeTouchSimulation(false));
     this.props.dispatch(changePixelRatio(id, 0));
   },
 
   onResizeViewport(id, width, height) {
     this.props.dispatch(resizeViewport(id, width, height));
   },
 
@@ -120,56 +130,63 @@ let App = createClass({
   onScreenshot() {
     this.props.dispatch(takeScreenshot());
   },
 
   onUpdateDeviceDisplayed(device, deviceType, displayed) {
     this.props.dispatch(updateDeviceDisplayed(device, deviceType, displayed));
   },
 
-  onUpdateDeviceModalOpen(isOpen) {
-    this.props.dispatch(updateDeviceModalOpen(isOpen));
+  onUpdateDeviceModal(isOpen, modalOpenedFromViewport) {
+    this.props.dispatch(updateDeviceModal(isOpen, modalOpenedFromViewport));
   },
 
   render() {
     let {
       devices,
       displayPixelRatio,
       location,
       networkThrottling,
       screenshot,
       touchSimulation,
       viewports,
     } = this.props;
 
     let {
+      onAddCustomDevice,
       onBrowserMounted,
       onChangeDevice,
       onChangeNetworkThrottling,
       onChangePixelRatio,
       onChangeTouchSimulation,
       onContentResize,
       onDeviceListUpdate,
       onExit,
-      onRemoveDevice,
+      onRemoveCustomDevice,
+      onRemoveDeviceAssociation,
       onResizeViewport,
       onRotateViewport,
       onScreenshot,
       onUpdateDeviceDisplayed,
-      onUpdateDeviceModalOpen,
+      onUpdateDeviceModal,
     } = this;
 
     let selectedDevice = "";
     let selectedPixelRatio = { value: 0 };
 
     if (viewports.length) {
       selectedDevice = viewports[0].device;
       selectedPixelRatio = viewports[0].pixelRatio;
     }
 
+    let deviceAdderViewportTemplate = {};
+    if (devices.modalOpenedFromViewport !== null) {
+      deviceAdderViewportTemplate = viewports[devices.modalOpenedFromViewport];
+    }
+
     return dom.div(
       {
         id: "app",
       },
       GlobalToolbar({
         devices,
         displayPixelRatio,
         networkThrottling,
@@ -186,25 +203,28 @@ let App = createClass({
       Viewports({
         devices,
         location,
         screenshot,
         viewports,
         onBrowserMounted,
         onChangeDevice,
         onContentResize,
-        onRemoveDevice,
+        onRemoveDeviceAssociation,
         onRotateViewport,
         onResizeViewport,
-        onUpdateDeviceModalOpen,
+        onUpdateDeviceModal,
       }),
       DeviceModal({
+        deviceAdderViewportTemplate,
         devices,
+        onAddCustomDevice,
         onDeviceListUpdate,
+        onRemoveCustomDevice,
         onUpdateDeviceDisplayed,
-        onUpdateDeviceModalOpen,
+        onUpdateDeviceModal,
       })
     );
   },
 
 });
 
 module.exports = connect(state => state)(App);
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/components/device-adder.js
@@ -0,0 +1,252 @@
+/* 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/. */
+
+/* eslint-env browser */
+
+"use strict";
+
+const { DOM: dom, createClass, createFactory, PropTypes, addons } =
+  require("devtools/client/shared/vendor/react");
+
+const { getFormatStr, getStr } = require("../utils/l10n");
+const Types = require("../types");
+const ViewportDimension = createFactory(require("./viewport-dimension"));
+
+module.exports = createClass({
+  displayName: "DeviceAdder",
+
+  propTypes: {
+    devices: PropTypes.shape(Types.devices).isRequired,
+    viewportTemplate: PropTypes.shape(Types.viewport).isRequired,
+    onAddCustomDevice: PropTypes.func.isRequired,
+  },
+
+  mixins: [ addons.PureRenderMixin ],
+
+  getInitialState() {
+    return {};
+  },
+
+  componentWillReceiveProps(nextProps) {
+    let {
+      width,
+      height,
+    } = nextProps.viewportTemplate;
+
+    this.setState({
+      width,
+      height,
+    });
+  },
+
+  onChangeSize(width, height) {
+    this.setState({
+      width,
+      height,
+    });
+  },
+
+  onDeviceAdderShow() {
+    this.setState({
+      deviceAdderDisplayed: true,
+    });
+  },
+
+  onDeviceAdderSave() {
+    let {
+      devices,
+      onAddCustomDevice,
+    } = this.props;
+
+    if (!this.pixelRatioInput.checkValidity()) {
+      return;
+    }
+    if (devices.custom.find(device => device.name == this.nameInput.value)) {
+      this.nameInput.setCustomValidity("Device name already in use");
+      return;
+    }
+
+    this.setState({
+      deviceAdderDisplayed: false,
+    });
+    onAddCustomDevice({
+      name: this.nameInput.value,
+      width: this.state.width,
+      height: this.state.height,
+      pixelRatio: parseFloat(this.pixelRatioInput.value),
+      userAgent: this.userAgentInput.value,
+      touch: this.touchInput.checked,
+    });
+  },
+
+  render() {
+    let {
+      devices,
+      viewportTemplate,
+    } = this.props;
+
+    let {
+      deviceAdderDisplayed,
+      height,
+      width,
+    } = this.state;
+
+    if (!deviceAdderDisplayed) {
+      return dom.div(
+        {
+          id: "device-adder"
+        },
+        dom.button(
+          {
+            id: "device-adder-show",
+            onClick: this.onDeviceAdderShow,
+          },
+          getStr("responsive.addDevice")
+        )
+      );
+    }
+
+    // If a device is currently selected, fold its attributes into a single object for use
+    // as the starting values of the form.  If no device is selected, use the values for
+    // the current window.
+    let deviceName;
+    let normalizedViewport = Object.assign({}, viewportTemplate);
+    if (viewportTemplate.device) {
+      let device = devices[viewportTemplate.deviceType].find(d => {
+        return d.name == viewportTemplate.device;
+      });
+      deviceName = getFormatStr("responsive.customDeviceNameFromBase", device.name);
+      Object.assign(normalizedViewport, {
+        pixelRatio: device.pixelRatio,
+        userAgent: device.userAgent,
+        touch: device.touch,
+      });
+    } else {
+      deviceName = getStr("responsive.customDeviceName");
+      Object.assign(normalizedViewport, {
+        pixelRatio: window.devicePixelRatio,
+        userAgent: navigator.userAgent,
+        touch: false,
+      });
+    }
+
+    return dom.div(
+      {
+        id: "device-adder"
+      },
+      dom.div(
+        {
+          id: "device-adder-content",
+        },
+        dom.div(
+          {
+            id: "device-adder-column-1",
+          },
+          dom.label(
+            {
+              id: "device-adder-name",
+            },
+            dom.span(
+              {
+                className: "device-adder-label",
+              },
+              getStr("responsive.deviceAdderName")
+            ),
+            dom.input({
+              defaultValue: deviceName,
+              ref: input => {
+                this.nameInput = input;
+              },
+            })
+          ),
+          dom.label(
+            {
+              id: "device-adder-size",
+            },
+            dom.span(
+              {
+                className: "device-adder-label"
+              },
+              getStr("responsive.deviceAdderSize")
+            ),
+            ViewportDimension({
+              viewport: {
+                width,
+                height,
+              },
+              onChangeSize: this.onChangeSize,
+              onRemoveDeviceAssociation: () => {},
+            })
+          ),
+          dom.label(
+            {
+              id: "device-adder-pixel-ratio",
+            },
+            dom.span(
+              {
+                className: "device-adder-label"
+              },
+              getStr("responsive.deviceAdderPixelRatio")
+            ),
+            dom.input({
+              type: "number",
+              step: "any",
+              defaultValue: normalizedViewport.pixelRatio,
+              ref: input => {
+                this.pixelRatioInput = input;
+              },
+            })
+          )
+        ),
+        dom.div(
+          {
+            id: "device-adder-column-2",
+          },
+          dom.label(
+            {
+              id: "device-adder-user-agent",
+            },
+            dom.span(
+              {
+                className: "device-adder-label"
+              },
+              getStr("responsive.deviceAdderUserAgent")
+            ),
+            dom.input({
+              defaultValue: normalizedViewport.userAgent,
+              ref: input => {
+                this.userAgentInput = input;
+              },
+            })
+          ),
+          dom.label(
+            {
+              id: "device-adder-touch",
+            },
+            dom.span(
+              {
+                className: "device-adder-label"
+              },
+              getStr("responsive.deviceAdderTouch")
+            ),
+            dom.input({
+              defaultChecked: normalizedViewport.touch,
+              type: "checkbox",
+              ref: input => {
+                this.touchInput = input;
+              },
+            })
+          )
+        ),
+      ),
+      dom.button(
+        {
+          id: "device-adder-save",
+          onClick: this.onDeviceAdderSave,
+        },
+        getStr("responsive.deviceAdderSave")
+      )
+    );
+  },
+});
--- a/devtools/client/responsive.html/components/device-modal.js
+++ b/devtools/client/responsive.html/components/device-modal.js
@@ -1,29 +1,34 @@
 /* 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/. */
 
 /* eslint-env browser */
 
 "use strict";
 
-const { DOM: dom, createClass, PropTypes, addons } =
+const { DOM: dom, createClass, createFactory, PropTypes, addons } =
   require("devtools/client/shared/vendor/react");
-const { getStr } = require("../utils/l10n");
+
+const { getStr, getFormatStr } = require("../utils/l10n");
 const Types = require("../types");
+const DeviceAdder = createFactory(require("./device-adder"));
 
 module.exports = createClass({
   displayName: "DeviceModal",
 
   propTypes: {
+    deviceAdderViewportTemplate: PropTypes.shape(Types.viewport).isRequired,
     devices: PropTypes.shape(Types.devices).isRequired,
+    onAddCustomDevice: PropTypes.func.isRequired,
     onDeviceListUpdate: PropTypes.func.isRequired,
+    onRemoveCustomDevice: PropTypes.func.isRequired,
     onUpdateDeviceDisplayed: PropTypes.func.isRequired,
-    onUpdateDeviceModalOpen: PropTypes.func.isRequired,
+    onUpdateDeviceModal: PropTypes.func.isRequired,
   },
 
   mixins: [ addons.PureRenderMixin ],
 
   getInitialState() {
     return {};
   },
 
@@ -58,17 +63,17 @@ module.exports = createClass({
     });
   },
 
   onDeviceModalSubmit() {
     let {
       devices,
       onDeviceListUpdate,
       onUpdateDeviceDisplayed,
-      onUpdateDeviceModalOpen,
+      onUpdateDeviceModal,
     } = this.props;
 
     let preferredDevices = {
       "added": new Set(),
       "removed": new Set(),
     };
 
     for (let type of devices.types) {
@@ -83,36 +88,39 @@ module.exports = createClass({
 
         if (this.state[device.name] != device.displayed) {
           onUpdateDeviceDisplayed(device, type, this.state[device.name]);
         }
       }
     }
 
     onDeviceListUpdate(preferredDevices);
-    onUpdateDeviceModalOpen(false);
+    onUpdateDeviceModal(false);
   },
 
   onKeyDown(event) {
     if (!this.props.devices.isModalOpen) {
       return;
     }
     // Escape keycode
     if (event.keyCode === 27) {
       let {
-        onUpdateDeviceModalOpen
+        onUpdateDeviceModal
       } = this.props;
-      onUpdateDeviceModalOpen(false);
+      onUpdateDeviceModal(false);
     }
   },
 
   render() {
     let {
+      deviceAdderViewportTemplate,
       devices,
-      onUpdateDeviceModalOpen,
+      onAddCustomDevice,
+      onRemoveCustomDevice,
+      onUpdateDeviceModal,
     } = this.props;
 
     const sortedDevices = {};
     for (let type of devices.types) {
       sortedDevices[type] = Object.assign([], devices[type])
         .sort((a, b) => a.name.localeCompare(b.name));
     }
 
@@ -123,17 +131,17 @@ module.exports = createClass({
       },
       dom.div(
         {
           className: "device-modal container",
         },
         dom.button({
           id: "device-close-button",
           className: "toolbar-button devtools-button",
-          onClick: () => onUpdateDeviceModalOpen(false),
+          onClick: () => onUpdateDeviceModal(false),
         }),
         dom.div(
           {
             className: "device-modal-content",
           },
           devices.types.map(type => {
             return dom.div(
               {
@@ -142,43 +150,68 @@ module.exports = createClass({
               },
               dom.header(
                 {
                   className: "device-header",
                 },
                 type
               ),
               sortedDevices[type].map(device => {
+                let details = getFormatStr(
+                  "responsive.deviceDetails", device.width, device.height,
+                  device.pixelRatio, device.userAgent, device.touch
+                );
+
+                let removeDeviceButton;
+                if (type == "custom") {
+                  removeDeviceButton = dom.button({
+                    className: "device-remove-button toolbar-button devtools-button",
+                    onClick: () => onRemoveCustomDevice(device),
+                  });
+                }
+
                 return dom.label(
                   {
                     className: "device-label",
                     key: device.name,
+                    title: details,
                   },
                   dom.input({
                     className: "device-input-checkbox",
                     type: "checkbox",
                     value: device.name,
                     checked: this.state[device.name],
                     onChange: this.onDeviceCheckboxChange,
                   }),
-                  device.name
+                  dom.span(
+                    {
+                      className: "device-name",
+                    },
+                    device.name
+                  ),
+                  removeDeviceButton
                 );
               })
             );
           })
         ),
+        DeviceAdder({
+          devices,
+          viewportTemplate: deviceAdderViewportTemplate,
+          onAddCustomDevice,
+        }),
         dom.button(
           {
             id: "device-submit-button",
             onClick: this.onDeviceModalSubmit,
           },
           getStr("responsive.done")
         )
       ),
       dom.div(
         {
           className: "modal-overlay",
-          onClick: () => onUpdateDeviceModalOpen(false),
+          onClick: () => onUpdateDeviceModal(false),
         }
       )
     );
   },
 });
--- a/devtools/client/responsive.html/components/device-selector.js
+++ b/devtools/client/responsive.html/components/device-selector.js
@@ -12,40 +12,42 @@ const Types = require("../types");
 const OPEN_DEVICE_MODAL_VALUE = "OPEN_DEVICE_MODAL";
 
 module.exports = createClass({
   displayName: "DeviceSelector",
 
   propTypes: {
     devices: PropTypes.shape(Types.devices).isRequired,
     selectedDevice: PropTypes.string.isRequired,
+    viewportId: PropTypes.number.isRequired,
     onChangeDevice: PropTypes.func.isRequired,
     onResizeViewport: PropTypes.func.isRequired,
-    onUpdateDeviceModalOpen: PropTypes.func.isRequired,
+    onUpdateDeviceModal: PropTypes.func.isRequired,
   },
 
   mixins: [ addons.PureRenderMixin ],
 
   onSelectChange({ target }) {
     let {
       devices,
+      viewportId,
       onChangeDevice,
       onResizeViewport,
-      onUpdateDeviceModalOpen,
+      onUpdateDeviceModal,
     } = this.props;
 
     if (target.value === OPEN_DEVICE_MODAL_VALUE) {
-      onUpdateDeviceModalOpen(true);
+      onUpdateDeviceModal(true, viewportId);
       return;
     }
     for (let type of devices.types) {
       for (let device of devices[type]) {
         if (device.name === target.value) {
           onResizeViewport(device.width, device.height);
-          onChangeDevice(device);
+          onChangeDevice(device, type);
           return;
         }
       }
     }
   },
 
   render() {
     let {
--- a/devtools/client/responsive.html/components/moz.build
+++ b/devtools/client/responsive.html/components/moz.build
@@ -1,16 +1,17 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'browser.js',
+    'device-adder.js',
     'device-modal.js',
     'device-selector.js',
     'dpr-selector.js',
     'global-toolbar.js',
     'network-throttling-selector.js',
     'resizable-viewport.js',
     'viewport-dimension.js',
     'viewport-toolbar.js',
--- a/devtools/client/responsive.html/components/resizable-viewport.js
+++ b/devtools/client/responsive.html/components/resizable-viewport.js
@@ -25,20 +25,20 @@ module.exports = createClass({
     devices: PropTypes.shape(Types.devices).isRequired,
     location: Types.location.isRequired,
     screenshot: PropTypes.shape(Types.screenshot).isRequired,
     swapAfterMount: PropTypes.bool.isRequired,
     viewport: PropTypes.shape(Types.viewport).isRequired,
     onBrowserMounted: PropTypes.func.isRequired,
     onChangeDevice: PropTypes.func.isRequired,
     onContentResize: PropTypes.func.isRequired,
-    onRemoveDevice: PropTypes.func.isRequired,
+    onRemoveDeviceAssociation: PropTypes.func.isRequired,
     onResizeViewport: PropTypes.func.isRequired,
     onRotateViewport: PropTypes.func.isRequired,
-    onUpdateDeviceModalOpen: PropTypes.func.isRequired,
+    onUpdateDeviceModal: PropTypes.func.isRequired,
   },
 
   getInitialState() {
     return {
       isResizing: false,
       lastClientX: 0,
       lastClientY: 0,
       ignoreX: false,
@@ -109,17 +109,17 @@ module.exports = createClass({
     this.props.onResizeViewport(width, height);
     // Change the device selector back to an unselected device
     // TODO: Bug 1332754: Logic like this probably belongs in the action creator.
     if (this.props.viewport.device) {
       // In bug 1329843 and others, we may eventually stop this approach of removing the
       // the properties of the device on resize.  However, at the moment, there is no
       // way to edit dPR when a device is selected, and there is no UI at all for editing
       // UA, so it's important to keep doing this for now.
-      this.props.onRemoveDevice();
+      this.props.onRemoveDeviceAssociation();
     }
 
     this.setState({
       lastClientX,
       lastClientY
     });
   },
 
@@ -130,17 +130,17 @@ module.exports = createClass({
       screenshot,
       swapAfterMount,
       viewport,
       onBrowserMounted,
       onChangeDevice,
       onContentResize,
       onResizeViewport,
       onRotateViewport,
-      onUpdateDeviceModalOpen,
+      onUpdateDeviceModal,
     } = this.props;
 
     let resizeHandleClass = "viewport-resize-handle";
     if (screenshot.isCapturing) {
       resizeHandleClass += " hidden";
     }
 
     let contentClass = "viewport-content";
@@ -149,21 +149,21 @@ module.exports = createClass({
     }
 
     return dom.div(
       {
         className: "resizable-viewport",
       },
       ViewportToolbar({
         devices,
-        selectedDevice: viewport.device,
+        viewport,
         onChangeDevice,
         onResizeViewport,
         onRotateViewport,
-        onUpdateDeviceModalOpen,
+        onUpdateDeviceModal,
       }),
       dom.div(
         {
           className: contentClass,
           style: {
             width: viewport.width + "px",
             height: viewport.height + "px",
           },
--- a/devtools/client/responsive.html/components/viewport-dimension.js
+++ b/devtools/client/responsive.html/components/viewport-dimension.js
@@ -10,18 +10,18 @@ const { DOM: dom, createClass, PropTypes
 const Constants = require("../constants");
 const Types = require("../types");
 
 module.exports = createClass({
   displayName: "ViewportDimension",
 
   propTypes: {
     viewport: PropTypes.shape(Types.viewport).isRequired,
-    onRemoveDevice: PropTypes.func.isRequired,
-    onResizeViewport: PropTypes.func.isRequired,
+    onChangeSize: PropTypes.func.isRequired,
+    onRemoveDeviceAssociation: PropTypes.func.isRequired,
   },
 
   getInitialState() {
     let { width, height } = this.props.viewport;
 
     return {
       width,
       height,
@@ -111,20 +111,20 @@ module.exports = createClass({
       });
 
       return;
     }
 
     // Change the device selector back to an unselected device
     // TODO: Bug 1332754: Logic like this probably belongs in the action creator.
     if (this.props.viewport.device) {
-      this.props.onRemoveDevice();
+      this.props.onRemoveDeviceAssociation();
     }
-    this.props.onResizeViewport(parseInt(this.state.width, 10),
-                                parseInt(this.state.height, 10));
+    this.props.onChangeSize(parseInt(this.state.width, 10),
+                            parseInt(this.state.height, 10));
   },
 
   render() {
     let editableClass = "viewport-dimension-editable";
     let inputClass = "viewport-dimension-input";
 
     if (this.state.isEditing) {
       editableClass += " editing";
--- a/devtools/client/responsive.html/components/viewport-toolbar.js
+++ b/devtools/client/responsive.html/components/viewport-toolbar.js
@@ -11,45 +11,46 @@ const { getStr } = require("../utils/l10
 const Types = require("../types");
 const DeviceSelector = createFactory(require("./device-selector"));
 
 module.exports = createClass({
   displayName: "ViewportToolbar",
 
   propTypes: {
     devices: PropTypes.shape(Types.devices).isRequired,
-    selectedDevice: PropTypes.string.isRequired,
+    viewport: PropTypes.shape(Types.viewport).isRequired,
     onChangeDevice: PropTypes.func.isRequired,
     onResizeViewport: PropTypes.func.isRequired,
     onRotateViewport: PropTypes.func.isRequired,
-    onUpdateDeviceModalOpen: PropTypes.func.isRequired,
+    onUpdateDeviceModal: PropTypes.func.isRequired,
   },
 
   mixins: [ addons.PureRenderMixin ],
 
   render() {
     let {
       devices,
-      selectedDevice,
+      viewport,
       onChangeDevice,
       onResizeViewport,
       onRotateViewport,
-      onUpdateDeviceModalOpen,
+      onUpdateDeviceModal,
     } = this.props;
 
     return dom.div(
       {
         className: "viewport-toolbar container",
       },
       DeviceSelector({
         devices,
-        selectedDevice,
+        selectedDevice: viewport.device,
+        viewportId: viewport.id,
         onChangeDevice,
         onResizeViewport,
-        onUpdateDeviceModalOpen,
+        onUpdateDeviceModal,
       }),
       dom.button({
         className: "viewport-rotate-button toolbar-button devtools-button",
         onClick: onRotateViewport,
         title: getStr("responsive.rotate"),
       })
     );
   },
--- a/devtools/client/responsive.html/components/viewport.js
+++ b/devtools/client/responsive.html/components/viewport.js
@@ -19,38 +19,38 @@ module.exports = createClass({
     devices: PropTypes.shape(Types.devices).isRequired,
     location: Types.location.isRequired,
     screenshot: PropTypes.shape(Types.screenshot).isRequired,
     swapAfterMount: PropTypes.bool.isRequired,
     viewport: PropTypes.shape(Types.viewport).isRequired,
     onBrowserMounted: PropTypes.func.isRequired,
     onChangeDevice: PropTypes.func.isRequired,
     onContentResize: PropTypes.func.isRequired,
-    onRemoveDevice: PropTypes.func.isRequired,
+    onRemoveDeviceAssociation: PropTypes.func.isRequired,
     onResizeViewport: PropTypes.func.isRequired,
     onRotateViewport: PropTypes.func.isRequired,
-    onUpdateDeviceModalOpen: PropTypes.func.isRequired,
+    onUpdateDeviceModal: PropTypes.func.isRequired,
   },
 
-  onChangeDevice(device) {
+  onChangeDevice(device, deviceType) {
     let {
       viewport,
       onChangeDevice,
     } = this.props;
 
-    onChangeDevice(viewport.id, device);
+    onChangeDevice(viewport.id, device, deviceType);
   },
 
-  onRemoveDevice() {
+  onRemoveDeviceAssociation() {
     let {
       viewport,
-      onRemoveDevice,
+      onRemoveDeviceAssociation,
     } = this.props;
 
-    onRemoveDevice(viewport.id);
+    onRemoveDeviceAssociation(viewport.id);
   },
 
   onResizeViewport(width, height) {
     let {
       viewport,
       onResizeViewport,
     } = this.props;
 
@@ -70,45 +70,45 @@ module.exports = createClass({
     let {
       devices,
       location,
       screenshot,
       swapAfterMount,
       viewport,
       onBrowserMounted,
       onContentResize,
-      onUpdateDeviceModalOpen,
+      onUpdateDeviceModal,
     } = this.props;
 
     let {
       onChangeDevice,
-      onRemoveDevice,
+      onRemoveDeviceAssociation,
       onRotateViewport,
       onResizeViewport,
     } = this;
 
     return dom.div(
       {
         className: "viewport",
       },
       ViewportDimension({
         viewport,
-        onRemoveDevice,
-        onResizeViewport,
+        onChangeSize: onResizeViewport,
+        onRemoveDeviceAssociation,
       }),
       ResizableViewport({
         devices,
         location,
         screenshot,
         swapAfterMount,
         viewport,
         onBrowserMounted,
         onChangeDevice,
         onContentResize,
-        onRemoveDevice,
+        onRemoveDeviceAssociation,
         onResizeViewport,
         onRotateViewport,
-        onUpdateDeviceModalOpen,
+        onUpdateDeviceModal,
       })
     );
   },
 
 });
--- a/devtools/client/responsive.html/components/viewports.js
+++ b/devtools/client/responsive.html/components/viewports.js
@@ -17,35 +17,35 @@ module.exports = createClass({
   propTypes: {
     devices: PropTypes.shape(Types.devices).isRequired,
     location: Types.location.isRequired,
     screenshot: PropTypes.shape(Types.screenshot).isRequired,
     viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
     onBrowserMounted: PropTypes.func.isRequired,
     onChangeDevice: PropTypes.func.isRequired,
     onContentResize: PropTypes.func.isRequired,
-    onRemoveDevice: PropTypes.func.isRequired,
+    onRemoveDeviceAssociation: PropTypes.func.isRequired,
     onResizeViewport: PropTypes.func.isRequired,
     onRotateViewport: PropTypes.func.isRequired,
-    onUpdateDeviceModalOpen: PropTypes.func.isRequired,
+    onUpdateDeviceModal: PropTypes.func.isRequired,
   },
 
   render() {
     let {
       devices,
       location,
       screenshot,
       viewports,
       onBrowserMounted,
       onChangeDevice,
       onContentResize,
-      onRemoveDevice,
+      onRemoveDeviceAssociation,
       onResizeViewport,
       onRotateViewport,
-      onUpdateDeviceModalOpen,
+      onUpdateDeviceModal,
     } = this.props;
 
     return dom.div(
       {
         id: "viewports",
       },
       viewports.map((viewport, i) => {
         return Viewport({
@@ -53,18 +53,18 @@ module.exports = createClass({
           devices,
           location,
           screenshot,
           swapAfterMount: i == 0,
           viewport,
           onBrowserMounted,
           onChangeDevice,
           onContentResize,
-          onRemoveDevice,
+          onRemoveDeviceAssociation,
           onResizeViewport,
           onRotateViewport,
-          onUpdateDeviceModalOpen,
+          onUpdateDeviceModal,
         });
       })
     );
   },
 
 });
--- a/devtools/client/responsive.html/index.css
+++ b/devtools/client/responsive.html/index.css
@@ -33,17 +33,18 @@
     url("./images/select-arrow.svg#dark-selected");
 }
 
 * {
   box-sizing: border-box;
 }
 
 #root,
-html, body {
+html,
+body {
   height: 100%;
   margin: 0;
 }
 
 #app {
   /* Center the viewports container */
   display: flex;
   align-items: center;
@@ -361,16 +362,17 @@ select > option.divider {
 .viewport-dimension-input {
   color: var(--theme-body-color-inactive);
   transition: all 0.25s ease;
 }
 
 .viewport-dimension-editable.editing,
 .viewport-dimension-input.editing {
   color: var(--viewport-active-color);
+  outline: none;
 }
 
 .viewport-dimension-editable.editing {
   border-bottom: 1px solid var(--theme-selection-background);
 }
 
 .viewport-dimension-editable.editing.invalid {
   border-bottom: 1px solid #d92215;
@@ -419,17 +421,17 @@ select > option.divider {
   display: none;
   position: absolute;
   margin: auto;
   top: 0;
   bottom: 0;
   left: 0;
   right: 0;
   width: 642px;
-  height: 612px;
+  height: 650px;
   z-index: 1;
 }
 
 /* Handles the opening/closing of the modal */
 #device-modal-wrapper.opened .device-modal {
   animation: fade-in-and-up 0.3s ease;
   animation-fill-mode: forwards;
   display: block;
@@ -452,19 +454,19 @@ select > option.divider {
   opacity: 0.5;
 }
 
 .device-modal-content {
   display: flex;
   flex-direction: column;
   flex-wrap: wrap;
   overflow: auto;
-  height: 550px;
+  height: 515px;
   width: 600px;
-  margin: 20px;
+  margin: 20px 20px 0;
 }
 
 #device-close-button,
 #device-close-button::before {
   position: absolute;
   top: 5px;
   right: 2px;
   width: 12px;
@@ -489,33 +491,123 @@ select > option.divider {
   padding: 0 0 3px 23px;
 }
 
 .device-label {
   font-size: 11px;
   padding-bottom: 3px;
   display: flex;
   align-items: center;
+  /* Largest size without horizontal scrollbars */
+  max-width: 181px;
 }
 
 .device-input-checkbox {
   margin-right: 5px;
 }
 
+.device-name {
+  flex: 1;
+}
+
+.device-remove-button,
+.device-remove-button::before {
+  width: 12px;
+  height: 12px;
+}
+
+.device-remove-button::before {
+  background-image: url("./images/close.svg");
+  margin: -6px 0 0 -6px;
+}
+
 #device-submit-button {
   background-color: var(--theme-tab-toolbar-background);
   border-width: 1px 0 0 0;
   border-top-width: 1px;
   border-top-style: solid;
   border-top-color: var(--theme-splitter-color);
   color: var(--theme-body-color);
   width: 100%;
   height: 20px;
+  position: absolute;
+  bottom: 0;
 }
 
 #device-submit-button:hover {
   background-color: var(--toolbar-tab-hover);
 }
 
 #device-submit-button:hover:active {
   background-color: var(--submit-button-active-background-color);
   color: var(--submit-button-active-color);
 }
+
+/**
+ * Device Adder
+ */
+
+#device-adder {
+  display: flex;
+  flex-direction: column;
+  margin: 0 20px;
+}
+
+#device-adder-content {
+  display: flex;
+}
+
+#device-adder-column-1 {
+  flex: 1;
+  margin-right: 10px;
+}
+
+#device-adder-column-2 {
+  flex: 2;
+}
+
+#device-adder button {
+  background-color: var(--theme-tab-toolbar-background);
+  border: 1px solid var(--theme-splitter-color);
+  border-radius: 2px;
+  color: var(--theme-body-color);
+  margin: 0 auto;
+}
+
+#device-adder label {
+  display: flex;
+  margin-bottom: 5px;
+  align-items: center;
+}
+
+#device-adder label > input,
+#device-adder label > .viewport-dimension {
+  flex: 1;
+  margin: 0;
+}
+
+#device-adder input {
+  background: transparent;
+  border: 1px solid transparent;
+  text-align: center;
+  color: var(--theme-body-color-inactive);
+  transition: all 0.25s ease;
+}
+
+#device-adder input:focus {
+  color: var(--viewport-active-color);
+}
+
+#device-adder label > input:focus,
+#device-adder label > .viewport-dimension:focus  {
+  border-bottom: 1px solid var(--theme-selection-background);
+  outline: none;
+}
+
+.device-adder-label {
+  display: inline-block;
+  margin-right: 5px;
+  min-width: 35px;
+}
+
+#device-adder #device-adder-save {
+  margin-top: 5px;
+}
--- a/devtools/client/responsive.html/manager.js
+++ b/devtools/client/responsive.html/manager.js
@@ -462,18 +462,18 @@ ResponsiveUI.prototype = {
         this.onChangeTouchSimulation(event);
         break;
       case "content-resize":
         this.onContentResize(event);
         break;
       case "exit":
         this.onExit();
         break;
-      case "remove-device":
-        this.onRemoveDevice(event);
+      case "remove-device-association":
+        this.onRemoveDeviceAssociation(event);
         break;
     }
   },
 
   onChangeDevice: Task.async(function* (event) {
     let { userAgent, pixelRatio, touch } = event.data.device;
     yield this.updateUserAgent(userAgent);
     yield this.updateDPPX(pixelRatio);
@@ -507,17 +507,17 @@ ResponsiveUI.prototype = {
     });
   },
 
   onExit() {
     let { browserWindow, tab } = this;
     ResponsiveUIManager.closeIfNeeded(browserWindow, tab);
   },
 
-  onRemoveDevice: Task.async(function* (event) {
+  onRemoveDeviceAssociation: Task.async(function* (event) {
     yield this.updateUserAgent();
     yield this.updateDPPX();
     yield this.updateTouchSimulation();
     // Used by tests
     this.emit("device-removed");
   }),
 
   updateDPPX: Task.async(function* (dppx) {
--- a/devtools/client/responsive.html/reducers/devices.js
+++ b/devtools/client/responsive.html/reducers/devices.js
@@ -5,25 +5,27 @@
 "use strict";
 
 const {
   ADD_DEVICE,
   ADD_DEVICE_TYPE,
   LOAD_DEVICE_LIST_START,
   LOAD_DEVICE_LIST_ERROR,
   LOAD_DEVICE_LIST_END,
+  REMOVE_DEVICE,
   UPDATE_DEVICE_DISPLAYED,
-  UPDATE_DEVICE_MODAL_OPEN,
+  UPDATE_DEVICE_MODAL,
 } = require("../actions/index");
 
 const Types = require("../types");
 
 const INITIAL_DEVICES = {
   types: [],
   isModalOpen: false,
+  modalOpenedFromViewport: null,
   listState: Types.deviceListState.INITIALIZED,
 };
 
 let reducers = {
 
   [ADD_DEVICE](devices, { device, deviceType }) {
     return Object.assign({}, devices, {
       [deviceType]: [...devices[deviceType], device],
@@ -64,19 +66,33 @@ let reducers = {
   },
 
   [LOAD_DEVICE_LIST_END](devices, action) {
     return Object.assign({}, devices, {
       listState: Types.deviceListState.LOADED,
     });
   },
 
-  [UPDATE_DEVICE_MODAL_OPEN](devices, { isOpen }) {
+  [REMOVE_DEVICE](devices, { device, deviceType }) {
+    let index = devices[deviceType].indexOf(device);
+    if (index < 0) {
+      return devices;
+    }
+
+    let list = [...devices[deviceType]];
+    list.splice(index, 1);
+    return Object.assign({}, devices, {
+      [deviceType]: list
+    });
+  },
+
+  [UPDATE_DEVICE_MODAL](devices, { isOpen, modalOpenedFromViewport }) {
     return Object.assign({}, devices, {
       isModalOpen: isOpen,
+      modalOpenedFromViewport,
     });
   },
 
 };
 
 module.exports = function (devices = INITIAL_DEVICES, action) {
   let reducer = reducers[action.type];
   if (!reducer) {
--- a/devtools/client/responsive.html/reducers/viewports.js
+++ b/devtools/client/responsive.html/reducers/viewports.js
@@ -3,27 +3,28 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {
   ADD_VIEWPORT,
   CHANGE_DEVICE,
   CHANGE_PIXEL_RATIO,
-  REMOVE_DEVICE,
+  REMOVE_DEVICE_ASSOCIATION,
   RESIZE_VIEWPORT,
   ROTATE_VIEWPORT,
 } = require("../actions/index");
 
 let nextViewportId = 0;
 
 const INITIAL_VIEWPORTS = [];
 const INITIAL_VIEWPORT = {
   id: nextViewportId++,
   device: "",
+  deviceType: "",
   width: 320,
   height: 480,
   pixelRatio: {
     value: 0,
   },
 };
 
 let reducers = {
@@ -31,24 +32,25 @@ let reducers = {
   [ADD_VIEWPORT](viewports) {
     // For the moment, there can be at most one viewport.
     if (viewports.length === 1) {
       return viewports;
     }
     return [...viewports, Object.assign({}, INITIAL_VIEWPORT)];
   },
 
-  [CHANGE_DEVICE](viewports, { id, device }) {
+  [CHANGE_DEVICE](viewports, { id, device, deviceType }) {
     return viewports.map(viewport => {
       if (viewport.id !== id) {
         return viewport;
       }
 
       return Object.assign({}, viewport, {
         device,
+        deviceType,
       });
     });
   },
 
   [CHANGE_PIXEL_RATIO](viewports, { id, pixelRatio }) {
     return viewports.map(viewport => {
       if (viewport.id !== id) {
         return viewport;
@@ -57,24 +59,25 @@ let reducers = {
       return Object.assign({}, viewport, {
         pixelRatio: {
           value: pixelRatio
         },
       });
     });
   },
 
-  [REMOVE_DEVICE](viewports, { id }) {
+  [REMOVE_DEVICE_ASSOCIATION](viewports, { id }) {
     return viewports.map(viewport => {
       if (viewport.id !== id) {
         return viewport;
       }
 
       return Object.assign({}, viewport, {
         device: "",
+        deviceType: "",
       });
     });
   },
 
   [RESIZE_VIEWPORT](viewports, { id, width, height }) {
     return viewports.map(viewport => {
       if (viewport.id !== id) {
         return viewport;
--- a/devtools/client/responsive.html/test/browser/browser.ini
+++ b/devtools/client/responsive.html/test/browser/browser.ini
@@ -12,16 +12,17 @@ support-files =
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/framework/test/shared-head.js
   !/devtools/client/framework/test/shared-redux-head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_device_change.js]
+[browser_device_custom.js]
 [browser_device_modal_error.js]
 [browser_device_modal_exit.js]
 [browser_device_modal_submit.js]
 [browser_device_width.js]
 [browser_dpr_change.js]
 [browser_exit_button.js]
 [browser_frame_script_active.js]
 [browser_menu_item_01.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/test/browser/browser_device_custom.js
@@ -0,0 +1,152 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test adding and removing custom devices via the modal.
+
+const device = {
+  name: "Test Device",
+  width: 400,
+  height: 570,
+  pixelRatio: 1.5,
+  userAgent: "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0",
+  touch: true,
+  firefoxOS: false,
+  os: "android",
+};
+
+const TEST_URL = "data:text/html;charset=utf-8,";
+const Types = require("devtools/client/responsive.html/types");
+
+addRDMTask(TEST_URL, function* ({ ui }) {
+  let { toolWindow } = ui;
+  let { store, document } = toolWindow;
+  let React = toolWindow.require("devtools/client/shared/vendor/react");
+  let { Simulate } = React.addons.TestUtils;
+
+  // Wait until the viewport has been added and the device list has been loaded
+  yield waitUntilState(store, state => state.viewports.length == 1
+    && state.devices.listState == Types.deviceListState.LOADED);
+
+  let deviceSelector = document.querySelector(".viewport-device-selector");
+  let submitButton = document.querySelector("#device-submit-button");
+
+  openDeviceModal(ui);
+
+  info("Reveal device adder form, check that defaults match the viewport");
+  let adderShow = document.querySelector("#device-adder-show");
+  Simulate.click(adderShow);
+  testDeviceAdder(ui, {
+    name: "Custom Device",
+    width: 320,
+    height: 480,
+    pixelRatio: window.devicePixelRatio,
+    userAgent: navigator.userAgent,
+    touch: false,
+  });
+
+  info("Fill out device adder form and save");
+  setDeviceAdder(ui, device);
+  let adderSave = document.querySelector("#device-adder-save");
+  let saved = waitUntilState(store, state => state.devices.custom.length == 1);
+  Simulate.click(adderSave);
+  yield saved;
+
+  info("Enable device in modal");
+  let deviceCb = [...document.querySelectorAll(".device-input-checkbox")].find(cb => {
+    return cb.value == device.name;
+  });
+  ok(deviceCb, "Custom device checkbox added to modal");
+  deviceCb.click();
+  Simulate.click(submitButton);
+
+  info("Look for custom device in device selector");
+  let selectorOption = [...deviceSelector.options].find(opt => opt.value == device.name);
+  ok(selectorOption, "Custom device option added to device selector");
+});
+
+addRDMTask(TEST_URL, function* ({ ui }) {
+  let { toolWindow } = ui;
+  let { store, document } = toolWindow;
+  let React = toolWindow.require("devtools/client/shared/vendor/react");
+  let { Simulate } = React.addons.TestUtils;
+
+  // Wait until the viewport has been added and the device list has been loaded
+  yield waitUntilState(store, state => state.viewports.length == 1
+    && state.devices.listState == Types.deviceListState.LOADED);
+
+  let deviceSelector = document.querySelector(".viewport-device-selector");
+  let submitButton = document.querySelector("#device-submit-button");
+
+  info("Select existing device from the selector");
+  yield selectDevice(ui, "Test Device");
+
+  openDeviceModal(ui);
+
+  info("Reveal device adder form, check that defaults are based on selected device");
+  let adderShow = document.querySelector("#device-adder-show");
+  Simulate.click(adderShow);
+  testDeviceAdder(ui, Object.assign({}, device, {
+    name: "Test Device (Custom)",
+  }));
+
+  info("Remove previously added custom device");
+  let deviceRemoveButton = document.querySelector(".device-remove-button");
+  let removed = waitUntilState(store, state => state.devices.custom.length == 0);
+  Simulate.click(deviceRemoveButton);
+  yield removed;
+  Simulate.click(submitButton);
+
+  info("Ensure custom device was removed from device selector");
+  yield waitUntilState(store, state => state.viewports[0].device == "");
+  is(deviceSelector.value, "", "Device selector reset to no device");
+  let selectorOption = [...deviceSelector.options].find(opt => opt.value == device.name);
+  ok(!selectorOption, "Custom device option removed from device selector");
+});
+
+function testDeviceAdder(ui, expected) {
+  let { document } = ui.toolWindow;
+
+  let nameInput = document.querySelector("#device-adder-name input");
+  let [ widthInput, heightInput ] = document.querySelectorAll("#device-adder-size input");
+  let pixelRatioInput = document.querySelector("#device-adder-pixel-ratio input");
+  let userAgentInput = document.querySelector("#device-adder-user-agent input");
+  let touchInput = document.querySelector("#device-adder-touch input");
+
+  is(nameInput.value, expected.name, "Device name matches");
+  is(parseInt(widthInput.value, 10), expected.width, "Width matches");
+  is(parseInt(heightInput.value, 10), expected.height, "Height matches");
+  is(parseFloat(pixelRatioInput.value), expected.pixelRatio,
+     "devicePixelRatio matches");
+  is(userAgentInput.value, expected.userAgent, "User agent matches");
+  is(touchInput.checked, expected.touch, "Touch matches");
+}
+
+function setDeviceAdder(ui, value) {
+  let { toolWindow } = ui;
+  let { document } = ui.toolWindow;
+  let React = toolWindow.require("devtools/client/shared/vendor/react");
+  let { Simulate } = React.addons.TestUtils;
+
+  let nameInput = document.querySelector("#device-adder-name input");
+  let [ widthInput, heightInput ] = document.querySelectorAll("#device-adder-size input");
+  let pixelRatioInput = document.querySelector("#device-adder-pixel-ratio input");
+  let userAgentInput = document.querySelector("#device-adder-user-agent input");
+  let touchInput = document.querySelector("#device-adder-touch input");
+
+  nameInput.value = value.name;
+  Simulate.change(nameInput);
+  widthInput.value = value.width;
+  Simulate.change(widthInput);
+  Simulate.blur(widthInput);
+  heightInput.value = value.height;
+  Simulate.change(heightInput);
+  Simulate.blur(heightInput);
+  pixelRatioInput.value = value.pixelRatio;
+  Simulate.change(pixelRatioInput);
+  userAgentInput.value = value.userAgent;
+  Simulate.change(userAgentInput);
+  touchInput.checked = value.touch;
+  Simulate.change(touchInput);
+}
--- a/devtools/client/responsive.html/test/browser/head.js
+++ b/devtools/client/responsive.html/test/browser/head.js
@@ -55,16 +55,17 @@ Services.prefs.setCharPref("devtools.dev
 Services.prefs.setBoolPref("devtools.responsive.html.enabled", true);
 
 registerCleanupFunction(() => {
   flags.testing = false;
   Services.prefs.clearUserPref("devtools.devices.url");
   Services.prefs.clearUserPref("devtools.responsive.html.enabled");
   Services.prefs.clearUserPref("devtools.responsive.html.displayedDeviceList");
   asyncStorage.removeItem("devtools.devices.url_cache");
+  asyncStorage.removeItem("devtools.devices.local");
 });
 
 // This depends on the "devtools.responsive.html.enabled" pref
 const { ResponsiveUIManager } = require("resource://devtools/client/responsivedesign/responsivedesign.jsm");
 
 /**
  * Open responsive design mode for the given tab.
  */
@@ -237,40 +238,30 @@ function openDeviceModal({ toolWindow })
   info("Opening device modal through device selector.");
   select.value = OPEN_DEVICE_MODAL_VALUE;
   Simulate.change(select);
   ok(modal.classList.contains("opened") && !modal.classList.contains("closed"),
     "The device modal is displayed.");
 }
 
 function changeSelectValue({ toolWindow }, selector, value) {
+  let { document } = toolWindow;
+  let React = toolWindow.require("devtools/client/shared/vendor/react");
+  let { Simulate } = React.addons.TestUtils;
+
   info(`Selecting ${value} in ${selector}.`);
 
-  return new Promise(resolve => {
-    let select = toolWindow.document.querySelector(selector);
-    isnot(select, null, `selector "${selector}" should match an existing element.`);
-
-    let option = [...select.options].find(o => o.value === String(value));
-    isnot(option, undefined, `value "${value}" should match an existing option.`);
+  let select = document.querySelector(selector);
+  isnot(select, null, `selector "${selector}" should match an existing element.`);
 
-    let event = new toolWindow.UIEvent("change", {
-      view: toolWindow,
-      bubbles: true,
-      cancelable: true
-    });
+  let option = [...select.options].find(o => o.value === String(value));
+  isnot(option, undefined, `value "${value}" should match an existing option.`);
 
-    select.addEventListener("change", () => {
-      is(select.value, value,
-        `Select's option with value "${value}" should be selected.`);
-      resolve();
-    }, { once: true });
-
-    select.value = value;
-    select.dispatchEvent(event);
-  });
+  select.value = value;
+  Simulate.change(select);
 }
 
 const selectDevice = (ui, value) => Promise.all([
   once(ui, "device-changed"),
   changeSelectValue(ui, ".viewport-device-selector", value)
 ]);
 
 const selectDPR = (ui, value) =>
--- a/devtools/client/responsive.html/test/unit/test_change_device.js
+++ b/devtools/client/responsive.html/test/unit/test_change_device.js
@@ -29,14 +29,14 @@ add_task(function* () {
     "firefoxOS": true,
     "os": "fxos"
   }, "phones"));
   dispatch(addViewport());
 
   let viewport = getState().viewports[0];
   equal(viewport.device, "", "Default device is unselected");
 
-  dispatch(changeDevice(0, "Firefox OS Flame"));
+  dispatch(changeDevice(0, "Firefox OS Flame", "phones"));
 
   viewport = getState().viewports[0];
   equal(viewport.device, "Firefox OS Flame",
     "Changed to Firefox OS Flame device");
 });
--- a/devtools/client/responsive.html/types.js
+++ b/devtools/client/responsive.html/types.js
@@ -84,16 +84,19 @@ exports.devices = {
   consoles: PropTypes.arrayOf(PropTypes.shape(device)),
 
   // An array of watch devices
   watches: PropTypes.arrayOf(PropTypes.shape(device)),
 
   // Whether or not the device modal is open
   isModalOpen: PropTypes.bool,
 
+  // Viewport id that triggered the modal to open
+  modalOpenedFromViewport: PropTypes.number,
+
   // Device list state, possible values are exported above in an enum
   listState: PropTypes.oneOf(Object.keys(exports.deviceListState)),
 
 };
 
 /* VIEWPORT */
 
 /**
@@ -135,16 +138,19 @@ exports.touchSimulation = {
 exports.viewport = {
 
   // The id of the viewport
   id: PropTypes.number,
 
   // The currently selected device applied to the viewport
   device: PropTypes.string,
 
+  // The currently selected device type applied to the viewport
+  deviceType: PropTypes.string,
+
   // The width of the viewport
   width: PropTypes.number,
 
   // The height of the viewport
   height: PropTypes.number,
 
   // The devicePixelRatio of the viewport
   pixelRatio: PropTypes.shape(pixelRatio),
--- a/devtools/client/shared/developer-toolbar.js
+++ b/devtools/client/shared/developer-toolbar.js
@@ -74,109 +74,16 @@ var CommandUtils = {
    * Destroy the remote side of the requisition as well as the local side
    */
   destroyRequisition: function (requisition, target) {
     requisition.destroy();
     gcliInit.releaseSystem(target);
   },
 
   /**
-   * Read a toolbarSpec from preferences
-   * @param pref The name of the preference to read
-   */
-  getCommandbarSpec: function (pref) {
-    let value = prefBranch.getComplexValue(pref, Ci.nsISupportsString).data;
-    return JSON.parse(value);
-  },
-
-  /**
-   * Create a list of props for React components that manage the state of the buttons.
-   *
-   * @param {Array} toolbarSpec - An array of strings each of which is a GCLI command.
-   * @param {Object} target
-   * @param {Object} document - Used to listen to unload event of the window.
-   * @param {Requisition} requisition
-   * @param {Function} createButtonState - A function that provides a common interface
-   *                                       to create a button for the toolbox.
-   *
-   * @return {Array} List of ToolboxButton objects..
-   *
-   * Warning: this method uses the unload event of the window that owns the
-   * buttons that are of type checkbox. this means that we don't properly
-   * unregister event handlers until the window is destroyed.
-   */
-  createCommandButtons: function (toolbarSpec, target, document, requisition,
-                                  createButtonState) {
-    return util.promiseEach(toolbarSpec, typed => {
-      // Ask GCLI to parse the typed string (doesn't execute it)
-      return requisition.update(typed).then(() => {
-        // Ignore invalid commands
-        let command = requisition.commandAssignment.value;
-        if (command == null) {
-          throw new Error("No command '" + typed + "'");
-        }
-        if (!command.buttonId) {
-          throw new Error("Attempting to add a button to the toolbar, and the command " +
-                          "did not have an id.");
-        }
-        // Create the ToolboxButton.
-        let button = createButtonState({
-          id: command.buttonId,
-          className: command.buttonClass,
-          description: command.tooltipText || command.description,
-          onClick: requisition.updateExec.bind(requisition, typed)
-        });
-
-        // Allow the command button to be toggleable.
-        if (command.state) {
-          /**
-           * The onChange event should be called with an event object that
-           * contains a target property which specifies which target the event
-           * applies to. For legacy reasons the event object can also contain
-           * a tab property.
-           */
-          const onChange = (eventName, ev) => {
-            if (ev.target == target || ev.tab == target.tab) {
-              let updateChecked = (checked) => {
-                // This will emit a ToolboxButton update event.
-                button.isChecked = checked;
-              };
-
-              // isChecked would normally be synchronous. An annoying quirk
-              // of the 'csscoverage toggle' command forces us to accept a
-              // promise here, but doing Promise.resolve(reply).then(...) here
-              // makes this async for everyone, which breaks some tests so we
-              // treat non-promise replies separately to keep then synchronous.
-              let reply = command.state.isChecked(target);
-              if (typeof reply.then == "function") {
-                reply.then(updateChecked, console.error);
-              } else {
-                updateChecked(reply);
-              }
-            }
-          };
-
-          command.state.onChange(target, onChange);
-          onChange("", { target: target });
-
-          document.defaultView.addEventListener("unload", function (event) {
-            if (command.state.offChange) {
-              command.state.offChange(target, onChange);
-            }
-          }, { once: true });
-        }
-
-        requisition.clear();
-
-        return button;
-      });
-    });
-  },
-
-  /**
    * A helper function to create the environment object that is passed to
    * GCLI commands.
    * @param targetContainer An object containing a 'target' property which
    * reflects the current debug target
    */
   createEnvironment: function (container, targetProperty = "target") {
     if (!container[targetProperty].toString ||
         !/TabTarget/.test(container[targetProperty].toString())) {
--- a/devtools/client/shared/devices.js
+++ b/devtools/client/shared/devices.js
@@ -1,88 +1,113 @@
 /* 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 { Task } = require("devtools/shared/task");
 const { getJSON } = require("devtools/client/shared/getjson");
+const { LocalizationHelper } = require("devtools/shared/l10n");
+const L10N = new LocalizationHelper("devtools/client/locales/device.properties");
+
+loader.lazyRequireGetter(this, "asyncStorage", "devtools/shared/async-storage");
 
 const DEVICES_URL = "devtools.devices.url";
-const { LocalizationHelper } = require("devtools/shared/l10n");
-const L10N = new LocalizationHelper("devtools/client/locales/device.properties");
+const LOCAL_DEVICES = "devtools.devices.local";
 
 /* This is a catalog of common web-enabled devices and their properties,
  * intended for (mobile) device emulation.
  *
  * The properties of a device are:
  * - name: brand and model(s).
  * - width: viewport width.
  * - height: viewport height.
  * - pixelRatio: ratio from viewport to physical screen pixels.
  * - userAgent: UA string of the device's browser.
  * - touch: whether it has a touch screen.
- * - firefoxOS: whether Firefox OS is supported.
+ * - os: default OS, such as "ios", "fxos", "android".
  *
  * The device types are:
  *   ["phones", "tablets", "laptops", "televisions", "consoles", "watches"].
  *
+ * To propose new devices for the shared catalog, check out the repo at
+ * https://github.com/mozilla/simulated-devices and file a pull request.
+ *
  * You can easily add more devices to this catalog from your own code (e.g. an
  * addon) like so:
  *
  *   var myPhone = { name: "My Phone", ... };
  *   require("devtools/client/shared/devices").addDevice(myPhone, "phones");
  */
 
 // Local devices catalog that addons can add to.
-let localDevices = {};
+let localDevices;
+let localDevicesLoaded = false;
+
+// Load local devices from storage.
+let loadLocalDevices = Task.async(function* () {
+  if (localDevicesLoaded) {
+    return;
+  }
+  let devicesJSON = yield asyncStorage.getItem(LOCAL_DEVICES);
+  if (!devicesJSON) {
+    devicesJSON = "{}";
+  }
+  localDevices = JSON.parse(devicesJSON);
+  localDevicesLoaded = true;
+});
 
 // Add a device to the local catalog.
-function addDevice(device, type = "phones") {
+let addDevice = Task.async(function* (device, type = "phones") {
+  yield loadLocalDevices();
   let list = localDevices[type];
   if (!list) {
     list = localDevices[type] = [];
   }
   list.push(device);
-}
+  yield asyncStorage.setItem(LOCAL_DEVICES, JSON.stringify(localDevices));
+});
 exports.addDevice = addDevice;
 
 // Remove a device from the local catalog.
 // returns `true` if the device is removed, `false` otherwise.
-function removeDevice(device, type = "phones") {
+let removeDevice = Task.async(function* (device, type = "phones") {
+  yield loadLocalDevices();
   let list = localDevices[type];
   if (!list) {
     return false;
   }
 
   let index = list.findIndex(item => device);
 
   if (index === -1) {
     return false;
   }
 
   list.splice(index, 1);
+  yield asyncStorage.setItem(LOCAL_DEVICES, JSON.stringify(localDevices));
 
   return true;
-}
+});
 exports.removeDevice = removeDevice;
 
 // Get the complete devices catalog.
-function getDevices() {
+let getDevices = Task.async(function* () {
   // Fetch common devices from Mozilla's CDN.
-  return getJSON(DEVICES_URL).then(devices => {
-    for (let type in localDevices) {
-      if (!devices[type]) {
-        devices.TYPES.push(type);
-        devices[type] = [];
-      }
-      devices[type] = localDevices[type].concat(devices[type]);
+  let devices = yield getJSON(DEVICES_URL);
+  yield loadLocalDevices();
+  for (let type in localDevices) {
+    if (!devices[type]) {
+      devices.TYPES.push(type);
+      devices[type] = [];
     }
-    return devices;
-  });
-}
+    devices[type] = localDevices[type].concat(devices[type]);
+  }
+  return devices;
+});
 exports.getDevices = getDevices;
 
 // Get the localized string for a device type.
 function getDeviceString(deviceType) {
   return L10N.getStr("device." + deviceType);
 }
 exports.getDeviceString = getDeviceString;
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -537,18 +537,18 @@ KeyframeEffectReadOnly::ComposeStyle(
 
     // Bug 1333311 - We use two branches for Gecko and Stylo. However, it's
     // better to remove the duplicated code.
     if (isServoBackend) {
       // Servo backend
 
       // Bug 1329878 - Stylo: Implement accumulate and addition on Servo
       // AnimationValue.
-      RawServoAnimationValue* servoFromValue = segment->mServoFromValue;
-      RawServoAnimationValue* servoToValue = segment->mServoToValue;
+      RawServoAnimationValue* servoFromValue = segment->mFromValue.mServo;
+      RawServoAnimationValue* servoToValue = segment->mToValue.mServo;
 
       // For unsupported or non-animatable animation types, we get nullptrs.
       if (!servoFromValue || !servoToValue) {
         NS_ERROR("Compose style for unsupported or non-animatable property, "
                  "so get invalid RawServoAnimationValues");
         continue;
       }
 
@@ -594,34 +594,34 @@ KeyframeEffectReadOnly::ComposeStyle(
 
       if (!aStyleRule.mGecko) {
         // Allocate the style rule now that we know we have animation data.
         aStyleRule.mGecko = new AnimValuesStyleRule();
       }
 
       StyleAnimationValue fromValue =
         CompositeValue(prop.mProperty, aStyleRule.mGecko,
-                       segment->mFromValue,
+                       segment->mFromValue.mGecko,
                        segment->mFromComposite);
       StyleAnimationValue toValue =
         CompositeValue(prop.mProperty, aStyleRule.mGecko,
-                       segment->mToValue,
+                       segment->mToValue.mGecko,
                        segment->mToComposite);
 
       // Iteration composition for accumulate
       if (mEffectOptions.mIterationComposite ==
           IterationCompositeOperation::Accumulate &&
           computedTiming.mCurrentIteration > 0) {
         const AnimationPropertySegment& lastSegment =
           prop.mSegments.LastElement();
         // FIXME: Bug 1293492: Add a utility function to calculate both of
         // below StyleAnimationValues.
-        StyleAnimationValue lastValue = lastSegment.mToValue.IsNull()
+        StyleAnimationValue lastValue = lastSegment.mToValue.mGecko.IsNull()
           ? GetUnderlyingStyle(prop.mProperty, aStyleRule.mGecko)
-          : lastSegment.mToValue;
+          : lastSegment.mToValue.mGecko;
         fromValue =
           StyleAnimationValue::Accumulate(prop.mProperty,
                                           lastValue,
                                           Move(fromValue),
                                           computedTiming.mCurrentIteration);
         toValue =
           StyleAnimationValue::Accumulate(prop.mProperty,
                                           lastValue,
@@ -1010,20 +1010,20 @@ KeyframeEffectReadOnly::GetTargetStyleCo
 void
 DumpAnimationProperties(nsTArray<AnimationProperty>& aAnimationProperties)
 {
   for (auto& p : aAnimationProperties) {
     printf("%s\n", nsCSSProps::GetStringValue(p.mProperty).get());
     for (auto& s : p.mSegments) {
       nsString fromValue, toValue;
       Unused << StyleAnimationValue::UncomputeValue(p.mProperty,
-                                                    s.mFromValue,
+                                                    s.mFromValue.mGecko,
                                                     fromValue);
       Unused << StyleAnimationValue::UncomputeValue(p.mProperty,
-                                                    s.mToValue,
+                                                    s.mToValue.mGecko,
                                                     toValue);
       printf("  %f..%f: %s..%s\n", s.mFromKey, s.mToKey,
              NS_ConvertUTF16toUTF8(fromValue).get(),
              NS_ConvertUTF16toUTF8(toValue).get());
     }
   }
 }
 #endif
@@ -1130,37 +1130,38 @@ KeyframeEffectReadOnly::GetProperties(
     for (size_t segmentIdx = 0, segmentLen = property.mSegments.Length();
          segmentIdx < segmentLen;
          segmentIdx++)
     {
       const AnimationPropertySegment& segment = property.mSegments[segmentIdx];
 
       binding_detail::FastAnimationPropertyValueDetails fromValue;
       CreatePropertyValue(property.mProperty, segment.mFromKey,
-                          segment.mTimingFunction, segment.mFromValue,
+                          segment.mTimingFunction, segment.mFromValue.mGecko,
                           segment.mFromComposite, fromValue);
       // We don't apply timing functions for zero-length segments, so
       // don't return one here.
       if (segment.mFromKey == segment.mToKey) {
         fromValue.mEasing.Reset();
       }
       // The following won't fail since we have already allocated the capacity
       // above.
       propertyDetails.mValues.AppendElement(fromValue, mozilla::fallible);
 
       // Normally we can ignore the to-value for this segment since it is
       // identical to the from-value from the next segment. However, we need
       // to add it if either:
       // a) this is the last segment, or
       // b) the next segment's from-value differs.
       if (segmentIdx == segmentLen - 1 ||
-          property.mSegments[segmentIdx + 1].mFromValue != segment.mToValue) {
+          property.mSegments[segmentIdx + 1].mFromValue.mGecko !=
+            segment.mToValue.mGecko) {
         binding_detail::FastAnimationPropertyValueDetails toValue;
         CreatePropertyValue(property.mProperty, segment.mToKey,
-                            Nothing(), segment.mToValue,
+                            Nothing(), segment.mToValue.mGecko,
                             segment.mToComposite, toValue);
         // It doesn't really make sense to have a timing function on the
         // last property value or before a sudden jump so we just drop the
         // easing property altogether.
         toValue.mEasing.Reset();
         propertyDetails.mValues.AppendElement(toValue, mozilla::fallible);
       }
     }
@@ -1616,21 +1617,23 @@ KeyframeEffectReadOnly::CalculateCumulat
       // until we compose it.
       if (segment.mFromComposite != CompositeOperation::Replace ||
           segment.mToComposite != CompositeOperation::Replace) {
         mCumulativeChangeHint = ~nsChangeHint_Hints_CanIgnoreIfNotVisible;
         return;
       }
       RefPtr<nsStyleContext> fromContext =
         CreateStyleContextForAnimationValue(property.mProperty,
-                                            segment.mFromValue, aStyleContext);
+                                            segment.mFromValue.mGecko,
+                                            aStyleContext);
 
       RefPtr<nsStyleContext> toContext =
         CreateStyleContextForAnimationValue(property.mProperty,
-                                            segment.mToValue, aStyleContext);
+                                            segment.mToValue.mGecko,
+                                            aStyleContext);
 
       uint32_t equalStructs = 0;
       uint32_t samePointerStructs = 0;
       nsChangeHint changeHint =
         fromContext->CalcStyleDifference(toContext,
                                          nsChangeHint(0),
                                          &equalStructs,
                                          &samePointerStructs);
--- a/dom/animation/KeyframeEffectReadOnly.h
+++ b/dom/animation/KeyframeEffectReadOnly.h
@@ -55,19 +55,17 @@ enum class CompositeOperation : uint8_t;
 struct AnimationPropertyDetails;
 }
 
 struct AnimationPropertySegment
 {
   float mFromKey, mToKey;
   // NOTE: In the case that no keyframe for 0 or 1 offset is specified
   // the unit of mFromValue or mToValue is eUnit_Null.
-  StyleAnimationValue mFromValue, mToValue;
-  // FIXME add a deep == impl for RawServoAnimationValue
-  RefPtr<RawServoAnimationValue> mServoFromValue, mServoToValue;
+  AnimationValue mFromValue, mToValue;
 
   Maybe<ComputedTimingFunction> mTimingFunction;
   dom::CompositeOperation mFromComposite = dom::CompositeOperation::Replace;
   dom::CompositeOperation mToComposite = dom::CompositeOperation::Replace;
 
   bool operator==(const AnimationPropertySegment& aOther) const
   {
     return mFromKey == aOther.mFromKey &&
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -266,18 +266,17 @@ struct AdditionalProperty
  * whose value is a StyleAnimationValue.
  *
  * KeyframeValueEntry is used in GetAnimationPropertiesFromKeyframes
  * to gather data for each individual segment.
  */
 struct KeyframeValueEntry
 {
   nsCSSPropertyID mProperty;
-  StyleAnimationValue mValue;
-  RefPtr<RawServoAnimationValue> mServoValue;
+  AnimationValue mValue;
 
   float mOffset;
   Maybe<ComputedTimingFunction> mTimingFunction;
   dom::CompositeOperation mComposite;
 
   struct PropertyOffsetComparator
   {
     static bool Equals(const KeyframeValueEntry& aLhs,
@@ -699,17 +698,16 @@ KeyframeUtils::GetAnimationPropertiesFro
     const Keyframe& frame = aKeyframes[i];
     for (auto& value : aComputedValues[i]) {
       MOZ_ASSERT(frame.mComputedOffset != Keyframe::kComputedOffsetNotSet,
                  "Invalid computed offset");
       KeyframeValueEntry* entry = entries.AppendElement();
       entry->mOffset = frame.mComputedOffset;
       entry->mProperty = value.mProperty;
       entry->mValue = value.mValue;
-      entry->mServoValue = value.mServoValue;
       entry->mTimingFunction = frame.mTimingFunction;
       entry->mComposite =
         frame.mComposite ? frame.mComposite.value() : aEffectComposite;
     }
   }
 
   nsTArray<AnimationProperty> result;
   BuildSegmentsFromValueEntries(entries, result);
@@ -1399,18 +1397,16 @@ BuildSegmentsFromValueEntries(nsTArray<K
 
     // Now generate the segment.
     AnimationPropertySegment* segment =
       animationProperty->mSegments.AppendElement();
     segment->mFromKey        = aEntries[i].mOffset;
     segment->mToKey          = aEntries[j].mOffset;
     segment->mFromValue      = aEntries[i].mValue;
     segment->mToValue        = aEntries[j].mValue;
-    segment->mServoFromValue = aEntries[i].mServoValue;
-    segment->mServoToValue   = aEntries[j].mServoValue;
     segment->mTimingFunction = aEntries[i].mTimingFunction;
     segment->mFromComposite  = aEntries[i].mComposite;
     segment->mToComposite    = aEntries[j].mComposite;
 
     i = j;
   }
 }
 
@@ -1781,32 +1777,32 @@ GetCumulativeDistances(const nsTArray<Co
         for (size_t propIdx = 0; propIdx < pacedPropertyCount; ++propIdx) {
           nsCSSPropertyID prop = prevPacedValues[propIdx].mProperty;
           MOZ_ASSERT(pacedValues[propIdx].mProperty == prop,
                      "Property mismatch");
 
           double componentDistance = 0.0;
           if (StyleAnimationValue::ComputeDistance(
                 prop,
-                prevPacedValues[propIdx].mValue,
-                pacedValues[propIdx].mValue,
+                prevPacedValues[propIdx].mValue.mGecko,
+                pacedValues[propIdx].mValue.mGecko,
                 aStyleContext,
                 componentDistance)) {
             dist += componentDistance * componentDistance;
           }
         }
         dist = sqrt(dist);
       } else {
         // If the property is longhand, we just use the 1st value.
         // If ComputeDistance() fails, |dist| will remain zero so there will be
         // no distance between the previous paced value and this value.
         Unused <<
           StyleAnimationValue::ComputeDistance(aPacedProperty,
-                                               prevPacedValues[0].mValue,
-                                               pacedValues[0].mValue,
+                                               prevPacedValues[0].mValue.mGecko,
+                                               pacedValues[0].mValue.mGecko,
                                                aStyleContext,
                                                dist);
       }
       cumulativeDistances[i] = cumulativeDistances[preIdx] + dist;
     }
     prevPacedValues.SwapElements(pacedValues);
     preIdx = i;
   }
--- a/dom/base/test/browser.ini
+++ b/dom/base/test/browser.ini
@@ -2,18 +2,16 @@
 support-files =
   empty.html
   file_bug1011748_redirect.sjs
   file_bug1011748_OK.sjs
   file_messagemanager_unload.html
   file_use_counter_outer.html
   file_use_counter_svg_getElementById.svg
   file_use_counter_svg_currentScale.svg
-  file_use_counter_svg_background.html
-  file_use_counter_svg_list_style_image.html
   file_use_counter_svg_fill_pattern_definition.svg
   file_use_counter_svg_fill_pattern.svg
   file_use_counter_svg_fill_pattern_internal.svg
   file_use_counter_svg_fill_pattern_data.svg
 
 [browser_bug593387.js]
 [browser_bug902350.js]
 tags = mcb
--- a/dom/base/test/browser_use_counters.js
+++ b/dom/base/test/browser_use_counters.js
@@ -66,22 +66,16 @@ add_task(function* () {
 
   // Check that use counters are incremented by directly loading SVGs
   // that reference patterns defined in the same file or in data: URLs.
   yield check_use_counter_direct("file_use_counter_svg_fill_pattern_internal.svg",
                                  "PROPERTY_FILLOPACITY");
   // data: URLs don't correctly propagate to their referring document yet.
   //yield check_use_counter_direct("file_use_counter_svg_fill_pattern_data.svg",
   //                               "PROPERTY_FILL_OPACITY");
-
-  // Check that use counters are incremented by SVGs loaded as CSS images in
-  // pages loaded in iframes.  Again, SVG images in CSS aren't permitted to
-  // execute script, so we need to use properties here.
-  yield check_use_counter_iframe("file_use_counter_svg_list_style_image.html",
-                                 "PROPERTY_FILL");
 });
 
 add_task(function* () {
   let Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
   Telemetry.canRecordExtended = gOldParentCanRecord;
 
   yield ContentTask.spawn(gBrowser.selectedBrowser, { oldCanRecord: gOldContentCanRecord }, function* (arg) {
     Cu.import("resource://gre/modules/PromiseUtils.jsm");
deleted file mode 100644
--- a/dom/base/test/file_use_counter_svg_background.html
+++ /dev/null
@@ -1,22 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=968923
--->
-<head>
-  <meta charset="utf-8">
-  <title>Test for Bug 968923</title>
-  <style>
-/* Use a query string to work around imagelib caching.
-   Otherwise, we won't get use counters for this file.  */
-body { background-image: url('file_use_counter_svg_getElementById.svg?asbackground=1') }
-  </style>
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=968923">Mozilla Bug 968923</a>
-<img id="display" />
-<iframe id="content" src="about:blank">
-
-</iframe>
-</body>
-</html>
deleted file mode 100644
--- a/dom/base/test/file_use_counter_svg_list_style_image.html
+++ /dev/null
@@ -1,22 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=968923
--->
-<head>
-  <meta charset="utf-8">
-  <title>Test for Bug 968923</title>
-  <style>
-/* Use a query string to work around imagelib caching.
-   Otherwise, we won't get use counters for this file.  */
-ul { list-style-image: url('file_use_counter_svg_currentScale.svg?asliststyleimage=1') }
-  </style>
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=968923">Mozilla Bug 968923</a>
-<ul>
-  <li>Some text</li>
-  <li>Some other text</li>
-</ul>
-</body>
-</html>
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -805,19 +805,19 @@ MediaFormatReader::DemuxerProxy::NotifyD
     }
     return NotifyDataArrivedPromise::CreateAndResolve(true, __func__);
   });
 }
 
 static const char*
 TrackTypeToStr(TrackInfo::TrackType aTrack)
 {
-  MOZ_ASSERT(aTrack == TrackInfo::kAudioTrack ||
-             aTrack == TrackInfo::kVideoTrack ||
-             aTrack == TrackInfo::kTextTrack);
+  MOZ_ASSERT(aTrack == TrackInfo::kAudioTrack
+             || aTrack == TrackInfo::kVideoTrack
+             || aTrack == TrackInfo::kTextTrack);
   switch (aTrack) {
   case TrackInfo::kAudioTrack:
     return "Audio";
   case TrackInfo::kVideoTrack:
     return "Video";
   case TrackInfo::kTextTrack:
     return "Text";
   default:
@@ -1302,19 +1302,19 @@ MediaFormatReader::ShouldSkip(bool aSkip
                               media::TimeUnit aTimeThreshold)
 {
   MOZ_ASSERT(HasVideo());
   media::TimeUnit nextKeyframe;
   nsresult rv = mVideo.mTrackDemuxer->GetNextRandomAccessPoint(&nextKeyframe);
   if (NS_FAILED(rv)) {
     return aSkipToNextKeyframe;
   }
-  return (nextKeyframe < aTimeThreshold ||
-          (mVideo.mTimeThreshold
-           && mVideo.mTimeThreshold.ref().EndTime() < aTimeThreshold))
+  return (nextKeyframe < aTimeThreshold
+          || (mVideo.mTimeThreshold
+              && mVideo.mTimeThreshold.ref().EndTime() < aTimeThreshold))
          && nextKeyframe.ToMicroseconds() >= 0
          && !nextKeyframe.IsInfinite();
 }
 
 RefPtr<MediaDecoderReader::MediaDataPromise>
 MediaFormatReader::RequestVideoData(bool aSkipToNextKeyframe,
                                     int64_t aTimeThreshold)
 {
@@ -1767,18 +1767,18 @@ MediaFormatReader::HandleDemuxedSamples(
   // Decode all our demuxed frames.
   while (decoder.mQueuedSamples.Length()) {
     RefPtr<MediaRawData> sample = decoder.mQueuedSamples[0];
     RefPtr<SharedTrackInfo> info = sample->mTrackInfo;
 
     if (info && decoder.mLastStreamSourceID != info->GetID()) {
       bool supportRecycling = MediaPrefs::MediaDecoderCheckRecycling()
                               && decoder.mDecoder->SupportDecoderRecycling();
-      if (decoder.mNextStreamSourceID.isNothing() ||
-          decoder.mNextStreamSourceID.ref() != info->GetID()) {
+      if (decoder.mNextStreamSourceID.isNothing()
+          || decoder.mNextStreamSourceID.ref() != info->GetID()) {
         if (!supportRecycling) {
           LOG("%s stream id has changed from:%d to:%d, draining decoder.",
             TrackTypeToStr(aTrack), decoder.mLastStreamSourceID,
             info->GetID());
           decoder.mNeedDraining = true;
           decoder.mNextStreamSourceID = Some(info->GetID());
           ScheduleUpdate(aTrack);
           return;
@@ -1886,32 +1886,38 @@ MediaFormatReader::DrainDecoder(TrackTyp
 {
   MOZ_ASSERT(OnTaskQueue());
 
   auto& decoder = GetDecoderData(aTrack);
   if (!decoder.mNeedDraining || decoder.mDraining) {
     return;
   }
   decoder.mNeedDraining = false;
-  if (!decoder.mDecoder ||
-      decoder.mNumSamplesInput == decoder.mNumSamplesOutput) {
+  decoder.mDraining = true;
+  if (!decoder.mDecoder
+      || decoder.mNumSamplesInput == decoder.mNumSamplesOutput) {
     // No frames to drain.
     LOGV("Draining %s with nothing to drain", TrackTypeToStr(aTrack));
     NotifyDrainComplete(aTrack);
     return;
   }
-  decoder.mDraining = true;
   RefPtr<MediaFormatReader> self = this;
   decoder.mDecoder->Drain()
     ->Then(mTaskQueue, __func__,
            [self, this, aTrack, &decoder]
            (const MediaDataDecoder::DecodedData& aResults) {
              decoder.mDrainRequest.Complete();
-             NotifyNewOutput(aTrack, aResults);
-             NotifyDrainComplete(aTrack);
+             if (aResults.IsEmpty()) {
+               NotifyDrainComplete(aTrack);
+             } else {
+               NotifyNewOutput(aTrack, aResults);
+               // Let's see if we have any more data available to drain.
+               decoder.mNeedDraining = true;
+               decoder.mDraining = false;
+             }
            },
            [self, this, aTrack, &decoder](const MediaResult& aError) {
              decoder.mDrainRequest.Complete();
              NotifyError(aTrack, aError);
            })
     ->Track(decoder.mDrainRequest);
   LOG("Requesting %s decoder to drain", TrackTypeToStr(aTrack));
 }
@@ -2022,25 +2028,23 @@ MediaFormatReader::Update(TrackType aTra
         mVideo.mIsHardwareAccelerated =
           mVideo.mDecoder && mVideo.mDecoder->IsHardwareAccelerated(error);
       }
     } else if (decoder.HasFatalError()) {
       LOG("Rejecting %s promise: DECODE_ERROR", TrackTypeToStr(aTrack));
       decoder.RejectPromise(decoder.mError.ref(), __func__);
       return;
     } else if (decoder.mDrainComplete) {
-      bool wasDraining = decoder.mDraining;
       decoder.mDrainComplete = false;
       decoder.mDraining = false;
       if (decoder.mDemuxEOS) {
         LOG("Rejecting %s promise: EOS", TrackTypeToStr(aTrack));
         decoder.RejectPromise(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__);
       } else if (decoder.mWaitingForData) {
-        if (wasDraining && decoder.mLastSampleTime &&
-            !decoder.mNextStreamSourceID) {
+        if (decoder.mLastSampleTime && !decoder.mNextStreamSourceID) {
           // We have completed draining the decoder following WaitingForData.
           // Set up the internal seek machinery to be able to resume from the
           // last sample decoded.
           LOG("Seeking to last sample time: %lld",
               decoder.mLastSampleTime.ref().mStart.ToMicroseconds());
           InternalSeek(aTrack,
                        InternalSeekTarget(decoder.mLastSampleTime.ref(), true));
         }
@@ -2085,17 +2089,17 @@ MediaFormatReader::Update(TrackType aTra
       return;
     }
     decoder.mError.reset();
     LOG("%s decoded error count %d", TrackTypeToStr(aTrack),
                                      decoder.mNumOfConsecutiveError);
     media::TimeUnit nextKeyframe;
     if (aTrack == TrackType::kVideoTrack && !decoder.HasInternalSeekPending()
         && NS_SUCCEEDED(
-          decoder.mTrackDemuxer->GetNextRandomAccessPoint(&nextKeyframe))) {
+             decoder.mTrackDemuxer->GetNextRandomAccessPoint(&nextKeyframe))) {
       if (needsNewDecoder) {
         ShutdownDecoder(aTrack);
       }
       SkipVideoDemuxToNextKeyFrame(
         decoder.mLastSampleTime.refOr(TimeInterval()).Length());
     } else if (aTrack == TrackType::kAudioTrack) {
       decoder.Flush();
     }
@@ -2148,18 +2152,18 @@ MediaFormatReader::ReturnOutput(MediaDat
   MOZ_ASSERT(GetDecoderData(aTrack).HasPromise());
   MOZ_DIAGNOSTIC_ASSERT(aData->mType != MediaData::NULL_DATA);
   LOG("Resolved data promise for %s [%lld, %lld]", TrackTypeToStr(aTrack),
       aData->mTime, aData->GetEndTime());
 
   if (aTrack == TrackInfo::kAudioTrack) {
     AudioData* audioData = static_cast<AudioData*>(aData);
 
-    if (audioData->mChannels != mInfo.mAudio.mChannels ||
-        audioData->mRate != mInfo.mAudio.mRate) {
+    if (audioData->mChannels != mInfo.mAudio.mChannels
+        || audioData->mRate != mInfo.mAudio.mRate) {
       LOG("change of audio format (rate:%d->%d). "
           "This is an unsupported configuration",
           mInfo.mAudio.mRate, audioData->mRate);
       mInfo.mAudio.mRate = audioData->mRate;
       mInfo.mAudio.mChannels = audioData->mChannels;
     }
     mAudio.ResolvePromise(aData, __func__);
   } else if (aTrack == TrackInfo::kVideoTrack) {
@@ -2484,18 +2488,18 @@ MediaFormatReader::OnSeekFailed(TrackTyp
       Maybe<media::TimeUnit> nextSeekTime;
       // Find closest buffered time found after video seeked time.
       for (const auto& timeRange : mAudio.mTimeRanges) {
         if (timeRange.mStart >= mPendingSeekTime.ref()) {
           nextSeekTime.emplace(timeRange.mStart);
           break;
         }
       }
-      if (nextSeekTime.isNothing() ||
-          nextSeekTime.ref() > mFallbackSeekTime.ref()) {
+      if (nextSeekTime.isNothing()
+          || nextSeekTime.ref() > mFallbackSeekTime.ref()) {
         nextSeekTime = Some(mFallbackSeekTime.ref());
         LOG("Unable to seek audio to video seek time. A/V sync may be broken");
       } else {
         mFallbackSeekTime.reset();
       }
       mPendingSeekTime = nextSeekTime;
       DoAudioSeek();
       return;
@@ -2737,18 +2741,18 @@ MediaFormatReader::UpdateBuffered()
   if (HasAudio() && HasVideo()) {
     intervals = media::Intersection(mVideo.mTimeRanges, mAudio.mTimeRanges);
   } else if (HasAudio()) {
     intervals = mAudio.mTimeRanges;
   } else if (HasVideo()) {
     intervals = mVideo.mTimeRanges;
   }
 
-  if (!intervals.Length() ||
-      intervals.GetStart() == media::TimeUnit::FromMicroseconds(0)) {
+  if (!intervals.Length()
+      || intervals.GetStart() == media::TimeUnit::FromMicroseconds(0)) {
     // IntervalSet already starts at 0 or is empty, nothing to shift.
     mBuffered = intervals;
   } else {
     mBuffered =
       intervals.Shift(media::TimeUnit() - mInfo.mStartTime);
   }
 }
 
--- a/dom/media/platforms/PlatformDecoderModule.h
+++ b/dom/media/platforms/PlatformDecoderModule.h
@@ -246,18 +246,21 @@ public:
   // indicate that Decode should be called again before a MediaData is returned.
   virtual RefPtr<DecodePromise> Decode(MediaRawData* aSample) = 0;
 
   // Causes all complete samples in the pipeline that can be decoded to be
   // output. If the decoder can't produce samples from the current output,
   // it drops the input samples. The decoder may be holding onto samples
   // that are required to decode samples that it expects to get in future.
   // This is called when the demuxer reaches end of stream.
-  // This function is asynchronous. The MediaDataDecoder shall resolve the
-  // pending DecodePromise will all drained samples.
+  // This function is asynchronous.
+  // The MediaDataDecoder shall resolve the pending DecodePromise with drained
+  // samples. Drain will be called multiple times until the resolved
+  // DecodePromise is empty which indicates that there are no more samples to
+  // drain.
   virtual RefPtr<DecodePromise> Drain() = 0;
 
   // Causes all samples in the decoding pipeline to be discarded. When this
   // promise resolves, the decoder must be ready to accept new data for
   // decoding. This function is called when the demuxer seeks, before decoding
   // resumes after the seek. The current DecodePromise if any shall be rejected
   // with NS_ERROR_DOM_MEDIA_CANCELED
   virtual RefPtr<FlushPromise> Flush() = 0;
--- a/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp
@@ -6,50 +6,53 @@
 
 #include "mozilla/TaskQueue.h"
 
 #include "FFmpegAudioDecoder.h"
 #include "TimeUnits.h"
 
 #define MAX_CHANNELS 16
 
-namespace mozilla
-{
+namespace mozilla {
 
 FFmpegAudioDecoder<LIBAV_VER>::FFmpegAudioDecoder(FFmpegLibWrapper* aLib,
   TaskQueue* aTaskQueue, const AudioInfo& aConfig)
   : FFmpegDataDecoder(aLib, aTaskQueue, GetCodecId(aConfig.mMimeType))
 {
   MOZ_COUNT_CTOR(FFmpegAudioDecoder);
-  // Use a new MediaByteBuffer as the object will be modified during initialization.
+  // Use a new MediaByteBuffer as the object will be modified during
+  // initialization.
   if (aConfig.mCodecSpecificConfig && aConfig.mCodecSpecificConfig->Length()) {
     mExtraData = new MediaByteBuffer;
     mExtraData->AppendElements(*aConfig.mCodecSpecificConfig);
   }
 }
 
 RefPtr<MediaDataDecoder::InitPromise>
 FFmpegAudioDecoder<LIBAV_VER>::Init()
 {
   nsresult rv = InitDecoder();
 
-  return rv == NS_OK ? InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__)
-                     : InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+  return rv == NS_OK
+         ? InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__)
+         : InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                                        __func__);
 }
 
 void
 FFmpegAudioDecoder<LIBAV_VER>::InitCodecContext()
 {
   MOZ_ASSERT(mCodecContext);
   // We do not want to set this value to 0 as FFmpeg by default will
   // use the number of cores, which with our mozlibavutil get_cpu_count
   // isn't implemented.
   mCodecContext->thread_count = 1;
   // FFmpeg takes this as a suggestion for what format to use for audio samples.
-  // LibAV 0.8 produces rubbish float interleaved samples, request 16 bits audio.
+  // LibAV 0.8 produces rubbish float interleaved samples, request 16 bits
+  // audio.
   mCodecContext->request_sample_fmt =
     (mLib->mVersion == 53) ? AV_SAMPLE_FMT_S16 : AV_SAMPLE_FMT_FLT;
 }
 
 static AlignedAudioBuffer
 CopyAndPackAudio(AVFrame* aFrame, uint32_t aNumChannels, uint32_t aNumAFrames)
 {
   MOZ_ASSERT(aNumChannels <= MAX_CHANNELS);
--- a/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.h
+++ b/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.h
@@ -2,21 +2,20 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef __FFmpegAACDecoder_h__
 #define __FFmpegAACDecoder_h__
 
+#include "FFmpegDataDecoder.h"
 #include "FFmpegLibWrapper.h"
-#include "FFmpegDataDecoder.h"
 
-namespace mozilla
-{
+namespace mozilla {
 
 template <int V> class FFmpegAudioDecoder
 {
 };
 
 template <>
 class FFmpegAudioDecoder<LIBAV_VER> : public FFmpegDataDecoder<LIBAV_VER>
 {
--- a/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
@@ -1,28 +1,26 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "mozilla/SyncRunnable.h"
-#include "mozilla/TaskQueue.h"
 
 #include <string.h>
 #ifdef __GNUC__
 #include <unistd.h>
 #endif
 
 #include "FFmpegLog.h"
 #include "FFmpegDataDecoder.h"
+#include "mozilla/TaskQueue.h"
 #include "prsystem.h"
 
-namespace mozilla
-{
+namespace mozilla {
 
 StaticMutex FFmpegDataDecoder<LIBAV_VER>::sMonitor;
 
 FFmpegDataDecoder<LIBAV_VER>::FFmpegDataDecoder(FFmpegLibWrapper* aLib,
                                                 TaskQueue* aTaskQueue,
                                                 AVCodecID aCodecID)
   : mLib(aLib)
   , mCodecContext(nullptr)
--- a/dom/media/platforms/ffmpeg/FFmpegDataDecoder.h
+++ b/dom/media/platforms/ffmpeg/FFmpegDataDecoder.h
@@ -7,18 +7,17 @@
 #ifndef __FFmpegDataDecoder_h__
 #define __FFmpegDataDecoder_h__
 
 #include "PlatformDecoderModule.h"
 #include "FFmpegLibWrapper.h"
 #include "mozilla/StaticMutex.h"
 #include "FFmpegLibs.h"
 
-namespace mozilla
-{
+namespace mozilla {
 
 template <int V>
 class FFmpegDataDecoder : public MediaDataDecoder
 {
 };
 
 template <>
 class FFmpegDataDecoder<LIBAV_VER> : public MediaDataDecoder
@@ -37,17 +36,17 @@ public:
   RefPtr<ShutdownPromise> Shutdown() override;
 
   static AVCodec* FindAVCodec(FFmpegLibWrapper* aLib, AVCodecID aCodec);
 
 protected:
   // Flush and Drain operation, always run
   virtual RefPtr<FlushPromise> ProcessFlush();
   virtual void ProcessShutdown();
-  virtual void InitCodecContext() {}
+  virtual void InitCodecContext() { }
   AVFrame*        PrepareFrame();
   nsresult        InitDecoder();
 
   FFmpegLibWrapper* mLib;
 
   AVCodecContext* mCodecContext;
   AVFrame*        mFrame;
   RefPtr<MediaByteBuffer> mExtraData;
--- a/dom/media/platforms/ffmpeg/FFmpegDecoderModule.h
+++ b/dom/media/platforms/ffmpeg/FFmpegDecoderModule.h
@@ -7,33 +7,32 @@
 #ifndef __FFmpegDecoderModule_h__
 #define __FFmpegDecoderModule_h__
 
 #include "PlatformDecoderModule.h"
 #include "FFmpegLibWrapper.h"
 #include "FFmpegAudioDecoder.h"
 #include "FFmpegVideoDecoder.h"
 
-namespace mozilla
-{
+namespace mozilla {
 
 template <int V>
 class FFmpegDecoderModule : public PlatformDecoderModule
 {
 public:
   static already_AddRefed<PlatformDecoderModule>
   Create(FFmpegLibWrapper* aLib)
   {
     RefPtr<PlatformDecoderModule> pdm = new FFmpegDecoderModule(aLib);
 
     return pdm.forget();
   }
 
-  explicit FFmpegDecoderModule(FFmpegLibWrapper* aLib) : mLib(aLib) {}
-  virtual ~FFmpegDecoderModule() {}
+  explicit FFmpegDecoderModule(FFmpegLibWrapper* aLib) : mLib(aLib) { }
+  virtual ~FFmpegDecoderModule() { }
 
   already_AddRefed<MediaDataDecoder>
   CreateVideoDecoder(const CreateDecoderParams& aParams) override
   {
     // Temporary - forces use of VPXDecoder when alpha is present.
     // Bug 1263836 will handle alpha scenario once implemented. It will shift
     // the check for alpha to PDMFactory but not itself remove the need for a
     // check.
@@ -68,19 +67,19 @@ public:
     }
     AVCodecID codec = audioCodec != AV_CODEC_ID_NONE ? audioCodec : videoCodec;
     return !!FFmpegDataDecoder<V>::FindAVCodec(mLib, codec);
   }
 
   ConversionRequired
   DecoderNeedsConversion(const TrackInfo& aConfig) const override
   {
-    if (aConfig.IsVideo() &&
-        (aConfig.mMimeType.EqualsLiteral("video/avc") ||
-         aConfig.mMimeType.EqualsLiteral("video/mp4"))) {
+    if (aConfig.IsVideo()
+        && (aConfig.mMimeType.EqualsLiteral("video/avc")
+            || aConfig.mMimeType.EqualsLiteral("video/mp4"))) {
       return ConversionRequired::kNeedAVCC;
     } else {
       return ConversionRequired::kNeedNone;
     }
   }
 
 private:
   FFmpegLibWrapper* mLib;
--- a/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp
@@ -5,18 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "FFmpegRuntimeLinker.h"
 #include "FFmpegLibWrapper.h"
 #include "mozilla/ArrayUtils.h"
 #include "FFmpegLog.h"
 #include "prlink.h"
 
-namespace mozilla
-{
+namespace mozilla {
 
 FFmpegRuntimeLinker::LinkStatus FFmpegRuntimeLinker::sLinkStatus =
   LinkStatus_INIT;
 const char* FFmpegRuntimeLinker::sLinkStatusLibraryName = "";
 
 template <int V> class FFmpegDecoderModule
 {
 public:
@@ -54,17 +53,18 @@ FFmpegRuntimeLinker::Init()
   // more precise error if possible.
   sLinkStatus = LinkStatus_NOT_FOUND;
 
   for (size_t i = 0; i < ArrayLength(sLibs); i++) {
     const char* lib = sLibs[i];
     PRLibSpec lspec;
     lspec.type = PR_LibSpec_Pathname;
     lspec.value.pathname = lib;
-    sLibAV.mAVCodecLib = PR_LoadLibraryWithFlags(lspec, PR_LD_NOW | PR_LD_LOCAL);
+    sLibAV.mAVCodecLib =
+      PR_LoadLibraryWithFlags(lspec, PR_LD_NOW | PR_LD_LOCAL);
     if (sLibAV.mAVCodecLib) {
       sLibAV.mAVUtilLib = sLibAV.mAVCodecLib;
       switch (sLibAV.Link()) {
         case FFmpegLibWrapper::LinkResult::Success:
           sLinkStatus = LinkStatus_SUCCEEDED;
           sLinkStatusLibraryName = lib;
           return true;
         case FFmpegLibWrapper::LinkResult::NoProvidedLib:
--- a/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.h
+++ b/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.h
@@ -4,18 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef __FFmpegRuntimeLinker_h__
 #define __FFmpegRuntimeLinker_h__
 
 #include "PlatformDecoderModule.h"
 
-namespace mozilla
-{
+namespace mozilla {
 
 class FFmpegRuntimeLinker
 {
 public:
   static bool Init();
   static already_AddRefed<PlatformDecoderModule> CreateDecoderModule();
   enum LinkStatus
   {
--- a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
@@ -1,41 +1,38 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "mozilla/TaskQueue.h"
-
-#include "nsThreadUtils.h"
-#include "ImageContainer.h"
-
-#include "MediaInfo.h"
-#include "VPXDecoder.h"
-#include "MP4Decoder.h"
-
 #include "FFmpegVideoDecoder.h"
 #include "FFmpegLog.h"
-#include "mozilla/PodOperations.h"
+#include "ImageContainer.h"
+#include "MediaInfo.h"
+#include "MP4Decoder.h"
+#include "VPXDecoder.h"
 
 #include "libavutil/pixfmt.h"
 #if LIBAVCODEC_VERSION_MAJOR < 54
 #define AVPixelFormat PixelFormat
 #define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P
 #define AV_PIX_FMT_YUVJ420P PIX_FMT_YUVJ420P
 #define AV_PIX_FMT_YUV444P PIX_FMT_YUV444P
 #define AV_PIX_FMT_NONE PIX_FMT_NONE
 #endif
+#include "mozilla/PodOperations.h"
+#include "mozilla/TaskQueue.h"
+#include "nsThreadUtils.h"
+
 
 typedef mozilla::layers::Image Image;
 typedef mozilla::layers::PlanarYCbCrImage PlanarYCbCrImage;
 
-namespace mozilla
-{
+namespace mozilla {
 
 /**
  * FFmpeg calls back to this function with a list of pixel formats it supports.
  * We choose a pixel format that we support and return it.
  * For now, we just look for YUV420P, YUVJ420P and YUV444 as those are the only
  * only non-HW accelerated format supported by FFmpeg's H264 and VP9 decoder.
  */
 static AVPixelFormat
@@ -66,56 +63,59 @@ FFmpegVideoDecoder<LIBAV_VER>::PtsCorrec
   : mNumFaultyPts(0)
   , mNumFaultyDts(0)
   , mLastPts(INT64_MIN)
   , mLastDts(INT64_MIN)
 {
 }
 
 int64_t
-FFmpegVideoDecoder<LIBAV_VER>::PtsCorrectionContext::GuessCorrectPts(int64_t aPts, int64_t aDts)
+FFmpegVideoDecoder<LIBAV_VER>::PtsCorrectionContext::GuessCorrectPts(
+  int64_t aPts, int64_t aDts)
 {
   int64_t pts = AV_NOPTS_VALUE;
 
   if (aDts != int64_t(AV_NOPTS_VALUE)) {
     mNumFaultyDts += aDts <= mLastDts;
     mLastDts = aDts;
   }
   if (aPts != int64_t(AV_NOPTS_VALUE)) {
     mNumFaultyPts += aPts <= mLastPts;
     mLastPts = aPts;
   }
-  if ((mNumFaultyPts <= mNumFaultyDts || aDts == int64_t(AV_NOPTS_VALUE)) &&
-      aPts != int64_t(AV_NOPTS_VALUE)) {
+  if ((mNumFaultyPts <= mNumFaultyDts || aDts == int64_t(AV_NOPTS_VALUE))
+      && aPts != int64_t(AV_NOPTS_VALUE)) {
     pts = aPts;
   } else {
     pts = aDts;
   }
   return pts;
 }
 
 void
 FFmpegVideoDecoder<LIBAV_VER>::PtsCorrectionContext::Reset()
 {
   mNumFaultyPts = 0;
   mNumFaultyDts = 0;
   mLastPts = INT64_MIN;
   mLastDts = INT64_MIN;
 }
 
-FFmpegVideoDecoder<LIBAV_VER>::FFmpegVideoDecoder(FFmpegLibWrapper* aLib,
-  TaskQueue* aTaskQueue, const VideoInfo& aConfig, ImageContainer* aImageContainer)
+FFmpegVideoDecoder<LIBAV_VER>::FFmpegVideoDecoder(
+  FFmpegLibWrapper* aLib, TaskQueue* aTaskQueue, const VideoInfo& aConfig,
+  ImageContainer* aImageContainer)
   : FFmpegDataDecoder(aLib, aTaskQueue, GetCodecId(aConfig.mMimeType))
   , mImageContainer(aImageContainer)
   , mInfo(aConfig)
   , mCodecParser(nullptr)
   , mLastInputDts(INT64_MIN)
 {
   MOZ_COUNT_CTOR(FFmpegVideoDecoder);
-  // Use a new MediaByteBuffer as the object will be modified during initialization.
+  // Use a new MediaByteBuffer as the object will be modified during
+  // initialization.
   mExtraData = new MediaByteBuffer;
   mExtraData->AppendElements(*aConfig.mExtraData);
 }
 
 RefPtr<MediaDataDecoder::InitPromise>
 FFmpegVideoDecoder<LIBAV_VER>::Init()
 {
   if (NS_FAILED(InitDecoder())) {
@@ -182,20 +182,19 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecode(
   if (inputSize && mCodecParser && (mCodecID == AV_CODEC_ID_VP8
 #if LIBAVCODEC_VERSION_MAJOR >= 55
       || mCodecID == AV_CODEC_ID_VP9
 #endif
       )) {
     while (inputSize) {
       uint8_t* data;
       int size;
-      int len = mLib->av_parser_parse2(mCodecParser, mCodecContext, &data, &size,
-                                       inputData, inputSize,
-                                       aSample->mTime, aSample->mTimecode,
-                                       aSample->mOffset);
+      int len = mLib->av_parser_parse2(
+        mCodecParser, mCodecContext, &data, &size, inputData, inputSize,
+        aSample->mTime, aSample->mTimecode, aSample->mOffset);
       if (size_t(len) > inputSize) {
         return NS_ERROR_DOM_MEDIA_DECODE_ERR;
       }
       inputData += len;
       inputSize -= len;
       if (size) {
         bool gotFrame = false;
         MediaResult rv = DoDecode(aSample, data, size, &gotFrame, aResults);
@@ -276,18 +275,19 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecode(
   if (!mDurationMap.Find(mFrame->pkt_dts, duration)) {
     NS_WARNING("Unable to retrieve duration from map");
     duration = aSample->mDuration;
     // dts are probably incorrectly reported ; so clear the map as we're
     // unlikely to find them in the future anyway. This also guards
     // against the map becoming extremely big.
     mDurationMap.Clear();
   }
-  FFMPEG_LOG("Got one frame output with pts=%lld dts=%lld duration=%lld opaque=%lld",
-              pts, mFrame->pkt_dts, duration, mCodecContext->reordered_opaque);
+  FFMPEG_LOG(
+    "Got one frame output with pts=%lld dts=%lld duration=%lld opaque=%lld",
+    pts, mFrame->pkt_dts, duration, mCodecContext->reordered_opaque);
 
   VideoData::YCbCrBuffer b;
   b.mPlanes[0].mData = mFrame->data[0];
   b.mPlanes[1].mData = mFrame->data[1];
   b.mPlanes[2].mData = mFrame->data[2];
 
   b.mPlanes[0].mStride = mFrame->linesize[0];
   b.mPlanes[1].mStride = mFrame->linesize[1];
--- a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h
+++ b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h
@@ -43,18 +43,20 @@ public:
 #endif
   }
   static AVCodecID GetCodecId(const nsACString& aMimeType);
 
 private:
   RefPtr<DecodePromise> ProcessDecode(MediaRawData* aSample) override;
   RefPtr<DecodePromise> ProcessDrain() override;
   RefPtr<FlushPromise> ProcessFlush() override;
-  MediaResult DoDecode(MediaRawData* aSample, bool* aGotFrame, DecodedData& aResults);
-  MediaResult DoDecode(MediaRawData* aSample, uint8_t* aData, int aSize, bool* aGotFrame, DecodedData& aResults);
+  MediaResult DoDecode(MediaRawData* aSample, bool* aGotFrame,
+                       DecodedData& aResults);
+  MediaResult DoDecode(MediaRawData* aSample, uint8_t* aData, int aSize,
+                       bool* aGotFrame, DecodedData& aResults);
   void OutputDelayedFrames();
 
   /**
    * This method allocates a buffer for FFmpeg's decoder, wrapped in an Image.
    * Currently it only supports Planar YUV420, which appears to be the only
    * non-hardware accelerated image format that FFmpeg's H264 decoder is
    * capable of outputting.
    */
@@ -62,34 +64,36 @@ private:
                                  AVFrame* aFrame);
 
   RefPtr<ImageContainer> mImageContainer;
   VideoInfo mInfo;
 
   // Parser used for VP8 and VP9 decoding.
   AVCodecParserContext* mCodecParser;
 
-  class PtsCorrectionContext {
+  class PtsCorrectionContext
+  {
   public:
     PtsCorrectionContext();
     int64_t GuessCorrectPts(int64_t aPts, int64_t aDts);
     void Reset();
     int64_t LastDts() const { return mLastDts; }
 
   private:
     int64_t mNumFaultyPts; /// Number of incorrect PTS values so far
     int64_t mNumFaultyDts; /// Number of incorrect DTS values so far
-    int64_t mLastPts;       /// PTS of the last frame
-    int64_t mLastDts;       /// DTS of the last frame
+    int64_t mLastPts;      /// PTS of the last frame
+    int64_t mLastDts;      /// DTS of the last frame
   };
 
   PtsCorrectionContext mPtsContext;
   int64_t mLastInputDts;
 
-  class DurationMap {
+  class DurationMap
+  {
   public:
     typedef Pair<int64_t, int64_t> DurationElement;
 
     // Insert Key and Duration pair at the end of our map.
     void Insert(int64_t aKey, int64_t aDuration)
     {
       mMap.AppendElement(MakePair(aKey, aDuration));
     }
--- a/dom/media/platforms/ffmpeg/ffvpx/FFVPXRuntimeLinker.cpp
+++ b/dom/media/platforms/ffmpeg/ffvpx/FFVPXRuntimeLinker.cpp
@@ -10,18 +10,17 @@
 #include "nsIFile.h"
 #include "prmem.h"
 #include "prlink.h"
 
 // We use a known symbol located in lgpllibs to determine its location.
 // soundtouch happens to be always included in lgpllibs
 #include "soundtouch/SoundTouch.h"
 
-namespace mozilla
-{
+namespace mozilla {
 
 template <int V> class FFmpegDecoderModule
 {
 public:
   static already_AddRefed<PlatformDecoderModule> Create(FFmpegLibWrapper*);
 };
 
 static FFmpegLibWrapper sFFVPXLib;
--- a/dom/push/Push.js
+++ b/dom/push/Push.js
@@ -93,35 +93,55 @@ Push.prototype = {
     console.debug("subscribe()", this._scope);
 
     let histogram = Services.telemetry.getHistogramById("PUSH_API_USED");
     histogram.add(true);
     return this.askPermission().then(() =>
       this.createPromise((resolve, reject) => {
         let callback = new PushSubscriptionCallback(this, resolve, reject);
 
-        if (!options || !options.applicationServerKey) {
+        if (!options || options.applicationServerKey === null) {
           PushService.subscribe(this._scope, this._principal, callback);
           return;
         }
 
-        let appServerKey = options.applicationServerKey;
-        let keyView = new this._window.Uint8Array(ArrayBuffer.isView(appServerKey) ?
-                                                  appServerKey.buffer : appServerKey);
+        let keyView = this._normalizeAppServerKey(options.applicationServerKey);
         if (keyView.byteLength === 0) {
           callback._rejectWithError(Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR);
           return;
         }
         PushService.subscribeWithKey(this._scope, this._principal,
-                                     appServerKey.length, appServerKey,
+                                     keyView.byteLength, keyView,
                                      callback);
       })
     );
   },
 
+  _normalizeAppServerKey: function(appServerKey) {
+    let key;
+    if (typeof appServerKey == "string") {
+      try {
+        key = Cu.cloneInto(ChromeUtils.base64URLDecode(appServerKey, {
+          padding: "reject",
+        }), this._window);
+      } catch (e) {
+        throw new this._window.DOMException(
+          "String contains an invalid character",
+          "InvalidCharacterError"
+        );
+      }
+    } else if (this._window.ArrayBuffer.isView(appServerKey)) {
+      key = appServerKey.buffer;
+    } else {
+      // `appServerKey` is an array buffer.
+      key = appServerKey;
+    }
+    return new this._window.Uint8Array(key);
+  },
+
   getSubscription: function() {
     console.debug("getSubscription()", this._scope);
 
     return this.createPromise((resolve, reject) => {
       let callback = new PushSubscriptionCallback(this, resolve, reject);
       PushService.getSubscription(this._scope, this._principal, callback);
     });
   },
--- a/dom/push/PushManager.cpp
+++ b/dom/push/PushManager.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/PushManager.h"
 
+#include "mozilla/Base64.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/PushManagerBinding.h"
 #include "mozilla/dom/PushSubscription.h"
 #include "mozilla/dom/PushSubscriptionOptionsBinding.h"
 #include "mozilla/dom/PushUtil.h"
 
@@ -575,26 +576,58 @@ PushManager::PerformSubscriptionActionFr
   RefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(worker, p);
   if (!proxy) {
     p->MaybeReject(NS_ERROR_DOM_PUSH_ABORT_ERR);
     return p.forget();
   }
 
   nsTArray<uint8_t> appServerKey;
   if (!aOptions.mApplicationServerKey.IsNull()) {
-    const OwningArrayBufferViewOrArrayBuffer& bufferSource =
-      aOptions.mApplicationServerKey.Value();
-    if (!PushUtil::CopyBufferSourceToArray(bufferSource, appServerKey) ||
-        appServerKey.IsEmpty()) {
-      p->MaybeReject(NS_ERROR_DOM_PUSH_INVALID_KEY_ERR);
+    nsresult rv = NormalizeAppServerKey(aOptions.mApplicationServerKey.Value(),
+                                        appServerKey);
+    if (NS_FAILED(rv)) {
+      p->MaybeReject(rv);
       return p.forget();
     }
   }
 
   RefPtr<GetSubscriptionRunnable> r =
     new GetSubscriptionRunnable(proxy, mScope, aAction, Move(appServerKey));
   MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r));
 
   return p.forget();
 }
 
+nsresult
+PushManager::NormalizeAppServerKey(const OwningArrayBufferViewOrArrayBufferOrString& aSource,
+                                   nsTArray<uint8_t>& aAppServerKey)
+{
+  if (aSource.IsString()) {
+    NS_ConvertUTF16toUTF8 base64Key(aSource.GetAsString());
+    FallibleTArray<uint8_t> decodedKey;
+    nsresult rv = Base64URLDecode(base64Key,
+                                  Base64URLDecodePaddingPolicy::Reject,
+                                  decodedKey);
+    if (NS_FAILED(rv)) {
+      return NS_ERROR_DOM_INVALID_CHARACTER_ERR;
+    }
+    aAppServerKey = decodedKey;
+  } else if (aSource.IsArrayBuffer()) {
+    if (!PushUtil::CopyArrayBufferToArray(aSource.GetAsArrayBuffer(),
+                                         aAppServerKey)) {
+      return NS_ERROR_DOM_PUSH_INVALID_KEY_ERR;
+    }
+  } else if (aSource.IsArrayBufferView()) {
+    if (!PushUtil::CopyArrayBufferViewToArray(aSource.GetAsArrayBufferView(),
+                                              aAppServerKey)) {
+      return NS_ERROR_DOM_PUSH_INVALID_KEY_ERR;
+    }
+  } else {
+    MOZ_CRASH("Uninitialized union: expected string, buffer, or view");
+  }
+  if (aAppServerKey.IsEmpty()) {
+    return NS_ERROR_DOM_PUSH_INVALID_KEY_ERR;
+  }
+  return NS_OK;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/push/PushManager.h
+++ b/dom/push/PushManager.h
@@ -42,16 +42,17 @@ class nsIPrincipal;
 
 namespace mozilla {
 namespace dom {
 
 namespace workers {
 class WorkerPrivate;
 }
 
+class OwningArrayBufferViewOrArrayBufferOrString;
 class Promise;
 class PushManagerImpl;
 struct PushSubscriptionOptionsInit;
 
 class PushManager final : public nsISupports
                         , public nsWrapperCache
 {
 public:
@@ -99,16 +100,20 @@ public:
 
   already_AddRefed<Promise>
   PermissionState(const PushSubscriptionOptionsInit& aOptions,
                   ErrorResult& aRv);
 
 private:
   ~PushManager();
 
+  nsresult
+  NormalizeAppServerKey(const OwningArrayBufferViewOrArrayBufferOrString& aSource,
+                        nsTArray<uint8_t>& aAppServerKey);
+
   // The following are only set and accessed on the main thread.
   nsCOMPtr<nsIGlobalObject> mGlobal;
   RefPtr<PushManagerImpl> mImpl;
 
   // Only used on the worker thread.
   nsString mScope;
 };
 } // namespace dom
--- a/dom/push/test/test_data.html
+++ b/dom/push/test/test_data.html
@@ -55,44 +55,16 @@ http://creativecommons.org/licenses/publ
     controlledFrame = yield injectControlledFrame();
   });
 
   var pushSubscription;
   add_task(function* subscribe() {
     pushSubscription = yield registration.pushManager.subscribe();
   });
 
-  function base64UrlDecode(s) {
-    s = s.replace(/-/g, '+').replace(/_/g, '/');
-
-    // Replace padding if it was stripped by the sender.
-    // See http://tools.ietf.org/html/rfc4648#section-4
-    switch (s.length % 4) {
-      case 0:
-        break; // No pad chars in this case
-      case 2:
-        s += '==';
-        break; // Two pad chars
-      case 3:
-        s += '=';
-        break; // One pad char
-      default:
-        throw new Error('Illegal base64url string!');
-    }
-
-    // With correct padding restored, apply the standard base64 decoder
-    var decoded = atob(s);
-
-    var array = new Uint8Array(new ArrayBuffer(decoded.length));
-    for (var i = 0; i < decoded.length; i++) {
-      array[i] = decoded.charCodeAt(i);
-    }
-    return array;
-  }
-
   add_task(function* compareJSONSubscription() {
     var json = pushSubscription.toJSON();
     is(json.endpoint, pushSubscription.endpoint, "Wrong endpoint");
 
     ["p256dh", "auth"].forEach(keyName => {
       isDeeply(
         base64UrlDecode(json.keys[keyName]),
         new Uint8Array(pushSubscription.getKey(keyName)),
--- a/dom/push/test/test_register_key.html
+++ b/dom/push/test/test_register_key.html
@@ -190,18 +190,117 @@ http://creativecommons.org/licenses/publ
         "Wrong exception type in worker for mismatched key");
       is(errorInfo.name, "InvalidStateError",
         "Wrong exception name in worker for mismatched key");
     } finally {
       isTestingMismatchedKey = false;
     }
   });
 
+  add_task(function* validKeyBuffer() {
+    var key = yield generateKey();
+    var pushSubscription = yield registration.pushManager.subscribe({
+      applicationServerKey: key.buffer,
+    });
+    is(pushSubscription.endpoint, "https://example.com/push/3",
+      "Wrong endpoint for subscription created with key buffer");
+    var subscriptionKey = pushSubscription.options.applicationServerKey;
+    isDeeply(new Uint8Array(subscriptionKey), key,
+      "App server key getter should match given key");
+  });
+
+  add_task(function* validKeyBufferInWorker() {
+    var key = yield generateKey();
+    var data = yield sendRequestToWorker({
+      type: "subscribeWithKey",
+      key: key.buffer,
+    });
+    is(data.endpoint, "https://example.com/push/4",
+      "Wrong endpoint for subscription with key buffer created in worker");
+    isDeeply(new Uint8Array(data.key), key,
+      "App server key getter should match given key for subscription created in worker");
+  });
+
+  add_task(function* validKeyString() {
+    var base64Key = "BOp8kf30nj6mKFFSPw_w3JAMS99Bac8zneMJ6B6lmKixUO5XTf4AtdPgYUgWke-XE25JHdcooyLgJML1R57jhKY";
+    var key = base64UrlDecode(base64Key);
+    var pushSubscription = yield registration.pushManager.subscribe({
+      applicationServerKey: base64Key,
+    });
+    is(pushSubscription.endpoint, "https://example.com/push/5",
+      "Wrong endpoint for subscription created with Base64-encoded key");
+    isDeeply(new Uint8Array(pushSubscription.options.applicationServerKey), key,
+      "App server key getter should match Base64-decoded key");
+  });
+
+  add_task(function* validKeyStringInWorker() {
+    var base64Key = "BOp8kf30nj6mKFFSPw_w3JAMS99Bac8zneMJ6B6lmKixUO5XTf4AtdPgYUgWke-XE25JHdcooyLgJML1R57jhKY";
+    var key = base64UrlDecode(base64Key);
+    var data = yield sendRequestToWorker({
+      type: "subscribeWithKey",
+      key: base64Key,
+    });
+    is(data.endpoint, "https://example.com/push/6",
+      "Wrong endpoint for subscription created with Base64-encoded key in worker");
+    isDeeply(new Uint8Array(data.key), key,
+      "App server key getter should match decoded key for subscription created in worker");
+  });
+
+  add_task(function* invalidKeyString() {
+    try {
+      yield registration.pushManager.subscribe({
+        applicationServerKey: "!@#$^&*",
+      });
+      ok(false, "Should reject for invalid Base64-encoded keys");
+    } catch (error) {
+      ok(error instanceof DOMException,
+        "Wrong exception type for invalid Base64-encoded key");
+      is(error.name, "InvalidCharacterError",
+        "Wrong exception name for invalid Base64-encoded key");
+    }
+  });
+
+  add_task(function* invalidKeyStringInWorker() {
+    var errorInfo = yield sendRequestToWorker({
+      type: "subscribeWithKey",
+      key: "!@#$^&*",
+    });
+    ok(errorInfo.isDOMException,
+      "Wrong exception type in worker for invalid Base64-encoded key");
+    is(errorInfo.name, "InvalidCharacterError",
+      "Wrong exception name in worker for invalid Base64-encoded key");
+  });
+
+  add_task(function* emptyKeyString() {
+    try {
+      yield registration.pushManager.subscribe({
+        applicationServerKey: "",
+      });
+      ok(false, "Should reject for empty key strings");
+    } catch (error) {
+      ok(error instanceof DOMException,
+        "Wrong exception type for empty key string");
+      is(error.name, "InvalidAccessError",
+        "Wrong exception name for empty key string");
+    }
+  });
+
+  add_task(function* emptyKeyStringInWorker() {
+    var errorInfo = yield sendRequestToWorker({
+      type: "subscribeWithKey",
+      key: "",
+    });
+    ok(errorInfo.isDOMException,
+      "Wrong exception type in worker for empty key string");
+    is(errorInfo.name, "InvalidAccessError",
+      "Wrong exception name in worker for empty key string");
+  });
+
   add_task(function* unsubscribe() {
-    is(subscriptions, 2, "Wrong subscription count");
+    is(subscriptions, 6, "Wrong subscription count");
     controlledFrame.remove();
   });
 
   add_task(function* unregister() {
     yield registration.unregister();
   });
 
 </script>
--- a/dom/push/test/test_utils.js
+++ b/dom/push/test/test_utils.js
@@ -238,8 +238,36 @@ function waitForActive(swr) {
     sw.addEventListener('statechange', function onStateChange(evt) {
       if (sw.state === 'activated') {
         sw.removeEventListener('statechange', onStateChange);
         resolve(swr);
       }
     });
   });
 }
+
+function base64UrlDecode(s) {
+  s = s.replace(/-/g, '+').replace(/_/g, '/');
+
+  // Replace padding if it was stripped by the sender.
+  // See http://tools.ietf.org/html/rfc4648#section-4
+  switch (s.length % 4) {
+    case 0:
+      break; // No pad chars in this case
+    case 2:
+      s += '==';
+      break; // Two pad chars
+    case 3:
+      s += '=';
+      break; // One pad char
+    default:
+      throw new Error('Illegal base64url string!');
+  }
+
+  // With correct padding restored, apply the standard base64 decoder
+  var decoded = atob(s);
+
+  var array = new Uint8Array(new ArrayBuffer(decoded.length));
+  for (var i = 0; i < decoded.length; i++) {
+    array[i] = decoded.charCodeAt(i);
+  }
+  return array;
+}
--- a/dom/security/test/hsts/head.js
+++ b/dom/security/test/hsts/head.js
@@ -292,17 +292,17 @@ var Observer = {
 
     is("blocked", test_settings[which_test].result[curTest.id], "HSTS priming "+
         which_test+":"+curTest.id+" expected "+
         test_settings[which_test].result[curTest.id]+", got blocked");
     test_settings[which_test].finished[curTest.id] = "blocked";
   },
   get_current_test: function(uri) {
     for (let item in test_servers) {
-      let re = RegExp('https?://'+test_servers[item].host);
+      let re = RegExp('https?://'+test_servers[item].host+'.*\/browser/dom/security/test/hsts/file_testserver.sjs');
       if (re.test(uri)) {
         return test_servers[item];
       }
     }
     return null;
   },
   http_on_modify_request: function (subject, topic, data) {
     let channel = subject.QueryInterface(Ci.nsIHttpChannel);
--- a/dom/webidl/PushManager.webidl
+++ b/dom/webidl/PushManager.webidl
@@ -4,17 +4,17 @@
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * The origin of this IDL file is
 * https://w3c.github.io/push-api/
 */
 
 dictionary PushSubscriptionOptionsInit {
   // boolean userVisibleOnly = false;
-  BufferSource? applicationServerKey = null;
+  (BufferSource or DOMString)? applicationServerKey = null;
 };
 
 // The main thread JS implementation. Please see comments in
 // dom/push/PushManager.h for the split between PushManagerImpl and PushManager.
 [JSImplementation="@mozilla.org/push/PushManager;1",
  ChromeOnly, Constructor(DOMString scope)]
 interface PushManagerImpl {
   Promise<PushSubscription>    subscribe(optional PushSubscriptionOptionsInit options);
--- a/image/ImageOps.cpp
+++ b/image/ImageOps.cpp
@@ -108,17 +108,20 @@ ImageOps::DecodeToSurface(nsIInputStream
 
   // Write the data into a SourceBuffer.
   NotNull<RefPtr<SourceBuffer>> sourceBuffer = WrapNotNull(new SourceBuffer());
   sourceBuffer->ExpectLength(length);
   rv = sourceBuffer->AppendFromInputStream(inputStream, length);
   if (NS_FAILED(rv)) {
     return nullptr;
   }
-  sourceBuffer->Complete(NS_OK);
+  // Make sure our sourceBuffer is marked as complete.
+  if (!sourceBuffer->IsComplete()) {
+    sourceBuffer->Complete(NS_OK);
+  }
 
   // Create a decoder.
   DecoderType decoderType =
     DecoderFactory::GetDecoderType(PromiseFlatCString(aMimeType).get());
   RefPtr<Decoder> decoder =
     DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer,
                                            Nothing(), ToSurfaceFlags(aFlags));
   if (!decoder) {
--- a/layout/base/AccessibleCaretManager.cpp
+++ b/layout/base/AccessibleCaretManager.cpp
@@ -424,40 +424,51 @@ AccessibleCaretManager::UpdateCaretsForS
     }
   }
 
   if (!mActiveCaret) {
     DispatchCaretStateChangedEvent(CaretChangedReason::Updateposition);
   }
 }
 
-void
+bool
 AccessibleCaretManager::UpdateCaretsForOverlappingTilt()
 {
-  if (mFirstCaret->IsVisuallyVisible() && mSecondCaret->IsVisuallyVisible()) {
-    if (mFirstCaret->Intersects(*mSecondCaret)) {
-      if (mFirstCaret->LogicalPosition().x <=
-          mSecondCaret->LogicalPosition().x) {
-        mFirstCaret->SetAppearance(Appearance::Left);
-        mSecondCaret->SetAppearance(Appearance::Right);
-      } else {
-        mFirstCaret->SetAppearance(Appearance::Right);
-        mSecondCaret->SetAppearance(Appearance::Left);
-      }
-    } else {
-      mFirstCaret->SetAppearance(Appearance::Normal);
-      mSecondCaret->SetAppearance(Appearance::Normal);
-    }
+  if (!mFirstCaret->IsVisuallyVisible() || !mSecondCaret->IsVisuallyVisible()) {
+    return false;
+  }
+
+  if (!mFirstCaret->Intersects(*mSecondCaret)) {
+    mFirstCaret->SetAppearance(Appearance::Normal);
+    mSecondCaret->SetAppearance(Appearance::Normal);
+    return false;
   }
+
+  if (mFirstCaret->LogicalPosition().x <=
+      mSecondCaret->LogicalPosition().x) {
+    mFirstCaret->SetAppearance(Appearance::Left);
+    mSecondCaret->SetAppearance(Appearance::Right);
+  } else {
+    mFirstCaret->SetAppearance(Appearance::Right);
+    mSecondCaret->SetAppearance(Appearance::Left);
+  }
+
+  return true;
 }
 
 void
 AccessibleCaretManager::UpdateCaretsForAlwaysTilt(nsIFrame* aStartFrame,
                                                   nsIFrame* aEndFrame)
 {
+  // When a short LTR word in RTL environment is selected, the two carets
+  // tilted inward might be overlapped. Make them tilt outward.
+  if (UpdateCaretsForOverlappingTilt()) {
+    return;
+  }
+
   if (mFirstCaret->IsVisuallyVisible()) {
     auto startFrameWritingMode = aStartFrame->GetWritingMode();
     mFirstCaret->SetAppearance(startFrameWritingMode.IsBidiLTR() ?
                                Appearance::Left : Appearance::Right);
   }
   if (mSecondCaret->IsVisuallyVisible()) {
     auto endFrameWritingMode = aEndFrame->GetWritingMode();
     mSecondCaret->SetAppearance(endFrameWritingMode.IsBidiLTR() ?
--- a/layout/base/AccessibleCaretManager.h
+++ b/layout/base/AccessibleCaretManager.h
@@ -221,17 +221,18 @@ protected:
   // Get caret mode based on current selection.
   virtual CaretMode GetCaretMode() const;
 
   // @return true if aStartFrame comes before aEndFrame.
   virtual bool CompareTreePosition(nsIFrame* aStartFrame,
                                    nsIFrame* aEndFrame) const;
 
   // Check if the two carets is overlapping to become tilt.
-  virtual void UpdateCaretsForOverlappingTilt();
+  // @return true if the two carets become tilt; false, otherwise.
+  virtual bool UpdateCaretsForOverlappingTilt();
 
   // Make the two carets always tilt.
   virtual void UpdateCaretsForAlwaysTilt(nsIFrame* aStartFrame,
                                          nsIFrame* aEndFrame);
 
   // Check whether AccessibleCaret is displayable in cursor mode or not.
   // @param aOutFrame returns frame of the cursor if it's displayable.
   // @param aOutOffset returns frame offset as well.
--- a/layout/base/gtest/TestAccessibleCaretManager.cpp
+++ b/layout/base/gtest/TestAccessibleCaretManager.cpp
@@ -87,17 +87,17 @@ public:
     }
 
     virtual bool IsCaretDisplayableInCursorMode(
       nsIFrame** aOutFrame = nullptr, int32_t* aOutOffset = nullptr) const override
     {
       return true;
     }
 
-    virtual void UpdateCaretsForOverlappingTilt() override {}
+    virtual bool UpdateCaretsForOverlappingTilt() override { return true; }
 
     virtual void UpdateCaretsForAlwaysTilt(nsIFrame* aStartFrame,
                                            nsIFrame* aEndFrame)
     {
       if (mFirstCaret->IsVisuallyVisible()) {
         mFirstCaret->SetAppearance(Appearance::Left);
       }
       if (mSecondCaret->IsVisuallyVisible()) {
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -546,17 +546,17 @@ GetSuitableScale(float aMaxScale, float 
     // (avoiding visually clunky delayerization).
     return aMaxScale;
   }
   return std::max(std::min(aMaxScale, displayVisibleRatio), aMinScale);
 }
 
 static inline void
 UpdateMinMaxScale(const nsIFrame* aFrame,
-                  const StyleAnimationValue& aValue,
+                  const AnimationValue& aValue,
                   gfxSize& aMinScale,
                   gfxSize& aMaxScale)
 {
   gfxSize size = aValue.GetScaleValue(aFrame);
   aMaxScale.width = std::max<float>(aMaxScale.width, size.width);
   aMaxScale.height = std::max<float>(aMaxScale.height, size.height);
   aMinScale.width = std::min<float>(aMinScale.width, size.width);
   aMinScale.height = std::min<float>(aMinScale.height, size.height);
@@ -586,17 +586,19 @@ GetMinAndMaxScaleForAnimationProperty(co
       }
 
       // We need to factor in the scale of the base style if the base style
       // will be used on the compositor.
       if (effect->NeedsBaseStyle(prop.mProperty)) {
         StyleAnimationValue baseStyle =
           EffectCompositor::GetBaseStyle(prop.mProperty, aFrame);
         MOZ_ASSERT(!baseStyle.IsNull(), "The base value should be set");
-        UpdateMinMaxScale(aFrame, baseStyle, aMinScale, aMaxScale);
+        // FIXME: Bug 1311257: We need to get the baseStyle for
+        //        RawServoAnimationValue.
+        UpdateMinMaxScale(aFrame, { baseStyle, nullptr }, aMinScale, aMaxScale);
       }
 
       for (const AnimationPropertySegment& segment : prop.mSegments) {
         // In case of add or accumulate composite, StyleAnimationValue does
         // not have a valid value.
         if (segment.mFromComposite == dom::CompositeOperation::Replace) {
           UpdateMinMaxScale(aFrame, segment.mFromValue, aMinScale, aMaxScale);
         }
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -154,17 +154,17 @@ ActiveScrolledRoot::ToString(const Activ
 }
 
 static inline CSSAngle
 MakeCSSAngle(const nsCSSValue& aValue)
 {
   return CSSAngle(aValue.GetAngleValue(), aValue.GetUnit());
 }
 
-static void AddTransformFunctions(nsCSSValueList* aList,
+static void AddTransformFunctions(const nsCSSValueList* aList,
                                   nsStyleContext* aContext,
                                   nsPresContext* aPresContext,
                                   TransformReferenceBox& aRefBox,
                                   InfallibleTArray<TransformFunction>& aFunctions)
 {
   if (aList->mValue.GetUnit() == eCSSUnit_None) {
     return;
   }
@@ -396,16 +396,30 @@ static void AddTransformFunctions(nsCSSV
         break;
       }
       default:
         NS_ERROR("Function not handled yet!");
     }
   }
 }
 
+static void
+AddTransformFunctions(const nsCSSValueSharedList* aList,
+                      const nsIFrame* aFrame,
+                      TransformReferenceBox& aRefBox,
+                      layers::Animatable& aAnimatable)
+{
+  MOZ_ASSERT(aList->mHead);
+  AddTransformFunctions(aList->mHead,
+                        aFrame->StyleContext(),
+                        aFrame->PresContext(),
+                        aRefBox,
+                        aAnimatable.get_ArrayOfTransformFunction());
+}
+
 static TimingFunction
 ToTimingFunction(const Maybe<ComputedTimingFunction>& aCTF)
 {
   if (aCTF.isNothing()) {
     return TimingFunction(null_t());
   }
 
   if (aCTF->HasSpline()) {
@@ -415,41 +429,43 @@ ToTimingFunction(const Maybe<ComputedTim
   }
 
   uint32_t type = aCTF->GetType() == nsTimingFunction::Type::StepStart ? 1 : 2;
   return TimingFunction(StepFunction(aCTF->GetSteps(), type));
 }
 
 static void
 SetAnimatable(nsCSSPropertyID aProperty,
-              const StyleAnimationValue& aAnimationValue,
+              const AnimationValue& aAnimationValue,
               nsIFrame* aFrame,
               TransformReferenceBox& aRefBox,
               layers::Animatable& aAnimatable)
 {
   MOZ_ASSERT(aFrame);
 
   if (aAnimationValue.IsNull()) {
     aAnimatable = null_t();
     return;
   }
 
   switch (aProperty) {
     case eCSSProperty_opacity:
-      aAnimatable = aAnimationValue.GetFloatValue();
+      aAnimatable = aAnimationValue.GetOpacity();
       break;
     case eCSSProperty_transform: {
       aAnimatable = InfallibleTArray<TransformFunction>();
-      nsCSSValueSharedList* list =
-        aAnimationValue.GetCSSValueSharedListValue();
-      AddTransformFunctions(list->mHead,
-                            aFrame->StyleContext(),
-                            aFrame->PresContext(),
-                            aRefBox,
-                            aAnimatable.get_ArrayOfTransformFunction());
+      if (aAnimationValue.mServo) {
+        RefPtr<nsCSSValueSharedList> list;
+        Servo_AnimationValues_GetTransform(aAnimationValue.mServo, &list);
+        AddTransformFunctions(list, aFrame, aRefBox, aAnimatable);
+      } else {
+        nsCSSValueSharedList* list =
+          aAnimationValue.mGecko.GetCSSValueSharedListValue();
+        AddTransformFunctions(list, aFrame, aRefBox, aAnimatable);
+      }
       break;
     }
     default:
       MOZ_ASSERT_UNREACHABLE("Unsupported property");
   }
 }
 
 static void
@@ -460,17 +476,19 @@ SetBaseAnimationStyle(nsCSSPropertyID aP
 {
   MOZ_ASSERT(aFrame);
 
   StyleAnimationValue baseValue =
     EffectCompositor::GetBaseStyle(aProperty, aFrame);
   MOZ_ASSERT(!baseValue.IsNull(),
              "The base value should be already there");
 
-  SetAnimatable(aProperty, baseValue, aFrame, aRefBox, aBaseStyle);
+  // FIXME: Bug 1311257: We need to get the baseValue for
+  //        RawServoAnimationValue.
+  SetAnimatable(aProperty, { baseValue, nullptr }, aFrame, aRefBox, aBaseStyle);
 }
 
 static void
 AddAnimationForProperty(nsIFrame* aFrame, const AnimationProperty& aProperty,
                         dom::Animation* aAnimation, Layer* aLayer,
                         AnimationData& aData, bool aPending)
 {
   MOZ_ASSERT(aLayer->AsContainerLayer(), "Should only animate ContainerLayer");
--- a/layout/reftests/svg/filters/css-filters/reftest.list
+++ b/layout/reftests/svg/filters/css-filters/reftest.list
@@ -61,8 +61,11 @@ fuzzy-if(d2d,1,10000) == saturate-desatu
 == saturate-one.html saturate-one-ref.html
 == saturate-percent.html saturate-percent-ref.html
 fuzzy-if(d2d,1,10000) == saturate-zero.html saturate-zero-ref.html
 fuzzy-if(d2d,1,10000) == sepia.html sepia-ref.html
 fuzzy-if(d2d,1,10000) == sepia-one.html sepia-one-ref.html
 fuzzy-if(d2d,1,10000) == sepia-over-one.html sepia-over-one-ref.html
 fuzzy-if(d2d,1,10000) == sepia-percent.html sepia-percent-ref.html
 == sepia-zero.html sepia-zero-ref.html
+
+fuzzy(2,125000) == scale-filtered-content-01.html scale-filtered-content-01-ref.html
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/filters/css-filters/scale-filtered-content-01-ref.html
@@ -0,0 +1,33 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<html>
+<head>
+  <title>CSS Filters: Filtered content should be rendered in device space</title>
+  <style type="text/css">
+    #filtered {
+      width: 100px;
+      height: 50px;
+      background-color: lime;
+      opacity: 0.5;
+      transform: translate(10px) scale(5);
+      transform-origin: 0 0;
+    }
+    .inner {
+      position: absolute;
+      background-color: blue;
+      width: 10px;
+      height:10px;
+    }
+  </style>
+</head>
+<body>
+  <p>You should see clear blue rects.</p>
+  <div id="filtered">
+    <div class="inner" style="left:10px; top:10px;"></div>
+    <div class="inner" style="left:50px; top:20px;"></div>
+    <div class="inner" style="left:80px; top:30px;"></div>
+  </div>
+</body>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/filters/css-filters/scale-filtered-content-01.html
@@ -0,0 +1,33 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<html>
+<head>
+  <title>CSS Filters: Filtered content should be rendered in device space</title>
+  <style type="text/css">
+    #filtered {
+      width: 100px;
+      height: 50px;
+      background-color: lime;
+      filter: opacity(50%);
+      transform: translate(10px) scale(5);
+      transform-origin: 0 0;
+    }
+    .inner {
+      position: absolute;
+      background-color: blue;
+      width: 10px;
+      height:10px;
+    }
+  </style>
+</head>
+<body>
+  <p>You should see clear blue rects.</p>
+  <div id="filtered">
+    <div class="inner" style="left:10px; top:10px;"></div>
+    <div class="inner" style="left:50px; top:20px;"></div>
+    <div class="inner" style="left:80px; top:30px;"></div>
+  </div>
+</body>
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -122,16 +122,21 @@ SERVO_BINDING_FUNC(Servo_AnimationValues
 SERVO_BINDING_FUNC(Servo_AnimationValues_Interpolate,
                    RawServoAnimationValueStrong,
                    RawServoAnimationValueBorrowed from,
                    RawServoAnimationValueBorrowed to,
                    double progress)
 SERVO_BINDING_FUNC(Servo_AnimationValues_Uncompute,
                    RawServoDeclarationBlockStrong,
                    RawServoAnimationValueBorrowedListBorrowed value)
+SERVO_BINDING_FUNC(Servo_AnimationValues_GetOpacity, float,
+                   RawServoAnimationValueBorrowed value)
+SERVO_BINDING_FUNC(Servo_AnimationValues_GetTransform, void,
+                   RawServoAnimationValueBorrowed value,
+                   RefPtr<nsCSSValueSharedList>* list)
 
 // Style attribute
 SERVO_BINDING_FUNC(Servo_ParseStyleAttribute, RawServoDeclarationBlockStrong,
                    const nsACString* data)
 SERVO_BINDING_FUNC(Servo_DeclarationBlock_CreateEmpty,
                    RawServoDeclarationBlockStrong)
 SERVO_BINDING_FUNC(Servo_DeclarationBlock_Clone, RawServoDeclarationBlockStrong,
                    RawServoDeclarationBlockBorrowed declarations)
--- a/layout/style/StyleAnimationValue.cpp
+++ b/layout/style/StyleAnimationValue.cpp
@@ -9,17 +9,16 @@
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/RuleNodeCacheConditions.h"
 #include "mozilla/StyleAnimationValue.h"
 #include "mozilla/StyleSetHandle.h"
 #include "mozilla/StyleSetHandleInlines.h"
 #include "mozilla/Tuple.h"
 #include "mozilla/UniquePtr.h"
-#include "nsStyleTransformMatrix.h"
 #include "nsAutoPtr.h"
 #include "nsCOMArray.h"
 #include "nsIStyleRule.h"
 #include "mozilla/css/StyleRule.h"
 #include "nsString.h"
 #include "nsStyleContext.h"
 #include "nsStyleSet.h"
 #include "nsComputedDOMStyle.h"
@@ -3453,27 +3452,27 @@ ComputeValuesFromStyleContext(
     CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProperty, aEnabledState) {
       if (nsCSSProps::kAnimTypeTable[*p] == eStyleAnimType_None) {
         // Skip non-animatable component longhands.
         continue;
       }
       PropertyStyleAnimationValuePair* pair = aValues.AppendElement();
       pair->mProperty = *p;
       if (!StyleAnimationValue::ExtractComputedValue(*p, aStyleContext,
-                                                     pair->mValue)) {
+                                                     pair->mValue.mGecko)) {
         return false;
       }
     }
     return true;
   }
 
   PropertyStyleAnimationValuePair* pair = aValues.AppendElement();
   pair->mProperty = aProperty;
   return StyleAnimationValue::ExtractComputedValue(aProperty, aStyleContext,
-                                                   pair->mValue);
+                                                   pair->mValue.mGecko);
 }
 
 static bool
 ComputeValuesFromStyleRule(nsCSSPropertyID aProperty,
                            CSSEnabledState aEnabledState,
                            nsStyleContext* aStyleContext,
                            css::StyleRule* aStyleRule,
                            nsTArray<PropertyStyleAnimationValuePair>& aValues,
@@ -3578,17 +3577,17 @@ StyleAnimationValue::ComputeValue(nsCSSP
                                        values, aIsContextSensitive);
   if (!ok) {
     return false;
   }
 
   MOZ_ASSERT(values.Length() == 1);
   MOZ_ASSERT(values[0].mProperty == aProperty);
 
-  aComputedValue = values[0].mValue;
+  aComputedValue = values[0].mValue.mGecko;
   return true;
 }
 
 template <class T>
 bool
 ComputeValuesFromSpecifiedValue(
     nsCSSPropertyID aProperty,
     CSSEnabledState aEnabledState,
@@ -4810,39 +4809,20 @@ StyleAnimationValue::ExtractComputedValu
       NS_NOTREACHED("shouldn't use on non-animatable properties");
   }
   return false;
 }
 
 gfxSize
 StyleAnimationValue::GetScaleValue(const nsIFrame* aForFrame) const
 {
-  MOZ_ASSERT(aForFrame);
   MOZ_ASSERT(GetUnit() == StyleAnimationValue::eUnit_Transform);
 
   nsCSSValueSharedList* list = GetCSSValueSharedListValue();
-  MOZ_ASSERT(list->mHead);
-
-  RuleNodeCacheConditions dontCare;
-  bool dontCareBool;
-  nsStyleTransformMatrix::TransformReferenceBox refBox(aForFrame);
-  Matrix4x4 transform = nsStyleTransformMatrix::ReadTransforms(
-                          list->mHead,
-                          aForFrame->StyleContext(),
-                          aForFrame->PresContext(), dontCare, refBox,
-                          aForFrame->PresContext()->AppUnitsPerDevPixel(),
-                          &dontCareBool);
-
-  Matrix transform2d;
-  bool canDraw2D = transform.CanDraw2D(&transform2d);
-  if (!canDraw2D) {
-    return gfxSize();
-  }
-
-  return ThebesMatrix(transform2d).ScaleFactors(true);
+  return nsStyleTransformMatrix::GetScaleValue(list, aForFrame);
 }
 
 StyleAnimationValue::StyleAnimationValue(int32_t aInt, Unit aUnit,
                                          IntegerConstructorType)
 {
   NS_ASSERTION(IsIntUnit(aUnit), "unit must be of integer type");
   mUnit = aUnit;
   mValue.mInt = aInt;
--- a/layout/style/StyleAnimationValue.h
+++ b/layout/style/StyleAnimationValue.h
@@ -13,16 +13,18 @@
 #include "mozilla/UniquePtr.h"
 #include "nsStringFwd.h"
 #include "nsStringBuffer.h"
 #include "nsCoord.h"
 #include "nsColor.h"
 #include "nsCSSProps.h"
 #include "nsCSSValue.h"
 #include "nsStyleCoord.h"
+#include "nsStyleTransformMatrix.h"
+#include "ServoBindings.h"
 
 class nsIFrame;
 class nsStyleContext;
 class gfx3DMatrix;
 struct RawServoDeclarationBlock;
 
 namespace mozilla {
 
@@ -577,17 +579,45 @@ private:
   static bool IsCSSValuePairListUnit(Unit aUnit) {
     return aUnit == eUnit_CSSValuePairList;
   }
   static bool IsStringUnit(Unit aUnit) {
     return aUnit == eUnit_UnparsedString;
   }
 };
 
+struct AnimationValue
+{
+  StyleAnimationValue mGecko;
+  RefPtr<RawServoAnimationValue> mServo;
+
+  bool operator==(const AnimationValue& aOther) const {
+    // FIXME: Bug 1337229: add a deep == impl for RawServoAnimationValue.
+    return mGecko == aOther.mGecko && mServo == aOther.mServo;
+  }
+
+  bool IsNull() const { return mGecko.IsNull() && !mServo; }
+
+  float GetOpacity() const {
+    return mServo ? Servo_AnimationValues_GetOpacity(mServo)
+                  : mGecko.GetFloatValue();
+  }
+
+  // Returns the scale for mGecko or mServo, which are calculated with
+  // reference to aFrame.
+  gfxSize GetScaleValue(const nsIFrame* aFrame) const {
+    if (mServo) {
+      RefPtr<nsCSSValueSharedList> list;
+      Servo_AnimationValues_GetTransform(mServo, &list);
+      return nsStyleTransformMatrix::GetScaleValue(list, aFrame);
+    }
+    return mGecko.GetScaleValue(aFrame);
+  }
+};
+
 struct PropertyStyleAnimationValuePair
 {
   nsCSSPropertyID mProperty;
-  StyleAnimationValue mValue;
-  RefPtr<RawServoAnimationValue> mServoValue;
+  AnimationValue mValue;
 };
 } // namespace mozilla
 
 #endif
--- a/layout/style/nsStyleTransformMatrix.cpp
+++ b/layout/style/nsStyleTransformMatrix.cpp
@@ -1254,9 +1254,34 @@ CSSValueArrayTo3DMatrix(nsCSSValue::Arra
   gfx::Float array[16];
   for (size_t i = 0; i < 16; ++i) {
     array[i] = aArray->Item(i+1).GetFloatValue();
   }
   Matrix4x4 m(array);
   return m;
 }
 
+gfxSize
+GetScaleValue(const nsCSSValueSharedList* aList,
+              const nsIFrame* aForFrame)
+{
+  MOZ_ASSERT(aList && aList->mHead);
+  MOZ_ASSERT(aForFrame);
+
+  RuleNodeCacheConditions dontCare;
+  bool dontCareBool;
+  TransformReferenceBox refBox(aForFrame);
+  Matrix4x4 transform = ReadTransforms(
+                          aList->mHead,
+                          aForFrame->StyleContext(),
+                          aForFrame->PresContext(), dontCare, refBox,
+                          aForFrame->PresContext()->AppUnitsPerDevPixel(),
+                          &dontCareBool);
+  Matrix transform2d;
+  bool canDraw2D = transform.CanDraw2D(&transform2d);
+  if (!canDraw2D) {
+    return gfxSize();
+  }
+
+  return ThebesMatrix(transform2d).ScaleFactors(true);
+}
+
 } // namespace nsStyleTransformMatrix
--- a/layout/style/nsStyleTransformMatrix.h
+++ b/layout/style/nsStyleTransformMatrix.h
@@ -225,11 +225,14 @@ namespace nsStyleTransformMatrix {
                          mozilla::gfx::Point3D& aScale,
                          ShearArray& aShear,
                          gfxQuaternion& aRotate,
                          mozilla::gfx::Point3D& aTranslate,
                          mozilla::gfx::Point4D& aPerspective);
 
   mozilla::gfx::Matrix CSSValueArrayTo2DMatrix(nsCSSValue::Array* aArray);
   mozilla::gfx::Matrix4x4 CSSValueArrayTo3DMatrix(nsCSSValue::Array* aArray);
+
+  gfxSize GetScaleValue(const nsCSSValueSharedList* aList,
+                        const nsIFrame* aForFrame);
 } // namespace nsStyleTransformMatrix
 
 #endif
--- a/layout/style/nsTransitionManager.cpp
+++ b/layout/style/nsTransitionManager.cpp
@@ -115,17 +115,17 @@ ElementPropertyTransition::UpdateStartVa
       MOZ_ASSERT(mProperties.Length() == 1 &&
                  mProperties[0].mSegments.Length() == 1,
                  "The transition should have one property and one segment");
       nsCSSValue cssValue;
       DebugOnly<bool> uncomputeResult =
         StyleAnimationValue::UncomputeValue(mProperties[0].mProperty,
                                             startValue,
                                             cssValue);
-      mProperties[0].mSegments[0].mFromValue = Move(startValue);
+      mProperties[0].mSegments[0].mFromValue.mGecko = Move(startValue);
       MOZ_ASSERT(uncomputeResult, "UncomputeValue should not fail");
       MOZ_ASSERT(mKeyframes.Length() == 2,
           "Transitions should have exactly two animation keyframes");
       MOZ_ASSERT(mKeyframes[0].mPropertyValues.Length() == 1,
           "Transitions should have exactly one property in their first "
           "frame");
       mKeyframes[0].mPropertyValues[0].mValue = cssValue;
     }
@@ -984,18 +984,18 @@ nsTransitionManager::ConsiderInitiatingT
       const AnimationPropertySegment& segment =
         oldPT->Properties()[0].mSegments[0];
       pt->mReplacedTransition.emplace(
         ElementPropertyTransition::ReplacedTransitionProperties({
           oldPT->GetAnimation()->GetStartTime().Value(),
           oldPT->GetAnimation()->PlaybackRate(),
           oldPT->SpecifiedTiming(),
           segment.mTimingFunction,
-          segment.mFromValue,
-          segment.mToValue
+          segment.mFromValue.mGecko,
+          segment.mToValue.mGecko
         })
       );
     }
     animations[currentIndex]->CancelFromStyle();
     oldPT = nullptr; // Clear pointer so it doesn't dangle
     animations[currentIndex] = animation;
   } else {
     if (!animations.AppendElement(animation)) {
--- a/layout/style/nsTransitionManager.h
+++ b/layout/style/nsTransitionManager.h
@@ -68,17 +68,17 @@ struct ElementPropertyTransition : publi
     // return a null value but also show a warning since we should be
     // detecting that kind of situation in advance and not generating a
     // transition in the first place.
     if (mProperties.Length() < 1 ||
         mProperties[0].mSegments.Length() < 1) {
       NS_WARNING("Failed to generate transition property values");
       return StyleAnimationValue();
     }
-    return mProperties[0].mSegments[0].mToValue;
+    return mProperties[0].mSegments[0].mToValue.mGecko;
   }
 
   // This is the start value to be used for a check for whether a
   // transition is being reversed.  Normally the same as
   // mProperties[0].mSegments[0].mFromValue, except when this transition
   // started as the reversal of another in-progress transition.
   // Needed so we can handle two reverses in a row.
   StyleAnimationValue mStartForReversingTest;
--- a/layout/svg/SVGGeometryFrame.cpp
+++ b/layout/svg/SVGGeometryFrame.cpp
@@ -114,17 +114,17 @@ nsDisplaySVGGeometry::Paint(nsDisplayLis
   // ToReferenceFrame includes our mRect offset, but painting takes
   // account of that too. To avoid double counting, we subtract that
   // here.
   nsPoint offset = ToReferenceFrame() - mFrame->GetPosition();
 
   gfxPoint devPixelOffset =
     nsLayoutUtils::PointToGfxPoint(offset, appUnitsPerDevPixel);
 
-  gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(mFrame) *
+  gfxMatrix tm = nsSVGUtils::GetCSSPxToDevPxMatrix(mFrame) *
                    gfxMatrix::Translation(devPixelOffset);
   DrawResult result =
     static_cast<SVGGeometryFrame*>(mFrame)->PaintSVG(*aCtx->ThebesContext(), tm);
 
   nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
 }
 
 void
--- a/layout/svg/SVGTextFrame.cpp
+++ b/layout/svg/SVGTextFrame.cpp
@@ -3110,17 +3110,17 @@ nsDisplaySVGText::Paint(nsDisplayListBui
   // ToReferenceFrame includes our mRect offset, but painting takes
   // account of that too. To avoid double counting, we subtract that
   // here.
   nsPoint offset = ToReferenceFrame() - mFrame->GetPosition();
 
   gfxPoint devPixelOffset =
     nsLayoutUtils::PointToGfxPoint(offset, appUnitsPerDevPixel);
 
-  gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(mFrame) *
+  gfxMatrix tm = nsSVGUtils::GetCSSPxToDevPxMatrix(mFrame) *
                    gfxMatrix::Translation(devPixelOffset);
 
   gfxContext* ctx = aCtx->ThebesContext();
   ctx->Save();
   DrawResult result = static_cast<SVGTextFrame*>(mFrame)->PaintSVG(*ctx, tm);
   nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
   ctx->Restore();
 }
--- a/layout/svg/nsFilterInstance.cpp
+++ b/layout/svg/nsFilterInstance.cpp
@@ -34,20 +34,20 @@ using namespace mozilla::image;
 FilterDescription
 nsFilterInstance::GetFilterDescription(nsIContent* aFilteredElement,
                                        const nsTArray<nsStyleFilter>& aFilterChain,
                                        bool aFilterInputIsTainted,
                                        const UserSpaceMetrics& aMetrics,
                                        const gfxRect& aBBox,
                                        nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages)
 {
-  gfxMatrix unused; // aPaintTransform arg not used since we're not painting
+  gfxMatrix identity;
   nsFilterInstance instance(nullptr, aFilteredElement, aMetrics,
                             aFilterChain, aFilterInputIsTainted, nullptr,
-                            unused, nullptr, nullptr, nullptr, &aBBox);
+                            identity, nullptr, nullptr, nullptr, &aBBox);
   if (!instance.IsInitialized()) {
     return FilterDescription();
   }
   return instance.ExtractDescriptionAndAdditionalImages(aOutAdditionalImages);
 }
 
 static UniquePtr<UserSpaceMetrics>
 UserSpaceMetricsForFrame(nsIFrame* aFrame)
@@ -65,64 +65,65 @@ nsFilterInstance::PaintFilteredFrame(nsI
                                      const gfxMatrix& aTransform,
                                      nsSVGFilterPaintCallback *aPaintCallback,
                                      const nsRegion *aDirtyArea)
 {
   auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
   UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
   // Hardcode InputIsTainted to true because we don't want JS to be able to
   // read the rendered contents of aFilteredFrame.
-  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
-                            filterChain, /* InputIsTainted */ true, aPaintCallback,
-                            aTransform, aDirtyArea, nullptr, nullptr, nullptr);
+  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
+                            *metrics, filterChain, /* InputIsTainted */ true,
+                            aPaintCallback, aTransform, aDirtyArea, nullptr,
+                            nullptr, nullptr);
   if (!instance.IsInitialized()) {
     return DrawResult::BAD_IMAGE;
   }
 
   return instance.Render(aDrawTarget);
 }
 
 nsRegion
 nsFilterInstance::GetPostFilterDirtyArea(nsIFrame *aFilteredFrame,
                                          const nsRegion& aPreFilterDirtyRegion)
 {
   if (aPreFilterDirtyRegion.IsEmpty()) {
     return nsRegion();
   }
 
-  gfxMatrix unused; // aPaintTransform arg not used since we're not painting
+  gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
   auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
   UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
   // Hardcode InputIsTainted to true because we don't want JS to be able to
   // read the rendered contents of aFilteredFrame.
-  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
-                            filterChain, /* InputIsTainted */ true, nullptr, unused,
-                            nullptr, &aPreFilterDirtyRegion);
+  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
+                            *metrics, filterChain, /* InputIsTainted */ true,
+                            nullptr, tm, nullptr, &aPreFilterDirtyRegion);
   if (!instance.IsInitialized()) {
     return nsRegion();
   }
 
   // We've passed in the source's dirty area so the instance knows about it.
   // Now we can ask the instance to compute the area of the filter output
   // that's dirty.
   return instance.ComputePostFilterDirtyRegion();
 }
 
 nsRegion
 nsFilterInstance::GetPreFilterNeededArea(nsIFrame *aFilteredFrame,
                                          const nsRegion& aPostFilterDirtyRegion)
 {
-  gfxMatrix unused; // aPaintTransform arg not used since we're not painting
+  gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
   auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
   UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
   // Hardcode InputIsTainted to true because we don't want JS to be able to
   // read the rendered contents of aFilteredFrame.
-  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
-                            filterChain, /* InputIsTainted */ true, nullptr, unused,
-                            &aPostFilterDirtyRegion);
+  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
+                            *metrics, filterChain, /* InputIsTainted */ true,
+                            nullptr, tm, &aPostFilterDirtyRegion);
   if (!instance.IsInitialized()) {
     return nsRect();
   }
 
   // Now we can ask the instance to compute the area of the source
   // that's needed.
   return instance.ComputeSourceNeededRect();
 }
@@ -138,25 +139,25 @@ nsFilterInstance::GetPostFilterBounds(ns
 
   nsRegion preFilterRegion;
   nsRegion* preFilterRegionPtr = nullptr;
   if (aPreFilterBounds) {
     preFilterRegion = *aPreFilterBounds;
     preFilterRegionPtr = &preFilterRegion;
   }
 
-  gfxMatrix unused; // aPaintTransform arg not used since we're not painting
+  gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
   auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
   UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
   // Hardcode InputIsTainted to true because we don't want JS to be able to
   // read the rendered contents of aFilteredFrame.
-  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
-                            filterChain, /* InputIsTainted */ true, nullptr, unused,
-                            nullptr, preFilterRegionPtr, aPreFilterBounds,
-                            aOverrideBBox);
+  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
+                            *metrics, filterChain, /* InputIsTainted */ true,
+                            nullptr, tm, nullptr, preFilterRegionPtr,
+                            aPreFilterBounds, aOverrideBBox);
   if (!instance.IsInitialized()) {
     return nsRect();
   }
 
   return instance.ComputePostFilterExtents();
 }
 
 nsFilterInstance::nsFilterInstance(nsIFrame *aTargetFrame,
@@ -198,21 +199,16 @@ nsFilterInstance::nsFilterInstance(nsIFr
   }
 
   // Get various transforms:
 
   gfxMatrix filterToUserSpace(mFilterSpaceToUserSpaceScale.width, 0.0f,
                               0.0f, mFilterSpaceToUserSpaceScale.height,
                               0.0f, 0.0f);
 
-  // Only used (so only set) when we paint:
-  if (mPaintCallback) {
-    mFilterSpaceToDeviceSpaceTransform = filterToUserSpace * mPaintTransform;
-  }
-
   mFilterSpaceToFrameSpaceInCSSPxTransform =
     filterToUserSpace * GetUserSpaceToFrameSpaceInCSSPxTransform();
   // mFilterSpaceToFrameSpaceInCSSPxTransform is always invertible
   mFrameSpaceInCSSPxToFilterSpaceTransform =
     mFilterSpaceToFrameSpaceInCSSPxTransform;
   mFrameSpaceInCSSPxToFilterSpaceTransform.Invert();
 
   nsIntRect targetBounds;
@@ -238,32 +234,29 @@ nsFilterInstance::nsFilterInstance(nsIFr
   mInitialized = true;
 }
 
 nsresult
 nsFilterInstance::ComputeUserSpaceToFilterSpaceScale()
 {
   gfxMatrix canvasTransform;
   if (mTargetFrame) {
-    canvasTransform = nsSVGUtils::GetCanvasTM(mTargetFrame);
-    if (canvasTransform.IsSingular()) {
+    mUserSpaceToFilterSpaceScale = mPaintTransform.ScaleFactors(true);
+    if (mUserSpaceToFilterSpaceScale.width <= 0.0f ||
+        mUserSpaceToFilterSpaceScale.height <= 0.0f) {
       // Nothing should be rendered.
       return NS_ERROR_FAILURE;
     }
+  } else {
+    mUserSpaceToFilterSpaceScale = gfxSize(1.0, 1.0);
   }
 
-  mUserSpaceToFilterSpaceScale = canvasTransform.ScaleFactors(true);
-  if (mUserSpaceToFilterSpaceScale.width <= 0.0f ||
-      mUserSpaceToFilterSpaceScale.height <= 0.0f) {
-    // Nothing should be rendered.
-    return NS_ERROR_FAILURE;
-  }
-
-  mFilterSpaceToUserSpaceScale = gfxSize(1.0f / mUserSpaceToFilterSpaceScale.width,
-                                         1.0f / mUserSpaceToFilterSpaceScale.height);
+  mFilterSpaceToUserSpaceScale =
+    gfxSize(1.0f / mUserSpaceToFilterSpaceScale.width,
+            1.0f / mUserSpaceToFilterSpaceScale.height);
   return NS_OK;
 }
 
 gfxRect
 nsFilterInstance::UserSpaceToFilterSpace(const gfxRect& aUserSpaceRect) const
 {
   gfxRect filterSpaceRect = aUserSpaceRect;
   filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale.width,
@@ -357,85 +350,74 @@ nsFilterInstance::ComputeNeededBoxes()
   sourceGraphicNeededRegion.And(sourceGraphicNeededRegion, mTargetBounds);
 
   mSourceGraphic.mNeededBounds = sourceGraphicNeededRegion.GetBounds();
   mFillPaint.mNeededBounds = fillPaintNeededRegion.GetBounds();
   mStrokePaint.mNeededBounds = strokePaintNeededRegion.GetBounds();
 }
 
 DrawResult
-nsFilterInstance::BuildSourcePaint(SourceInfo *aSource,
-                                   DrawTarget* aTargetDT)
+nsFilterInstance::BuildSourcePaint(SourceInfo *aSource)
 {
   MOZ_ASSERT(mTargetFrame);
   nsIntRect neededRect = aSource->mNeededBounds;
 
   RefPtr<DrawTarget> offscreenDT =
     gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
       neededRect.Size(), SurfaceFormat::B8G8R8A8);
   if (!offscreenDT || !offscreenDT->IsValid()) {
     return DrawResult::TEMPORARY_ERROR;
   }
 
-  gfxMatrix deviceToFilterSpace = GetFilterSpaceToDeviceSpaceTransform();
-  DebugOnly<bool> invertible = deviceToFilterSpace.Invert();
-  MOZ_ASSERT(invertible,
-             "The returning matix of GetFilterSpaceToDeviceSpaceTransform must"
-             "be an invertible matrix(not a singular one), since we already"
-             "checked it and early return if it's not from the caller side"
-             "(nsFilterInstance::Render)");
+  RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
+  MOZ_ASSERT(ctx); // already checked the draw target above
+  gfxContextAutoSaveRestore saver(ctx);
 
-  if (!mPaintTransform.IsSingular()) {
-    RefPtr<gfxContext> gfx = gfxContext::CreateOrNull(offscreenDT);
-    MOZ_ASSERT(gfx); // already checked the draw target above
-    gfx->Save();
-    gfx->Multiply(mPaintTransform *
-                  deviceToFilterSpace *
-                  gfxMatrix::Translation(-neededRect.TopLeft()));
-    GeneralPattern pattern;
-    if (aSource == &mFillPaint) {
-      nsSVGUtils::MakeFillPatternFor(mTargetFrame, gfx, &pattern);
-    } else if (aSource == &mStrokePaint) {
-      nsSVGUtils::MakeStrokePatternFor(mTargetFrame, gfx, &pattern);
-    }
-    if (pattern.GetPattern()) {
-      offscreenDT->FillRect(ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect))),
-                            pattern);
-    }
-    gfx->Restore();
+  ctx->SetMatrix(mPaintTransform *
+                 gfxMatrix::Translation(-neededRect.TopLeft()));
+  GeneralPattern pattern;
+  if (aSource == &mFillPaint) {
+    nsSVGUtils::MakeFillPatternFor(mTargetFrame, ctx, &pattern);
+  } else if (aSource == &mStrokePaint) {
+    nsSVGUtils::MakeStrokePatternFor(mTargetFrame, ctx, &pattern);
+  }
+
+  if (pattern.GetPattern()) {
+    offscreenDT->FillRect(ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect))),
+                          pattern);
   }
 
   aSource->mSourceSurface = offscreenDT->Snapshot();
   aSource->mSurfaceRect = neededRect;
 
   return DrawResult::SUCCESS;
 }
 
 DrawResult
-nsFilterInstance::BuildSourcePaints(DrawTarget* aTargetDT)
+nsFilterInstance::BuildSourcePaints()
 {
   if (!mFillPaint.mNeededBounds.IsEmpty()) {
-    DrawResult result = BuildSourcePaint(&mFillPaint, aTargetDT);
+    DrawResult result = BuildSourcePaint(&mFillPaint);
     if (result != DrawResult::SUCCESS) {
       return result;
     }
   }
 
   if (!mStrokePaint.mNeededBounds.IsEmpty()) {
-    DrawResult result = BuildSourcePaint(&mStrokePaint, aTargetDT);
+    DrawResult result = BuildSourcePaint(&mStrokePaint);
     if (result != DrawResult::SUCCESS) {
       return result;
     }
   }
 
   return  DrawResult::SUCCESS;
 }
 
 DrawResult
-nsFilterInstance::BuildSourceImage(DrawTarget* aTargetDT)
+nsFilterInstance::BuildSourceImage()
 {
   MOZ_ASSERT(mTargetFrame);
 
   nsIntRect neededRect = mSourceGraphic.mNeededBounds;
   if (neededRect.IsEmpty()) {
     return DrawResult::SUCCESS;
   }
 
@@ -459,29 +441,23 @@ nsFilterInstance::BuildSourceImage(DrawT
   //
   // (In theory it would be better to minimize error by having filtered SVG
   // graphics temporarily paint to user space when painting the sources and
   // only set a user space to filter space transform on the gfxContext
   // (since that would eliminate the transform multiplications from user
   // space to device space and back again). However, that would make the
   // code more complex while being hard to get right without introducing
   // subtle bugs, and in practice it probably makes no real difference.)
-  gfxMatrix deviceToFilterSpace = GetFilterSpaceToDeviceSpaceTransform();
-  DebugOnly<bool> invertible = deviceToFilterSpace.Invert();
-  MOZ_ASSERT(invertible,
-             "The returning matix of GetFilterSpaceToDeviceSpaceTransform must"
-             "be an invertible matrix(not a singular one), since we already"
-             "checked it and early return if it's not from the caller side"
-             "(nsFilterInstance::Render)");
-
   RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
   MOZ_ASSERT(ctx); // already checked the draw target above
-  ctx->SetMatrix(
-    ctx->CurrentMatrix().Translate(-neededRect.TopLeft()).
-                         PreMultiply(deviceToFilterSpace));
+  gfxMatrix devPxToCssPxTM = nsSVGUtils::GetCSSPxToDevPxMatrix(mTargetFrame);
+  DebugOnly<bool> invertible = devPxToCssPxTM.Invert();
+  MOZ_ASSERT(invertible);
+  ctx->SetMatrix(devPxToCssPxTM * mPaintTransform *
+                 gfxMatrix::Translation(-neededRect.TopLeft()));
 
   DrawResult result =
     mPaintCallback->Paint(*ctx, mTargetFrame, mPaintTransform, &dirty);
 
   mSourceGraphic.mSourceSurface = offscreenDT->Snapshot();
   mSourceGraphic.mSurfaceRect = neededRect;
 
   return result;
@@ -494,33 +470,37 @@ nsFilterInstance::Render(DrawTarget* aDr
 
   if (mPrimitiveDescriptions.IsEmpty()) {
     // An filter without any primitive. Treat it as success and paint nothing.
     return DrawResult::SUCCESS;
   }
 
   nsIntRect filterRect =
     mPostFilterDirtyRegion.GetBounds().Intersect(OutputFilterSpaceBounds());
-  gfxMatrix ctm = GetFilterSpaceToDeviceSpaceTransform();
-
-  if (filterRect.IsEmpty() || ctm.IsSingular()) {
+  if (filterRect.IsEmpty() || mPaintTransform.IsSingular()) {
     return DrawResult::SUCCESS;
   }
 
   AutoRestoreTransform autoRestoreTransform(aDrawTarget);
-  Matrix newTM = ToMatrix(ctm).PreTranslate(filterRect.x, filterRect.y) *
-                 aDrawTarget->GetTransform();
-  aDrawTarget->SetTransform(newTM);
+  gfxMatrix filterSpaceToUserSpace = mPaintTransform;
+  DebugOnly<bool> invertible = filterSpaceToUserSpace.Invert();
+  MOZ_ASSERT(invertible);
+  filterSpaceToUserSpace *= nsSVGUtils::GetCSSPxToDevPxMatrix(mTargetFrame);
+
+  aDrawTarget->SetTransform(ToMatrix(filterSpaceToUserSpace) *
+                            aDrawTarget->GetTransform() *
+                            Matrix::Translation(filterRect.TopLeft()));
 
   ComputeNeededBoxes();
-  DrawResult result = BuildSourceImage(aDrawTarget);
+
+  DrawResult result = BuildSourceImage();
   if (result != DrawResult::SUCCESS){
     return result;
   }
-  result = BuildSourcePaints(aDrawTarget);
+  result = BuildSourcePaints();
   if (result != DrawResult::SUCCESS){
     return result;
   }
 
   FilterSupport::RenderFilterDescription(
     aDrawTarget, mFilterDescription, IntRectToRect(filterRect),
     mSourceGraphic.mSourceSurface, mSourceGraphic.mSurfaceRect,
     mFillPaint.mSourceSurface, mFillPaint.mSurfaceRect,
--- a/layout/svg/nsFilterInstance.h
+++ b/layout/svg/nsFilterInstance.h
@@ -114,16 +114,17 @@ public:
    *   as aFilteredFrame's bbox ('bbox' is a specific SVG term), if non-null.
    * @param aPreFilterBounds The pre-filter visual overflow rect of
    *   aFilteredFrame, if non-null.
    */
   static nsRect GetPostFilterBounds(nsIFrame *aFilteredFrame,
                                     const gfxRect *aOverrideBBox = nullptr,
                                     const nsRect *aPreFilterBounds = nullptr);
 
+private:
   /**
    * @param aTargetFrame The frame of the filtered element under consideration,
    *   may be null.
    * @param aTargetContent The filtered element itself.
    * @param aMetrics The metrics to resolve SVG lengths against.
    * @param aFilterChain The list of filters to apply.
    * @param aFilterInputIsTainted Describes whether the SourceImage / SourceAlpha
    *   input is tainted. This affects whether feDisplacementMap will respect
@@ -196,25 +197,16 @@ public:
    * Sets the aDirty outparam to the pre-filter bounds in frame space of the
    * area of mTargetFrame that is needed in order to paint the filtered output
    * for a given post-filter dirtied area. The post-filter area must have been
    * specified before calling this method by passing it as the aPostFilterDirtyRegion
    * argument to the nsFilterInstance constructor.
    */
   nsRect ComputeSourceNeededRect();
 
-
-  /**
-   * Returns the transform from filter space to outer-<svg> device space.
-   */
-  gfxMatrix GetFilterSpaceToDeviceSpaceTransform() const {
-    return mFilterSpaceToDeviceSpaceTransform;
-  }
-
-private:
   struct SourceInfo {
     // Specifies which parts of the source need to be rendered.
     // Set by ComputeNeededBoxes().
     nsIntRect mNeededBounds;
 
     // The surface that contains the input rendering.
     // Set by BuildSourceImage / BuildSourcePaint.
     RefPtr<SourceSurface> mSourceSurface;
@@ -223,31 +215,30 @@ private:
     // Set by BuildSourceImage / BuildSourcePaint.
     IntRect mSurfaceRect;
   };
 
   /**
    * Creates a SourceSurface for either the FillPaint or StrokePaint graph
    * nodes
    */
-  DrawResult BuildSourcePaint(SourceInfo *aPrimitive,
-                            DrawTarget* aTargetDT);
+  DrawResult BuildSourcePaint(SourceInfo *aPrimitive);
 
   /**
    * Creates a SourceSurface for either the FillPaint and StrokePaint graph
    * nodes, fills its contents and assigns it to mFillPaint.mSourceSurface and
    * mStrokePaint.mSourceSurface respectively.
    */
-  DrawResult BuildSourcePaints(DrawTarget* aTargetDT);
+  DrawResult BuildSourcePaints();
 
   /**
    * Creates the SourceSurface for the SourceGraphic graph node, paints its
    * contents, and assigns it to mSourceGraphic.mSourceSurface.
    */
-  DrawResult BuildSourceImage(DrawTarget* aTargetDT);
+  DrawResult BuildSourceImage();
 
   /**
    * Build the list of FilterPrimitiveDescriptions that describes the filter's
    * filter primitives and their connections. This populates
    * mPrimitiveDescriptions and mInputImages. aFilterInputIsTainted describes
    * whether the SourceGraphic is tainted.
    */
   nsresult BuildPrimitives(const nsTArray<nsStyleFilter>& aFilterChain,
@@ -335,21 +326,16 @@ private:
   gfxRect mTargetBBox;
 
   /**
    * The SVG bbox of the element that is being filtered, in filter space.
    */
   nsIntRect mTargetBBoxInFilterSpace;
 
   /**
-   * The transform from filter space to outer-<svg> device space.
-   */
-  gfxMatrix mFilterSpaceToDeviceSpaceTransform;
-
-  /**
    * Transform rects between filter space and frame space in CSS pixels.
    */
   gfxMatrix mFilterSpaceToFrameSpaceInCSSPxTransform;
   gfxMatrix mFrameSpaceInCSSPxToFilterSpaceTransform;
 
   /**
    * The scale factors between user space and filter space.
    */
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -398,42 +398,40 @@ nsSVGIntegrationUtils::HitTestFrameForEf
   return nsSVGUtils::HitTestClip(firstFrame, userSpacePt);
 }
 
 class RegularFramePaintCallback : public nsSVGFilterPaintCallback
 {
 public:
   RegularFramePaintCallback(nsDisplayListBuilder* aBuilder,
                             LayerManager* aManager,
-                            const nsPoint& aOffset)
+                            const gfxPoint& aUserSpaceToFrameSpaceOffset)
     : mBuilder(aBuilder), mLayerManager(aManager),
-      mOffset(aOffset) {}
+      mUserSpaceToFrameSpaceOffset(aUserSpaceToFrameSpaceOffset) {}
 
   virtual DrawResult Paint(gfxContext& aContext, nsIFrame *aTarget,
                            const gfxMatrix& aTransform,
                            const nsIntRect* aDirtyRect) override
   {
     BasicLayerManager* basic = mLayerManager->AsBasicLayerManager();
+    RefPtr<gfxContext> oldCtx = basic->GetTarget();
     basic->SetTarget(&aContext);
 
-    gfxPoint devPixelOffset =
-      nsLayoutUtils::PointToGfxPoint(-mOffset,
-                                     aTarget->PresContext()->AppUnitsPerDevPixel());
-
     gfxContextMatrixAutoSaveRestore autoSR(&aContext);
-    aContext.SetMatrix(aContext.CurrentMatrix().Translate(devPixelOffset));
+    aContext.SetMatrix(aContext.CurrentMatrix().Translate(-mUserSpaceToFrameSpaceOffset));
 
     mLayerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, mBuilder);
+    basic->SetTarget(oldCtx);
     return DrawResult::SUCCESS;
   }
 
 private:
   nsDisplayListBuilder* mBuilder;
   LayerManager* mLayerManager;
-  nsPoint mOffset;
+  gfxPoint mUserSpaceToFrameSpaceOffset;
 };
 
 typedef nsSVGIntegrationUtils::PaintFramesParams PaintFramesParams;
 
 /**
  * Paint css-positioned-mask onto a given target(aMaskDT).
  */
 static DrawResult
@@ -444,17 +442,17 @@ PaintMaskSurface(const PaintFramesParams
                  const nsPoint& aOffsetToUserSpace)
 {
   MOZ_ASSERT(aMaskFrames.Length() > 0);
   MOZ_ASSERT(aMaskDT->GetFormat() == SurfaceFormat::A8);
   MOZ_ASSERT(aOpacity == 1.0 || aMaskFrames.Length() == 1);
 
   const nsStyleSVGReset *svgReset = aSC->StyleSVGReset();
   gfxMatrix cssPxToDevPxMatrix =
-    nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aParams.frame);
+    nsSVGUtils::GetCSSPxToDevPxMatrix(aParams.frame);
 
   nsPresContext* presContext = aParams.frame->PresContext();
   gfxPoint devPixelOffsetToUserSpace =
     nsLayoutUtils::PointToGfxPoint(aOffsetToUserSpace,
                                    presContext->AppUnitsPerDevPixel());
 
   RefPtr<gfxContext> maskContext = gfxContext::CreateOrNull(aMaskDT);
   MOZ_ASSERT(maskContext);
@@ -543,17 +541,17 @@ CreateAndPaintMaskSurface(const PaintFra
   MOZ_ASSERT(aMaskFrames.Length() > 0);
   MaskPaintResult paintResult;
 
   gfxContext& ctx = aParams.ctx;
 
   // Optimization for single SVG mask.
   if (((aMaskFrames.Length() == 1) && aMaskFrames[0])) {
     gfxMatrix cssPxToDevPxMatrix =
-    nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aParams.frame);
+      nsSVGUtils::GetCSSPxToDevPxMatrix(aParams.frame);
     paintResult.opacityApplied = true;
     nsSVGMaskFrame::MaskParams params(&ctx, aParams.frame, cssPxToDevPxMatrix,
                                       aOpacity, &paintResult.maskTransform,
                                       svgReset->mMask.mLayers[0].mMaskMode);
     Tie(paintResult.result, paintResult.maskSurface) =
       aMaskFrames[0]->GetMaskForMaskedFrame(params);
 
     if (!paintResult.maskSurface) {
@@ -648,71 +646,96 @@ ValidateSVGFrame(nsIFrame* aFrame)
       // The SVG spec says not to draw _anything_
       return false;
     }
   }
 
   return true;
 }
 
-/**
- * Setup transform matrix of a gfx context by a specific frame. Depend on
- * aClipCtx, this function may clip that context by the visual overflow area
- * of aFrame.
- *
- * @param aFrame is the target frame.
- * @param aOffsetToBoundingBox returns the offset between the reference frame
- *        and the bounding box of aFrame.
- * @oaram aOffsetToUserSpace returns the offset between the reference frame and
- *        the user space coordinate of aFrame.
- */
-static void
-SetupContextMatrix(nsIFrame* aFrame, const PaintFramesParams& aParams,
-                   nsPoint& aOffsetToBoundingBox, nsPoint& aOffsetToUserSpace)
+struct EffectOffsets {
+  // The offset between the reference frame and the bounding box of the
+  // target frame in app unit.
+  nsPoint  offsetToBoundingBox;
+  // The offset between the reference frame and the bounding box of the
+  // target frame in device unit.
+  gfxPoint offsetToBoundingBoxInDevPx;
+  // The offset between the reference frame and the bounding box of the
+  // target frame in app unit.
+  nsPoint  offsetToUserSpace;
+  // The offset between the reference frame and the bounding box of the
+  // target frame in device unit.
+  gfxPoint offsetToUserSpaceInDevPx;
+};
+
+EffectOffsets
+ComputeEffectOffset(nsIFrame* aFrame, const PaintFramesParams& aParams)
 {
-  aOffsetToBoundingBox = aParams.builder->ToReferenceFrame(aFrame) -
-                         nsSVGIntegrationUtils::GetOffsetToBoundingBox(aFrame);
+  EffectOffsets result;
+
+  result.offsetToBoundingBox =
+    aParams.builder->ToReferenceFrame(aFrame) -
+    nsSVGIntegrationUtils::GetOffsetToBoundingBox(aFrame);
   if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) {
     /* Snap the offset if the reference frame is not a SVG frame,
      * since other frames will be snapped to pixel when rendering. */
-    aOffsetToBoundingBox = nsPoint(
-      aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(aOffsetToBoundingBox.x),
-      aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(aOffsetToBoundingBox.y));
+    result.offsetToBoundingBox =
+      nsPoint(
+        aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(result.offsetToBoundingBox.x),
+        aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(result.offsetToBoundingBox.y));
   }
 
   // After applying only "aOffsetToBoundingBox", aParams.ctx would have its
   // origin at the top left corner of frame's bounding box (over all
   // continuations).
   // However, SVG painting needs the origin to be located at the origin of the
   // SVG frame's "user space", i.e. the space in which, for example, the
   // frame's BBox lives.
   // SVG geometry frames and foreignObject frames apply their own offsets, so
   // their position is relative to their user space. So for these frame types,
   // if we want aParams.ctx to be in user space, we first need to subtract the
   // frame's position so that SVG painting can later add it again and the
   // frame is painted in the right place.
-
   gfxPoint toUserSpaceGfx = nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame);
   nsPoint toUserSpace =
     nsPoint(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)),
             nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y)));
 
-  aOffsetToUserSpace = aOffsetToBoundingBox - toUserSpace;
+  result.offsetToUserSpace = result.offsetToBoundingBox - toUserSpace;
 
 #ifdef DEBUG
   bool hasSVGLayout = (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT);
-  NS_ASSERTION(hasSVGLayout || aOffsetToBoundingBox == aOffsetToUserSpace,
+  NS_ASSERTION(hasSVGLayout ||
+               result.offsetToBoundingBox == result.offsetToUserSpace,
                "For non-SVG frames there shouldn't be any additional offset");
 #endif
 
-  gfxPoint devPixelOffsetToUserSpace =
-    nsLayoutUtils::PointToGfxPoint(aOffsetToUserSpace,
+  result.offsetToUserSpaceInDevPx =
+    nsLayoutUtils::PointToGfxPoint(result.offsetToUserSpace,
+                                   aFrame->PresContext()->AppUnitsPerDevPixel());
+  result.offsetToBoundingBoxInDevPx =
+    nsLayoutUtils::PointToGfxPoint(result.offsetToBoundingBox,
                                    aFrame->PresContext()->AppUnitsPerDevPixel());
-  gfxContext& context = aParams.ctx;
-  context.SetMatrix(context.CurrentMatrix().Translate(devPixelOffsetToUserSpace));
+
+  return result;
+}
+
+/**
+ * Setup transform matrix of a gfx context by a specific frame. Move the
+ * origin of aParams.ctx to the user space of aFrame.
+ */
+static EffectOffsets
+MoveContextOriginToUserSpace(nsIFrame* aFrame, const PaintFramesParams& aParams)
+{
+  EffectOffsets offset = ComputeEffectOffset(aFrame, aParams);
+
+  aParams.ctx.SetMatrix(
+    aParams.ctx.CurrentMatrix().Translate(offset.offsetToUserSpaceInDevPx));
+
+  return offset;
 }
 
 bool
 nsSVGIntegrationUtils::IsMaskResourceReady(nsIFrame* aFrame)
 {
   nsIFrame* firstFrame =
     nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
   nsSVGEffects::EffectProperties effectProperties =
@@ -793,26 +816,23 @@ nsSVGIntegrationUtils::PaintMask(const P
   bool shouldPushOpacity = (maskUsage.opacity != 1.0) &&
                            (maskFrames.Length() != 1);
   if (shouldPushOpacity) {
     ctx.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, maskUsage.opacity);
     autoPop.SetContext(&ctx);
   }
 
   gfxContextMatrixAutoSaveRestore matSR;
-  nsPoint offsetToBoundingBox;
-  nsPoint offsetToUserSpace;
 
   // Paint clip-path-basic-shape onto ctx
   gfxContextAutoSaveRestore basicShapeSR;
   if (maskUsage.shouldApplyBasicShape) {
     matSR.SetContext(&ctx);
 
-    SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
-                       offsetToUserSpace);
+    MoveContextOriginToUserSpace(firstFrame, aParams);
 
     basicShapeSR.SetContext(&ctx);
     nsCSSClipPathInstance::ApplyBasicShapeClip(ctx, frame);
     if (!maskUsage.shouldGenerateMaskLayer) {
       // Only have basic-shape clip-path effect. Fill clipped region by
       // opaque white.
       ctx.SetColor(Color(1.0, 1.0, 1.0, 1.0));
       ctx.Fill();
@@ -821,36 +841,35 @@ nsSVGIntegrationUtils::PaintMask(const P
     }
   }
 
   // Paint mask onto ctx.
   if (maskUsage.shouldGenerateMaskLayer) {
     matSR.Restore();
     matSR.SetContext(&ctx);
 
-    SetupContextMatrix(frame, aParams, offsetToBoundingBox,
-                       offsetToUserSpace);
+    EffectOffsets offsets = MoveContextOriginToUserSpace(frame, aParams);
     result = PaintMaskSurface(aParams, maskTarget,
                               shouldPushOpacity ?  1.0 : maskUsage.opacity,
                               firstFrame->StyleContext(), maskFrames,
-                              ctx.CurrentMatrix(), offsetToUserSpace);
+                              ctx.CurrentMatrix(),
+                              offsets.offsetToUserSpace);
     if (result != DrawResult::SUCCESS) {
       return result;
     }
   }
 
   // Paint clip-path onto ctx.
   if (maskUsage.shouldGenerateClipMaskLayer || maskUsage.shouldApplyClipPath) {
     matSR.Restore();
     matSR.SetContext(&ctx);
 
-    SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
-                       offsetToUserSpace);
+    MoveContextOriginToUserSpace(firstFrame, aParams);
     Matrix clipMaskTransform;
-    gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(frame);
+    gfxMatrix cssPxToDevPxMatrix = nsSVGUtils::GetCSSPxToDevPxMatrix(frame);
 
     nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame();
     RefPtr<SourceSurface> maskSurface =
       maskUsage.shouldGenerateMaskLayer ? maskTarget->Snapshot() : nullptr;
     result =
       clipPathFrame->PaintClipMask(ctx, frame, cssPxToDevPxMatrix,
                                    &clipMaskTransform, maskSurface,
                                    ToMatrix(ctx.CurrentMatrix()));
@@ -900,22 +919,19 @@ nsSVGIntegrationUtils::PaintMaskAndClipP
      so make sure all applicable ones are set again. */
   nsIFrame* firstFrame =
     nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
   nsSVGEffects::EffectProperties effectProperties =
     nsSVGEffects::GetEffectProperties(firstFrame);
 
   nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame();
 
-  gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(frame);
+  gfxMatrix cssPxToDevPxMatrix = nsSVGUtils::GetCSSPxToDevPxMatrix(frame);
   nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
 
-  nsPoint offsetToBoundingBox;
-  nsPoint offsetToUserSpace;
-
   bool shouldGenerateMask = (maskUsage.opacity != 1.0f ||
                              maskUsage.shouldGenerateClipMaskLayer ||
                              maskUsage.shouldGenerateMaskLayer);
   bool shouldPushMask = false;
 
   /* Check if we need to do additional operations on this child's
    * rendering, which necessitates rendering into another surface. */
   if (shouldGenerateMask) {
@@ -926,22 +942,21 @@ nsSVGIntegrationUtils::PaintMaskAndClipP
     bool opacityApplied = false;
 
     if (maskUsage.shouldGenerateMaskLayer) {
       matSR.SetContext(&context);
 
       // For css-mask, we want to generate a mask for each continuation frame,
       // so we setup context matrix by the position of the current frame,
       // instead of the first continuation frame.
-      SetupContextMatrix(frame, aParams, offsetToBoundingBox,
-                         offsetToUserSpace);
+      EffectOffsets offsets = MoveContextOriginToUserSpace(frame, aParams);
       MaskPaintResult paintResult =
         CreateAndPaintMaskSurface(aParams, maskUsage.opacity,
                                   firstFrame->StyleContext(),
-                                  maskFrames, offsetToUserSpace);
+                                  maskFrames, offsets.offsetToUserSpace);
 
       if (paintResult.transparentBlackMask) {
         return paintResult.result;
       }
 
       result &= paintResult.result;
       maskSurface = paintResult.maskSurface;
       if (maskSurface) {
@@ -951,18 +966,17 @@ nsSVGIntegrationUtils::PaintMaskAndClipP
         opacityApplied = paintResult.opacityApplied;
       }
     }
 
     if (maskUsage.shouldGenerateClipMaskLayer) {
       matSR.Restore();
       matSR.SetContext(&context);
 
-      SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
-                         offsetToUserSpace);
+      MoveContextOriginToUserSpace(firstFrame, aParams);
       Matrix clipMaskTransform;
       DrawResult clipMaskResult;
       RefPtr<SourceSurface> clipMaskSurface;
       Tie(clipMaskResult, clipMaskSurface) =
         clipPathFrame->GetClipMask(context, frame, cssPxToDevPxMatrix,
                                    &clipMaskTransform, maskSurface,
                                    maskTransform);
 
@@ -979,18 +993,17 @@ nsSVGIntegrationUtils::PaintMaskAndClipP
     }
 
     // opacity != 1.0f.
     if (!maskUsage.shouldGenerateClipMaskLayer &&
         !maskUsage.shouldGenerateMaskLayer) {
       MOZ_ASSERT(maskUsage.opacity != 1.0f);
 
       matSR.SetContext(&context);
-      SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
-                         offsetToUserSpace);
+      MoveContextOriginToUserSpace(firstFrame, aParams);
       shouldPushMask = true;
     }
 
     if (shouldPushMask) {
       if (aParams.layerManager->GetRoot()->GetContentFlags() &
           Layer::CONTENT_COMPONENT_ALPHA) {
         context.PushGroupAndCopyBackground(gfxContentType::COLOR_ALPHA,
                                            opacityApplied
@@ -1006,18 +1019,17 @@ nsSVGIntegrationUtils::PaintMaskAndClipP
   }
 
   /* If this frame has only a trivial clipPath, set up cairo's clipping now so
    * we can just do normal painting and get it clipped appropriately.
    */
   if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) {
     gfxContextMatrixAutoSaveRestore matSR(&context);
 
-    SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
-                       offsetToUserSpace);
+    MoveContextOriginToUserSpace(firstFrame, aParams);
 
     MOZ_ASSERT(!maskUsage.shouldApplyClipPath ||
                !maskUsage.shouldApplyBasicShape);
     if (maskUsage.shouldApplyClipPath) {
       clipPathFrame->ApplyClipPath(context, frame, cssPxToDevPxMatrix);
     } else {
       nsCSSClipPathInstance::ApplyBasicShapeClip(context, frame);
     }
@@ -1093,56 +1105,46 @@ nsSVGIntegrationUtils::PaintFilter(const
   nsSVGEffects::EffectProperties effectProperties =
     nsSVGEffects::GetEffectProperties(firstFrame);
 
   if (effectProperties.HasInvalidFilter()) {
     return DrawResult::NOT_READY;
   }
 
   gfxContext& context = aParams.ctx;
-  nsPoint offsetToBoundingBox;
-  nsPoint offsetToUserSpace;
 
   gfxContextAutoSaveRestore autoSR(&context);
-  SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
-                     offsetToUserSpace);
+  EffectOffsets offsets = MoveContextOriginToUserSpace(firstFrame, aParams);
 
   if (opacity != 1.0f) {
     context.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity,
                                   nullptr, Matrix());
   }
 
   /* Paint the child and apply filters */
   RegularFramePaintCallback callback(aParams.builder, aParams.layerManager,
-                                     offsetToUserSpace);
-  nsRegion dirtyRegion = aParams.dirtyRect - offsetToBoundingBox;
-  gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(frame);
+                                     offsets.offsetToUserSpaceInDevPx);
+  nsRegion dirtyRegion = aParams.dirtyRect - offsets.offsetToBoundingBox;
+  gfxSize scaleFactors = context.CurrentMatrix().ScaleFactors(true);
+  gfxMatrix scaleMatrix(scaleFactors.width, 0.0f,
+                        0.0f, scaleFactors.height,
+                        0.0f, 0.0f);
+  gfxMatrix tm =
+    scaleMatrix * nsSVGUtils::GetCSSPxToDevPxMatrix(frame);
   DrawResult result =
     nsFilterInstance::PaintFilteredFrame(frame, context.GetDrawTarget(),
                                          tm, &callback, &dirtyRegion);
 
   if (opacity != 1.0f) {
     context.PopGroupAndBlend();
   }
 
   return result;
 }
 
-gfxMatrix
-nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame)
-{
-  int32_t appUnitsPerDevPixel = aNonSVGFrame->PresContext()->AppUnitsPerDevPixel();
-  float devPxPerCSSPx =
-    1 / nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPixel);
-
-  return gfxMatrix(devPxPerCSSPx, 0.0,
-                   0.0, devPxPerCSSPx,
-                   0.0, 0.0);
-}
-
 class PaintFrameCallback : public gfxDrawingCallback {
 public:
   PaintFrameCallback(nsIFrame* aFrame,
                      const nsSize aPaintServerSize,
                      const IntSize aRenderSize,
                      uint32_t aFlags)
    : mFrame(aFrame)
    , mPaintServerSize(aPaintServerSize)
--- a/layout/svg/nsSVGIntegrationUtils.h
+++ b/layout/svg/nsSVGIntegrationUtils.h
@@ -176,25 +176,16 @@ public:
 
   /**
    * Paint non-SVG frame with filter and opacity effect.
    */
   static DrawResult
   PaintFilter(const PaintFramesParams& aParams);
 
   /**
-   * SVG frames expect to paint in SVG user units, which are equal to CSS px
-   * units. This method provides a transform matrix to multiply onto a
-   * gfxContext's current transform to convert the context's current units from
-   * its usual dev pixels to SVG user units/CSS px to keep the SVG code happy.
-   */
-  static gfxMatrix
-  GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame);
-
-  /**
    * @param aRenderingContext the target rendering context in which the paint
    * server will be rendered
    * @param aTarget the target frame onto which the paint server will be
    * rendered
    * @param aPaintServer a first-continuation frame to use as the source
    * @param aFilter a filter to be applied when scaling
    * @param aDest the area the paint server image should be mapped to
    * @param aFill the area to be filled with copies of the paint server image
--- a/layout/svg/nsSVGOuterSVGFrame.cpp
+++ b/layout/svg/nsSVGOuterSVGFrame.cpp
@@ -621,17 +621,17 @@ nsDisplayOuterSVG::Paint(nsDisplayListBu
       ToOutsidePixels(appUnitsPerDevPixel);
 
   gfxPoint devPixelOffset =
     nsLayoutUtils::PointToGfxPoint(viewportRect.TopLeft(), appUnitsPerDevPixel);
 
   aContext->ThebesContext()->Save();
   // We include the offset of our frame and a scale from device pixels to user
   // units (i.e. CSS px) in the matrix that we pass to our children):
-  gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(mFrame) *
+  gfxMatrix tm = nsSVGUtils::GetCSSPxToDevPxMatrix(mFrame) *
                    gfxMatrix::Translation(devPixelOffset);
   DrawResult result =
     nsSVGUtils::PaintFrameWithEffects(mFrame, *aContext->ThebesContext(), tm,
                                       &contentAreaDirtyRect);
   nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
   aContext->ThebesContext()->Restore();
 
 #if defined(DEBUG) && defined(SVG_DEBUG_PAINT_TIMING)
--- a/layout/svg/nsSVGUtils.cpp
+++ b/layout/svg/nsSVGUtils.cpp
@@ -394,25 +394,25 @@ nsSVGUtils::GetOuterSVGFrameAndCoveredRe
 }
 
 gfxMatrix
 nsSVGUtils::GetCanvasTM(nsIFrame *aFrame)
 {
   // XXX yuck, we really need a common interface for GetCanvasTM
 
   if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) {
-    return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame);
+    return GetCSSPxToDevPxMatrix(aFrame);
   }
 
   nsIAtom* type = aFrame->GetType();
   if (type == nsGkAtoms::svgForeignObjectFrame) {
     return static_cast<nsSVGForeignObjectFrame*>(aFrame)->GetCanvasTM();
   }
   if (type == nsGkAtoms::svgOuterSVGFrame) {
-    return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame);
+    return GetCSSPxToDevPxMatrix(aFrame);
   }
 
   nsSVGContainerFrame *containerFrame = do_QueryFrame(aFrame);
   if (containerFrame) {
     return containerFrame->GetCanvasTM();
   }
 
   return static_cast<SVGGeometryFrame*>(aFrame)->GetCanvasTM();
@@ -1859,8 +1859,20 @@ nsRect
 nsSVGUtils::ToCanvasBounds(const gfxRect &aUserspaceRect,
                            const gfxMatrix &aToCanvas,
                            const nsPresContext *presContext)
 {
   return nsLayoutUtils::RoundGfxRectToAppRect(
                           aToCanvas.TransformBounds(aUserspaceRect),
                           presContext->AppUnitsPerDevPixel());
 }
+
+gfxMatrix
+nsSVGUtils::GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame)
+{
+  int32_t appUnitsPerDevPixel = aNonSVGFrame->PresContext()->AppUnitsPerDevPixel();
+  float devPxPerCSSPx =
+    1 / nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPixel);
+
+  return gfxMatrix(devPxPerCSSPx, 0.0,
+                   0.0, devPxPerCSSPx,
+                   0.0, 0.0);
+}
\ No newline at end of file
--- a/layout/svg/nsSVGUtils.h
+++ b/layout/svg/nsSVGUtils.h
@@ -592,11 +592,20 @@ public:
     { }
   };
 
   static void
   DetermineMaskUsage(nsIFrame* aFrame, bool aHandleOpacity, MaskUsage& aUsage);
 
   static float
   ComputeOpacity(nsIFrame* aFrame, bool aHandleOpacity);
+
+  /**
+   * SVG frames expect to paint in SVG user units, which are equal to CSS px
+   * units. This method provides a transform matrix to multiply onto a
+   * gfxContext's current transform to convert the context's current units from
+   * its usual dev pixels to SVG user units/CSS px to keep the SVG code happy.
+   */
+  static gfxMatrix
+  GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame);
 };
 
 #endif
--- a/mobile/android/base/java/org/mozilla/gecko/media/AudioFocusAgent.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/AudioFocusAgent.java
@@ -16,16 +16,17 @@ public class AudioFocusAgent {
 
     private static Context mContext;
     private AudioManager mAudioManager;
     private OnAudioFocusChangeListener mAfChangeListener;
 
     public static final String OWN_FOCUS = "own_focus";
     public static final String LOST_FOCUS = "lost_focus";
     public static final String LOST_FOCUS_TRANSIENT = "lost_focus_transient";
+    public static final String LOST_FOCUS_TRANSIENT_CAN_DUCK = "lost_focus_transient_can_duck";
 
     private String mAudioFocusState = LOST_FOCUS;
 
     @WrapForJNI(calledFrom = "gecko")
     public static void notifyStartedPlaying() {
         if (!isAttachedToContext()) {
             return;
         }
@@ -60,23 +61,30 @@ public class AudioFocusAgent {
                         mAudioFocusState = LOST_FOCUS;
                         break;
                     case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                         Log.d(LOGTAG, "onAudioFocusChange, AUDIOFOCUS_LOSS_TRANSIENT");
                         notifyObservers("AudioFocusChanged", "lostAudioFocusTransiently");
                         notifyMediaControlService(MediaControlService.ACTION_PAUSE_BY_AUDIO_FOCUS);
                         mAudioFocusState = LOST_FOCUS_TRANSIENT;
                         break;
+                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+                        Log.d(LOGTAG, "onAudioFocusChange, AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK");
+                        notifyMediaControlService(MediaControlService.ACTION_START_AUDIO_DUCK);
+                        mAudioFocusState = LOST_FOCUS_TRANSIENT_CAN_DUCK;
+                        break;
                     case AudioManager.AUDIOFOCUS_GAIN:
-                        if (!mAudioFocusState.equals(LOST_FOCUS_TRANSIENT)) {
-                            return;
+                        if (mAudioFocusState.equals(LOST_FOCUS_TRANSIENT_CAN_DUCK)) {
+                            Log.d(LOGTAG, "onAudioFocusChange, AUDIOFOCUS_GAIN (from DUCKING)");
+                            notifyMediaControlService(MediaControlService.ACTION_STOP_AUDIO_DUCK);
+                        } else if (mAudioFocusState.equals(LOST_FOCUS_TRANSIENT)) {
+                            Log.d(LOGTAG, "onAudioFocusChange, AUDIOFOCUS_GAIN");
+                            notifyObservers("AudioFocusChanged", "gainAudioFocus");
+                            notifyMediaControlService(MediaControlService.ACTION_RESUME_BY_AUDIO_FOCUS);
                         }
-                        Log.d(LOGTAG, "onAudioFocusChange, AUDIOFOCUS_GAIN");
-                        notifyObservers("AudioFocusChanged", "gainAudioFocus");
-                        notifyMediaControlService(MediaControlService.ACTION_RESUME_BY_AUDIO_FOCUS);
                         mAudioFocusState = OWN_FOCUS;
                         break;
                     default:
                 }
             }
         };
         notifyMediaControlService(MediaControlService.ACTION_INIT);
     }
--- a/mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java
@@ -8,16 +8,17 @@ import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.media.AudioManager;
+import android.media.VolumeProvider;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.support.annotation.CheckResult;
 import android.support.annotation.VisibleForTesting;
 import android.support.v4.app.NotificationManagerCompat;
@@ -38,20 +39,27 @@ public class MediaControlService extends
     private static final String LOGTAG = "MediaControlService";
 
     public static final String ACTION_INIT           = "action_init";
     public static final String ACTION_RESUME         = "action_resume";
     public static final String ACTION_PAUSE          = "action_pause";
     public static final String ACTION_STOP           = "action_stop";
     public static final String ACTION_RESUME_BY_AUDIO_FOCUS = "action_resume_audio_focus";
     public static final String ACTION_PAUSE_BY_AUDIO_FOCUS  = "action_pause_audio_focus";
-
+    public static final String ACTION_START_AUDIO_DUCK      = "action_start_audio_duck";
+    public static final String ACTION_STOP_AUDIO_DUCK       = "action_stop_audio_duck";
     private static final int MEDIA_CONTROL_ID = 1;
     private static final String MEDIA_CONTROL_PREF = "dom.audiochannel.mediaControl";
 
+    // This is maximum volume level difference when audio ducking. The number is arbitrary.
+    private static final int AUDIO_DUCK_MAX_STEPS = 3;
+    private enum AudioDucking { START, STOP };
+    private boolean mSupportsDucking = false;
+    private int mAudioDuckCurrentSteps = 0;
+
     private MediaSession mSession;
     private MediaController mController;
     private HeadSetStateReceiver mHeadSetStateReceiver;
 
     private PrefsHelper.PrefHandler mPrefsObserver;
     private final String[] mPrefs = { MEDIA_CONTROL_PREF };
 
     private boolean mInitialize = false;
@@ -208,16 +216,44 @@ public class MediaControlService extends
                 mController.getTransportControls().stop();
                 break;
             case ACTION_PAUSE_BY_AUDIO_FOCUS :
                 mController.getTransportControls().sendCustomAction(ACTION_PAUSE_BY_AUDIO_FOCUS, null);
                 break;
             case ACTION_RESUME_BY_AUDIO_FOCUS :
                 mController.getTransportControls().sendCustomAction(ACTION_RESUME_BY_AUDIO_FOCUS, null);
                 break;
+            case ACTION_START_AUDIO_DUCK:
+                handleAudioDucking(AudioDucking.START);
+                break;
+            case ACTION_STOP_AUDIO_DUCK :
+                handleAudioDucking(AudioDucking.STOP);
+                break;
+        }
+    }
+
+    private void handleAudioDucking(AudioDucking audioDucking) {
+        if (!mInitialize || !mSupportsDucking) {
+            return;
+        }
+
+        int currentVolume = mController.getPlaybackInfo().getCurrentVolume();
+        int maxVolume = mController.getPlaybackInfo().getMaxVolume();
+
+        int adjustDirection;
+        if (audioDucking == AudioDucking.START) {
+            mAudioDuckCurrentSteps = Math.min(AUDIO_DUCK_MAX_STEPS, currentVolume);
+            adjustDirection = AudioManager.ADJUST_LOWER;
+        } else {
+            mAudioDuckCurrentSteps = Math.min(mAudioDuckCurrentSteps, maxVolume - currentVolume);
+            adjustDirection = AudioManager.ADJUST_RAISE;
+        }
+
+        for (int i = 0; i < mAudioDuckCurrentSteps; i++) {
+            mController.adjustVolume(adjustDirection, 0);
         }
     }
 
     private void getGeckoPreference() {
         mPrefsObserver = new PrefsHelper.PrefHandlerBase() {
             @Override
             public void prefValue(String pref, boolean value) {
                 if (pref.equals(MEDIA_CONTROL_PREF)) {
@@ -242,19 +278,27 @@ public class MediaControlService extends
             }
         };
         PrefsHelper.addObserver(mPrefs, mPrefsObserver);
     }
 
     private void initMediaSession() {
         // Android MediaSession is introduced since version L.
         mSession = new MediaSession(getApplicationContext(),
-                                    "fennec media session");
+                "fennec media session");
         mController = new MediaController(getApplicationContext(),
-                                          mSession.getSessionToken());
+                mSession.getSessionToken());
+
+        int volumeControl = mController.getPlaybackInfo().getVolumeControl();
+        if (volumeControl == VolumeProvider.VOLUME_CONTROL_ABSOLUTE ||
+                volumeControl == VolumeProvider.VOLUME_CONTROL_RELATIVE) {
+            mSupportsDucking = true;
+        } else {
+            Log.w(LOGTAG, "initMediaSession, Session does not support volume absolute or relative volume control");
+        }
 
         mSession.setCallback(new MediaSession.Callback() {
             @Override
             public void onCustomAction(String action, Bundle extras) {
                 if (action.equals(ACTION_PAUSE_BY_AUDIO_FOCUS)) {
                     Log.d(LOGTAG, "Controller, pause by audio focus changed");
                     setState(State.PAUSED);
                 } else if (action.equals(ACTION_RESUME_BY_AUDIO_FOCUS)) {
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/AndroidImport.java
+++ b/mobile/android/base/java/org/mozilla/gecko/preferences/AndroidImport.java
@@ -48,16 +48,17 @@ public class AndroidImport implements Ru
             public static final String TITLE = "title";
             public static final String CREATED = "created";
             public static final String FAVICON = "favicon";
         }
     }
 
     public static final Uri SAMSUNG_BOOKMARKS_URI = Uri.parse("content://com.sec.android.app.sbrowser.browser/bookmarks");
     public static final Uri SAMSUNG_HISTORY_URI = Uri.parse("content://com.sec.android.app.sbrowser.browser/history");
+    public static final String SAMSUNG_BOOKMARK_TYPE = "bookmark_type";
     public static final String SAMSUNG_MANUFACTURER = "samsung";
 
     private static final String LOGTAG = "AndroidImport";
     private final Context mContext;
     private final Runnable mOnDoneRunnable;
     private final ArrayList<ContentProviderOperation> mOperations;
     private final ContentResolver mCr;
     private final LocalBrowserDB mDB;
@@ -74,18 +75,19 @@ public class AndroidImport implements Ru
         mImportBookmarks = doBookmarks;
         mImportHistory = doHistory;
     }
 
     public void mergeBookmarks() {
         Cursor cursor = null;
         try {
             cursor = query(LegacyBrowserProvider.BOOKMARKS_URI,
-                               SAMSUNG_BOOKMARKS_URI,
-                               LegacyBrowserProvider.BookmarkColumns.BOOKMARK + " = 1");
+                           LegacyBrowserProvider.BookmarkColumns.BOOKMARK + " = 1",
+                           SAMSUNG_BOOKMARKS_URI,
+                           SAMSUNG_BOOKMARK_TYPE + " = 1");
 
             if (cursor != null) {
                 final int faviconCol = cursor.getColumnIndexOrThrow(LegacyBrowserProvider.BookmarkColumns.FAVICON);
                 final int titleCol = cursor.getColumnIndexOrThrow(LegacyBrowserProvider.BookmarkColumns.TITLE);
                 final int urlCol = cursor.getColumnIndexOrThrow(LegacyBrowserProvider.BookmarkColumns.URL);
                 // http://code.google.com/p/android/issues/detail?id=17969
                 final int createCol = cursor.getColumnIndex(LegacyBrowserProvider.BookmarkColumns.CREATED);
 
@@ -120,20 +122,21 @@ public class AndroidImport implements Ru
 
         flushBatchOperations();
     }
 
     public void mergeHistory() {
         ArrayList<ContentValues> visitsToSynthesize = new ArrayList<>();
         Cursor cursor = null;
         try {
-            cursor = query (LegacyBrowserProvider.BOOKMARKS_URI,
-                                SAMSUNG_HISTORY_URI,
-                                LegacyBrowserProvider.BookmarkColumns.BOOKMARK + " = 0 AND " +
-                                LegacyBrowserProvider.BookmarkColumns.VISITS + " > 0");
+            cursor = query(LegacyBrowserProvider.BOOKMARKS_URI,
+                           LegacyBrowserProvider.BookmarkColumns.BOOKMARK + " = 0 AND " +
+                                   LegacyBrowserProvider.BookmarkColumns.VISITS + " > 0",
+                           SAMSUNG_HISTORY_URI,
+                           null);
 
             if (cursor != null) {
                 final int dateCol = cursor.getColumnIndexOrThrow(LegacyBrowserProvider.BookmarkColumns.DATE);
                 final int faviconCol = cursor.getColumnIndexOrThrow(LegacyBrowserProvider.BookmarkColumns.FAVICON);
                 final int titleCol = cursor.getColumnIndexOrThrow(LegacyBrowserProvider.BookmarkColumns.TITLE);
                 final int urlCol = cursor.getColumnIndexOrThrow(LegacyBrowserProvider.BookmarkColumns.URL);
                 final int visitsCol = cursor.getColumnIndexOrThrow(LegacyBrowserProvider.BookmarkColumns.VISITS);
 
@@ -186,23 +189,23 @@ public class AndroidImport implements Ru
         }
 
         final DiskStorage storage = DiskStorage.get(mContext);
 
         storage.putIcon(url, bitmap);
         storage.putMapping(url, iconUrl);
     }
 
-    protected Cursor query(Uri mainUri, Uri fallbackUri, String condition) {
-        final Cursor cursor = mCr.query(mainUri, null, condition, null, null);
+    protected Cursor query(Uri mainUri, String mainCondition, Uri fallbackUri, String fallbackCondition) {
+        final Cursor cursor = mCr.query(mainUri, null, mainCondition, null, null);
         if (Build.MANUFACTURER.equals(SAMSUNG_MANUFACTURER) && (cursor == null || cursor.getCount() == 0)) {
             if (cursor != null) {
                 cursor.close();
             }
-            return mCr.query(fallbackUri, null, null, null, null);
+            return mCr.query(fallbackUri, null, fallbackCondition, null, null);
         }
         return cursor;
     }
 
     protected void flushBatchOperations() {
         Log.d(LOGTAG, "Flushing " + mOperations.size() + " DB operations");
         try {
             // We don't really care for the results, this is best-effort.
--- a/mobile/android/chrome/content/ActionBarHandler.js
+++ b/mobile/android/chrome/content/ActionBarHandler.js
@@ -321,17 +321,17 @@ var ActionBarHandler = {
 
   /**
    * Actionbar callback methods.
    */
   actions: {
 
     SELECT_ALL: {
       id: "selectall_action",
-      label: Strings.browser.GetStringFromName("contextmenu.selectAll"),
+      label: () => Strings.browser.GetStringFromName("contextmenu.selectAll"),
       icon: "drawable://ab_select_all",
       order: 5,
       floatingOrder: 5,
 
       selector: {
         matches: function(element, win) {
           // For editable, check its length. For default contentWindow, assume
           // true, else there'd been nothing to long-press to open ActionBar.
@@ -355,17 +355,17 @@ var ActionBarHandler = {
         // Close ActionBarHandler, then selectAll, and display handles.
         ActionBarHandler._getSelectAllController(element, win).selectAll();
         UITelemetry.addEvent("action.1", "actionbar", null, "select_all");
       },
     },
 
     CUT: {
       id: "cut_action",
-      label: Strings.browser.GetStringFromName("contextmenu.cut"),
+      label: () => Strings.browser.GetStringFromName("contextmenu.cut"),
       icon: "drawable://ab_cut",
       order: 4,
       floatingOrder: 1,
 
       selector: {
         matches: function(element, win) {
           // Can cut from editable, or design-mode document.
           if (!element && !ActionBarHandler._isInDesignMode(win)) {
@@ -400,17 +400,17 @@ var ActionBarHandler = {
 
         ActionBarHandler._uninit();
         UITelemetry.addEvent("action.1", "actionbar", null, "cut");
       },
     },
 
     COPY: {
       id: "copy_action",
-      label: Strings.browser.GetStringFromName("contextmenu.copy"),
+      label: () => Strings.browser.GetStringFromName("contextmenu.copy"),
       icon: "drawable://ab_copy",
       order: 3,
       floatingOrder: 2,
 
       selector: {
         matches: function(element, win) {
           // Don't allow "copy" from password fields.
           if (element instanceof Ci.nsIDOMHTMLInputElement &&
@@ -433,17 +433,17 @@ var ActionBarHandler = {
 
         ActionBarHandler._uninit();
         UITelemetry.addEvent("action.1", "actionbar", null, "copy");
       },
     },
 
     PASTE: {
       id: "paste_action",
-      label: Strings.browser.GetStringFromName("contextmenu.paste"),
+      label: () => Strings.browser.GetStringFromName("contextmenu.paste"),
       icon: "drawable://ab_paste",
       order: 2,
       floatingOrder: 3,
 
       selector: {
         matches: function(element, win) {
           // Can paste to editable, or design-mode document.
           if (!element && !ActionBarHandler._isInDesignMode(win)) {
@@ -466,17 +466,17 @@ var ActionBarHandler = {
           paste(Ci.nsIClipboard.kGlobalClipboard);
         ActionBarHandler._uninit();
         UITelemetry.addEvent("action.1", "actionbar", null, "paste");
       },
     },
 
     CALL: {
       id: "call_action",
-      label: Strings.browser.GetStringFromName("contextmenu.call"),
+      label: () => Strings.browser.GetStringFromName("contextmenu.call"),
       icon: "drawable://phone",
       order: 1,
       floatingOrder: 0,
 
       selector: {
         matches: function(element, win) {
           return (ActionBarHandler._getSelectedPhoneNumber() != null);
         },
@@ -523,17 +523,17 @@ var ActionBarHandler = {
         );
 
         UITelemetry.addEvent("action.1", "actionbar", null, "search");
       },
     },
 
     SEARCH_ADD: {
       id: "search_add_action",
-      label: Strings.browser.GetStringFromName("contextmenu.addSearchEngine3"),
+      label: () => Strings.browser.GetStringFromName("contextmenu.addSearchEngine3"),
       icon: "drawable://ab_add_search_engine",
       order: 0,
       floatingOrder: 8,
 
       selector: {
         matches: function(element, win) {
           if(!(element instanceof HTMLInputElement)) {
             return false;
@@ -568,17 +568,17 @@ var ActionBarHandler = {
             ActionBarHandler._sendActionBarActions(true);
           }
         });
       },
     },
 
     SHARE: {
       id: "share_action",
-      label: Strings.browser.GetStringFromName("contextmenu.share"),
+      label: () => Strings.browser.GetStringFromName("contextmenu.share"),
       icon: "drawable://ic_menu_share",
       order: 0,
       floatingOrder: 4,
 
       selector: {
         matches: function(element, win) {
           if (!ParentalControls.isAllowed(ParentalControls.SHARE)) {
             return false;
--- a/moz.configure
+++ b/moz.configure
@@ -259,24 +259,27 @@ tup = check_prog('TUP', tup_progs)
 check_prog('DOXYGEN', ('doxygen',), allow_missing=True)
 check_prog('XARGS', ('xargs',))
 
 @depends(target)
 def extra_programs(target):
     if target.kernel == 'Darwin':
         return namespace(
             DSYMUTIL=('dsymutil', 'llvm-dsymutil'),
-            GENISOIMAGE=('genisoimage',),
+            MKFSHFS=('newfs_hfs', 'mkfs.hfsplus'),
+            HFS_TOOL=('hfsplus',)
         )
     if target.os == 'GNU' and target.kernel == 'Linux':
         return namespace(RPMBUILD=('rpmbuild',))
 
 check_prog('DSYMUTIL', delayed_getattr(extra_programs, 'DSYMUTIL'),
            allow_missing=True)
-check_prog('GENISOIMAGE', delayed_getattr(extra_programs, 'GENISOIMAGE'),
+check_prog('MKFSHFS', delayed_getattr(extra_programs, 'MKFSHFS'),
+           allow_missing=True)
+check_prog('HFS_TOOL', delayed_getattr(extra_programs, 'HFS_TOOL'),
            allow_missing=True)
 check_prog('RPMBUILD', delayed_getattr(extra_programs, 'RPMBUILD'),
            allow_missing=True)
 
 option('--enable-system-hunspell',
        help="Use system hunspell (located with pkgconfig)")
 
 @depends('--enable-system-hunspell', compile_environment)
--- a/mozglue/build/AsanOptions.cpp
+++ b/mozglue/build/AsanOptions.cpp
@@ -22,14 +22,20 @@
 //   while the other doesn't, ASan will report false positives.
 //
 //   detect_leaks=0 - Disable LeakSanitizer. This is required because
 //   otherwise leak checking will be enabled for various building and
 //   testing executables where we don't care much about leaks. Enabling
 //   this will also likely require setting LSAN_OPTIONS with a suppression
 //   file, as in build/sanitizers/lsan_suppressions.txt.
 //
+//   allocator_may_return_null=1 - Tell ASan to return NULL when an allocation
+//   fails instead of aborting the program. This allows us to handle failing
+//   allocations the same way we would handle them with a regular allocator and
+//   also uncovers potential bugs that might occur in these situations.
+//
 extern "C" MOZ_ASAN_BLACKLIST
 const char* __asan_default_options() {
-    return "allow_user_segv_handler=1:alloc_dealloc_mismatch=0:detect_leaks=0";
+    return "allow_user_segv_handler=1:alloc_dealloc_mismatch=0:detect_leaks=0"
+           ":allocator_may_return_null=1";
 }
 
 #endif
--- a/python/mozbuild/mozpack/dmg.py
+++ b/python/mozbuild/mozpack/dmg.py
@@ -1,21 +1,23 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+import buildconfig
 import errno
 import mozfile
 import os
 import platform
 import shutil
 import subprocess
 
 is_linux = platform.system() == 'Linux'
 
+
 def mkdir(dir):
     if not os.path.isdir(dir):
         try:
             os.makedirs(dir)
         except OSError as e:
             if e.errno != errno.EEXIST:
                 raise
 
@@ -29,60 +31,91 @@ def rsync(source, dest):
     'rsync the contents of directory source into directory dest'
     # Ensure a trailing slash so rsync copies the *contents* of source.
     if not source.endswith('/'):
         source += '/'
     subprocess.check_call(['rsync', '-a', '--copy-unsafe-links',
                            source, dest])
 
 
-def set_folder_icon(dir):
+def set_folder_icon(dir, tmpdir):
     'Set HFS attributes of dir to use a custom icon'
     if not is_linux:
-        #TODO: bug 1197325 - figure out how to support this on Linux
         subprocess.check_call(['SetFile', '-a', 'C', dir])
+    else:
+        hfs = os.path.join(tmpdir, 'staged.hfs')
+        subprocess.check_call([
+            buildconfig.substs['HFS_TOOL'],  hfs, 'attr', '/', 'C'])
+
+
+def generate_hfs_file(stagedir, tmpdir, volume_name):
+    '''
+    When cross compiling, we zero fill an hfs file, that we will turn into
+    a DMG. To do so we test the size of the staged dir, and add some slight
+    padding to that.
+    '''
+    if is_linux:
+        hfs = os.path.join(tmpdir, 'staged.hfs')
+        output = subprocess.check_output(['du', '-s', stagedir])
+        size = (int(output.split()[0]) / 1000)  # Get in MB
+        size = int(size * 1.02)  # Bump the used size slightly larger.
+        # Setup a proper file sized out with zero's
+        subprocess.check_call(['dd', 'if=/dev/zero', 'of={}'.format(hfs),
+                               'bs=1M', 'count={}'.format(size)])
+        subprocess.check_call([
+            buildconfig.substs['MKFSHFS'], '-v', volume_name,
+            hfs])
+
+
+def create_app_symlink(stagedir, tmpdir):
+    '''
+    Make a symlink to /Applications. The symlink name is a space
+    so we don't have to localize it. The Applications folder icon
+    will be shown in Finder, which should be clear enough for users.
+    '''
+    if is_linux:
+        hfs = os.path.join(tmpdir, 'staged.hfs')
+        subprocess.check_call([
+            buildconfig.substs['HFS_TOOL'], hfs, 'symlink',
+            '/ ', '/Applications'])
+    else:
+        os.symlink('/Applications', os.path.join(stagedir, ' '))
 
 
 def create_dmg_from_staged(stagedir, output_dmg, tmpdir, volume_name):
     'Given a prepared directory stagedir, produce a DMG at output_dmg.'
     if not is_linux:
         # Running on OS X
         hybrid = os.path.join(tmpdir, 'hybrid.dmg')
         subprocess.check_call(['hdiutil', 'makehybrid', '-hfs',
                                '-hfs-volume-name', volume_name,
                                '-hfs-openfolder', stagedir,
                                '-ov', stagedir,
                                '-o', hybrid])
         subprocess.check_call(['hdiutil', 'convert', '-format', 'UDBZ',
                                '-imagekey', 'bzip2-level=9',
                                '-ov', hybrid, '-o', output_dmg])
     else:
-        import buildconfig
-        uncompressed = os.path.join(tmpdir, 'uncompressed.dmg')
+        hfs = os.path.join(tmpdir, 'staged.hfs')
         subprocess.check_call([
-            buildconfig.substs['GENISOIMAGE'],
-            '-V', volume_name,
-            '-D', '-R', '-apple', '-no-pad',
-            '-o', uncompressed,
-            stagedir
-        ])
+            buildconfig.substs['HFS_TOOL'], hfs, 'addall', stagedir])
         subprocess.check_call([
             buildconfig.substs['DMG_TOOL'],
-            'dmg',
-            uncompressed,
+            'build',
+            hfs,
             output_dmg
         ],
                               # dmg is seriously chatty
                               stdout=open(os.devnull, 'wb'))
 
+
 def check_tools(*tools):
     '''
     Check that each tool named in tools exists in SUBSTS and is executable.
     '''
-    import buildconfig
     for tool in tools:
         path = buildconfig.substs[tool]
         if not path:
             raise Exception('Required tool "%s" not found' % tool)
         if not os.path.isfile(path):
             raise Exception('Required tool "%s" not found at path "%s"' % (tool, path))
         if not os.access(path, os.X_OK):
             raise Exception('Required tool "%s" at path "%s" is not executable' % (tool, path))
@@ -95,27 +128,25 @@ def create_dmg(source_directory, output_
     Use volume_name as the disk image volume name, and
     use extra_files as a list of tuples of (filename, relative path) to copy
     into the disk image.
     '''
     if platform.system() not in ('Darwin', 'Linux'):
         raise Exception("Don't know how to build a DMG on '%s'" % platform.system())
 
     if is_linux:
-        check_tools('DMG_TOOL', 'GENISOIMAGE')
+        check_tools('DMG_TOOL', 'MKFSHFS', 'HFS_TOOL')
     with mozfile.TemporaryDirectory() as tmpdir:
         stagedir = os.path.join(tmpdir, 'stage')
         os.mkdir(stagedir)
         # Copy the app bundle over using rsync
         rsync(source_directory, stagedir)
         # Copy extra files
         for source, target in extra_files:
             full_target = os.path.join(stagedir, target)
             mkdir(os.path.dirname(full_target))
             shutil.copyfile(source, full_target)
-        # Make a symlink to /Applications. The symlink name is a space
-        # so we don't have to localize it. The Applications folder icon
-        # will be shown in Finder, which should be clear enough for users.
-        os.symlink('/Applications', os.path.join(stagedir, ' '))
+        generate_hfs_file(stagedir, tmpdir, volume_name)
+        create_app_symlink(stagedir, tmpdir)
         # Set the folder attributes to use a custom icon
-        set_folder_icon(stagedir)
+        set_folder_icon(stagedir, tmpdir)
         chmod(stagedir)
         create_dmg_from_staged(stagedir, output_dmg, tmpdir, volume_name)
--- a/services/fxaccounts/FxAccountsProfile.jsm
+++ b/services/fxaccounts/FxAccountsProfile.jsm
@@ -94,26 +94,26 @@ this.FxAccountsProfile.prototype = {
       this._currentFetchPromise = null;
     }
     return this.fxa.getProfileCache()
       .then(profileCache => {
         const etag = profileCache ? profileCache.etag : null;
         return this.client.fetchProfile(etag);
       })
       .then(response => {
-        return this._cacheProfile(response);
+        // response may be null if the profile was not modified (same ETag).
+        return response ? this._cacheProfile(response) : null;
       })
       .then(body => { // finally block
         onFinally();
+        // body may be null if the profile was not modified
         return body;
       }, err => {
         onFinally();
-        if (err.code != 304) { // fetchProfile() throws when the profile wasn't modified
-          throw err;
-        }
+        throw err;
       });
   },
 
   _fetchAndCacheProfile() {
     if (!this._currentFetchPromise) {
       this._currentFetchPromise = this._fetchAndCacheProfileInternal();
     }
     return this._currentFetchPromise;
--- a/services/fxaccounts/FxAccountsProfileClient.jsm
+++ b/services/fxaccounts/FxAccountsProfileClient.jsm
@@ -122,17 +122,18 @@ this.FxAccountsProfileClient.prototype =
    *
    * @param {String} path
    *        Profile server path, i.e "/profile".
    * @param {String} method
    *        Type of request, i.e "GET".
    * @param {String} token
    * @param {String} etag
    * @return Promise
-   *         Resolves: {body: Object, etag: Object} Successful response from the Profile server.
+   *         Resolves: {body: Object, etag: Object} Successful response from the Profile server
+                        or null if 304 is hit (same ETag).
    *         Rejects: {FxAccountsProfileClientError} Profile client error.
    * @private
    */
   _rawRequest(path, method, token, etag) {
     return new Promise((resolve, reject) => {
       let profileDataUrl = this.serverURL + path;
       let request = new this._Request(profileDataUrl);
       method = method.toUpperCase();
@@ -149,16 +150,19 @@ this.FxAccountsProfileClient.prototype =
             error: ERROR_NETWORK,
             errno: ERRNO_NETWORK,
             message: error.toString(),
           }));
         }
 
         let body = null;
         try {
+          if (request.response.status == 304) {
+            return resolve(null);
+          }
           body = JSON.parse(request.response.body);
         } catch (e) {
           return reject(new FxAccountsProfileClientError({
             error: ERROR_PARSE,
             errno: ERRNO_PARSE,
             code: request.response.status,
             message: request.response.body,
           }));
--- a/services/fxaccounts/tests/xpcshell/test_profile.js
+++ b/services/fxaccounts/tests/xpcshell/test_profile.js
@@ -286,16 +286,39 @@ add_task(function* fetchAndCacheProfileO
   client.fetchProfile = function() {
     return Promise.resolve({body: { avatar: "myimg"}});
   };
 
   let got = yield profile._fetchAndCacheProfile();
   do_check_eq(got.avatar, "myimg");
 });
 
+add_test(function fetchAndCacheProfile_alreadyCached() {
+  let cachedUrl = "cachedurl";
+  let fxa = mockFxa();
+  fxa.profileCache = { profile: { avatar: cachedUrl }, etag: "bogusETag" };
+  let client = mockClient(fxa);
+  client.fetchProfile = function(etag) {
+    do_check_eq(etag, "bogusETag");
+    return Promise.resolve(null);
+  };
+
+  let profile = CreateFxAccountsProfile(fxa, client);
+  profile._cacheProfile = function(toCache) {
+    do_throw("This method should not be called.");
+  };
+
+  return profile._fetchAndCacheProfile()
+    .then(result => {
+      do_check_eq(result, null);
+      do_check_eq(fxa.profileCache.profile.avatar, cachedUrl);
+      run_next_test();
+    });
+});
+
 // Check that a new profile request within PROFILE_FRESHNESS_THRESHOLD of the
 // last one doesn't kick off a new request to check the cached copy is fresh.
 add_task(function* fetchAndCacheProfileAfterThreshold() {
   let numFetches = 0;
   let client = mockClient(mockFxa());
   client.fetchProfile = function() {
     numFetches += 1;
     return Promise.resolve({ avatar: "myimg"});
--- a/services/fxaccounts/tests/xpcshell/test_profile_client.js
+++ b/services/fxaccounts/tests/xpcshell/test_profile_client.js
@@ -67,17 +67,17 @@ let mockResponseError = function(error) 
   };
 };
 
 add_test(function successfulResponse() {
   let client = new FxAccountsProfileClient(PROFILE_OPTIONS);
   let response = {
     success: true,
     status: STATUS_SUCCESS,
-    headers: { etag:"bogusETag" },
+    headers: { etag: "bogusETag" },
     body: "{\"email\":\"someone@restmail.net\",\"uid\":\"0d5c1a89b8c54580b8e3e8adadae864a\"}",
   };
 
   client._Request = new mockResponse(response);
   client.fetchProfile()
     .then(
       function(result) {
         do_check_eq(client._Request._requestUri, "http://127.0.0.1:1111/v1/profile");
@@ -107,16 +107,34 @@ add_test(function setsIfNoneMatchETagHea
         do_check_eq(result.body.email, "someone@restmail.net");
         do_check_eq(result.body.uid, "0d5c1a89b8c54580b8e3e8adadae864a");
         do_check_true(req.ifNoneMatchSet);
         run_next_test();
       }
     );
 });
 
+add_test(function successful304Response() {
+  let client = new FxAccountsProfileClient(PROFILE_OPTIONS);
+  let response = {
+    success: true,
+    headers: { etag: "bogusETag" },
+    status: 304,
+  };
+
+  client._Request = new mockResponse(response);
+  client.fetchProfile()
+    .then(
+      function(result) {
+        do_check_eq(result, null);
+        run_next_test();
+      }
+    );
+});
+
 add_test(function parseErrorResponse() {
   let client = new FxAccountsProfileClient(PROFILE_OPTIONS);
   let response = {
     success: true,
     status: STATUS_SUCCESS,
     body: "unexpected",
   };
 
new file mode 100644
--- /dev/null
+++ b/servo/.hgignore
@@ -0,0 +1,30 @@
+^\.servo
+^\.cargo
+^\.servobuild
+^target/
+^ports/android/bin
+^ports/android/libs
+^ports/android/local.properties
+^ports/android/obj
+^python/_virtualenv
+^python/tidy/servo_tidy\.egg-info
+~$
+\.pkl$
+\.pyc$
+\.swp$
+\.swo$
+\.csv$
+
+\.DS_Store$
+Servo\.app/
+\.config\.mk\.last$
+/glfw
+
+# Editors
+
+# IntelliJ
+\.idea
+\.iws$
+
+# VSCode
+\.vscode
--- a/servo/.travis.yml
+++ b/servo/.travis.yml
@@ -34,16 +34,13 @@ matrix:
             - freeglut3-dev
             - gperf
             - libosmesa6-dev
             - libgles2-mesa-dev
             - python-virtualenv
             - xorg-dev
             - ccache
             - libdbus-glib-1-dev
-            - libavformat-dev
-            - libavcodec-dev
-            - libavutil-dev
             - libedit-dev
 
 branches:
   only:
     - master
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -91,34 +91,34 @@ source = "registry+https://github.com/ru
 dependencies = [
  "mp3-metadata 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "mp4parse 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "ogg_metadata 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "azure"
-version = "0.10.1"
-source = "git+https://github.com/servo/rust-azure#e8c85e2edf9c586cf0c944e285a1f9076bd0acb7"
+version = "0.11.0"
+source = "git+https://github.com/servo/rust-azure#5cdfed2682af67998ebfa91cbfb6fcfc0778bd82"
 dependencies = [
  "cmake 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
- "core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "core-graphics 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "core-text 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-foundation 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-graphics 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-text 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "freetype 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-egl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-freetype-sys 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "servo-skia 0.20130412.25 (registry+https://github.com/rust-lang/crates.io-index)",
- "x11 2.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "servo-skia 0.30000001.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "x11 2.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "backtrace"
 version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "backtrace-sys 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -147,17 +147,17 @@ dependencies = [
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "bindgen"
-version = "0.20.2"
+version = "0.21.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "aster 0.38.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cexpr 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "clang-sys 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "clap 2.20.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "env_logger 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -278,43 +278,43 @@ source = "registry+https://github.com/ru
 name = "bytes"
 version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "canvas"
 version = "0.0.1"
 dependencies = [
- "azure 0.10.1 (git+https://github.com/servo/rust-azure)",
+ "azure 0.11.0 (git+https://github.com/servo/rust-azure)",
  "canvas_traits 0.0.1",
  "cssparser 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "gleam 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gleam 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
- "offscreen_gl_context 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "offscreen_gl_context 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "plugins 0.0.1",
  "servo_config 0.0.1",
- "webrender_traits 0.11.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.14.0 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "canvas_traits"
 version = "0.0.1"
 dependencies = [
  "cssparser 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "plugins 0.0.1",
  "serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
- "webrender_traits 0.11.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.14.0 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "caseless"
 version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -334,17 +334,17 @@ name = "cfg-if"
 version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "cgl"
 version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "gleam 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gleam 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "clang-sys"
 version = "0.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
@@ -388,22 +388,22 @@ name = "cmake"
 version = "0.1.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "gcc 0.3.41 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "cocoa"
-version = "0.6.0"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "block 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "core-graphics 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-graphics 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "objc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "color_quant"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -425,32 +425,32 @@ dependencies = [
 ]
 
 [[package]]
 name = "compositing"
 version = "0.0.1"
 dependencies = [
  "euclid 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "gfx_traits 0.0.1",
- "gleam 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gleam 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "image 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
  "plugins 0.0.1",
  "profile_traits 0.0.1",
  "script_traits 0.0.1",
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
  "servo_url 0.0.1",
  "style_traits 0.0.1",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
- "webrender 0.12.0 (git+https://github.com/servo/webrender)",
- "webrender_traits 0.11.0 (git+https://github.com/servo/webrender)",
+ "webrender 0.15.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.14.0 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "constellation"
 version = "0.0.1"
 dependencies = [
  "backtrace 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bluetooth_traits 0.0.1",
@@ -463,28 +463,28 @@ dependencies = [
  "gaol 0.0.1 (git+https://github.com/servo/gaol)",
  "gfx 0.0.1",
  "gfx_traits 0.0.1",
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "layout_traits 0.0.1",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
- "offscreen_gl_context 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "offscreen_gl_context 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "plugins 0.0.1",
  "profile_traits 0.0.1",
  "script_traits 0.0.1",
  "serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
  "servo_rand 0.0.1",
  "servo_remutex 0.0.1",
  "servo_url 0.0.1",
  "style_traits 0.0.1",
- "webrender_traits 0.11.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.14.0 (git+https://github.com/servo/webrender)",
  "webvr_traits 0.0.1",
 ]
 
 [[package]]
 name = "content-blocker"
 version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
@@ -501,57 +501,52 @@ dependencies = [
  "openssl 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "core-foundation"
-version = "0.2.3"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-foundation-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "core-foundation-sys"
-version = "0.2.3"
+version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "core-graphics"
-version = "0.5.0"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-foundation 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "core-text"
-version = "2.0.1"
+version = "3.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "core-graphics 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-foundation 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-graphics 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "crossbeam"
-version = "0.2.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
 name = "cssparser"
 version = "0.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -651,24 +646,16 @@ dependencies = [
  "msg 0.0.1",
  "serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_url 0.0.1",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "dlib"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "libloading 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
 name = "dtoa"
 version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "dwmapi-sys"
 version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -690,36 +677,36 @@ dependencies = [
  "serde_codegen 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "embedding"
 version = "0.0.1"
 dependencies = [
- "cocoa 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cocoa 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "compositing 0.0.1",
  "devtools 0.0.1",
  "euclid 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "gleam 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gleam 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "glutin_app 0.0.1",
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "libservo 0.0.1",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
  "objc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "plugins 0.0.1",
  "script_traits 0.0.1",
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
  "servo_url 0.0.1",
  "style_traits 0.0.1",
- "webrender_traits 0.11.0 (git+https://github.com/servo/webrender)",
- "x11 2.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "webrender_traits 0.14.0 (git+https://github.com/servo/webrender)",
+ "x11 2.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "encoding"
 version = "0.2.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -829,16 +816,21 @@ name = "env_logger"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
+name = "error-chain"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
 name = "euclid"
 version = "0.10.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -883,26 +875,16 @@ name = "freetype"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-freetype-sys 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "fs2"
-version = "0.2.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
 name = "futf"
 version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "debug_unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "mac 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -947,17 +929,17 @@ dependencies = [
  "cssparser 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "env_logger 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "num_cpus 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "selectors 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "selectors 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_url 0.0.1",
  "style 0.0.1",
  "style_traits 0.0.1",
  "stylo_tests 0.0.1",
 ]
 
 [[package]]
 name = "getopts"
@@ -966,19 +948,19 @@ source = "registry+https://github.com/ru
 
 [[package]]
 name = "gfx"
 version = "0.0.1"
 dependencies = [
  "app_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "core-graphics 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "core-text 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-foundation 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-graphics 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-text 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "fontsan 0.3.2 (git+https://github.com/servo/fontsan)",
  "freetype 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gfx_traits 0.0.1",
  "harfbuzz-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -993,25 +975,25 @@ dependencies = [
  "plugins 0.0.1",
  "range 0.0.1",
  "serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-fontconfig 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_atoms 0.0.1",
  "servo_geometry 0.0.1",
  "servo_url 0.0.1",
- "simd 0.1.1 (git+https://github.com/huonw/simd)",
+ "simd 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "style 0.0.1",
  "style_traits 0.0.1",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "truetype 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-script 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "webrender_traits 0.11.0 (git+https://github.com/servo/webrender)",
- "xi-unicode 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "webrender_traits 0.14.0 (git+https://github.com/servo/webrender)",
+ "xi-unicode 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "gfx_tests"
 version = "0.0.1"
 dependencies = [
  "gfx 0.0.1",
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1046,17 +1028,17 @@ source = "registry+https://github.com/ru
 dependencies = [
  "khronos_api 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "xml-rs 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "gleam"
-version = "0.2.30"
+version = "0.2.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "gl_generator 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "glob"
@@ -1066,33 +1048,33 @@ source = "registry+https://github.com/ru
 [[package]]
 name = "glutin_app"
 version = "0.0.1"
 dependencies = [
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "compositing 0.0.1",
  "euclid 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gleam 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gleam 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
  "osmesa-src 12.0.1 (git+https://github.com/servo/osmesa-src)",
  "osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "script_traits 0.0.1",
  "servo-egl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "servo-glutin 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "servo-glutin 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
  "servo_url 0.0.1",
  "style_traits 0.0.1",
  "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "webrender_traits 0.11.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.14.0 (git+https://github.com/servo/webrender)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "x11 2.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "x11 2.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "glx"
 version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "gl_generator 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1261,22 +1243,22 @@ dependencies = [
 
 [[package]]
 name = "inflate"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "io-surface"
-version = "0.5.0"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cgl 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "gleam 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-foundation 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gleam 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "leaky-cow 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "ipc-channel"
 version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1368,29 +1350,29 @@ dependencies = [
  "ordered-float 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "plugins 0.0.1",
  "profile_traits 0.0.1",
  "range 0.0.1",
  "rayon 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "script_layout_interface 0.0.1",
  "script_traits 0.0.1",
- "selectors 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "selectors 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
  "servo_url 0.0.1",
  "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "style 0.0.1",
  "style_traits 0.0.1",
  "unicode-bidi 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-script 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "webrender_traits 0.11.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.14.0 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "layout_tests"
 version = "0.0.1"
 dependencies = [
  "layout 0.0.1",
 ]
@@ -1414,47 +1396,42 @@ dependencies = [
  "net_traits 0.0.1",
  "parking_lot 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "plugins 0.0.1",
  "profile_traits 0.0.1",
  "rayon 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "script 0.0.1",
  "script_layout_interface 0.0.1",
  "script_traits 0.0.1",
- "selectors 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "selectors 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
  "servo_url 0.0.1",
  "style 0.0.1",
- "webrender_traits 0.11.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.14.0 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "layout_traits"
 version = "0.0.1"
 dependencies = [
  "gfx 0.0.1",
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
  "profile_traits 0.0.1",
  "script_traits 0.0.1",
  "servo_url 0.0.1",
- "webrender_traits 0.11.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.14.0 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "lazy_static"
-version = "0.1.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "lazy_static"
 version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "leak"
 version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
@@ -1504,17 +1481,17 @@ dependencies = [
  "constellation 0.0.1",
  "debugger 0.0.1",
  "devtools 0.0.1",
  "devtools_traits 0.0.1",
  "env_logger 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "gaol 0.0.1 (git+https://github.com/servo/gaol)",
  "gfx 0.0.1",
- "gleam 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gleam 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "layout 0.0.1",
  "layout_thread 0.0.1",
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net 0.0.1",
  "net_traits 0.0.1",
@@ -1525,18 +1502,18 @@ dependencies = [
  "script_layout_interface 0.0.1",
  "script_traits 0.0.1",
  "servo_config 0.0.1",
  "servo_url 0.0.1",
  "sig 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "style 0.0.1",
  "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "webdriver_server 0.0.1",
- "webrender 0.12.0 (git+https://github.com/servo/webrender)",
- "webrender_traits 0.11.0 (git+https://github.com/servo/webrender)",
+ "webrender 0.15.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.14.0 (git+https://github.com/servo/webrender)",
  "webvr 0.0.1",
  "webvr_traits 0.0.1",
 ]
 
 [[package]]
 name = "libz-sys"
 version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1578,24 +1555,23 @@ source = "registry+https://github.com/ru
 name = "memchr"
 version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "memmap"
-version = "0.2.3"
+name = "metadeps"
+version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "fs2 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "error-chain 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "toml 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "mime"
 version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1677,17 +1653,17 @@ version = "0.0.1"
 dependencies = [
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "plugins 0.0.1",
  "serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
- "webrender_traits 0.11.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.14.0 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "net"
 version = "0.0.1"
 dependencies = [
  "brotli 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "content-blocker 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1713,17 +1689,17 @@ dependencies = [
  "servo_config 0.0.1",
  "servo_url 0.0.1",
  "threadpool 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "tinyfiledialogs 2.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "uuid 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "webrender_traits 0.11.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.14.0 (git+https://github.com/servo/webrender)",
  "websocket 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "net2"
 version = "0.2.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
@@ -1773,17 +1749,17 @@ dependencies = [
  "msg 0.0.1",
  "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
  "servo_url 0.0.1",
  "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "uuid 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "webrender_traits 0.11.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.14.0 (git+https://github.com/servo/webrender)",
  "websocket 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "net_traits_tests"
 version = "0.0.1"
 dependencies = [
  "net_traits 0.0.1",
@@ -1864,33 +1840,33 @@ dependencies = [
 
 [[package]]
 name = "odds"
 version = "0.2.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "offscreen_gl_context"
-version = "0.5.1"
+version = "0.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cgl 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-foundation 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gl_generator 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "gleam 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gleam 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "x11 2.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "x11 2.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "ogg"
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2268,16 +2244,17 @@ name = "script"
 version = "0.0.1"
 dependencies = [
  "angle 0.1.2 (git+https://github.com/servo/angle?branch=servo)",
  "app_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "audio-video-metadata 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bluetooth_traits 0.0.1",
+ "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "canvas_traits 0.0.1",
  "caseless 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "cmake 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "cookie 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "devtools_traits 0.0.1",
  "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2295,47 +2272,47 @@ dependencies = [
  "jstraceable_derive 0.0.1",
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "mime 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "mime_guess 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
  "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
- "offscreen_gl_context 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "offscreen_gl_context 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "open 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "plugins 0.0.1",
  "profile_traits 0.0.1",
  "range 0.0.1",
  "ref_filter_map 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "ref_slice 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "script_layout_interface 0.0.1",
  "script_traits 0.0.1",
- "selectors 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "selectors 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_atoms 0.0.1",
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
  "servo_rand 0.0.1",
  "servo_url 0.0.1",
  "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "style 0.0.1",
  "style_traits 0.0.1",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "tinyfiledialogs 2.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "uuid 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "webrender_traits 0.11.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.14.0 (git+https://github.com/servo/webrender)",
  "websocket 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "webvr 0.0.1",
  "webvr_traits 0.0.1",
  "xml5ever 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "script_layout_interface"
@@ -2354,17 +2331,17 @@ dependencies = [
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
  "plugins 0.0.1",
  "profile_traits 0.0.1",
  "range 0.0.1",
  "script_traits 0.0.1",
- "selectors 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "selectors 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_url 0.0.1",
  "style 0.0.1",
 ]
 
 [[package]]
 name = "script_tests"
 version = "0.0.1"
 dependencies = [
@@ -2389,32 +2366,32 @@ dependencies = [
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)",
  "hyper_serde 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
- "offscreen_gl_context 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "offscreen_gl_context 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "plugins 0.0.1",
  "profile_traits 0.0.1",
  "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_url 0.0.1",
  "style_traits 0.0.1",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "webvr_traits 0.0.1",
 ]
 
 [[package]]
 name = "selectors"
-version = "0.15.1"
+version = "0.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -2534,61 +2511,58 @@ version = "4.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cmake 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "servo-glutin"
-version = "0.7.0"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "android_glue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "cgl 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "cocoa 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "core-graphics 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cocoa 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-foundation 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-graphics 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwmapi-sys 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gl_generator 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "image 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "objc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "shared_library 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "shell32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "wayland-client 0.5.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "wayland-kbd 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "wayland-window 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "x11-dl 2.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "x11-dl 2.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "servo-skia"
-version = "0.20130412.25"
+version = "0.30000001.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cgl 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "cmake 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "expat-sys 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "gleam 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gleam 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "glx 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "io-surface 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "io-surface 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-egl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-fontconfig-sys 4.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-freetype-sys 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "servo-glutin 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "x11 2.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "servo-glutin 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "x11 2.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "servo_atoms"
 version = "0.0.1"
 dependencies = [
  "string_cache 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "string_cache_codegen 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2695,18 +2669,18 @@ source = "registry+https://github.com/ru
 
 [[package]]
 name = "signpost"
 version = "0.1.0"
 source = "git+https://github.com/pcwalton/signpost.git#7ed712507f343c38646b9d1fefd049166f9c9a18"
 
 [[package]]
 name = "simd"
-version = "0.1.1"
-source = "git+https://github.com/huonw/simd#0d85d25d5cc3788062b252e31ad48dd19e805e90"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "siphasher"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "slab"
@@ -2761,17 +2735,17 @@ version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "style"
 version = "0.0.1"
 dependencies = [
  "app_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "bindgen 0.20.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bindgen 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2788,17 +2762,17 @@ dependencies = [
  "owning_ref 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "pdqsort 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "rayon 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
- "selectors 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "selectors 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_atoms 0.0.1",
  "servo_config 0.0.1",
  "servo_url 0.0.1",
  "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "style_traits 0.0.1",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2814,17 +2788,17 @@ dependencies = [
  "cssparser 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "html5ever-atoms 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "owning_ref 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "rayon 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
- "selectors 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "selectors 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_atoms 0.0.1",
  "servo_config 0.0.1",
  "servo_url 0.0.1",
  "style 0.0.1",
  "style_traits 0.0.1",
 ]
 
 [[package]]
@@ -2851,17 +2825,17 @@ dependencies = [
  "env_logger 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "geckoservo 0.0.1",
  "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "num_cpus 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "selectors 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "selectors 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_url 0.0.1",
  "style 0.0.1",
  "style_traits 0.0.1",
 ]
 
 [[package]]
 name = "syn"
 version = "0.10.7"
@@ -2946,28 +2920,16 @@ dependencies = [
 name = "tempdir"
 version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "tempfile"
-version = "2.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
- "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
 name = "tendril"
 version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
  "futf 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "mac 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "utf-8 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3171,68 +3133,16 @@ version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "wayland-client"
-version = "0.5.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "dlib 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
- "wayland-scanner 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "wayland-sys 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "wayland-kbd"
-version = "0.3.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "dlib 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
- "memmap 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "wayland-client 0.5.12 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "wayland-scanner"
-version = "0.5.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "xml-rs 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "wayland-sys"
-version = "0.5.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "dlib 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "wayland-window"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "tempfile 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "wayland-client 0.5.12 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
 name = "webdriver"
 version = "0.20.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "backtrace 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cookie 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3261,55 +3171,55 @@ dependencies = [
  "servo_url 0.0.1",
  "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "uuid 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "webdriver 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "webrender"
-version = "0.12.0"
-source = "git+https://github.com/servo/webrender#c8ca454555935c78f9b271dbfcf6fc6171edc9c9"
+version = "0.15.0"
+source = "git+https://github.com/servo/webrender#512b4a1433a7827ef7cfd121d2f99fdd91ecf80e"
 dependencies = [
  "app_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "core-graphics 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "core-text 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-graphics 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-text 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "freetype 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gamma-lut 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "gleam 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gleam 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
- "offscreen_gl_context 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "offscreen_gl_context 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "threadpool 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
- "webrender_traits 0.11.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.14.0 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "webrender_traits"
-version = "0.11.0"
-source = "git+https://github.com/servo/webrender#c8ca454555935c78f9b271dbfcf6fc6171edc9c9"
+version = "0.14.0"
+source = "git+https://github.com/servo/webrender#512b4a1433a7827ef7cfd121d2f99fdd91ecf80e"
 dependencies = [
  "app_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "core-graphics 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-graphics 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "gleam 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gleam 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "offscreen_gl_context 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "offscreen_gl_context 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "websocket"
 version = "0.17.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3329,17 +3239,17 @@ dependencies = [
 name = "webvr"
 version = "0.0.1"
 dependencies = [
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "script_traits 0.0.1",
  "servo_config 0.0.1",
- "webrender_traits 0.11.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.14.0 (git+https://github.com/servo/webrender)",
  "webvr_traits 0.0.1",
 ]
 
 [[package]]
 name = "winapi"
 version = "0.2.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
@@ -3367,41 +3277,41 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "x11"
-version = "2.11.0"
+version = "2.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
- "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "metadeps 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "x11-dl"
-version = "2.11.0"
+version = "2.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "xdg"
 version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "xi-unicode"
-version = "0.0.1"
+version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "xml-rs"
 version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3430,21 +3340,21 @@ dependencies = [
 "checksum android_injected_glue 0.2.1 (git+https://github.com/mmatyas/android-rs-injected-glue)" = "<none>"
 "checksum angle 0.1.2 (git+https://github.com/servo/angle?branch=servo)" = "<none>"
 "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
 "checksum app_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "636ee56f12e31dbc11dc0a1ac6004f08b04e6e6595963716fc8130e90d4e04cf"
 "checksum arrayvec 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d89f1b0e242270b5b797778af0c8d182a1a2ccac5d8d6fadf414223cc0fab096"
 "checksum aster 0.38.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2c9b49e42a449c0b79d8acb91db37621de0978064dca7d3288ddcf030123e5b3"
 "checksum atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb2dcb6e6d35f20276943cc04bb98e538b348d525a04ac79c10021561d202f21"
 "checksum audio-video-metadata 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "03da2550cb89fe3faf218c179261c26cf7891c4234707c15f5d09ebb32ae2400"
-"checksum azure 0.10.1 (git+https://github.com/servo/rust-azure)" = "<none>"
+"checksum azure 0.11.0 (git+https://github.com/servo/rust-azure)" = "<none>"
 "checksum backtrace 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f551bc2ddd53aea015d453ef0b635af89444afa5ed2405dd0b2062ad5d600d80"
 "checksum backtrace-sys 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3602e8d8c43336088a8505fa55cae2b3884a9be29440863a11528a42f46f6bb7"
 "checksum bincode 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "55eb0b7fd108527b0c77860f75eca70214e11a8b4c6ef05148c54c05a25d48ad"
-"checksum bindgen 0.20.2 (registry+https://github.com/rust-lang/crates.io-index)" = "712c11d1fc3d402340a8b3c1548b3a7b62774f128ea9abf9e8dca3d08a32bf76"
+"checksum bindgen 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cba697ecbf51e4a1d1d84c6dee5339d22a5f9f5e04694e53e873ea26257a73f2"
 "checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c"
 "checksum bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5b97c2c8e8bbb4251754f559df8af22fb264853c7d009084a576cdf12565089d"
 "checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3"
 "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
 "checksum block 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
 "checksum blurdroid 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4a86fbb3818e7f850410e026bfac7742fe86cbf4acf49f5752936b32d1f7eb8"
 "checksum blurmock 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "68dd72da3a3bb40f3d3bdd366c4cf8e2b1d208c366304f382c80cef8126ca8da"
 "checksum blurz 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4d49796c8d5a1b5f6b2b8686e46ed4ab842987c477f765b69f1d3e8df6072608"
@@ -3456,34 +3366,32 @@ dependencies = [
 "checksum caseless 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7943e9a31ba2742c288cb755e66a2af03b708e3878c921d56204263270314f8a"
 "checksum cexpr 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "393a5f0088efbe41f9d1fcd062f24e83c278608420e62109feb2c8abee07de7d"
 "checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c"
 "checksum cgl 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8bdd78cca65a739cb5475dbf6b6bbb49373e327f4a6f2b499c0f98632df38c10"
 "checksum clang-sys 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "822ea22bbbef9f5934e9477860545fb0311a1759e43a276de42e2856c605aa2b"
 "checksum clap 2.20.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73ec15755425b87a9766c3ef47000c01aae155266f9a4c2f1ffc962ee612863e"
 "checksum clippy_lints 0.0.112 (registry+https://github.com/rust-lang/crates.io-index)" = "51461bf5f0862158b3239e55af263d5fe67620ccbb824f87c9ed0f7cd1ce1184"
 "checksum cmake 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "a3a6805df695087e7c1bcd9a82e03ad6fb864c8e67ac41b1348229ce5b7f0407"
-"checksum cocoa 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2b7c71e7da239e6c2a5916b42dba3ed087b46ca613de687fa17560ee84b1adf5"
+"checksum cocoa 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8afbd6c6e37c1339655701a2a17a86188916a775d1e89fb329737600a1747f98"
 "checksum color_quant 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a475fc4af42d83d28adf72968d9bcfaf035a1a9381642d8e85d8a04957767b0d"
 "checksum compiletest_rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f3f344389765ad7bec166f64c1b39ed6dd2b54d81c4c5dd8af789169351d380c"
 "checksum content-blocker 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5893d533bce3cea1f59dd239ae577d4f88e4eccb4bed3b1d9cd51a33b187f7f6"
 "checksum cookie 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0e3d6405328b6edb412158b3b7710e2634e23f3614b9bb1c412df7952489a626"
-"checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67"
-"checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d"
-"checksum core-graphics 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8bd94d0f0b2bbcbfeeb670fc48654afde7a13c2c551ca9d2b9a6cfafdafe1a64"
-"checksum core-text 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "642fa40165b6c53bbd3f636951ffc3e1a3fd3c47e7d00598523c3e8c9321ed0c"
-"checksum crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0c5ea215664ca264da8a9d9c3be80d2eaf30923c259d03e870388eb927508f97"
+"checksum core-foundation 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f51ce3b8ebe311c56de14231eb57572c15abebd2d32b3bcb99bcdb9c101f5ac3"
+"checksum core-foundation-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "41115a6aa5d3e1e5ef98148373f25971d1fad53818553f216495f9e67e90a624"
+"checksum core-graphics 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b205856aba54bfd36e69a1058f45fbe0d3c37be7375309dcff4a22a2a631fea"
+"checksum core-text 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9703f459a41e622b15ca612dbc5fa4b30b6545a32864a83e0fdc538cfa08969c"
 "checksum cssparser 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bd03c0b039e63f6756e905f902f881cb547a5393229e6bfa9b0b69646cc1d83f"
 "checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850"
 "checksum dbus 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "94d266a872aaf68b50d02083c429a3686935ab6ab54824290509cdc422673eaf"
 "checksum debug_unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9a032eac705ca39214d169f83e3d3da290af06d8d1d344d1baad2fd002dca4b3"
 "checksum deflate 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "24c5f3de3a8e183ab9a169654b652407e5e80bed40986bcca92c2b088b9bfa80"
 "checksum deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1614659040e711785ed8ea24219140654da1729f3ec8a47a9719d041112fe7bf"
 "checksum device 0.0.1 (git+https://github.com/servo/devices)" = "<none>"
-"checksum dlib 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "148bce4ce1c36c4509f29cb54e62c2bd265551a9b00b38070fad551a851866ec"
 "checksum dtoa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0dd841b58510c9618291ffa448da2e4e0f699d984d436122372f446dae62263d"
 "checksum dwmapi-sys 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07c4c7cc7b396419bc0a4d90371d0cee16cb5053b53647d287c0b728000c41fe"
 "checksum dwrote 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "5998238340a4625b5e1cf52341bd330c5ad91a39a41527ed8af20f95a258a96c"
 "checksum encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec"
 "checksum encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91"
 "checksum encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81"
 "checksum encoding-index-simpchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7"
 "checksum encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a"
@@ -3491,32 +3399,32 @@ dependencies = [
 "checksum encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
 "checksum energy-monitor 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fe872d0664f1cc60db36349af245d892ee67d3c8f78055df0ebc43271fd4e05c"
 "checksum energymon 0.3.0 (git+https://github.com/energymon/energymon-rust.git)" = "<none>"
 "checksum energymon-builder 0.3.0 (git+https://github.com/energymon/energymon-sys.git)" = "<none>"
 "checksum energymon-default-sys 0.3.0 (git+https://github.com/energymon/energymon-sys.git)" = "<none>"
 "checksum energymon-sys 0.3.0 (git+https://github.com/energymon/energymon-sys.git)" = "<none>"
 "checksum enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180"
 "checksum env_logger 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "99971fb1b635fe7a0ee3c4d065845bb93cca80a23b5613b5613391ece5de4144"
+"checksum error-chain 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "318cb3c71ee4cdea69fdc9e15c173b245ed6063e1709029e8fd32525a881120f"
 "checksum euclid 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f93a556290e09f379cbfaa4f75ac52a72a3d2deb7d04076f312cdb2e6acba28e"
 "checksum expat-sys 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cef36cd1a8a02d28b91d97347c63247b9e4cb8a8e36df36f8201dc87a1c0859c"
 "checksum flate2 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "d4e4d0c15ef829cbc1b7cda651746be19cceeb238be7b1049227b14891df9e25"
 "checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344"
 "checksum fontsan 0.3.2 (git+https://github.com/servo/fontsan)" = "<none>"
 "checksum freetype 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fde23272c687e4570aefec06cb71174ec0f5284b725deac4e77ba2665d635faf"
-"checksum fs2 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bcd414e5a1a979b931bb92f41b7a54106d3f6d2e6c253e9ce943b7cd468251ef"
 "checksum futf 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7a9689380a2553b51c564b3d9178075c68ebd0b397972c783acfd28b46c28ad"
 "checksum gamma-lut 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca6b9023d24a7faff85abfa82fabd382f7ecbbde95af5143f1c215fd94e91cd"
 "checksum gaol 0.0.1 (git+https://github.com/servo/gaol)" = "<none>"
 "checksum gcc 0.3.41 (registry+https://github.com/rust-lang/crates.io-index)" = "3689e1982a563af74960ae3a4758aa632bb8fd984cfc3cc3b60ee6109477ab6e"
 "checksum gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0912515a8ff24ba900422ecda800b52f4016a56251922d397c576bf92c690518"
 "checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685"
 "checksum gif 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "01c7c19a035de94bd7afbaa62c241aadfbdf1a70f560b348d2312eafa566ca16"
 "checksum gl_generator 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1d8edc81c5ae84605a62f5dac661a2313003b26d59839f81d47d46cf0f16a55"
-"checksum gleam 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)" = "6af023107aa969ccf8868a0304fead4b2f813c19aa9a6a243fddc041f3e51da5"
+"checksum gleam 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)" = "9590e0e578d528a080c5abac678e7efbe349a73c7316faafd4073edf5f462d01"
 "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
 "checksum glx 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b280007fa9c7442cfd1e0b1addb8d1a59240267110e8705f8f7e2c7bfb7e2f72"
 "checksum harfbuzz-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6b76113246f5c089dcf272cf89c3f61168a4d77b50ec5b2c1fab8c628c9ea762"
 "checksum heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "5a376f7402b85be6e0ba504243ecbc0709c48019ecc6286d0540c2e359050c88"
 "checksum heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b6876925b6c3de6f9073f016f425de0076ab68cf30522107fa586ae6524abfe"
 "checksum heartbeats-simple 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "78c0810722eacd0bdd3f1f691524bd9900bf8fed1947f6b883c10ddecd2560b1"
 "checksum heartbeats-simple-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53c4b67617665d7f4172f381f9843c1bec6a4fccc9a9226529e5b1be40dc1301"
 "checksum hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2da7d3a34cf6406d9d700111b8eafafe9a251de41ae71d8052748259343b58"
@@ -3524,39 +3432,38 @@ dependencies = [
 "checksum html5ever-atoms 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4109e35fec157307b918eb9d5b7018e2fa771aea0c04831e22003ac4722fbd1b"
 "checksum httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a6e7a63e511f9edffbab707141fbb8707d1a3098615fb2adbd5769cdfcc9b17d"
 "checksum hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)" = "bcb3fc65554155980167fb821d05c7c66177f92464976c0b676a19d9e03387a7"
 "checksum hyper_serde 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "572d2168173019de312a050a24f2ad33ac2ac7895a2139fbf21ee6b6f470a24e"
 "checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11"
 "checksum image 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6fb38e372d9288be8fa60bea6f05342e4a35244c221cd9fe4c01ded02c86e60c"
 "checksum immeta 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3e76ecb1d64979a91c7fc5b7c0495ef1467e3cbff759044f2b88878a5a845ef7"
 "checksum inflate 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e7e0062d2dc2f17d2f13750d95316ae8a2ff909af0fda957084f5defd87c43bb"
-"checksum io-surface 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c93eb4952ee5b903c4193391779f90209e1b75ba55911097fa494f35e975846"
+"checksum io-surface 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "10d25285115b9d34be1328fdc5af15d34174472a9f23d1994d2d14a7ec8c537a"
 "checksum ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "675587430ede6756dd03fdfdf9888f22f83855fd131c8451d842a710b059e571"
 "checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1"
 "checksum jpeg-decoder 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "5c4ff3d14e7ef3522471ab712832c3dd50001f7fb7aa4cdc48af811d63b531e9"
 "checksum js 0.1.4 (git+https://github.com/servo/rust-mozjs)" = "<none>"
 "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
 "checksum khronos_api 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "09c9d3760673c427d46f91a0350f0a84a52e6bc5a84adf26dc610b6c52436630"
 "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
-"checksum lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "cf186d1a8aa5f5bee5fd662bc9c1b949e0259e1bcc379d1f006847b0080c7417"
 "checksum lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6abe0ee2e758cd6bc8a2cd56726359007748fbf4128da998b65d0b70f881e19b"
 "checksum leak 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bd100e01f1154f2908dfa7d02219aeab25d0b9c7fa955164192e3245255a0c73"
 "checksum leaky-cow 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40a8225d44241fd324a8af2806ba635fc7c8a7e9a7de4d5cf3ef54e71f5926fc"
 "checksum libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)" = "684f330624d8c3784fb9558ca46c4ce488073a8d22450415c5eb4f4cfb0d11b5"
 "checksum libloading 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "84816a8c6ed8163dfe0dbdd2b09d35c6723270ea77a4c7afa4bedf038a36cb99"
 "checksum libressl-pnacl-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "cbc058951ab6a3ef35ca16462d7642c4867e6403520811f28537a4e2f2db3e71"
 "checksum libz-sys 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "905c72a0c260bcd89ddca5afa1c46bebd29b52878a3d58c86865ea42402f88e6"
 "checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054"
 "checksum lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084"
 "checksum mac 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1b1db08c0d0ddbb591e65f1da58d1cefccc94a2faa0c55bf979ce215a3e04d5e"
 "checksum malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
 "checksum matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efd7622e3022e1a6eaa602c4cea8912254e5582c9c692e9167714182244801b1"
 "checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4"
-"checksum memmap 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f20f72ed93291a72e22e8b16bb18762183bb4943f0f483da5b8be1a9e8192752"
+"checksum metadeps 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829fffe7ea1d747e23f64be972991bc516b2f1ac2ae4a3b33d8bea150c410151"
 "checksum mime 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5c93a4bd787ddc6e7833c519b73a50883deb5863d76d9b71eb8216fb7f94e66"
 "checksum mime_guess 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76da6df85047af8c0edfa53f48eb1073012ce1cc95c8fedc0a374f659a89dd65"
 "checksum miniz-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d1f4d337a01c32e1f2122510fed46393d53ca35a7f429cb0450abaedfa3ed54"
 "checksum mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a637d1ca14eacae06296a008fa7ad955347e34efcb5891cfd8ba05491a37907e"
 "checksum miow 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3e690c5df6b2f60acd45d56378981e827ff8295562fc8d34f573deb267a59cd1"
 "checksum mozjs_sys 0.0.0 (git+https://github.com/servo/mozjs)" = "<none>"
 "checksum mp3-metadata 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2f61cf32f7fc3cec83a15a255ac60bceb6cac59a7ce190cb824ca25c0fce0feb"
 "checksum mp4parse 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1e1736d06703a9cb5228b5a8151acc79bf5ba7669a810243852bcad4d3a25504"
@@ -3566,17 +3473,17 @@ dependencies = [
 "checksum nom 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce"
 "checksum num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "fb24d9bfb3f222010df27995441ded1e954f8f69cd35021f6bef02ca9552fb92"
 "checksum num-iter 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "287a1c9969a847055e1122ec0ea7a5c5d6f72aad97934e131c83d5c08ab4e45c"
 "checksum num-rational 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "54ff603b8334a72fbb27fe66948aac0abaaa40231b3cecd189e76162f6f38aaf"
 "checksum num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "a16a42856a256b39c6d3484f097f6713e14feacd9bfb02290917904fae46c81c"
 "checksum num_cpus 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a225d1e2717567599c24f88e49f00856c6e825a12125181ee42c4257e3688d39"
 "checksum objc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "877f30f37acef6749b1841cceab289707f211aecfc756553cd63976190e6cc2e"
 "checksum odds 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "c3df9b730298cea3a1c3faa90b7e2f9df3a9c400d0936d6015e6165734eefcba"
-"checksum offscreen_gl_context 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2fe2fe54ba2b6ea8f43a17b16c13168c5bbf008e0fc91b34122a11f637e2728a"
+"checksum offscreen_gl_context 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "68c15a74828e622dbeeea77798944806f2a42dbaa613df048da37eccc55d21d9"
 "checksum ogg 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "426d8dc59cdd206be1925461087350385c0a02f291d87625829c6d08e72b457b"
 "checksum ogg_metadata 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e755cc735fa6faa709cb23048433d9201d6caa85fa96215386ccdd5e9b40ad01"
 "checksum open 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3478ed1686bd1300c8a981a940abc92b06fac9cbef747f4c668d4e032ff7b842"
 "checksum openssl 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "c4117b6244aac42ed0150a6019b4d953d28247c5dd6ae6f46ae469b5f2318733"
 "checksum openssl-sys 0.7.17 (registry+https://github.com/rust-lang/crates.io-index)" = "89c47ee94c352eea9ddaf8e364be7f978a3bb6d66d73176572484238dd5a5c3f"
 "checksum openssl-sys-extras 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "11c5e1dba7d3d03d80f045bf0d60111dc69213b67651e7c889527a3badabb9fa"
 "checksum openssl-verify 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ed86cce894f6b0ed4572e21eb34026f1dc8869cb9ee3869029131bc8c3feb2d"
 "checksum ordered-float 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cc511538298611a79d5a4ddfbb75315b866d942ed26a00bdc3590795c68b7279"
@@ -3605,53 +3512,52 @@ dependencies = [
 "checksum regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4278c17d0f6d62dfef0ab00028feb45bd7d2102843f80763474eeb1be8a10c01"
 "checksum regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9191b1f57603095f105d317e375d19b1c9c5c3185ea9633a99a6dcbed04457"
 "checksum rust-webvr 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0f1c2770eade344950b6959fb7f4c658200a252a61f265b3487383b82fafe61e"
 "checksum rustc-demangle 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1430d286cadb237c17c885e25447c982c97113926bb579f4379c0eca8d9586dc"
 "checksum rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "237546c689f20bb44980270c73c3b9edd0891c1be49cc1274406134a66d3957b"
 "checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084"
 "checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7"
 "checksum scoped_threadpool 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3ef399c8893e8cb7aa9696e895427fab3a6bf265977bb96e126f24ddd2cda85a"
-"checksum selectors 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)" = "966334243238d1ef939c51576c55ffd3fd32245bd603a45f9dfdb3439a26e9a7"
+"checksum selectors 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "48ed8bbe599ed52966241c818c92dc024702666f51b4e8ab538b2108c1d6d43a"
 "checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac"
 "checksum semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2d5b7638a1f03815d94e88cb3b3c08e87f0db4d683ef499d1836aaf70a45623f"
 "checksum serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)" = "793aa8d4a777e46a68bbf88998cd957e638427ba5bfb0de22c92ff277b65bd21"
 "checksum serde_codegen 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "a4c5d8a33087d8984f9535daa62a6498a08f6476050b00ab9339dd847e4c25cc"
 "checksum serde_codegen_internals 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "afad7924a009f859f380e4a2e3a509a845c2ac66435fcead74a4d983b21ae806"
 "checksum serde_derive 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "ce44e5f4264b39e9d29c875357b7cc3ebdfb967bb9e22bfb5e44ffa400af5306"
 "checksum serde_json 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3f7d3c184d35801fb8b32b46a7d58d57dbcc150b0eb2b46a1eb79645e8ecfd5b"
 "checksum servo-egl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "21069a884c33fe6ee596975e1f3849ed88c4ec857fbaf11d33672d8ebe051217"
 "checksum servo-fontconfig 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "93f799b649b4a2bf362398910eca35240704c7e765e780349b2bb1070d892262"
 "checksum servo-fontconfig-sys 4.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a0af4a4d7746467921486e5c5420f815cc016a6bf5574210d8e9c00f4afae224"
 "checksum servo-freetype-sys 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9232032c2e85118c0282c6562c84cab12316e655491ba0a5d1905b2320060d1b"
-"checksum servo-glutin 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0d40f80ae976cae0f90eab80688904020ce88cf67a44a80762150c5fc22f54e6"
-"checksum servo-skia 0.20130412.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9f931e476961c0b45d91165e729fe0aa657b807efe6206eb80ccba066829342a"
+"checksum servo-glutin 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4be44fce7796e15831378ebb9d1e4689a2deb2516e0bb61ba8a608a9260a1d5f"
+"checksum servo-skia 0.30000001.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c25888c50f685ec8c2f7eb9dc8b34a515e2e50677cf090979d5c27531aed5327"
 "checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c"
 "checksum shared_library 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fb04126b6fcfd2710fb5b6d18f4207b6c535f2850a7e1a43bcd526d44f30a79a"
 "checksum shell32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72f20b8f3c060374edb8046591ba28f62448c369ccbdc7b02075103fb3a9e38d"
 "checksum sig 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c6649e43c1a1e68d29ed56d0dc3b5b6cf3b901da77cf107c4066b9e3da036df5"
 "checksum signpost 0.1.0 (git+https://github.com/pcwalton/signpost.git)" = "<none>"
-"checksum simd 0.1.1 (git+https://github.com/huonw/simd)" = "<none>"
+"checksum simd 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a94d14a2ae1f1f110937de5fb69e494372560181c7e1739a097fcc2cee37ba0"
 "checksum siphasher 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2ffc669b726f2bc9a3bcff66e5e23b56ba6bf70e22a34c3d7b6d0b3450b65b84"
 "checksum slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d807fd58c4181bbabed77cb3b891ba9748241a552bcc5be698faaebefc54f46e"
 "checksum smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "fcc8d19212aacecf95e4a7a2179b26f7aeb9732a915cf01f05b0d3e044865410"
 "checksum solicit 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "172382bac9424588d7840732b250faeeef88942e37b6e35317dce98cafdd75b2"
 "checksum string_cache 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c4d192db2123fac37399e1ca61557904a5c3fb6fc24c73d2e47b15d20dc32470"
 "checksum string_cache_codegen 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0c9dfe1a7c8bba1ecb90730d269fdc08afe93d23c28dd6c4aa5cabd79a05a05e"
 "checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc"
 "checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"
 "checksum syn 0.10.7 (registry+https://github.com/rust-lang/crates.io-index)" = "e5aaf7e4a0f90d7285445c881dcc28f1a510e4962b4ffcdde17f5775b2960df6"
 "checksum synstructure 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e13808dc5a739d5ff1cff4a2361afdf4ec52d42221c7ddc1d8c438f5a53746a7"
 "checksum syntex 0.54.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb3f52553a966675982404dc34028291b347e0c9a9c0b0b34f2da6be8a0443f8"
 "checksum syntex_errors 0.54.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dee2f6e49c075f71332bb775219d5982bee6732d26227fa1ae1b53cdb12f5cc5"
 "checksum syntex_pos 0.54.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8df3921c7945dfb9ffc53aa35adb2cf4313b5ab5f079c3619b3d4eb82a0efc2b"
 "checksum syntex_syntax 0.54.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dc960085bae44591e22d01f6c0e82a8aec832f8659aca556cdf8ecbdac2bb47b"
 "checksum target_build_utils 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "54c550e226618cd35334b75e92bfa5437c61474bdb75c38bf330ab5a8037b77c"
 "checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
-"checksum tempfile 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9270837a93bad1b1dac18fe67e786b3c960513af86231f6f4f57fddd594ff0c8"
 "checksum tendril 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cebf864c2d90394a1b66d6fe45963f9a177f2af81a0edea5060f77627f9c4587"
 "checksum term 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3deff8a2b3b6607d6d7cc32ac25c0b33709453ca9cceac006caac51e963cf94a"
 "checksum term_size 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "71662702fe5cd2cf95edd4ad655eea42f24a87a0e44059cbaa4e55260b7bc331"
 "checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a"
 "checksum thread_local 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7793b722f0f77ce716e7f1acf416359ca32ff24d04ffbac4269f44a4a83be05d"
 "checksum threadpool 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "59f6d3eff89920113dac9db44dde461d71d01e88a5b57b258a0466c32b5d7fe1"
 "checksum time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "211b63c112206356ef1ff9b19355f43740fc3f85960c598a93d3a3d3ba7beade"
 "checksum tinyfiledialogs 2.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d401358cd71aca93d5f4fccd3db5b87d970ae70fe457911929d99f4a87f7531"
@@ -3670,27 +3576,22 @@ dependencies = [
 "checksum url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5ba8a749fb4479b043733416c244fa9d1d3af3d7c23804944651c8a448cb87e"
 "checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47"
 "checksum utf-8 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9aee9ba280438b56d1ebc5329f2094f0ff457f811eeeff0b278d75aa99db400"
 "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
 "checksum uuid 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a9ff57156caf7e22f37baf3c9d8f6ce8194842c23419dafcb0716024514d162"
 "checksum vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cac5efe5cb0fa14ec2f84f83c701c562ee63f6dcc680861b21d65c682adfb05f"
 "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
 "checksum walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff"
-"checksum wayland-client 0.5.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ced3094c157b5cc0a08d40530e1a627d9f88b9a436971338d2646439128a559e"
-"checksum wayland-kbd 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "73bc10e84c1da90777beffecd24742baea17564ffc2a9918af41871c748eb050"
-"checksum wayland-scanner 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "5a1869370d6bafcbabae8724511d803f4e209a70e94ad94a4249269534364f66"
-"checksum wayland-sys 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9633f7fe5de56544215f82eaf1b76bf1b584becf7f08b58cbef4c2c7d10e803a"
-"checksum wayland-window 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "309b69d3a863c9c21422d889fb7d98cf02f8a2ca054960a49243ce5b67ad884c"
 "checksum webdriver 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cdc28802daddee94267a657ffeac2593a33881fb7a3a44fedd320b1319efcaf6"
-"checksum webrender 0.12.0 (git+https://github.com/servo/webrender)" = "<none>"
-"checksum webrender_traits 0.11.0 (git+https://github.com/servo/webrender)" = "<none>"
+"checksum webrender 0.15.0 (git+https://github.com/servo/webrender)" = "<none>"
+"checksum webrender_traits 0.14.0 (git+https://github.com/servo/webrender)" = "<none>"
 "checksum websocket 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4a1a6ea5ed0367f32eb3d94dcc58859ef4294b5f75ba983dbf56ac314af45d"
 "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
 "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
 "checksum ws 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7c47e9ca2f5c47d27f731b1bb9bb50cc05f9886bb84fbd52afa0ff97f4f61b06"
 "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
-"checksum x11 2.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "be719a282c8d2debd87999849347346259c58ff0acdac3f7f3b48b58652dd773"
-"checksum x11-dl 2.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4e4c7f0a7fb861a1bde4aa23bbda9509bda6b87de4d47c322f86e4c88241ebdd"
+"checksum x11 2.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "124eb405bf0262a54e1a982d4ffe4cd1c24261bdb306e49996e2ce7d492284a8"
+"checksum x11-dl 2.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bf1f9986368c9bbdd8191a783a7ceb42e0c9c6d3348616c873f829b3288a139c"
 "checksum xdg 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "77b831a5ba77110f438f0ac5583aafeb087f70432998ba6b7dcb1d32185db453"
-"checksum xi-unicode 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "315c4e158d7fa277e3ea35b32e50bc07e9a0c8de9130a7cc4bdeab42ddc7b442"
+"checksum xi-unicode 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "12ea8eda4b1eb72f02d148402e23832d56a33f55d8c1b2d5bcdde91d79d47cb1"
 "checksum xml-rs 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b15eed12692bd59d15e98ee7f8dc8408465b992d8ddb4d1672c24865132ec7"
 "checksum xml5ever 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd22ccaa7c71c003165a5a4aabce8bb3f19a9378e399b03528e486954160ad5f"
--- a/servo/README.md
+++ b/servo/README.md
@@ -16,18 +16,17 @@ Visit the [Servo Project page](https://s
 
 Please select your operating system:
 * [OS X](#os-x)
 * [Debian-based Linuxes](#on-debian-based-linuxes)
 * [Fedora](#on-fedora)
 * [Arch Linux](#on-arch-linux)
 * [openSUSE](#on-opensuse-linux)
 * [Gentoo Linux](#on-gentoo-linux)
-* [Microsoft Windows (MSVC)](#on-windows-msvc)
-* [Microsoft Windows (mingw)](#on-windows-mingw)
+* [Microsoft Windows](#on-windows-msvc--mingw)
 * [Android](#cross-compilation-for-android)
 
 #### OS X
 #### On OS X (homebrew)
 
 ``` sh
 brew install automake pkg-config python cmake yasm
 pip install virtualenv
@@ -88,66 +87,60 @@ sudo pacman -S --needed base-devel git p
 #### On Gentoo Linux
 
 ```sh
 sudo emerge net-misc/curl media-libs/freeglut \
     media-libs/freetype media-libs/mesa dev-util/gperf \
     dev-python/virtualenv dev-python/pip dev-libs/openssl \
     x11-libs/libXmu media-libs/glu x11-base/xorg-server
 ```
-#### On Windows MSVC
-
-Install Git for Windows (https://git-scm.com/download/win). DO allow it to add git.exe to the PATH (default
-settings for the installer are fine).
+#### On Windows (MSVC & MinGW)
 
-Install Visual Studio 2015 Community Edition (https://www.visualstudio.com/). You MUST add "Visual C++" to the
-list of installed components. It is not on by default.
-
-Install Python for Windows (https://www.python.org/downloads/release/python-2711/). The windows x86-64 MSI installer is fine.
+1. Install Python for Windows (https://www.python.org/downloads/release/python-2711/). The windows x86-64 MSI installer is fine.
 You should change the installation to install the "Add python.exe to Path" feature.
 
-Install virtualenv.
+2. Install virtualenv.
 
-In a normal Windows Shell (cmd.exe or "Command Prompt" from the start menu), do:
-```
+ In a normal Windows Shell (cmd.exe or "Command Prompt" from the start menu), do:
+ ```
 pip install virtualenv
 ```
-If this does not work, you may need to reboot for the changed PATH settings (by the python installer) to take effect.
+ If this does not work, you may need to reboot for the changed PATH settings (by the python installer) to take effect.
 
-#### On Windows mingw
+3. __(MSVC only)__ Install Git for Windows (https://git-scm.com/download/win). DO allow it to add git.exe to the PATH (default
+settings for the installer are fine).
 
-Download Python for Windows [here](https://www.python.org/downloads/release/python-2711/). This is
-required for the SpiderMonkey build on Windows.
+4. __(MSVC only)__ Install Visual Studio 2015 Community Edition (https://www.visualstudio.com/). You MUST add "Visual C++" to the
+list of installed components. It is not on by default.
 
-Install MSYS2 from [here](https://msys2.github.io/). After you have done so, open an MSYS shell
+5. __(MinGW only)__ Install MSYS2 (https://msys2.github.io/). After you have done so, open an MSYS shell
 window and update the core libraries and install new packages. The extra step at the end is to
 downgrade GCC to 5.4, as the GCC6 versions in mingw currently fail to compile some of our
 dependencies. We are upgrading to a gcc-free build on Windows as soon as possible:
 
-```sh
+ ```sh
 pacman -Su
-pacman -Sy git mingw-w64-x86_64-toolchain mingw-w64-x86_64-freetype \
-    mingw-w64-x86_64-icu mingw-w64-x86_64-nspr mingw-w64-x86_64-ca-certificates \
+pacman -Sy git mingw-w64-x86_64-toolchain mingw-w64-x86_64-icu \
+    mingw-w64-x86_64-nspr mingw-w64-x86_64-ca-certificates \
     mingw-w64-x86_64-expat mingw-w64-x86_64-cmake tar diffutils patch \
     patchutils make python2-setuptools
 export GCC_URL=http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc
 export GCC_EXT=5.4.0-1-any.pkg.tar.xz
 pacman -U --noconfirm $GCC_URL-$GCC_EXT $GCC_URL-ada-$GCC_EXT \
     $GCC_URL-fortran-$GCC_EXT $GCC_URL-libgfortran-$GCC_EXT $GCC_URL-libs-$GCC_EXT \
     $GCC_URL-objc-$GCC_EXT
-easy_install-2.7 pip virtualenv
 ```
 
-Add the following line to the end of `.profile` in your home directory:
+ Add the following line to the end of `.profile` in your home directory:
 
-```
-export PATH=/c/Python27:$PATH
+ ```
+export PATH=/c/Python27:/c/Python27/Scripts:$PATH
 ```
 
-Now, open a MINGW64 (not MSYS!) shell window, and you should be able to build
+ Now, open a MINGW64 (not MSYS!) shell window, and you should be able to build
 servo as usual!
 
 #### Cross-compilation for Android
 
 Pre-installed Android tools are needed. See wiki for
 [details](https://github.com/servo/servo/wiki/Building-for-Android)
 
 ## The Rust compiler
--- a/servo/appveyor.yml
+++ b/servo/appveyor.yml
@@ -44,17 +44,17 @@ cache:
 install:
   - if %TARGET:*-msvc=msvc%==msvc set BUILD_ENV=msvc
   - if %TARGET:*-gnu=gnu%==gnu set BUILD_ENV=gnu
   - if %BUILD_ENV%==gnu  set PATH=C:\msys64\mingw64\bin;C:\msys64\usr\bin\;%PATH%
   - if %BUILD_ENV%==gnu  set MSYSTEM=MINGW64
   - if %BUILD_ENV%==gnu  set MSYS=winsymlinks=lnk
   - if %BUILD_ENV%==gnu  bash -lc "echo $MSYSTEM; pacman --needed --noconfirm -Sy pacman-mirrors"
   - if %BUILD_ENV%==gnu  bash -lc "pacman --noconfirm -Sy"
-  - if %BUILD_ENV%==gnu  bash -lc "pacman -Sy --needed --noconfirm mingw-w64-x86_64-ccache mingw-w64-x86_64-toolchain mingw-w64-x86_64-freetype mingw-w64-x86_64-icu mingw-w64-x86_64-nspr mingw-w64-x86_64-ca-certificates mingw-w64-x86_64-expat mingw-w64-x86_64-cmake tar diffutils patch patchutils make python2-setuptools"
+  - if %BUILD_ENV%==gnu  bash -lc "pacman -Sy --needed --noconfirm mingw-w64-x86_64-ccache mingw-w64-x86_64-toolchain mingw-w64-x86_64-icu mingw-w64-x86_64-nspr mingw-w64-x86_64-ca-certificates mingw-w64-x86_64-expat mingw-w64-x86_64-cmake tar diffutils patch patchutils make python2-setuptools"
   # Downgrade msys2 build GCC to 5.4.0-1 - https://github.com/servo/servo/issues/12512
   - if %BUILD_ENV%==gnu  set GCC_URL=http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc
   - if %BUILD_ENV%==gnu  set GCC_EXT=5.4.0-1-any.pkg.tar.xz
   - if %BUILD_ENV%==gnu  bash -lc "pacman -U --noconfirm $GCC_URL-$GCC_EXT $GCC_URL-ada-$GCC_EXT $GCC_URL-fortran-$GCC_EXT $GCC_URL-libgfortran-$GCC_EXT $GCC_URL-libs-$GCC_EXT $GCC_URL-objc-$GCC_EXT"
 
 # Uncomment these lines to expose RDP access information to the build machine in the build log.
 #init:
 #  - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
--- a/servo/components/canvas_traits/lib.rs
+++ b/servo/components/canvas_traits/lib.rs
@@ -412,25 +412,29 @@ pub fn byte_swap(data: &mut [u8]) {
     while i < length {
         let r = data[i + 2];
         data[i + 2] = data[i + 0];
         data[i + 0] = r;
         i += 4;
     }
 }
 
+pub fn multiply_u8_pixel(a: u8, b: u8) -> u8 {
+    return (a as u32 * b as u32 / 255) as u8;
+}
+
 pub fn byte_swap_and_premultiply(data: &mut [u8]) {
     let length = data.len();
 
     let mut i = 0;
     while i < length {
         let r = data[i + 2];
         let g = data[i + 1];
         let b = data[i + 0];
         let a = data[i + 3];
 
-        data[i + 0] = ((r as u32) * (a as u32) / 255) as u8;
-        data[i + 1] = ((g as u32) * (a as u32) / 255) as u8;
-        data[i + 2] = ((b as u32) * (a as u32) / 255) as u8;
+        data[i + 0] = multiply_u8_pixel(r, a);
+        data[i + 1] = multiply_u8_pixel(g, a);
+        data[i + 2] = multiply_u8_pixel(b, a);
 
         i += 4;
     }
 }
--- a/servo/components/compositing/compositor.rs
+++ b/servo/components/compositing/compositor.rs
@@ -356,21 +356,16 @@ impl RenderNotifier {
 impl webrender_traits::RenderNotifier for RenderNotifier {
     fn new_frame_ready(&mut self) {
         self.compositor_proxy.recomposite(CompositingReason::NewWebRenderFrame);
     }
 
     fn new_scroll_frame_ready(&mut self, composite_needed: bool) {
         self.compositor_proxy.send(Msg::NewScrollFrameReady(composite_needed));
     }
-
-    fn pipeline_size_changed(&mut self,
-                             _: webrender_traits::PipelineId,
-                             _: Option<webrender_traits::LayoutSize>) {
-    }
 }
 
 // Used to dispatch functions from webrender to the main thread's event loop.
 struct CompositorThreadDispatcher {
     compositor_proxy: Box<CompositorProxy>
 }
 
 impl webrender_traits::RenderDispatcher for CompositorThreadDispatcher {
--- a/servo/components/constellation/pipeline.rs
+++ b/servo/components/constellation/pipeline.rs
@@ -23,16 +23,17 @@ use script_traits::{ConstellationControl
 use script_traits::{DocumentActivity, InitialScriptState};
 use script_traits::{LayoutControlMsg, LayoutMsg, LoadData, MozBrowserEvent};
 use script_traits::{NewLayoutInfo, SWManagerMsg, SWManagerSenders, ScriptMsg};
 use script_traits::{ScriptThreadFactory, TimerEventRequest, WindowSizeData};
 use servo_config::opts::{self, Opts};
 use servo_config::prefs::{PREFS, Pref};
 use servo_url::ServoUrl;
 use std::collections::HashMap;
+#[cfg(not(windows))]
 use std::env;
 use std::ffi::OsStr;
 use std::io::Error as IOError;
 use std::process;
 use std::rc::Rc;
 use std::sync::mpsc::Sender;
 use style_traits::{PagePx, ViewportPx};
 use webrender_traits;
--- a/servo/components/gfx/Cargo.toml
+++ b/servo/components/gfx/Cargo.toml
@@ -34,31 +34,31 @@ servo_atoms = {path = "../atoms"}
 servo_geometry = {path = "../geometry"}
 servo_url = {path = "../url"}
 serde_derive = "0.8"
 smallvec = "0.1"
 style = {path = "../style"}
 style_traits = {path = "../style_traits"}
 time = "0.1.12"
 unicode-script = {version = "0.1", features = ["harfbuzz"]}
-xi-unicode = "0.0.1"
+xi-unicode = "0.1.0"
 
 [dependencies.webrender_traits]
 git = "https://github.com/servo/webrender"
 default-features = false
 features = ["serde_derive", "ipc"]
 
 [target.'cfg(target_os = "macos")'.dependencies]
 byteorder = "1.0"
-core-foundation = "0.2"
-core-graphics = "0.5"
-core-text = "2.0"
+core-foundation = "0.3"
+core-graphics = "0.6"
+core-text = "3.0"
 
 [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
 freetype = "0.2"
 servo-fontconfig = "0.2.1"
 
-[target.'cfg(any(target_arch = "x86_64", target_arch = "aarch64"))'.dependencies]
-simd = {git = "https://github.com/huonw/simd"}
+[target.'cfg(any(target_feature = "sse2", target_feature = "neon"))'.dependencies]
+simd = "0.2.0"
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.1.5"
 truetype = "0.24"
--- a/servo/components/gfx/font_cache_thread.rs
+++ b/servo/components/gfx/font_cache_thread.rs
@@ -20,17 +20,17 @@ use std::borrow::ToOwned;
 use std::collections::HashMap;
 use std::fmt;
 use std::mem;
 use std::ops::Deref;
 use std::sync::{Arc, Mutex};
 use std::thread;
 use std::u32;
 use style::font_face::{EffectiveSources, Source};
-use style::properties::longhands::font_family::computed_value::FontFamily;
+use style::properties::longhands::font_family::computed_value::{FontFamily, FamilyName};
 use webrender_traits;
 
 /// A list of font templates that make up a given font family.
 struct FontTemplates {
     templates: Vec<FontTemplate>,
 }
 
 #[derive(Serialize, Deserialize, Debug)]
@@ -264,17 +264,17 @@ impl FontCache {
                                                               bytes,
                                                               sender.clone());
                             channel_to_self.send(command).unwrap();
                         }
                     }
                 });
             }
             Source::Local(ref font) => {
-                let font_face_name = LowercaseString::new(font.name());
+                let font_face_name = LowercaseString::new(&font.0);
                 let templates = &mut self.web_families.get_mut(&family_name).unwrap();
                 let mut found = false;
                 for_each_variation(&font_face_name, |path| {
                     found = true;
                     templates.add_template(Atom::from(&*path), None);
                 });
                 if found {
                     sender.send(()).unwrap();
@@ -456,18 +456,18 @@ impl FontCacheThread {
 
         match reply {
             Reply::GetFontTemplateReply(data) => {
                 data.unwrap()
             }
         }
     }
 
-    pub fn add_web_font(&self, family: FontFamily, sources: EffectiveSources, sender: IpcSender<()>) {
-        self.chan.send(Command::AddWebFont(LowercaseString::new(family.name()), sources, sender)).unwrap();
+    pub fn add_web_font(&self, family: FamilyName, sources: EffectiveSources, sender: IpcSender<()>) {
+        self.chan.send(Command::AddWebFont(LowercaseString::new(&family.0), sources, sender)).unwrap();
     }
 
     pub fn exit(&self) {
         let (response_chan, response_port) = ipc::channel().unwrap();
         self.chan.send(Command::Exit(response_chan)).expect("Couldn't send FontCacheThread exit message");
         response_port.recv().expect("Couldn't receive FontCacheThread reply");
     }
 }
--- a/servo/components/gfx/lib.rs
+++ b/servo/components/gfx/lib.rs
@@ -1,25 +1,29 @@
 /* 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/. */
 
-// For simd (currently x86_64/aarch64)
-#![cfg_attr(any(target_os = "linux", target_os = "android", target_os = "windows"), feature(heap_api))]
+// For SIMD
+#![feature(cfg_target_feature)]
+#![cfg_attr(any(target_os = "linux", target_os = "android"), feature(heap_api))]
 
-#![feature(alloc)]
+#![cfg_attr(any(target_os = "linux", target_os = "android"), feature(alloc))]
 #![feature(box_syntax)]
 #![feature(plugin)]
 #![feature(range_contains)]
 #![feature(unique)]
 
 #![plugin(plugins)]
 
 #![deny(unsafe_code)]
 
+#[cfg(any(target_os = "linux", target_os = "android"))]
+extern crate alloc;
+
 extern crate app_units;
 #[macro_use]
 extern crate bitflags;
 
 // Mac OS-specific library dependencies
 #[cfg(target_os = "macos")] extern crate byteorder;
 #[cfg(target_os = "macos")] extern crate core_foundation;
 #[cfg(target_os = "macos")] extern crate core_graphics;
@@ -57,17 +61,17 @@ extern crate ordered_float;
 extern crate range;
 #[cfg(target_os = "macos")]
 extern crate serde;
 #[macro_use]
 extern crate serde_derive;
 extern crate servo_geometry;
 extern crate servo_url;
 #[macro_use] extern crate servo_atoms;
-#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
+#[cfg(any(target_feature = "sse2", target_feature = "neon"))]
 extern crate simd;
 extern crate smallvec;
 extern crate style;
 extern crate style_traits;
 extern crate time;
 extern crate unicode_script;
 extern crate webrender_traits;
 extern crate xi_unicode;
--- a/servo/components/gfx/platform/freetype/font_context.rs
+++ b/servo/components/gfx/platform/freetype/font_context.rs
@@ -1,22 +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/. */
 
-extern crate alloc;
-
+use alloc::heap;
 use freetype::freetype::FT_Add_Default_Modules;
 use freetype::freetype::FT_Done_Library;
 use freetype::freetype::FT_Library;
 use freetype::freetype::FT_Memory;
 use freetype::freetype::FT_MemoryRec_;
 use freetype::freetype::FT_New_Library;
 use heapsize::{HeapSizeOf, heap_size_of};
-use self::alloc::heap;
 use std::os::raw::{c_long, c_void};
 use std::ptr;
 use std::rc::Rc;
 
 // We pass a |User| struct -- via an opaque |void*| -- to FreeType each time a new instance is
 // created. FreeType passes it back to the ft_alloc/ft_realloc/ft_free callbacks. We use it to
 // record the memory usage of each FreeType instance.
 pub struct User {
--- a/servo/components/gfx/text/glyph.rs
+++ b/servo/components/gfx/text/glyph.rs
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use app_units::Au;
 use euclid::point::Point2D;
 use range::{self, EachIndex, Range, RangeIndex};
-#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
+#[cfg(any(target_feature = "sse2", target_feature = "neon"))]
 use simd::u32x4;
 use std::{fmt, mem, u16};
 use std::cmp::{Ordering, PartialOrd};
 use std::vec::Vec;
 
 pub use gfx_traits::ByteIndex;
 
 /// GlyphEntry is a port of Gecko's CompressedGlyph scheme for storing glyph data compactly.
@@ -69,17 +69,17 @@ impl GlyphEntry {
 }
 
 /// The id of a particular glyph within a font
 pub type GlyphId = u32;
 
 // TODO: make this more type-safe.
 
 const FLAG_CHAR_IS_SPACE: u32       = 0x40000000;
-#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
+#[cfg(any(target_feature = "sse2", target_feature = "neon"))]
 const FLAG_CHAR_IS_SPACE_SHIFT: u32 = 30;
 const FLAG_IS_SIMPLE_GLYPH: u32     = 0x80000000;
 
 // glyph advance; in Au's.
 const GLYPH_ADVANCE_MASK: u32       = 0x3FFF0000;
 const GLYPH_ADVANCE_SHIFT: u32      = 16;
 const GLYPH_ID_MASK: u32            = 0x0000FFFF;
 
@@ -586,17 +586,17 @@ impl<'a> GlyphStore {
                     advance + glyph.advance() + extra_word_spacing
                 } else {
                     advance + glyph.advance()
                 }
             })
     }
 
     #[inline]
-    #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
+    #[cfg(any(target_feature = "sse2", target_feature = "neon"))]
     fn advance_for_byte_range_simple_glyphs(&self, range: &Range<ByteIndex>, extra_word_spacing: Au) -> Au {
         let advance_mask = u32x4::splat(GLYPH_ADVANCE_MASK);
         let space_flag_mask = u32x4::splat(FLAG_CHAR_IS_SPACE);
         let mut simd_advance = u32x4::splat(0);
         let mut simd_spaces = u32x4::splat(0);
         let begin = range.begin().to_usize();
         let len = range.length().to_usize();
         let num_simd_iterations = len / 4;
@@ -627,26 +627,26 @@ impl<'a> GlyphStore {
             leftover_advance = leftover_advance + self.entry_buffer[i].advance();
             if self.entry_buffer[i].char_is_space() {
                 leftover_spaces += 1;
             }
         }
         Au(advance) + leftover_advance + extra_word_spacing * (spaces + leftover_spaces)
     }
 
-    /// When SIMD isn't available (non-x86_x64/aarch64), fallback to the slow path.
+    /// When SIMD isn't available, fallback to the slow path.
     #[inline]
-    #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
+    #[cfg(not(any(target_feature = "sse2", target_feature = "neon")))]
     fn advance_for_byte_range_simple_glyphs(&self, range: &Range<ByteIndex>, extra_word_spacing: Au) -> Au {
         self.advance_for_byte_range_slow_path(range, extra_word_spacing)
     }
 
     /// Used for SIMD.
     #[inline]
-    #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
+    #[cfg(any(target_feature = "sse2", target_feature = "neon"))]
     #[allow(unsafe_code)]
     fn transmute_entry_buffer_to_u32_buffer(&self) -> &[u32] {
         unsafe { mem::transmute(self.entry_buffer.as_slice()) }
     }
 
     pub fn char_is_space(&self, i: ByteIndex) -> bool {
         assert!(i < self.len());
         self.entry_buffer[i.to_usize()].char_is_space()
--- a/servo/components/layout/Cargo.toml
+++ b/servo/components/layout/Cargo.toml
@@ -29,17 +29,17 @@ net_traits = {path = "../net_traits"}
 ordered-float = "0.2.2"
 parking_lot = "0.3.3"
 plugins = {path = "../plugins"}
 profile_traits = {path = "../profile_traits"}
 range = {path = "../range"}
 rayon = "0.6"
 script_layout_interface = {path = "../script_layout_interface"}
 script_traits = {path = "../script_traits"}
-selectors = "0.15.1"
+selectors = "0.17"
 serde = "0.8"
 serde_derive = "0.8"
 servo_geometry = {path = "../geometry"}
 serde_json = "0.8"
 servo_config = {path = "../config"}
 servo_url = {path = "../url"}
 smallvec = "0.1"
 style = {path = "../style"}
--- a/servo/components/layout/animation.rs
+++ b/servo/components/layout/animation.rs
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! CSS transitions and animations.
 
-use context::SharedLayoutContext;
+use context::LayoutContext;
 use flow::{self, Flow};
 use gfx::display_list::OpaqueNode;
 use ipc_channel::ipc::IpcSender;
 use msg::constellation_msg::PipelineId;
 use script_traits::{AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg};
 use std::collections::HashMap;
 use std::sync::mpsc::Receiver;
 use style::animation::{Animation, update_style_for_animation};
@@ -127,17 +127,17 @@ pub fn update_animation_state(constellat
                       .unwrap();
 }
 
 /// 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: &SharedLayoutContext,
+pub fn recalc_style_for_animations(context: &LayoutContext,
                                    flow: &mut Flow,
                                    animations: &HashMap<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();
--- a/servo/components/layout/block.rs
+++ b/servo/components/layout/block.rs
@@ -23,17 +23,17 @@
 //! "CSS-SIZING" refers to the W3C "CSS Intrinsic & Extrinsic Sizing Module Level 3" document
 //! available here:
 //!
 //!   http://dev.w3.org/csswg/css-sizing/
 
 #![deny(unsafe_code)]
 
 use app_units::{Au, MAX_AU};
-use context::{LayoutContext, SharedLayoutContext};
+use context::LayoutContext;
 use display_list_builder::{BorderPaintingMode, DisplayListBuildState, FragmentDisplayListBuilding};
 use display_list_builder::BlockFlowDisplayListBuilding;
 use euclid::{Point2D, Rect, Size2D};
 use floats::{ClearType, FloatKind, Floats, PlacementInfo};
 use flow::{self, BaseFlow, EarlyAbsolutePositionInfo, Flow, FlowClass, ForceNonfloatedFlag};
 use flow::{BLOCK_POSITION_IS_STATIC, CLEARS_LEFT, CLEARS_RIGHT};
 use flow::{CONTAINS_TEXT_OR_REPLACED_FRAGMENTS, INLINE_POSITION_IS_STATIC};
 use flow::{FragmentationContext, MARGINS_CANNOT_COLLAPSE, PreorderFlowTraversal};
@@ -762,21 +762,21 @@ impl BlockFlow {
     /// with the extra content moved to another fragment (a flow like this one) which is returned.
     /// See `Flow::fragment`.
     ///
     /// The return value is always `None` when `fragmentation_context` is `None`.
     ///
     /// `inline(always)` because this is only ever called by in-order or non-in-order top-level
     /// methods.
     #[inline(always)]
-    pub fn assign_block_size_block_base<'a>(&mut self,
-                                            layout_context: &'a LayoutContext<'a>,
-                                            mut fragmentation_context: Option<FragmentationContext>,
-                                            margins_may_collapse: MarginsMayCollapseFlag)
-                                            -> Option<Arc<Flow>> {
+    pub fn assign_block_size_block_base(&mut self,
+                                        layout_context: &LayoutContext,
+                                        mut fragmentation_context: Option<FragmentationContext>,
+                                        margins_may_collapse: MarginsMayCollapseFlag)
+                                        -> Option<Arc<Flow>> {
         let _scope = layout_debug_scope!("assign_block_size_block_base {:x}",
                                          self.base.debug_id());
 
         let mut break_at = None;
         let content_box = self.fragment.content_box();
         if self.base.restyle_damage.contains(REFLOW) {
             // Our current border-box position.
             let mut cur_b = Au(0);
@@ -1457,19 +1457,19 @@ impl BlockFlow {
     ///
     /// Note that this is part of the assign-block-sizes traversal, not the assign-inline-sizes
     /// traversal as one might expect. That is because, in general, float placement cannot occur
     /// until heights are assigned. To work around this unfortunate circular dependency, by the
     /// time we get here we have already estimated the width of the block formatting context based
     /// on the floats we could see at the time of inline-size assignment. The job of this function,
     /// therefore, is not only to assign the final size but also to perform the layout again for
     /// this block formatting context if our speculation was wrong.
-    fn assign_inline_position_for_formatting_context<'a>(&mut self,
-                                                         layout_context: &'a LayoutContext<'a>,
-                                                         content_box: LogicalRect<Au>) {
+    fn assign_inline_position_for_formatting_context(&mut self,
+                                                     layout_context: &LayoutContext,
+                                                     content_box: LogicalRect<Au>) {
         debug_assert!(self.formatting_context_type() != FormattingContextType::None);
 
         if !self.base.restyle_damage.intersects(REFLOW_OUT_OF_FLOW | REFLOW) {
             return
         }
 
         // We do this first to avoid recomputing our inline size when we propagate it.
         self.base.restyle_damage.remove(REFLOW_OUT_OF_FLOW | REFLOW);
@@ -1541,20 +1541,20 @@ impl BlockFlow {
         // If float speculation failed, fixup our layout, and re-layout all the children.
         if self.fragment.margin_box_inline_size() != self.base.position.size.inline {
             debug!("assign_inline_position_for_formatting_context: float speculation failed");
             // Fix-up our own layout.
             // We can't just traverse_flow_tree_preorder ourself, because that would re-run
             // float speculation, instead of acting on the actual results.
             self.fragment.border_box.size.inline = inline_size;
             // Assign final-final inline sizes on all our children.
-            self.assign_inline_sizes(&layout_context.shared.style_context);
+            self.assign_inline_sizes(layout_context);
             // Re-run layout on our children.
             for child in flow::mut_base(self).children.iter_mut() {
-                sequential::traverse_flow_tree_preorder(child, layout_context.shared);
+                sequential::traverse_flow_tree_preorder(child, layout_context);
             }
             // Assign our final-final block size.
             self.assign_block_size(layout_context);
         }
 
         debug_assert_eq!(self.fragment.margin_box_inline_size(), self.base.position.size.inline);
     }
 
@@ -1878,19 +1878,20 @@ impl Flow for BlockFlow {
         self.fragment.restyle_damage.remove(BUBBLE_ISIZES);
     }
 
     /// Recursively (top-down) determines the actual inline-size of child contexts and fragments.
     /// When called on this context, the context has had its inline-size set by the parent context.
     ///
     /// Dual fragments consume some inline-size first, and the remainder is assigned to all child
     /// (block) contexts.
-    fn assign_inline_sizes(&mut self, shared_context: &SharedStyleContext) {
+    fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
         let _scope = layout_debug_scope!("block::assign_inline_sizes {:x}", self.base.debug_id());
 
+        let shared_context = layout_context.shared_context();
         self.compute_inline_sizes(shared_context);
 
         // Move in from the inline-start border edge.
         let inline_start_content_edge = self.fragment.border_box.start.i +
             self.fragment.border_padding.inline_start;
 
         let padding_and_borders = self.fragment.border_padding.inline_start_end();
 
@@ -1909,21 +1910,21 @@ impl Flow for BlockFlow {
     }
 
     fn place_float_if_applicable<'a>(&mut self) {
         if self.base.flags.is_float() {
             self.place_float();
         }
     }
 
-    fn assign_block_size_for_inorder_child_if_necessary<'a>(&mut self,
-                                                            layout_context: &'a LayoutContext<'a>,
-                                                            parent_thread_id: u8,
-                                                            content_box: LogicalRect<Au>)
-                                                            -> bool {
+    fn assign_block_size_for_inorder_child_if_necessary(&mut self,
+                                                        layout_context: &LayoutContext,
+                                                        parent_thread_id: u8,
+                                                        content_box: LogicalRect<Au>)
+                                                        -> bool {
         if self.base.flags.is_float() {
             return false
         }
 
         let is_formatting_context = self.formatting_context_type() != FormattingContextType::None;
         if !self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) && is_formatting_context {
             self.assign_inline_position_for_formatting_context(layout_context, content_box);
         }
@@ -1945,17 +1946,17 @@ impl Flow for BlockFlow {
             let delta = self.base.position.size.block;
             self.base.floats.translate(LogicalSize::new(writing_mode, Au(0), -delta));
             return true
         }
 
         false
     }
 
-    fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
+    fn assign_block_size(&mut self, ctx: &LayoutContext) {
         let remaining = Flow::fragment(self, ctx, None);
         debug_assert!(remaining.is_none());
     }
 
     fn fragment(&mut self, layout_context: &LayoutContext,
                 fragmentation_context: Option<FragmentationContext>)
                 -> Option<Arc<Flow>> {
         if self.fragment.is_replaced() {
@@ -1993,17 +1994,17 @@ impl Flow for BlockFlow {
                    flow::base(self).debug_id());
             self.assign_block_size_block_base(
                 layout_context,
                 fragmentation_context,
                 MarginsMayCollapseFlag::MarginsMayCollapse)
         }
     }
 
-    fn compute_absolute_position(&mut self, _layout_context: &SharedLayoutContext) {
+    fn compute_absolute_position(&mut self, _layout_context: &LayoutContext) {
         // FIXME (mbrubeck): Get the real container size, taking the container writing mode into
         // account.  Must handle vertical writing modes.
         let container_size = Size2D::new(self.base.block_container_inline_size, Au(0));
 
         if self.is_root() {
             self.base.clip = ClippingRegion::max();
         }
 
--- a/servo/components/layout/construct.rs
+++ b/servo/components/layout/construct.rs
@@ -10,17 +10,17 @@
 //! intermediate data that goes with a DOM node and hasn't found its "home" yet-maybe it's a box,
 //! maybe it's an absolute or fixed position thing that hasn't found its containing block yet.
 //! Construction items bubble up the tree from children to parents until they find their homes.
 
 #![deny(unsafe_code)]
 
 use app_units::Au;
 use block::BlockFlow;
-use context::LayoutContext;
+use context::{LayoutContext, with_thread_local_font_context};