Merge m-c to fx-team. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Sat, 02 Apr 2016 14:00:14 -0400
changeset 291487 1f8433e85420fc2156ac0dbd68186cef5cf1ebe8
parent 291476 0cdbbf26ef7feca6f0059efa61952bedbaf9e8aa (current diff)
parent 291474 55d557f4d73ee58664bdf2fa85aaab555224722e (diff)
child 291496 d50240348923d829500a0284d832599804987e5c
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone48.0a1
Merge m-c to fx-team. a=merge
--- a/browser/components/customizableui/test/browser_1089591_still_customizable_after_reset.js
+++ b/browser/components/customizableui/test/browser_1089591_still_customizable_after_reset.js
@@ -3,18 +3,17 @@
 // Dragging the elements again after a reset should work
 add_task(function* () {
   yield startCustomizing();
   let historyButton = document.getElementById("wrapper-history-panelmenu");
   let devButton = document.getElementById("wrapper-developer-button");
 
   ok(historyButton && devButton, "Draggable elements should exist");
   simulateItemDrag(historyButton, devButton);
-  gCustomizeMode.reset();
-  yield waitForCondition(() => !gCustomizeMode.resetting);
+  yield gCustomizeMode.reset();
   ok(CustomizableUI.inDefaultState, "Should be back in default state");
 
   historyButton = document.getElementById("wrapper-history-panelmenu");
   devButton = document.getElementById("wrapper-developer-button");
   ok(historyButton && devButton, "Draggable elements should exist");
   simulateItemDrag(historyButton, devButton);
 
   yield endCustomizing();
--- a/browser/components/customizableui/test/browser_923857_customize_mode_event_wrapping_during_reset.js
+++ b/browser/components/customizableui/test/browser_923857_customize_mode_event_wrapping_during_reset.js
@@ -9,17 +9,16 @@ add_task(function*() {
   yield startCustomizing();
   let devButton = document.getElementById("developer-button");
   let downloadsButton = document.getElementById("downloads-button");
   let searchBox = document.getElementById("search-container");
   let palette = document.getElementById("customization-palette");
   ok(devButton && downloadsButton && searchBox && palette, "Stuff should exist");
   simulateItemDrag(devButton, downloadsButton);
   simulateItemDrag(searchBox, palette);
-  gCustomizeMode.reset();
-  yield waitForCondition(() => !gCustomizeMode.resetting);
+  yield gCustomizeMode.reset();
   ok(CustomizableUI.inDefaultState, "Should be back in default state");
   yield endCustomizing();
 });
 
 add_task(function* asyncCleanup() {
   yield resetCustomization();
 });
--- a/browser/components/customizableui/test/browser_938980_navbar_collapsed.js
+++ b/browser/components/customizableui/test/browser_938980_navbar_collapsed.js
@@ -32,18 +32,17 @@ add_task(function*() {
 
   setToolbarVisibility(bookmarksToolbar, true);
   setToolbarVisibility(navbar, false);
   ok(!bookmarksToolbar.collapsed, "bookmarksToolbar should be visible now");
   ok(navbar.collapsed, "navbar should be collapsed");
   is(CustomizableUI.inDefaultState, false, "Should no longer be in default state");
 
   yield startCustomizing();
-  gCustomizeMode.reset();
-  yield waitForCondition(() => !gCustomizeMode.resetting);
+  yield gCustomizeMode.reset();
   yield endCustomizing();
 
   is(bookmarksToolbar.collapsed, true, "Customization reset should restore collapsed-state to the bookmarks toolbar");
   ok(!tabsToolbar.collapsed, "TabsToolbar should not be collapsed");
   ok(bookmarksToolbar.collapsed, "The bookmarksToolbar should be collapsed after reset");
   ok(CustomizableUI.inDefaultState, "Everything should be back to default state");
 });
 
@@ -56,18 +55,17 @@ add_task(function*() {
   }
   ok(CustomizableUI.inDefaultState, "Everything should be in its default state");
 
   is(menubar.getBoundingClientRect().height, 0, "menubar should be hidden by default");
   setToolbarVisibility(menubar, true);
   isnot(menubar.getBoundingClientRect().height, 0, "menubar should be visible now");
 
   yield startCustomizing();
-  gCustomizeMode.reset();
-  yield waitForCondition(() => !gCustomizeMode.resetting);
+  yield gCustomizeMode.reset();
 
   is(menubar.getAttribute("autohide"), "true", "The menubar should have autohide=true after reset in customization mode");
   is(menubar.getBoundingClientRect().height, 0, "The menubar should have height=0 after reset in customization mode");
 
   yield endCustomizing();
 
   is(menubar.getAttribute("autohide"), "true", "The menubar should have autohide=true after reset");
   is(menubar.getBoundingClientRect().height, 0, "The menubar should have height=0 after reset");
@@ -84,18 +82,17 @@ add_task(function*() {
   is(CustomizableUI.inDefaultState, false, "Should no longer be in default state");
 
   yield startCustomizing();
 
   ok(!bookmarksToolbar.collapsed, "The bookmarksToolbar should be visible before reset");
   ok(!navbar.collapsed, "The navbar should be visible before reset");
   ok(!tabsToolbar.collapsed, "TabsToolbar should not be collapsed");
 
-  gCustomizeMode.reset();
-  yield waitForCondition(() => !gCustomizeMode.resetting);
+  yield gCustomizeMode.reset();
 
   ok(bookmarksToolbar.collapsed, "The bookmarksToolbar should be collapsed after reset");
   ok(!tabsToolbar.collapsed, "TabsToolbar should not be collapsed");
   ok(!navbar.collapsed, "The navbar should still be visible after reset");
   ok(CustomizableUI.inDefaultState, "Everything should be back to default state");
   yield endCustomizing();
 });
 
--- a/browser/components/customizableui/test/browser_970511_undo_restore_default.js
+++ b/browser/components/customizableui/test/browser_970511_undo_restore_default.js
@@ -16,19 +16,19 @@ add_task(function*() {
   let undoResetButton = document.getElementById("customization-undo-reset-button");
   is(undoResetButton.hidden, true, "The undo button is hidden before reset");
 
   yield gCustomizeMode.reset();
 
   ok(CustomizableUI.inDefaultState, "In default state after reset");
   is(undoResetButton.hidden, false, "The undo button is visible after reset");
 
-  undoResetButton.click();
-  yield waitForCondition(() => !gCustomizeMode.resetting);
-  ok(!CustomizableUI.inDefaultState, "Not in default state after reset-undo");
+  yield gCustomizeMode.undoReset()
+
+  ok(!CustomizableUI.inDefaultState, "Not in default state after undo-reset");
   is(undoResetButton.hidden, true, "The undo button is hidden after clicking on the undo button");
   is(CustomizableUI.getPlacementOfWidget(homeButtonId), null, "Home button is in palette");
 
   yield gCustomizeMode.reset();
 });
 
 // Performing an action after a reset will hide the reset button.
 add_task(function*() {
--- a/browser/components/newtab/NewTabMessages.jsm
+++ b/browser/components/newtab/NewTabMessages.jsm
@@ -8,51 +8,66 @@
 /* exported NewTabMessages */
 
 "use strict";
 
 const {utils: Cu} = Components;
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "PreviewProvider",
+                                  "resource:///modules/PreviewProvider.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
                                   "resource:///modules/NewTabPrefsProvider.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabWebChannel",
                                   "resource:///modules/NewTabWebChannel.jsm");
 
 this.EXPORTED_SYMBOLS = ["NewTabMessages"];
 
 const PREF_ENABLED = "browser.newtabpage.remote";
 
 // Action names are from the content's perspective. in from chrome == out from content
 // Maybe replace the ACTION objects by a bi-directional Map a bit later?
 const ACTIONS = {
   prefs: {
     inPrefs: "REQUEST_PREFS",
     outPrefs: "RECEIVE_PREFS",
-    action_types: new Set(["REQUEST_PREFS", "RECEIVE_PREFS"]),
-  }
+    action_types: new Set(["REQUEST_PREFS"]),
+  },
+  preview: {
+    inThumb: "REQUEST_THUMB",
+    outThumb: "RECEIVE_THUMB",
+    action_types: new Set(["REQUEST_THUMB"]),
+  },
 };
 
 let NewTabMessages = {
 
   _prefs: {},
 
   /** NEWTAB EVENT HANDLERS **/
 
   /*
    * Return to the originator all newtabpage prefs. A point-to-point request.
    */
   handlePrefRequest(actionName, {target}) {
-    if (ACTIONS.prefs.action_types.has(actionName)) {
+    if (ACTIONS.prefs.inPrefs === actionName) {
       let results = NewTabPrefsProvider.prefs.newtabPagePrefs;
       NewTabWebChannel.send(ACTIONS.prefs.outPrefs, results, target);
     }
   },
 
+  handlePreviewRequest(actionName, {data, target}) {
+    if (ACTIONS.preview.inThumb === actionName) {
+      PreviewProvider.getThumbnail(data).then(imgData => {
+        NewTabWebChannel.send(ACTIONS.preview.outThumb, {url: data, imgData}, target);
+      });
+    }
+  },
+
   /*
    * Broadcast preference changes to all open newtab pages
    */
   handlePrefChange(actionName, value) {
     let prefChange = {};
     prefChange[actionName] = value;
     NewTabWebChannel.broadcast(ACTIONS.prefs.outPrefs, prefChange);
   },
@@ -63,33 +78,47 @@ let NewTabMessages = {
         this.uninit();
       } else if (!this._prefs.enabled && value) {
         this.init();
       }
     }
   },
 
   init() {
+    this.handlePrefRequest = this.handlePrefRequest.bind(this);
+    this.handlePreviewRequest = this.handlePreviewRequest.bind(this);
+    this.handlePrefChange = this.handlePrefChange.bind(this);
+    this._handleEnabledChange = this._handleEnabledChange.bind(this);
+
+    NewTabPrefsProvider.prefs.init();
+    NewTabWebChannel.init();
+
     this._prefs.enabled = Preferences.get(PREF_ENABLED, false);
 
     if (this._prefs.enabled) {
-      NewTabWebChannel.on(ACTIONS.prefs.inPrefs, this.handlePrefRequest.bind(this));
-      NewTabPrefsProvider.prefs.on(PREF_ENABLED, this._handleEnabledChange.bind(this));
+      NewTabWebChannel.on(ACTIONS.prefs.inPrefs, this.handlePrefRequest);
+      NewTabWebChannel.on(ACTIONS.preview.inThumb, this.handlePreviewRequest);
+
+      NewTabPrefsProvider.prefs.on(PREF_ENABLED, this._handleEnabledChange);
 
       for (let pref of NewTabPrefsProvider.newtabPagePrefSet) {
-        NewTabPrefsProvider.prefs.on(pref, this.handlePrefChange.bind(this));
+        NewTabPrefsProvider.prefs.on(pref, this.handlePrefChange);
       }
     }
   },
 
   uninit() {
     this._prefs.enabled = Preferences.get(PREF_ENABLED, false);
 
     if (this._prefs.enabled) {
       NewTabPrefsProvider.prefs.off(PREF_ENABLED, this._handleEnabledChange);
 
       NewTabWebChannel.off(ACTIONS.prefs.inPrefs, this.handlePrefRequest);
+      NewTabWebChannel.off(ACTIONS.prefs.inThumb, this.handlePreviewRequest);
       for (let pref of NewTabPrefsProvider.newtabPagePrefSet) {
         NewTabPrefsProvider.prefs.off(pref, this.handlePrefChange);
       }
     }
+
+    NewTabPrefsProvider.prefs.uninit();
+    NewTabWebChannel.uninit();
   }
 };
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/PreviewProvider.jsm
@@ -0,0 +1,49 @@
+/* global XPCOMUtils, BackgroundPageThumbs, FileUtils, PageThumbsStorage, Task, MIMEService */
+/* exported PreviewProvider */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["PreviewProvider"];
+
+const {utils: Cu} = Components;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/PageThumbs.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+const {OS} = Cu.import("resource://gre/modules/osfile.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "BackgroundPageThumbs",
+  "resource://gre/modules/BackgroundPageThumbs.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "MIMEService",
+  "@mozilla.org/mime;1", "nsIMIMEService");
+
+let PreviewProvider = {
+  /**
+   * Returns a thumbnail as a data URI for a url, creating it if necessary
+   *
+   * @param {String} url
+   *        a url to obtain a thumbnail for
+   * @return {Promise} A Promise that resolves with a base64 encoded thumbnail
+   */
+  getThumbnail: Task.async(function* PreviewProvider_getThumbnail(url) {
+    try {
+      yield BackgroundPageThumbs.captureIfMissing(url);
+      let imgPath = PageThumbsStorage.getFilePathForURL(url);
+
+      // OS.File object used to easily read off-thread
+      let file = yield OS.File.open(imgPath, {read: true, existing: true});
+
+      // nsIFile object needed for MIMEService
+      let nsFile = FileUtils.File(imgPath);
+
+      let contentType = MIMEService.getTypeFromFile(nsFile);
+      let bytes = yield file.read();
+      let encodedData = btoa(String.fromCharCode.apply(null, bytes));
+      file.close();
+      return `data:${contentType};base64,${encodedData}`;
+    } catch (err) {
+      Cu.reportError(`PreviewProvider_getThumbnail error: ${err}`);
+      throw err;
+    }
+  })
+};
--- a/browser/components/newtab/moz.build
+++ b/browser/components/newtab/moz.build
@@ -11,17 +11,18 @@ XPCSHELL_TESTS_MANIFESTS += [
 ]
 
 EXTRA_JS_MODULES += [
     'NewTabMessages.jsm',
     'NewTabPrefsProvider.jsm',
     'NewTabRemoteResources.jsm',
     'NewTabURL.jsm',
     'NewTabWebChannel.jsm',
-    'PlacesProvider.jsm'
+    'PlacesProvider.jsm',
+    'PreviewProvider.jsm'
 ]
 
 XPIDL_SOURCES += [
     'nsIAboutNewTabService.idl',
 ]
 
 XPIDL_MODULE = 'browser-newtab'
 
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/tests/browser/blue_page.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+  <meta charset="utf-8">
+</head>
+<body style="background-color: blue">
+</body>
+</html>
--- a/browser/components/newtab/tests/browser/browser.ini
+++ b/browser/components/newtab/tests/browser/browser.ini
@@ -1,10 +1,13 @@
 [DEFAULT]
 support-files =
+  blue_page.html
   dummy_page.html
   newtabwebchannel_basic.html
   newtabmessages_prefs.html
+  newtabmessages_preview.html
 
+[browser_PreviewProvider.js]
 [browser_remotenewtab_pageloads.js]
 [browser_newtab_overrides.js]
 [browser_newtabmessages.js]
 [browser_newtabwebchannel.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/tests/browser/browser_PreviewProvider.js
@@ -0,0 +1,90 @@
+/* globals XPCOMUtils, Services, PreviewProvider, registerCleanupFunction */
+"use strict";
+
+let Cu = Components.utils;
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PreviewProvider",
+                                  "resource:///modules/PreviewProvider.jsm");
+
+var oldEnabledPref = Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled");
+Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", false);
+
+registerCleanupFunction(function() {
+  while (gBrowser.tabs.length > 1) {
+    gBrowser.removeTab(gBrowser.tabs[1]);
+  }
+  Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", oldEnabledPref);
+});
+
+const TEST_URL = "https://example.com/browser/browser/components/newtab/tests/browser/blue_page.html";
+
+function pixelsForDataURI(dataURI, options) {
+  return new Promise(resolve => {
+    if (!options) {
+      options = {};
+    }
+    let {width, height} = options;
+    if (!width) {
+      width = 100;
+    }
+    if (!height) {
+      height = 100;
+    }
+
+    let htmlns = "http://www.w3.org/1999/xhtml";
+    let img = document.createElementNS(htmlns, "img");
+    img.setAttribute("src", dataURI);
+
+    img.addEventListener("load", function onLoad() {
+      img.removeEventListener("load", onLoad, true);
+      let canvas = document.createElementNS(htmlns, "canvas");
+      canvas.setAttribute("width", width);
+      canvas.setAttribute("height", height);
+      let ctx = canvas.getContext("2d");
+      ctx.drawImage(img, 0, 0, width, height);
+      let result = ctx.getImageData(0, 0, width, height).data;
+      resolve(result);
+    });
+  });
+}
+
+function* chunk_four(listData) {
+  let index = 0;
+  while (index < listData.length) {
+    yield listData.slice(index, index + 5);
+    index += 4;
+  }
+}
+
+add_task(function* open_page() {
+  let dataURI = yield PreviewProvider.getThumbnail(TEST_URL);
+  let pixels = yield pixelsForDataURI(dataURI, {width: 10, height: 10});
+  let rgbCount = {r: 0, g: 0, b: 0, a: 0};
+  for (let [r, g, b, a] of chunk_four(pixels)) {
+    if (r === 255) {
+      rgbCount.r += 1;
+    }
+    if (g === 255) {
+      rgbCount.g += 1;
+    }
+    if (b === 255) {
+      rgbCount.b += 1;
+    }
+    if (a === 255) {
+      rgbCount.a += 1;
+    }
+  }
+  Assert.equal(`${rgbCount.r},${rgbCount.g},${rgbCount.b},${rgbCount.a}`,
+      "0,0,100,100", "there should be 100 blue-only pixels at full opacity");
+});
+
+add_task(function* invalid_url() {
+  try {
+    yield PreviewProvider.getThumbnail("invalid:URL");
+  } catch (err) {
+    Assert.ok(true, "URL Failed");
+  }
+});
--- a/browser/components/newtab/tests/browser/browser_newtabmessages.js
+++ b/browser/components/newtab/tests/browser/browser_newtabmessages.js
@@ -12,17 +12,16 @@ function setup() {
   Preferences.set("browser.newtabpage.enhanced", true);
   Preferences.set("browser.newtabpage.remote.mode", "test");
   Preferences.set("browser.newtabpage.remote", true);
   NewTabMessages.init();
 }
 
 function cleanup() {
   NewTabMessages.uninit();
-  NewTabWebChannel.tearDownState();
   Preferences.set("browser.newtabpage.remote", false);
   Preferences.set("browser.newtabpage.remote.mode", "production");
 }
 registerCleanupFunction(cleanup);
 
 /*
  * Sanity tests for pref messages
  */
@@ -50,8 +49,37 @@ add_task(function* prefMessages_request(
         resolve();
       });
     });
     Preferences.set("browser.newtabpage.enhanced", false);
     yield prefChangeAck;
   });
   cleanup();
 });
+
+/*
+ * Sanity tests for preview messages
+ */
+add_task(function* previewMessages_request() {
+  setup();
+  var oldEnabledPref = Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled");
+  Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", false);
+
+  let testURL = "https://example.com/browser/browser/components/newtab/tests/browser/newtabmessages_preview.html";
+
+  let tabOptions = {
+    gBrowser,
+    url: testURL
+  };
+
+  let previewResponseAck = new Promise(resolve => {
+    NewTabWebChannel.once("responseAck", () => {
+      ok(true, "a request response has been received");
+      resolve();
+    });
+  });
+
+  yield BrowserTestUtils.withNewTab(tabOptions, function*() {
+    yield previewResponseAck;
+  });
+  cleanup();
+  Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", oldEnabledPref);
+});
--- a/browser/components/newtab/tests/browser/browser_newtabwebchannel.js
+++ b/browser/components/newtab/tests/browser/browser_newtabwebchannel.js
@@ -2,33 +2,42 @@
 
 "use strict";
 
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabWebChannel",
                                   "resource:///modules/NewTabWebChannel.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabMessages",
+                                  "resource:///modules/NewTabMessages.jsm");
 
 const TEST_URL = "https://example.com/browser/browser/components/newtab/tests/browser/newtabwebchannel_basic.html";
 const TEST_URL_2 = "http://mochi.test:8888/browser/browser/components/newtab/tests/browser/newtabwebchannel_basic.html";
 
+function setup(mode = "test") {
+  Preferences.set("browser.newtabpage.remote.mode", mode);
+  Preferences.set("browser.newtabpage.remote", true);
+  NewTabWebChannel.init();
+  NewTabMessages.init();
+}
+
 function cleanup() {
-  NewTabWebChannel.tearDownState();
+  NewTabMessages.uninit();
+  NewTabWebChannel.uninit();
   Preferences.set("browser.newtabpage.remote", false);
   Preferences.set("browser.newtabpage.remote.mode", "production");
 }
 registerCleanupFunction(cleanup);
 
 /*
  * Tests flow of messages from newtab to chrome and chrome to newtab
  */
 add_task(function* open_webchannel_basic() {
-  Preferences.set("browser.newtabpage.remote.mode", "test");
-  Preferences.set("browser.newtabpage.remote", true);
+  setup();
 
   let tabOptions = {
     gBrowser,
     url: TEST_URL
   };
 
   let messagePromise = new Promise(resolve => {
     NewTabWebChannel.once("foo", function(name, msg) {
@@ -67,18 +76,17 @@ add_task(function* open_webchannel_basic
   yield unloadPromise;
   cleanup();
 });
 
 /*
  * Tests message broadcast reaches all open newtab pages
  */
 add_task(function* webchannel_broadcast() {
-  Preferences.set("browser.newtabpage.remote.mode", "test");
-  Preferences.set("browser.newtabpage.remote", true);
+  setup();
 
   let countingMessagePromise = new Promise(resolve => {
     let count = 0;
     NewTabWebChannel.on("foo", function test_message(name, msg) {
       count += 1;
       if (count === 2) {
         NewTabWebChannel.off("foo", test_message);
         resolve(msg.target);
@@ -128,18 +136,17 @@ add_task(function* webchannel_broadcast(
   yield countingUnloadPromise;
   cleanup();
 });
 
 /*
  * Tests switching modes
  */
 add_task(function* webchannel_switch() {
-  Preferences.set("browser.newtabpage.remote.mode", "test");
-  Preferences.set("browser.newtabpage.remote", true);
+  setup();
 
   function newMessagePromise() {
     return new Promise(resolve => {
       NewTabWebChannel.once("foo", function(name, msg) {
         resolve(msg.target);
       }.bind(this));
     });
   }
@@ -190,18 +197,17 @@ add_task(function* webchannel_switch() {
 
   Cu.forceGC();
   is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
   yield unloadPromise;
   cleanup();
 });
 
 add_task(function* open_webchannel_reload() {
-  Preferences.set("browser.newtabpage.remote.mode", "test");
-  Preferences.set("browser.newtabpage.remote", true);
+  setup();
 
   let tabOptions = {
     gBrowser,
     url: TEST_URL
   };
 
   let messagePromise = new Promise(resolve => {
     NewTabWebChannel.once("foo", function(name, msg) {
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/tests/browser/newtabmessages_preview.html
@@ -0,0 +1,37 @@
+<html>
+  <head>
+    <meta charset="utf8">
+    <title>Newtab WebChannel test</title>
+  </head>
+  <body>
+    <script>
+      let thumbURL = "https://example.com/browser/browser/components/newtab/tests/browser/blue_page.html";
+
+      window.addEventListener("WebChannelMessageToContent", function(e) {
+        if (e.detail.message && e.detail.message.type === "RECEIVE_THUMB") {
+          if (e.detail.message.data.imgData && e.detail.message.data.url === thumbURL) {
+            let reply = new window.CustomEvent("WebChannelMessageToChrome", {
+              detail: {
+                id: "newtab",
+                message: JSON.stringify({type: "responseAck"}),
+              }
+            });
+            window.dispatchEvent(reply);
+          }
+        }
+      }, true);
+
+      document.onreadystatechange = function () {
+        if (document.readyState === "complete") {
+          let msg = new window.CustomEvent("WebChannelMessageToChrome", {
+            detail: {
+              id: "newtab",
+              message: JSON.stringify({type: "REQUEST_THUMB", data: thumbURL}),
+            }
+          });
+          window.dispatchEvent(msg);
+        }
+      };
+    </script>
+  </body>
+</html>
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -22,22 +22,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource:///modules/AboutNewTab.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DirectoryLinksProvider",
                                   "resource:///modules/DirectoryLinksProvider.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
                                   "resource://gre/modules/NewTabUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
-                                  "resource:///modules/NewTabPrefsProvider.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "NewTabWebChannel",
-                                  "resource:///modules/NewTabWebChannel.jsm");
-
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabMessages",
                                   "resource:///modules/NewTabMessages.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "UITour",
                                   "resource:///modules/UITour.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
@@ -754,18 +748,16 @@ BrowserGlue.prototype = {
     webrtcUI.init();
     AboutHome.init();
 
     DirectoryLinksProvider.init();
     NewTabUtils.init();
     NewTabUtils.links.addProvider(DirectoryLinksProvider);
     AboutNewTab.init();
 
-    NewTabPrefsProvider.prefs.init();
-    NewTabWebChannel.init();
     NewTabMessages.init();
 
     SessionStore.init();
     BrowserUITelemetry.init();
     ContentSearch.init();
     FormValidationHandler.init();
 
     ContentClick.init();
@@ -1061,18 +1053,16 @@ BrowserGlue.prototype = {
       let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
                          .getService(Ci.nsIAppStartup);
       appStartup.trackStartupCrashEnd();
     } catch (e) {
       Cu.reportError("Could not end startup crash tracking in quit-application-granted: " + e);
     }
 
     SelfSupportBackend.uninit();
-    NewTabPrefsProvider.prefs.uninit();
-    NewTabWebChannel.uninit();
     NewTabMessages.uninit();
 
     AboutNewTab.uninit();
     webrtcUI.uninit();
     FormValidationHandler.uninit();
     if (AppConstants.NIGHTLY_BUILD) {
       AddonWatcher.uninit();
     }
--- a/devtools/client/shared/components/tree.js
+++ b/devtools/client/shared/components/tree.js
@@ -213,23 +213,27 @@ const Tree = module.exports = createClas
       if (currentDepth >= this.props.autoExpandDepth ||
           this.state.seen.has(item)) {
         return;
       }
 
       this.props.onExpand(item);
       this.state.seen.add(item);
 
-      for (let child of this.props.getChildren(item)) {
-        autoExpand(child, currentDepth + 1);
+      const children = this.props.getChildren(item);
+      const length = children.length;
+      for (let i = 0; i < length; i++) {
+        autoExpand(children[i], currentDepth + 1);
       }
     };
 
-    for (let root of this.props.getRoots()) {
-      autoExpand(root, 0);
+    const roots = this.props.getRoots();
+    const length = roots.length;
+    for (let i = 0; i < length; i++) {
+      autoExpand(roots[i], 0);
     }
   },
 
   render() {
     const traversal = this._dfsFromRoots();
 
     // Remove 1 from `begin` and add 2 to `end` so that the top and bottom of
     // the page are filled with the previous and next items respectively,
@@ -310,49 +314,55 @@ const Tree = module.exports = createClas
     }
 
     const nextDepth = _depth + 1;
 
     if (nextDepth > maxDepth) {
       return traversal;
     }
 
-    for (let child of this.props.getChildren(item)) {
-      this._dfs(child, maxDepth, traversal, nextDepth);
+    const children = this.props.getChildren(item);
+    const length = children.length;
+    for (let i = 0; i < length; i++) {
+      this._dfs(children[i], maxDepth, traversal, nextDepth);
     }
 
     return traversal;
   },
 
   /**
    * Perform a pre-order depth-first search over the whole forest.
    */
   _dfsFromRoots(maxDepth = Infinity) {
     const traversal = [];
 
-    for (let root of this.props.getRoots()) {
-      this._dfs(root, maxDepth, traversal);
+    const roots = this.props.getRoots();
+    const length = roots.length;
+    for (let i = 0; i < length; i++) {
+      this._dfs(roots[i], maxDepth, traversal);
     }
 
     return traversal;
   },
 
   /**
    * Expands current row.
    *
    * @param {Object} item
    * @param {Boolean} expandAllChildren
    */
   _onExpand: oncePerAnimationFrame(function (item, expandAllChildren) {
     if (this.props.onExpand) {
       this.props.onExpand(item);
 
       if (expandAllChildren) {
-        for (let { item: child } of this._dfs(item)) {
-          this.props.onExpand(child);
+        const children = this._dfs(item);
+        const length = children.length;
+        for (let i = 0; i < length; i++) {
+          this.props.onExpand(children[i].item);
         }
       }
     }
   }),
 
   /**
    * Collapses current row.
    *
@@ -448,17 +458,21 @@ const Tree = module.exports = createClas
    * Sets the previous node relative to the currently focused item, to focused.
    */
   _focusPrevNode: oncePerAnimationFrame(function () {
     // Start a depth first search and keep going until we reach the currently
     // focused node. Focus the previous node in the DFS, if it exists. If it
     // doesn't exist, we're at the first node already.
 
     let prev;
-    for (let { item } of this._dfsFromRoots()) {
+
+    const traversal = this._dfsFromRoots();
+    const length = traversal.length;
+    for (let i = 0; i < length; i++) {
+      const item = traversal[i].item;
       if (item === this.props.focused) {
         break;
       }
       prev = item;
     }
 
     if (prev === undefined) {
       return;
@@ -472,20 +486,21 @@ const Tree = module.exports = createClas
    * or sibling row.
    */
   _focusNextNode: oncePerAnimationFrame(function () {
     // Start a depth first search and keep going until we reach the currently
     // focused node. Focus the next node in the DFS, if it exists. If it
     // doesn't exist, we're at the last node already.
 
     const traversal = this._dfsFromRoots();
-
+    const length = traversal.length;
     let i = 0;
-    for (let { item } of traversal) {
-      if (item === this.props.focused) {
+
+    while (i < length) {
+      if (traversal[i].item === this.props.focused) {
         break;
       }
       i++;
     }
 
     if (i + 1 < traversal.length) {
       this._focus(traversal[i + 1].item);
     }
--- a/docshell/base/nsDefaultURIFixup.cpp
+++ b/docshell/base/nsDefaultURIFixup.cpp
@@ -1066,17 +1066,17 @@ nsDefaultURIFixup::KeywordURIFixup(const
     // host is not whitelisted, so we do a keyword search *anyway*:
     rv = TryKeywordFixupForURIInfo(aFixupInfo->mOriginalInput, aFixupInfo,
                                    aPostData);
   }
   return rv;
 }
 
 bool
-nsDefaultURIFixup::IsDomainWhitelisted(const nsAutoCString aAsciiHost,
+nsDefaultURIFixup::IsDomainWhitelisted(const nsACString& aAsciiHost,
                                        const uint32_t aDotLoc)
 {
   if (sDNSFirstForSingleWords) {
     return true;
   }
   // Check if this domain is whitelisted as an actual
   // domain (which will prevent a keyword query)
   // NB: any processing of the host here should stay in sync with
@@ -1093,17 +1093,17 @@ nsDefaultURIFixup::IsDomainWhitelisted(c
   return Preferences::GetBool(pref.get(), false);
 }
 
 NS_IMETHODIMP
 nsDefaultURIFixup::IsDomainWhitelisted(const nsACString& aDomain,
                                        const uint32_t aDotLoc,
                                        bool* aResult)
 {
-  *aResult = IsDomainWhitelisted(nsAutoCString(aDomain), aDotLoc);
+  *aResult = IsDomainWhitelisted(aDomain, aDotLoc);
   return NS_OK;
 }
 
 /* Implementation of nsIURIFixupInfo */
 NS_IMPL_ISUPPORTS(nsDefaultURIFixupInfo, nsIURIFixupInfo)
 
 nsDefaultURIFixupInfo::nsDefaultURIFixupInfo(const nsACString& aOriginalInput)
   : mFixupChangedProtocol(false)
--- a/docshell/base/nsDefaultURIFixup.h
+++ b/docshell/base/nsDefaultURIFixup.h
@@ -35,17 +35,17 @@ private:
                            nsIInputStream** aPostData);
   nsresult TryKeywordFixupForURIInfo(const nsACString& aStringURI,
                                      nsDefaultURIFixupInfo* aFixupInfo,
                                      nsIInputStream** aPostData);
   bool PossiblyByteExpandedFileName(const nsAString& aIn);
   bool PossiblyHostPortUrl(const nsACString& aUrl);
   bool MakeAlternateURI(nsIURI* aURI);
   bool IsLikelyFTP(const nsCString& aHostSpec);
-  bool IsDomainWhitelisted(const nsAutoCString aAsciiHost,
+  bool IsDomainWhitelisted(const nsACString& aAsciiHost,
                            const uint32_t aDotLoc);
 };
 
 class nsDefaultURIFixupInfo : public nsIURIFixupInfo
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIURIFIXUPINFO
--- a/dom/base/nsFrameMessageManager.cpp
+++ b/dom/base/nsFrameMessageManager.cpp
@@ -64,16 +64,18 @@
 #  undef SendMessage
 # endif
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::dom::ipc;
 
+static const int kMinTelemetryMessageSize = 8192;
+
 nsFrameMessageManager::nsFrameMessageManager(mozilla::dom::ipc::MessageManagerCallback* aCallback,
                                              nsFrameMessageManager* aParentManager,
                                              /* mozilla::dom::ipc::MessageManagerFlags */ uint32_t aFlags)
  : mChrome(!!(aFlags & mozilla::dom::ipc::MM_CHROME)),
    mGlobal(!!(aFlags & mozilla::dom::ipc::MM_GLOBAL)),
    mIsProcessManager(!!(aFlags & mozilla::dom::ipc::MM_PROCESSMANAGER)),
    mIsBroadcaster(!!(aFlags & mozilla::dom::ipc::MM_BROADCASTER)),
    mOwnsCallback(!!(aFlags & mozilla::dom::ipc::MM_OWNSCALLBACK)),
@@ -725,16 +727,22 @@ nsFrameMessageManager::SendMessage(const
     return NS_ERROR_UNEXPECTED;
   }
 
   StructuredCloneData data;
   if (aArgc >= 2 && !GetParamsForMessage(aCx, aJSON, JS::UndefinedHandleValue, data)) {
     return NS_ERROR_DOM_DATA_CLONE_ERR;
   }
 
+  if (data.DataLength() >= kMinTelemetryMessageSize) {
+    Telemetry::Accumulate(Telemetry::MESSAGE_MANAGER_MESSAGE_SIZE,
+                          NS_ConvertUTF16toUTF8(aMessageName),
+                          data.DataLength());
+  }
+
   JS::Rooted<JSObject*> objects(aCx);
   if (aArgc >= 3 && aObjects.isObject()) {
     objects = &aObjects.toObject();
   }
 
   nsTArray<StructuredCloneData> retval;
 
   sSendingSyncMessage |= aIsSync;
@@ -805,16 +813,22 @@ nsFrameMessageManager::DispatchAsyncMess
                                             JSContext* aCx,
                                             uint8_t aArgc)
 {
   StructuredCloneData data;
   if (aArgc >= 2 && !GetParamsForMessage(aCx, aJSON, aTransfers, data)) {
     return NS_ERROR_DOM_DATA_CLONE_ERR;
   }
 
+  if (data.DataLength() >= kMinTelemetryMessageSize) {
+    Telemetry::Accumulate(Telemetry::MESSAGE_MANAGER_MESSAGE_SIZE,
+                          NS_ConvertUTF16toUTF8(aMessageName),
+                          data.DataLength());
+  }
+
   JS::Rooted<JSObject*> objects(aCx);
   if (aArgc >= 3 && aObjects.isObject()) {
     objects = &aObjects.toObject();
   }
 
   return DispatchAsyncMessageInternal(aCx, aMessageName, data, objects,
                                       aPrincipal);
 }
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -1359,17 +1359,16 @@ GK_ATOM(colorInterpolation, "color-inter
 GK_ATOM(colorInterpolationFilters, "color-interpolation-filters")
 GK_ATOM(colorProfile, "color-profile")
 GK_ATOM(cursor, "cursor")
 GK_ATOM(cx, "cx")
 GK_ATOM(cy, "cy")
 GK_ATOM(d, "d")
 GK_ATOM(darken, "darken")
 GK_ATOM(defs, "defs")
-GK_ATOM(definition_src, "definition-src")
 GK_ATOM(deg, "deg")
 GK_ATOM(desc, "desc")
 GK_ATOM(diffuseConstant, "diffuseConstant")
 GK_ATOM(dilate, "dilate")
 GK_ATOM(direction, "direction")
 GK_ATOM(disable, "disable")
 GK_ATOM(discrete, "discrete")
 GK_ATOM(divisor, "divisor")
@@ -1433,37 +1432,32 @@ GK_ATOM(foreignObject, "foreignObject")
 GK_ATOM(fractalNoise, "fractalNoise")
 GK_ATOM(fx, "fx")
 GK_ATOM(fy, "fy")
 GK_ATOM(G, "G")
 GK_ATOM(g, "g")
 GK_ATOM(gamma, "gamma")
 // 'generic' conflicts with msvc11 winrt compiler extensions
 GK_ATOM(generic_, "generic")
-GK_ATOM(glyph, "glyph")
 GK_ATOM(glyphRef, "glyphRef")
-GK_ATOM(glyph_orientation_horizontal, "glyph-orientation-horizontal")
-GK_ATOM(glyph_orientation_vertical, "glyph-orientation-vertical")
 GK_ATOM(grad, "grad")
 GK_ATOM(gradientTransform, "gradientTransform")
 GK_ATOM(gradientUnits, "gradientUnits")
 GK_ATOM(hardLight, "hard-light")
-GK_ATOM(hkern, "hkern")
 GK_ATOM(hue, "hue")
 GK_ATOM(hueRotate, "hueRotate")
 GK_ATOM(identity, "identity")
 GK_ATOM(image_rendering, "image-rendering")
 GK_ATOM(in, "in")
 GK_ATOM(in2, "in2")
 GK_ATOM(intercept, "intercept")
 GK_ATOM(k1, "k1")
 GK_ATOM(k2, "k2")
 GK_ATOM(k3, "k3")
 GK_ATOM(k4, "k4")
-GK_ATOM(kerning, "kerning")
 GK_ATOM(kernelMatrix, "kernelMatrix")
 GK_ATOM(kernelUnitLength, "kernelUnitLength")
 GK_ATOM(lengthAdjust, "lengthAdjust")
 GK_ATOM(letter_spacing, "letter-spacing")
 GK_ATOM(lighten, "lighten")
 GK_ATOM(lighting_color, "lighting-color")
 GK_ATOM(limitingConeAngle, "limitingConeAngle")
 GK_ATOM(linear, "linear")
@@ -1582,19 +1576,19 @@ GK_ATOM(textPath, "textPath")
 GK_ATOM(tref, "tref")
 GK_ATOM(tspan, "tspan")
 GK_ATOM(turbulence, "turbulence")
 GK_ATOM(unicode_bidi, "unicode-bidi")
 GK_ATOM(userSpaceOnUse, "userSpaceOnUse")
 GK_ATOM(view, "view")
 GK_ATOM(viewBox, "viewBox")
 GK_ATOM(viewTarget, "viewTarget")
-GK_ATOM(vkern, "vkern")
 GK_ATOM(white_space, "white-space")
 GK_ATOM(word_spacing, "word-spacing")
+GK_ATOM(writing_mode, "writing-mode")
 GK_ATOM(x, "x")
 GK_ATOM(x1, "x1")
 GK_ATOM(x2, "x2")
 GK_ATOM(xChannelSelector, "xChannelSelector")
 GK_ATOM(xor_, "xor")
 GK_ATOM(y, "y")
 GK_ATOM(y1, "y1")
 GK_ATOM(y2, "y2")
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -6445,20 +6445,17 @@ nsGlobalWindow::CanMoveResizeWindows(boo
       uint32_t itemCount;
       if (NS_SUCCEEDED(treeOwner->GetTargetableShellCount(&itemCount)) &&
           itemCount > 1) {
         return false;
       }
     }
   }
 
-  // The preference is useful for the webapp runtime. Webapps should be able
-  // to resize or move their window.
-  if (mDocShell && !Preferences::GetBool("dom.always_allow_move_resize_window",
-                                         false)) {
+  if (mDocShell) {
     bool allow;
     nsresult rv = mDocShell->GetAllowWindowControl(&allow);
     if (NS_SUCCEEDED(rv) && !allow)
       return false;
   }
 
   if (gMouseDown && !gDragServiceDisabled) {
     nsCOMPtr<nsIDragService> ds =
--- a/dom/base/nsTreeSanitizer.cpp
+++ b/dom/base/nsTreeSanitizer.cpp
@@ -325,19 +325,19 @@ nsIAtom** const kElementsSVG[] = {
   &nsGkAtoms::font, // font
   &nsGkAtoms::font_face, // font-face
   &nsGkAtoms::font_face_format, // font-face-format
   &nsGkAtoms::font_face_name, // font-face-name
   &nsGkAtoms::font_face_src, // font-face-src
   &nsGkAtoms::font_face_uri, // font-face-uri
   &nsGkAtoms::foreignObject, // foreignObject
   &nsGkAtoms::g, // g
-  &nsGkAtoms::glyph, // glyph
+  // glyph
   &nsGkAtoms::glyphRef, // glyphRef
-  &nsGkAtoms::hkern, // hkern
+  // hkern
   &nsGkAtoms::image, // image
   &nsGkAtoms::line, // line
   &nsGkAtoms::linearGradient, // linearGradient
   &nsGkAtoms::marker, // marker
   &nsGkAtoms::mask, // mask
   &nsGkAtoms::metadata, // metadata
   &nsGkAtoms::missingGlyph, // missingGlyph
   &nsGkAtoms::mpath, // mpath
@@ -353,17 +353,17 @@ nsIAtom** const kElementsSVG[] = {
   &nsGkAtoms::symbol, // symbol
   &nsGkAtoms::text, // text
   &nsGkAtoms::textPath, // textPath
   &nsGkAtoms::title, // title
   &nsGkAtoms::tref, // tref
   &nsGkAtoms::tspan, // tspan
   &nsGkAtoms::use, // use
   &nsGkAtoms::view, // view
-  &nsGkAtoms::vkern, // vkern
+  // vkern
   nullptr
 };
 
 nsIAtom** const kAttributesSVG[] = {
   // accent-height
   &nsGkAtoms::accumulate, // accumulate
   &nsGkAtoms::additive, // additive
   &nsGkAtoms::alignment_baseline, // alignment-baseline
@@ -426,18 +426,18 @@ nsIAtom** const kAttributesSVG[] = {
   &nsGkAtoms::format, // format
   &nsGkAtoms::from, // from
   &nsGkAtoms::fx, // fx
   &nsGkAtoms::fy, // fy
   // g1
   // g2
   // glyph-name
   // glyphRef
-  &nsGkAtoms::glyph_orientation_horizontal, // glyph-orientation-horizontal
-  &nsGkAtoms::glyph_orientation_vertical, // glyph-orientation-vertical
+  // glyph-orientation-horizontal
+  // glyph-orientation-vertical
   &nsGkAtoms::gradientTransform, // gradientTransform
   &nsGkAtoms::gradientUnits, // gradientUnits
   &nsGkAtoms::height, // height
   // horiz-adv-x
   // horiz-origin-x
   // horiz-origin-y
   &nsGkAtoms::id, // id
   // ideographic
@@ -445,17 +445,17 @@ nsIAtom** const kAttributesSVG[] = {
   &nsGkAtoms::in, // in
   &nsGkAtoms::in2, // in2
   &nsGkAtoms::intercept, // intercept
   // k
   &nsGkAtoms::k1, // k1
   &nsGkAtoms::k2, // k2
   &nsGkAtoms::k3, // k3
   &nsGkAtoms::k4, // k4
-  &nsGkAtoms::kerning, // kerning
+  // kerning
   &nsGkAtoms::kernelMatrix, // kernelMatrix
   &nsGkAtoms::kernelUnitLength, // kernelUnitLength
   &nsGkAtoms::keyPoints, // keyPoints
   &nsGkAtoms::keySplines, // keySplines
   &nsGkAtoms::keyTimes, // keyTimes
   &nsGkAtoms::lang, // lang
   // lengthAdjust
   &nsGkAtoms::letter_spacing, // letter-spacing
@@ -575,17 +575,17 @@ nsIAtom** const kAttributesSVG[] = {
   // vert-origin-x
   // vert-origin-y
   &nsGkAtoms::viewBox, // viewBox
   &nsGkAtoms::viewTarget, // viewTarget
   &nsGkAtoms::visibility, // visibility
   &nsGkAtoms::width, // width
   // widths
   &nsGkAtoms::word_spacing, // word-spacing
-  // writing-mode
+  &nsGkAtoms::writing_mode, // writing-mode
   &nsGkAtoms::x, // x
   // x-height
   &nsGkAtoms::x1, // x1
   &nsGkAtoms::x2, // x2
   &nsGkAtoms::xChannelSelector, // xChannelSelector
   &nsGkAtoms::y, // y
   &nsGkAtoms::y1, // y1
   &nsGkAtoms::y2, // y2
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -2998,19 +2998,16 @@ MayResolveGlobal(const JSAtomState& aNam
 bool
 EnumerateGlobal(JSContext* aCx, JS::Handle<JSObject*> aObj);
 
 template <class T>
 struct CreateGlobalOptions
 {
   static MOZ_CONSTEXPR_VAR ProtoAndIfaceCache::Kind ProtoAndIfaceCacheKind =
     ProtoAndIfaceCache::NonWindowLike;
-  // Intl API is broken and makes JS_InitStandardClasses fail intermittently,
-  // see bug 934889.
-  static MOZ_CONSTEXPR_VAR bool ForceInitStandardClassesToFalse = true;
   static void TraceGlobal(JSTracer* aTrc, JSObject* aObj)
   {
     mozilla::dom::TraceProtoAndIfaceCache(aTrc, aObj);
   }
   static bool PostCreateGlobal(JSContext* aCx, JS::Handle<JSObject*> aGlobal)
   {
     MOZ_ALWAYS_TRUE(TryPreserveWrapper(aGlobal));
 
@@ -3018,17 +3015,16 @@ struct CreateGlobalOptions
   }
 };
 
 template <>
 struct CreateGlobalOptions<nsGlobalWindow>
 {
   static MOZ_CONSTEXPR_VAR ProtoAndIfaceCache::Kind ProtoAndIfaceCacheKind =
     ProtoAndIfaceCache::WindowLike;
-  static MOZ_CONSTEXPR_VAR bool ForceInitStandardClassesToFalse = false;
   static void TraceGlobal(JSTracer* aTrc, JSObject* aObj);
   static bool PostCreateGlobal(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
 };
 
 nsresult
 RegisterDOMNames();
 
 // The return value is whatever the ProtoHandleGetter we used
@@ -3069,17 +3065,16 @@ CreateGlobal(JSContext* aCx, T* aNative,
                                     CreateGlobalOptions<T>::ProtoAndIfaceCacheKind);
 
     if (!CreateGlobalOptions<T>::PostCreateGlobal(aCx, aGlobal)) {
       return nullptr;
     }
   }
 
   if (aInitStandardClasses &&
-      !CreateGlobalOptions<T>::ForceInitStandardClassesToFalse &&
       !JS_InitStandardClasses(aCx, aGlobal)) {
     NS_WARNING("Failed to init standard classes");
     return nullptr;
   }
 
   JS::Handle<JSObject*> proto = GetProto(aCx, aGlobal);
   if (!proto || !JS_SplicePrototype(aCx, aGlobal, proto)) {
     NS_WARNING("Failed to set proto");
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -950,35 +950,16 @@ DOMInterfaces = {
     'nativeType': 'mozilla::dom::workers::PushEvent',
 },
 
 'PushMessageData': {
     'headerFile': 'ServiceWorkerEvents.h',
     'nativeType': 'mozilla::dom::workers::PushMessageData',
 },
 
-'PushManager': [{
-    'workers': False,
-    'headerFile': 'mozilla/dom/PushManager.h',
-    'nativeType': 'mozilla::dom::PushManager',
-}, {
-    'workers': True,
-    'headerFile': 'mozilla/dom/PushManager.h',
-    'nativeType': 'mozilla::dom::WorkerPushManager',
-}],
-
-'PushSubscription': [{
-    'workers': False,
-    'headerFile': 'mozilla/dom/PushManager.h',
-}, {
-    'workers': True,
-    'headerFile': 'mozilla/dom/PushManager.h',
-    'nativeType': 'mozilla::dom::WorkerPushSubscription',
-}],
-
 'Range': {
     'nativeType': 'nsRange',
     'binaryNames': {
         '__stringifier': 'ToString'
     }
 },
 
 'Rect': {
@@ -1012,16 +993,17 @@ DOMInterfaces = {
 'ServiceWorkerGlobalScope': {
     'headerFile': 'mozilla/dom/WorkerScope.h',
     'workers': True,
 },
 
 'ServiceWorkerRegistration': [{
     'nativeType': 'mozilla::dom::ServiceWorkerRegistrationMainThread',
     'headerFile': 'mozilla/dom/ServiceWorkerRegistration.h',
+    'implicitJSContext': [ 'pushManager' ],
 }, {
     'workers': True,
     'nativeType': 'mozilla::dom::ServiceWorkerRegistrationWorkerThread',
     'headerFile': 'mozilla/dom/ServiceWorkerRegistration.h',
 }],
 
 'SharedWorker': {
     'nativeType': 'mozilla::dom::workers::SharedWorker',
--- a/dom/indexedDB/ScriptErrorHelper.cpp
+++ b/dom/indexedDB/ScriptErrorHelper.cpp
@@ -62,31 +62,31 @@ public:
     , mInnerWindowID(aInnerWindowID)
     , mIsChrome(aIsChrome)
   {
     MOZ_ASSERT(!NS_IsMainThread());
     mMessage.SetIsVoid(true);
   }
 
   static void
-  DumpLocalizedMessage(const nsCString& aMessageName,
+  DumpLocalizedMessage(const nsACString& aMessageName,
                        const nsAString& aFilename,
                        uint32_t aLineNumber,
                        uint32_t aColumnNumber,
                        uint32_t aSeverityFlag,
                        bool aIsChrome,
                        uint64_t aInnerWindowID)
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(!aMessageName.IsEmpty());
 
     nsXPIDLString localizedMessage;
     if (NS_WARN_IF(NS_FAILED(
       nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
-                                         aMessageName.get(),
+                                         aMessageName.BeginReading(),
                                          localizedMessage)))) {
       return;
     }
 
     Dump(localizedMessage,
          aFilename,
          aLineNumber,
          aColumnNumber,
@@ -219,17 +219,17 @@ ScriptErrorHelper::DumpLocalizedMessage(
                                         const nsAString& aFilename,
                                         uint32_t aLineNumber,
                                         uint32_t aColumnNumber,
                                         uint32_t aSeverityFlag,
                                         bool aIsChrome,
                                         uint64_t aInnerWindowID)
 {
   if (NS_IsMainThread()) {
-    ScriptErrorRunnable::DumpLocalizedMessage(nsAutoCString(aMessageName),
+    ScriptErrorRunnable::DumpLocalizedMessage(aMessageName,
                                               aFilename,
                                               aLineNumber,
                                               aColumnNumber,
                                               aSeverityFlag,
                                               aIsChrome,
                                               aInnerWindowID);
   } else {
     RefPtr<ScriptErrorRunnable> runnable =
--- a/dom/push/Push.js
+++ b/dom/push/Push.js
@@ -51,18 +51,17 @@ Push.prototype = {
 
     this._window = aWindow;
 
     this.initDOMRequestHelper(aWindow);
 
     this._principal = aWindow.document.nodePrincipal;
   },
 
-  setScope: function(scope){
-    console.debug("setScope()", scope);
+  __init: function(scope) {
     this._scope = scope;
   },
 
   askPermission: function (aAllowCallback, aCancelCallback) {
     console.debug("askPermission()");
 
     return this.createPromise((resolve, reject) => {
       let permissionDenied = () => {
@@ -204,17 +203,16 @@ PushSubscriptionCallback.prototype = {
     }
 
     let publicKey = this._getKey(subscription, "p256dh");
     let authSecret = this._getKey(subscription, "auth");
     let sub = new pushManager._window.PushSubscription(subscription.endpoint,
                                                        pushManager._scope,
                                                        publicKey,
                                                        authSecret);
-    sub.setPrincipal(pushManager._principal);
     this.resolve(sub);
   },
 
   _getKey: function(subscription, name) {
     let outKeyLen = {};
     let rawKey = subscription.getKey(name, outKeyLen);
     if (!outKeyLen.value) {
       return null;
--- a/dom/push/PushManager.cpp
+++ b/dom/push/PushManager.cpp
@@ -1,50 +1,46 @@
 /* -*- 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/PushSubscriptionBinding.h"
-#include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
+#include "mozilla/dom/PushSubscription.h"
 
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseWorkerProxy.h"
 
 #include "nsIGlobalObject.h"
 #include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
 #include "nsIPushService.h"
 
 #include "nsComponentManagerUtils.h"
-#include "nsFrameMessageManager.h"
-#include "nsContentCID.h"
+#include "nsContentUtils.h"
 
 #include "WorkerRunnable.h"
 #include "WorkerPrivate.h"
 #include "WorkerScope.h"
 
 namespace mozilla {
 namespace dom {
 
 using namespace workers;
 
 namespace {
 
 nsresult
 GetPermissionState(nsIPrincipal* aPrincipal,
-                            PushPermissionState& aState)
+                   PushPermissionState& aState)
 {
   nsCOMPtr<nsIPermissionManager> permManager =
     mozilla::services::GetPermissionManager();
 
   if (!permManager) {
     return NS_ERROR_FAILURE;
   }
   uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
@@ -58,555 +54,60 @@ GetPermissionState(nsIPrincipal* aPrinci
 
   if (permission == nsIPermissionManager::ALLOW_ACTION) {
     aState = PushPermissionState::Granted;
   } else if (permission == nsIPermissionManager::DENY_ACTION) {
     aState = PushPermissionState::Denied;
   } else {
     aState = PushPermissionState::Prompt;
   }
+
   return NS_OK;
 }
 
-void
-SubscriptionToJSON(PushSubscriptionJSON& aJSON, const nsString& aEndpoint,
-                   const nsTArray<uint8_t>& aRawP256dhKey,
-                   const nsTArray<uint8_t>& aAuthSecret)
-{
-  aJSON.mEndpoint.Construct();
-  aJSON.mEndpoint.Value() = aEndpoint;
-
-  aJSON.mKeys.mP256dh.Construct();
-  nsresult rv = Base64URLEncode(aRawP256dhKey.Length(),
-                                aRawP256dhKey.Elements(),
-                                aJSON.mKeys.mP256dh.Value());
-  Unused << NS_WARN_IF(NS_FAILED(rv));
-
-  aJSON.mKeys.mAuth.Construct();
-  rv = Base64URLEncode(aAuthSecret.Length(), aAuthSecret.Elements(),
-                       aJSON.mKeys.mAuth.Value());
-  Unused << NS_WARN_IF(NS_FAILED(rv));
-}
-
-} // anonymous namespace
-
-class UnsubscribeResultCallback final : public nsIUnsubscribeResultCallback
-{
-public:
-  NS_DECL_ISUPPORTS
-
-  explicit UnsubscribeResultCallback(Promise* aPromise)
-    : mPromise(aPromise)
-  {
-    AssertIsOnMainThread();
-  }
-
-  NS_IMETHOD
-  OnUnsubscribe(nsresult aStatus, bool aSuccess) override
-  {
-    if (NS_SUCCEEDED(aStatus)) {
-      mPromise->MaybeResolve(aSuccess);
-    } else {
-      mPromise->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE);
-    }
-
-    return NS_OK;
-  }
-
-private:
-  ~UnsubscribeResultCallback()
-  {}
-
-  RefPtr<Promise> mPromise;
-};
-
-NS_IMPL_ISUPPORTS(UnsubscribeResultCallback, nsIUnsubscribeResultCallback)
-
-already_AddRefed<Promise>
-PushSubscription::Unsubscribe(ErrorResult& aRv)
-{
-  MOZ_ASSERT(mPrincipal);
-
-  nsCOMPtr<nsIPushService> service =
-    do_GetService("@mozilla.org/push/Service;1");
-  if (NS_WARN_IF(!service)) {
-    aRv = NS_ERROR_FAILURE;
-    return nullptr;
-  }
-
-  RefPtr<Promise> p = Promise::Create(mGlobal, aRv);
-  if (NS_WARN_IF(aRv.Failed())) {
-    return nullptr;
-  }
-
-  RefPtr<UnsubscribeResultCallback> callback =
-    new UnsubscribeResultCallback(p);
-  Unused << NS_WARN_IF(NS_FAILED(
-    service->Unsubscribe(mScope, mPrincipal, callback)));
-  return p.forget();
-}
-
-void
-PushSubscription::ToJSON(PushSubscriptionJSON& aJSON)
-{
-  SubscriptionToJSON(aJSON, mEndpoint, mRawP256dhKey, mAuthSecret);
-}
-
-PushSubscription::PushSubscription(nsIGlobalObject* aGlobal,
-                                   const nsAString& aEndpoint,
-                                   const nsAString& aScope,
-                                   const nsTArray<uint8_t>& aRawP256dhKey,
-                                   const nsTArray<uint8_t>& aAuthSecret)
-  : mGlobal(aGlobal)
-  , mEndpoint(aEndpoint)
-  , mScope(aScope)
-  , mRawP256dhKey(aRawP256dhKey)
-  , mAuthSecret(aAuthSecret)
-{
-}
-
-PushSubscription::~PushSubscription()
-{
-}
-
-JSObject*
-PushSubscription::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
-{
-  return PushSubscriptionBinding::Wrap(aCx, this, aGivenProto);
-}
-
-void
-PushSubscription::GetKey(JSContext* aCx,
-                         PushEncryptionKeyName aType,
-                         JS::MutableHandle<JSObject*> aKey)
-{
-  if (aType == PushEncryptionKeyName::P256dh && !mRawP256dhKey.IsEmpty()) {
-    aKey.set(ArrayBuffer::Create(aCx,
-                                 mRawP256dhKey.Length(),
-                                 mRawP256dhKey.Elements()));
-  } else if (aType == PushEncryptionKeyName::Auth && !mAuthSecret.IsEmpty()) {
-    aKey.set(ArrayBuffer::Create(aCx,
-                                 mAuthSecret.Length(),
-                                 mAuthSecret.Elements()));
-  } else {
-    aKey.set(nullptr);
-  }
-}
-
-void
-PushSubscription::SetPrincipal(nsIPrincipal* aPrincipal)
-{
-  MOZ_ASSERT(!mPrincipal);
-  mPrincipal = aPrincipal;
-}
-
-// static
-already_AddRefed<PushSubscription>
-PushSubscription::Constructor(GlobalObject& aGlobal,
-                              const nsAString& aEndpoint,
-                              const nsAString& aScope,
-                              const Nullable<ArrayBuffer>& aP256dhKey,
-                              const Nullable<ArrayBuffer>& aAuthSecret,
-                              ErrorResult& aRv)
-{
-  MOZ_ASSERT(!aEndpoint.IsEmpty());
-  MOZ_ASSERT(!aScope.IsEmpty());
-
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
-
-  nsTArray<uint8_t> rawKey;
-  if (!aP256dhKey.IsNull()) {
-    const ArrayBuffer& key = aP256dhKey.Value();
-    key.ComputeLengthAndData();
-    rawKey.InsertElementsAt(0, key.Data(), key.Length());
-  }
-
-  nsTArray<uint8_t> authSecret;
-  if (!aAuthSecret.IsNull()) {
-    const ArrayBuffer& sekrit = aAuthSecret.Value();
-    sekrit.ComputeLengthAndData();
-    authSecret.InsertElementsAt(0, sekrit.Data(), sekrit.Length());
-  }
-  RefPtr<PushSubscription> sub = new PushSubscription(global,
-                                                      aEndpoint,
-                                                      aScope,
-                                                      rawKey,
-                                                      authSecret);
-
-  return sub.forget();
-}
-
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushSubscription, mGlobal, mPrincipal)
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(PushSubscription)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(PushSubscription)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushSubscription)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
-
-PushManager::PushManager(nsIGlobalObject* aGlobal, const nsAString& aScope)
-  : mGlobal(aGlobal), mScope(aScope)
-{
-  AssertIsOnMainThread();
-}
-
-PushManager::~PushManager()
-{}
-
-JSObject*
-PushManager::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
-{
-  // XXXnsm I don't know if this is the right way to do it, but I want to assert
-  // that an implementation has been set before this object gets exposed to JS.
-  MOZ_ASSERT(mImpl);
-  return PushManagerBinding::Wrap(aCx, this, aGivenProto);
-}
-
-void
-PushManager::SetPushManagerImpl(PushManagerImpl& foo, ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!mImpl);
-  mImpl = &foo;
-}
-
-already_AddRefed<Promise>
-PushManager::Subscribe(ErrorResult& aRv)
-{
-  MOZ_ASSERT(mImpl);
-  return mImpl->Subscribe(aRv);
-}
-
-already_AddRefed<Promise>
-PushManager::GetSubscription(ErrorResult& aRv)
-{
-  MOZ_ASSERT(mImpl);
-  return mImpl->GetSubscription(aRv);
-}
-
-already_AddRefed<Promise>
-PushManager::PermissionState(ErrorResult& aRv)
-{
-  MOZ_ASSERT(mImpl);
-  return mImpl->PermissionState(aRv);
-}
-
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushManager, mGlobal, mImpl)
-NS_IMPL_CYCLE_COLLECTING_ADDREF(PushManager)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(PushManager)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushManager)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
-
-// WorkerPushSubscription
-
-WorkerPushSubscription::WorkerPushSubscription(const nsAString& aEndpoint,
-                                               const nsAString& aScope,
-                                               const nsTArray<uint8_t>& aRawP256dhKey,
-                                               const nsTArray<uint8_t>& aAuthSecret)
-  : mEndpoint(aEndpoint)
-  , mScope(aScope)
-  , mRawP256dhKey(aRawP256dhKey)
-  , mAuthSecret(aAuthSecret)
-{
-  MOZ_ASSERT(!aScope.IsEmpty());
-  MOZ_ASSERT(!aEndpoint.IsEmpty());
-}
-
-WorkerPushSubscription::~WorkerPushSubscription()
-{}
-
-JSObject*
-WorkerPushSubscription::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
-{
-  return PushSubscriptionBinding_workers::Wrap(aCx, this, aGivenProto);
-}
-
-// static
-already_AddRefed<WorkerPushSubscription>
-WorkerPushSubscription::Constructor(GlobalObject& aGlobal,
-                                    const nsAString& aEndpoint,
-                                    const nsAString& aScope,
-                                    const Nullable<ArrayBuffer>& aP256dhKey,
-                                    const Nullable<ArrayBuffer>& aAuthSecret,
-                                    ErrorResult& aRv)
-{
-  WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
-  MOZ_ASSERT(worker);
-  worker->AssertIsOnWorkerThread();
-
-  nsTArray<uint8_t> rawKey;
-  if (!aP256dhKey.IsNull()) {
-    const ArrayBuffer& key = aP256dhKey.Value();
-    key.ComputeLengthAndData();
-    rawKey.SetLength(key.Length());
-    rawKey.ReplaceElementsAt(0, key.Length(), key.Data(), key.Length());
-  }
-
-  nsTArray<uint8_t> authSecret;
-  if (!aAuthSecret.IsNull()) {
-    const ArrayBuffer& sekrit = aAuthSecret.Value();
-    sekrit.ComputeLengthAndData();
-    authSecret.SetLength(sekrit.Length());
-    authSecret.ReplaceElementsAt(0, sekrit.Length(),
-                                 sekrit.Data(), sekrit.Length());
-  }
-  RefPtr<WorkerPushSubscription> sub = new WorkerPushSubscription(aEndpoint,
-                                                                  aScope,
-                                                                  rawKey,
-                                                                  authSecret);
-
-  return sub.forget();
-}
-
-void
-WorkerPushSubscription::GetKey(JSContext* aCx,
-                               PushEncryptionKeyName aType,
-                               JS::MutableHandle<JSObject*> aKey)
-{
-  if (aType == mozilla::dom::PushEncryptionKeyName::P256dh &&
-      !mRawP256dhKey.IsEmpty()) {
-    aKey.set(ArrayBuffer::Create(aCx,
-                                 mRawP256dhKey.Length(),
-                                 mRawP256dhKey.Elements()));
-  } else if (aType == mozilla::dom::PushEncryptionKeyName::Auth &&
-             !mAuthSecret.IsEmpty()) {
-    aKey.set(ArrayBuffer::Create(aCx,
-                                 mAuthSecret.Length(),
-                                 mAuthSecret.Elements()));
-  } else {
-    aKey.set(nullptr);
-  }
-}
-
-class UnsubscribeResultRunnable final : public WorkerRunnable
-{
-public:
-  UnsubscribeResultRunnable(PromiseWorkerProxy* aProxy,
-                            nsresult aStatus,
-                            bool aSuccess)
-    : WorkerRunnable(aProxy->GetWorkerPrivate(), WorkerThreadModifyBusyCount)
-    , mProxy(aProxy)
-    , mStatus(aStatus)
-    , mSuccess(aSuccess)
-  {
-    AssertIsOnMainThread();
-  }
-
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
-  {
-    MOZ_ASSERT(aWorkerPrivate);
-    aWorkerPrivate->AssertIsOnWorkerThread();
-
-    RefPtr<Promise> promise = mProxy->WorkerPromise();
-    if (NS_SUCCEEDED(mStatus)) {
-      promise->MaybeResolve(mSuccess);
-    } else {
-      promise->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE);
-    }
-
-    mProxy->CleanUp();
-    return true;
-  }
-private:
-  ~UnsubscribeResultRunnable()
-  {}
-
-  RefPtr<PromiseWorkerProxy> mProxy;
-  nsresult mStatus;
-  bool mSuccess;
-};
-
-class WorkerUnsubscribeResultCallback final : public nsIUnsubscribeResultCallback
-{
-public:
-  NS_DECL_ISUPPORTS
-
-  explicit WorkerUnsubscribeResultCallback(PromiseWorkerProxy* aProxy)
-    : mProxy(aProxy)
-  {
-    AssertIsOnMainThread();
-  }
-
-  NS_IMETHOD
-  OnUnsubscribe(nsresult aStatus, bool aSuccess) override
-  {
-    AssertIsOnMainThread();
-    MOZ_ASSERT(mProxy, "OnUnsubscribe() called twice?");
-
-    RefPtr<PromiseWorkerProxy> proxy = mProxy.forget();
-
-    MutexAutoLock lock(proxy->Lock());
-    if (proxy->CleanedUp()) {
-      return NS_OK;
-    }
-
-    RefPtr<UnsubscribeResultRunnable> r =
-      new UnsubscribeResultRunnable(proxy, aStatus, aSuccess);
-    r->Dispatch();
-    return NS_OK;
-  }
-
-private:
-  ~WorkerUnsubscribeResultCallback()
-  {
-  }
-
-  RefPtr<PromiseWorkerProxy> mProxy;
-};
-
-NS_IMPL_ISUPPORTS(WorkerUnsubscribeResultCallback, nsIUnsubscribeResultCallback)
-
-class UnsubscribeRunnable final : public nsRunnable
-{
-public:
-  UnsubscribeRunnable(PromiseWorkerProxy* aProxy,
-                      const nsAString& aScope)
-    : mProxy(aProxy)
-    , mScope(aScope)
-  {
-    MOZ_ASSERT(aProxy);
-    MOZ_ASSERT(!aScope.IsEmpty());
-  }
-
-  NS_IMETHOD
-  Run() override
-  {
-    AssertIsOnMainThread();
-
-    nsCOMPtr<nsIPrincipal> principal;
-    {
-      MutexAutoLock lock(mProxy->Lock());
-      if (mProxy->CleanedUp()) {
-        return NS_OK;
-      }
-      principal = mProxy->GetWorkerPrivate()->GetPrincipal();
-    }
-    MOZ_ASSERT(principal);
-
-    RefPtr<WorkerUnsubscribeResultCallback> callback =
-      new WorkerUnsubscribeResultCallback(mProxy);
-
-    nsCOMPtr<nsIPushService> service =
-      do_GetService("@mozilla.org/push/Service;1");
-    if (!service) {
-      callback->OnUnsubscribe(NS_ERROR_FAILURE, false);
-      return NS_OK;
-    }
-
-    if (NS_WARN_IF(NS_FAILED(service->Unsubscribe(mScope, principal, callback)))) {
-      callback->OnUnsubscribe(NS_ERROR_FAILURE, false);
-      return NS_OK;
-    }
-    return NS_OK;
-  }
-
-private:
-  ~UnsubscribeRunnable()
-  {}
-
-  RefPtr<PromiseWorkerProxy> mProxy;
-  nsString mScope;
-};
-
-already_AddRefed<Promise>
-WorkerPushSubscription::Unsubscribe(ErrorResult &aRv)
-{
-  WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
-  MOZ_ASSERT(worker);
-  worker->AssertIsOnWorkerThread();
-
-  nsCOMPtr<nsIGlobalObject> global = worker->GlobalScope();
-  RefPtr<Promise> p = Promise::Create(global, aRv);
-  if (NS_WARN_IF(aRv.Failed())) {
-    return nullptr;
-  }
-
-  RefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(worker, p);
-  if (!proxy) {
-    p->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE);
-    return p.forget();
-  }
-
-  RefPtr<UnsubscribeRunnable> r =
-    new UnsubscribeRunnable(proxy, mScope);
-  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r));
-
-  return p.forget();
-}
-
-void
-WorkerPushSubscription::ToJSON(PushSubscriptionJSON& aJSON)
-{
-  SubscriptionToJSON(aJSON, mEndpoint, mRawP256dhKey, mAuthSecret);
-}
-
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WorkerPushSubscription)
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(WorkerPushSubscription)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(WorkerPushSubscription)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkerPushSubscription)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
-
-// WorkerPushManager
-
-WorkerPushManager::WorkerPushManager(const nsAString& aScope)
-  : mScope(aScope)
-{
-}
-
-JSObject*
-WorkerPushManager::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
-{
-  return PushManagerBinding_workers::Wrap(aCx, this, aGivenProto);
-}
-
 class GetSubscriptionResultRunnable final : public WorkerRunnable
 {
 public:
-  GetSubscriptionResultRunnable(PromiseWorkerProxy* aProxy,
+  GetSubscriptionResultRunnable(WorkerPrivate* aWorkerPrivate,
+                                already_AddRefed<PromiseWorkerProxy>&& aProxy,
                                 nsresult aStatus,
                                 const nsAString& aEndpoint,
                                 const nsAString& aScope,
-                                const nsTArray<uint8_t>& aRawP256dhKey,
-                                const nsTArray<uint8_t>& aAuthSecret)
-    : WorkerRunnable(aProxy->GetWorkerPrivate(), WorkerThreadModifyBusyCount)
-    , mProxy(aProxy)
+                                nsTArray<uint8_t>&& aRawP256dhKey,
+                                nsTArray<uint8_t>&& aAuthSecret)
+    : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
+    , mProxy(Move(aProxy))
     , mStatus(aStatus)
     , mEndpoint(aEndpoint)
     , mScope(aScope)
-    , mRawP256dhKey(aRawP256dhKey)
-    , mAuthSecret(aAuthSecret)
+    , mRawP256dhKey(Move(aRawP256dhKey))
+    , mAuthSecret(Move(aAuthSecret))
   { }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     RefPtr<Promise> promise = mProxy->WorkerPromise();
     if (NS_SUCCEEDED(mStatus)) {
       if (mEndpoint.IsEmpty()) {
         promise->MaybeResolve(JS::NullHandleValue);
       } else {
-        RefPtr<WorkerPushSubscription> sub =
-            new WorkerPushSubscription(mEndpoint, mScope,
-                                       mRawP256dhKey, mAuthSecret);
+        RefPtr<PushSubscription> sub =
+            new PushSubscription(nullptr, mEndpoint, mScope,
+                                 Move(mRawP256dhKey), Move(mAuthSecret));
         promise->MaybeResolve(sub);
       }
     } else if (NS_ERROR_GET_MODULE(mStatus) == NS_ERROR_MODULE_DOM_PUSH ) {
       promise->MaybeReject(mStatus);
     } else {
       promise->MaybeReject(NS_ERROR_DOM_PUSH_ABORT_ERR);
     }
 
     mProxy->CleanUp();
+
     return true;
   }
 private:
   ~GetSubscriptionResultRunnable()
   {}
 
   RefPtr<PromiseWorkerProxy> mProxy;
   nsresult mStatus;
@@ -629,38 +130,39 @@ public:
 
   NS_IMETHOD
   OnPushSubscription(nsresult aStatus,
                      nsIPushSubscription* aSubscription) override
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(mProxy, "OnPushSubscription() called twice?");
 
-    RefPtr<PromiseWorkerProxy> proxy = mProxy.forget();
-
-    MutexAutoLock lock(proxy->Lock());
-    if (proxy->CleanedUp()) {
+    MutexAutoLock lock(mProxy->Lock());
+    if (mProxy->CleanedUp()) {
       return NS_OK;
     }
 
     nsAutoString endpoint;
     nsTArray<uint8_t> rawP256dhKey, authSecret;
     if (NS_SUCCEEDED(aStatus)) {
       aStatus = GetSubscriptionParams(aSubscription, endpoint, rawP256dhKey,
                                       authSecret);
     }
 
+    WorkerPrivate* worker = mProxy->GetWorkerPrivate();
     RefPtr<GetSubscriptionResultRunnable> r =
-      new GetSubscriptionResultRunnable(proxy,
+      new GetSubscriptionResultRunnable(worker,
+                                        mProxy.forget(),
                                         aStatus,
                                         endpoint,
                                         mScope,
-                                        rawP256dhKey,
-                                        authSecret);
-    r->Dispatch();
+                                        Move(rawP256dhKey),
+                                        Move(authSecret));
+    MOZ_ALWAYS_TRUE(r->Dispatch());
+
     return NS_OK;
   }
 
   // Convenience method for use in this file.
   void
   OnPushSubscriptionError(nsresult aStatus)
   {
     Unused << NS_WARN_IF(NS_FAILED(
@@ -672,16 +174,17 @@ protected:
   {}
 
 private:
   inline nsresult
   FreeKeys(nsresult aStatus, uint8_t* aKey, uint8_t* aAuthSecret)
   {
     NS_Free(aKey);
     NS_Free(aAuthSecret);
+
     return aStatus;
   }
 
   nsresult
   GetSubscriptionParams(nsIPushSubscription* aSubscription,
                         nsAString& aEndpoint,
                         nsTArray<uint8_t>& aRawP256dhKey,
                         nsTArray<uint8_t>& aAuthSecret)
@@ -729,69 +232,71 @@ private:
 
 NS_IMPL_ISUPPORTS(GetSubscriptionCallback, nsIPushSubscriptionCallback)
 
 class GetSubscriptionRunnable final : public nsRunnable
 {
 public:
   GetSubscriptionRunnable(PromiseWorkerProxy* aProxy,
                           const nsAString& aScope,
-                          WorkerPushManager::SubscriptionAction aAction)
+                          PushManager::SubscriptionAction aAction)
     : mProxy(aProxy)
     , mScope(aScope), mAction(aAction)
   {}
 
   NS_IMETHOD
   Run() override
   {
     AssertIsOnMainThread();
 
     nsCOMPtr<nsIPrincipal> principal;
+
     {
       // Bug 1228723: If permission is revoked or an error occurs, the
       // subscription callback will be called synchronously. This causes
       // `GetSubscriptionCallback::OnPushSubscription` to deadlock when
       // it tries to acquire the lock.
       MutexAutoLock lock(mProxy->Lock());
       if (mProxy->CleanedUp()) {
         return NS_OK;
       }
       principal = mProxy->GetWorkerPrivate()->GetPrincipal();
     }
+
     MOZ_ASSERT(principal);
 
     RefPtr<GetSubscriptionCallback> callback = new GetSubscriptionCallback(mProxy, mScope);
 
     PushPermissionState state;
     nsresult rv = GetPermissionState(principal, state);
     if (NS_FAILED(rv)) {
       callback->OnPushSubscriptionError(NS_ERROR_FAILURE);
       return NS_OK;
     }
 
     if (state != PushPermissionState::Granted) {
-      if (mAction == WorkerPushManager::GetSubscriptionAction) {
+      if (mAction == PushManager::GetSubscriptionAction) {
         callback->OnPushSubscriptionError(NS_OK);
         return NS_OK;
       }
       callback->OnPushSubscriptionError(NS_ERROR_DOM_PUSH_DENIED_ERR);
       return NS_OK;
     }
 
     nsCOMPtr<nsIPushService> service =
       do_GetService("@mozilla.org/push/Service;1");
-    if (!service) {
+    if (NS_WARN_IF(!service)) {
       callback->OnPushSubscriptionError(NS_ERROR_FAILURE);
       return NS_OK;
     }
 
-    if (mAction == WorkerPushManager::SubscribeAction) {
+    if (mAction == PushManager::SubscribeAction) {
       rv = service->Subscribe(mScope, principal, callback);
     } else {
-      MOZ_ASSERT(mAction == WorkerPushManager::GetSubscriptionAction);
+      MOZ_ASSERT(mAction == PushManager::GetSubscriptionAction);
       rv = service->GetSubscription(mScope, principal, callback);
     }
 
     if (NS_WARN_IF(NS_FAILED(rv))) {
       callback->OnPushSubscriptionError(NS_ERROR_FAILURE);
       return NS_OK;
     }
 
@@ -799,57 +304,19 @@ public:
   }
 
 private:
   ~GetSubscriptionRunnable()
   {}
 
   RefPtr<PromiseWorkerProxy> mProxy;
   nsString mScope;
-  WorkerPushManager::SubscriptionAction mAction;
+  PushManager::SubscriptionAction mAction;
 };
 
-already_AddRefed<Promise>
-WorkerPushManager::PerformSubscriptionAction(SubscriptionAction aAction, ErrorResult& aRv)
-{
-  WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
-  MOZ_ASSERT(worker);
-  worker->AssertIsOnWorkerThread();
-
-  nsCOMPtr<nsIGlobalObject> global = worker->GlobalScope();
-  RefPtr<Promise> p = Promise::Create(global, aRv);
-  if (NS_WARN_IF(aRv.Failed())) {
-    return nullptr;
-  }
-
-  RefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(worker, p);
-  if (!proxy) {
-    p->MaybeReject(NS_ERROR_DOM_PUSH_ABORT_ERR);
-    return p.forget();
-  }
-
-  RefPtr<GetSubscriptionRunnable> r =
-    new GetSubscriptionRunnable(proxy, mScope, aAction);
-  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r));
-
-  return p.forget();
-}
-
-already_AddRefed<Promise>
-WorkerPushManager::Subscribe(ErrorResult& aRv)
-{
-  return PerformSubscriptionAction(SubscribeAction, aRv);
-}
-
-already_AddRefed<Promise>
-WorkerPushManager::GetSubscription(ErrorResult& aRv)
-{
-  return PerformSubscriptionAction(GetSubscriptionAction, aRv);
-}
-
 class PermissionResultRunnable final : public WorkerRunnable
 {
 public:
   PermissionResultRunnable(PromiseWorkerProxy *aProxy,
                            nsresult aStatus,
                            PushPermissionState aState)
     : WorkerRunnable(aProxy->GetWorkerPrivate(), WorkerThreadModifyBusyCount)
     , mProxy(aProxy)
@@ -868,16 +335,17 @@ public:
     RefPtr<Promise> promise = mProxy->WorkerPromise();
     if (NS_SUCCEEDED(mStatus)) {
       promise->MaybeResolve(mState);
     } else {
       promise->MaybeReject(aCx, JS::UndefinedHandleValue);
     }
 
     mProxy->CleanUp();
+
     return true;
   }
 
 private:
   ~PermissionResultRunnable()
   {}
 
   RefPtr<PromiseWorkerProxy> mProxy;
@@ -904,30 +372,121 @@ public:
     PushPermissionState state;
     nsresult rv = GetPermissionState(
       mProxy->GetWorkerPrivate()->GetPrincipal(),
       state
     );
 
     RefPtr<PermissionResultRunnable> r =
       new PermissionResultRunnable(mProxy, rv, state);
-    r->Dispatch();
+    MOZ_ALWAYS_TRUE(r->Dispatch());
+
     return NS_OK;
   }
 
 private:
   ~PermissionStateRunnable()
   {}
 
   RefPtr<PromiseWorkerProxy> mProxy;
 };
 
+} // anonymous namespace
+
+PushManager::PushManager(nsIGlobalObject* aGlobal, PushManagerImpl* aImpl)
+  : mGlobal(aGlobal)
+  , mImpl(aImpl)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(aImpl);
+}
+
+PushManager::PushManager(const nsAString& aScope)
+  : mScope(aScope)
+{
+#ifdef DEBUG
+  // There's only one global on a worker, so we don't need to pass a global
+  // object to the constructor.
+  WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+  MOZ_ASSERT(worker);
+  worker->AssertIsOnWorkerThread();
+#endif
+}
+
+PushManager::~PushManager()
+{}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushManager, mGlobal, mImpl)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PushManager)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PushManager)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushManager)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject*
+PushManager::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return PushManagerBinding::Wrap(aCx, this, aGivenProto);
+}
+
+// static
+already_AddRefed<PushManager>
+PushManager::Constructor(GlobalObject& aGlobal,
+                         const nsAString& aScope,
+                         ErrorResult& aRv)
+{
+  if (!NS_IsMainThread()) {
+    RefPtr<PushManager> ret = new PushManager(aScope);
+    return ret.forget();
+  }
+
+  RefPtr<PushManagerImpl> impl = PushManagerImpl::Constructor(aGlobal,
+                                                              aGlobal.Context(),
+                                                              aScope, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+  RefPtr<PushManager> ret = new PushManager(global, impl);
+
+  return ret.forget();
+}
+
 already_AddRefed<Promise>
-WorkerPushManager::PermissionState(ErrorResult& aRv)
+PushManager::Subscribe(ErrorResult& aRv)
+{
+  if (mImpl) {
+    MOZ_ASSERT(NS_IsMainThread());
+    return mImpl->Subscribe(aRv);
+  }
+
+  return PerformSubscriptionActionFromWorker(SubscribeAction, aRv);
+}
+
+already_AddRefed<Promise>
+PushManager::GetSubscription(ErrorResult& aRv)
 {
+  if (mImpl) {
+    MOZ_ASSERT(NS_IsMainThread());
+    return mImpl->GetSubscription(aRv);
+  }
+
+  return PerformSubscriptionActionFromWorker(GetSubscriptionAction, aRv);
+}
+
+already_AddRefed<Promise>
+PushManager::PermissionState(ErrorResult& aRv)
+{
+  if (mImpl) {
+    MOZ_ASSERT(NS_IsMainThread());
+    return mImpl->PermissionState(aRv);
+  }
+
   WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
   MOZ_ASSERT(worker);
   worker->AssertIsOnWorkerThread();
 
   nsCOMPtr<nsIGlobalObject> global = worker->GlobalScope();
   RefPtr<Promise> p = Promise::Create(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
@@ -941,20 +500,37 @@ WorkerPushManager::PermissionState(Error
 
   RefPtr<PermissionStateRunnable> r =
     new PermissionStateRunnable(proxy);
   NS_DispatchToMainThread(r);
 
   return p.forget();
 }
 
-WorkerPushManager::~WorkerPushManager()
-{}
+already_AddRefed<Promise>
+PushManager::PerformSubscriptionActionFromWorker(
+  SubscriptionAction aAction, ErrorResult& aRv)
+{
+  WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+  MOZ_ASSERT(worker);
+  worker->AssertIsOnWorkerThread();
+
+  nsCOMPtr<nsIGlobalObject> global = worker->GlobalScope();
+  RefPtr<Promise> p = Promise::Create(global, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WorkerPushManager)
-NS_IMPL_CYCLE_COLLECTING_ADDREF(WorkerPushManager)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(WorkerPushManager)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkerPushManager)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
+  RefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(worker, p);
+  if (!proxy) {
+    p->MaybeReject(NS_ERROR_DOM_PUSH_ABORT_ERR);
+    return p.forget();
+  }
+
+  RefPtr<GetSubscriptionRunnable> r =
+    new GetSubscriptionRunnable(proxy, mScope, aAction);
+  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r));
+
+  return p.forget();
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/push/PushManager.h
+++ b/dom/push/PushManager.h
@@ -1,33 +1,28 @@
 /* -*- 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/. */
 
 /**
- * We would like to expose PushManager and PushSubscription on window and
- * workers. Parts of the Push API is implemented in JS out of necessity due to:
- * 1) Using frame message managers, in which
- *    nsIMessageListener::receiveMessage() must be in JS.
- * 2) It is easier to use certain APIs like the permission prompt and Promises
- *    from JS.
+ * PushManager and PushSubscription are exposed on the main and worker threads.
+ * The main thread version is implemented in Push.js. The JS implementation
+ * makes it easier to use certain APIs like the permission prompt and Promises.
  *
- * Unfortunately, JS-implemented WebIDL is not supported off the main thread. To
- * aid in fixing this, the nsIPushClient is introduced which deals with part (1)
- * above. Part (2) is handled by PushManagerImpl on the main thread. PushManager
- * wraps this in C++ since our bindings code cannot accomodate "JS-implemented
- * on the main thread, C++ on the worker" bindings. PushManager simply forwards
- * the calls to the JS component.
+ * Unfortunately, JS-implemented WebIDL is not supported off the main thread.
+ * To work around this, we use a chain of runnables to query the JS-implemented
+ * nsIPushService component for subscription information, and return the
+ * results to the worker. We don't have to deal with permission prompts, since
+ * we just reject calls if the principal does not have permission.
  *
- * On the worker threads, we don't have to deal with permission prompts, instead
- * we just reject calls if the principal does not have permission. On workers
- * WorkerPushManager dispatches runnables to the main thread which directly call
- * nsIPushClient.
+ * On the main thread, PushManager wraps a JS-implemented PushManagerImpl
+ * instance. The C++ wrapper is necessary because our bindings code cannot
+ * accomodate "JS-implemented on the main thread, C++ on the worker" bindings.
  *
  * PushSubscription is in C++ on both threads since it isn't particularly
  * verbose to implement in C++ compared to JS.
  */
 
 #ifndef mozilla_dom_PushManager_h
 #define mozilla_dom_PushManager_h
 
@@ -35,226 +30,81 @@
 
 #include "mozilla/AlreadyAddRefed.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/TypedArray.h"
 
 #include "nsCOMPtr.h"
 #include "mozilla/RefPtr.h"
-#include "jsapi.h"
 
 class nsIGlobalObject;
 class nsIPrincipal;
 
-#include "mozilla/dom/PushSubscriptionBinding.h"
-
 namespace mozilla {
 namespace dom {
 
 namespace workers {
 class WorkerPrivate;
 }
 
 class Promise;
 class PushManagerImpl;
 
-class PushSubscription final : public nsISupports
-                             , public nsWrapperCache
-{
-public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PushSubscription)
-
-  explicit PushSubscription(nsIGlobalObject* aGlobal,
-                            const nsAString& aEndpoint,
-                            const nsAString& aScope,
-                            const nsTArray<uint8_t>& aP256dhKey,
-                            const nsTArray<uint8_t>& aAuthSecret);
-
-  JSObject*
-  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
-
-  nsIGlobalObject*
-  GetParentObject() const
-  {
-    return mGlobal;
-  }
-
-  void
-  GetEndpoint(nsAString& aEndpoint) const
-  {
-    aEndpoint = mEndpoint;
-  }
-
-  void
-  GetKey(JSContext* cx,
-         PushEncryptionKeyName aType,
-         JS::MutableHandle<JSObject*> aKey);
-
-  static already_AddRefed<PushSubscription>
-  Constructor(GlobalObject& aGlobal,
-              const nsAString& aEndpoint,
-              const nsAString& aScope,
-              const Nullable<ArrayBuffer>& aP256dhKey,
-              const Nullable<ArrayBuffer>& aAuthSecret,
-              ErrorResult& aRv);
-
-  void
-  SetPrincipal(nsIPrincipal* aPrincipal);
-
-  already_AddRefed<Promise>
-  Unsubscribe(ErrorResult& aRv);
-
-  void
-  ToJSON(PushSubscriptionJSON& aJSON);
-
-protected:
-  ~PushSubscription();
-
-private:
-  nsCOMPtr<nsIGlobalObject> mGlobal;
-  nsCOMPtr<nsIPrincipal> mPrincipal;
-  nsString mEndpoint;
-  nsString mScope;
-  nsTArray<uint8_t> mRawP256dhKey;
-  nsTArray<uint8_t> mAuthSecret;
-};
-
 class PushManager final : public nsISupports
                         , public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PushManager)
 
-  explicit PushManager(nsIGlobalObject* aGlobal, const nsAString& aScope);
+  enum SubscriptionAction {
+    SubscribeAction,
+    GetSubscriptionAction,
+  };
+
+  // The main thread constructor.
+  PushManager(nsIGlobalObject* aGlobal, PushManagerImpl* aImpl);
+
+  // The worker thread constructor.
+  explicit PushManager(const nsAString& aScope);
 
   nsIGlobalObject*
   GetParentObject() const
   {
     return mGlobal;
   }
 
   JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
-  already_AddRefed<Promise>
-  Subscribe(ErrorResult& aRv);
-
-  already_AddRefed<Promise>
-  GetSubscription(ErrorResult& aRv);
-
-  already_AddRefed<Promise>
-  PermissionState(ErrorResult& aRv);
-
-  void
-  SetPushManagerImpl(PushManagerImpl& foo, ErrorResult& aRv);
-
-protected:
-  ~PushManager();
-
-private:
-  nsCOMPtr<nsIGlobalObject> mGlobal;
-  RefPtr<PushManagerImpl> mImpl;
-  nsString mScope;
-};
-
-class WorkerPushSubscription final : public nsISupports
-                                   , public nsWrapperCache
-{
-public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WorkerPushSubscription)
-
-  explicit WorkerPushSubscription(const nsAString& aEndpoint,
-                                  const nsAString& aScope,
-                                  const nsTArray<uint8_t>& aRawP256dhKey,
-                                  const nsTArray<uint8_t>& aAuthSecret);
-
-  nsIGlobalObject*
-  GetParentObject() const
-  {
-    return nullptr;
-  }
-
-  JSObject*
-  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
-
-  static already_AddRefed<WorkerPushSubscription>
-  Constructor(GlobalObject& aGlobal,
-              const nsAString& aEndpoint,
-              const nsAString& aScope,
-              const Nullable<ArrayBuffer>& aP256dhKey,
-              const Nullable<ArrayBuffer>& aAuthSecret,
+  static already_AddRefed<PushManager>
+  Constructor(GlobalObject& aGlobal, const nsAString& aScope,
               ErrorResult& aRv);
 
-  void
-  GetEndpoint(nsAString& aEndpoint) const
-  {
-    aEndpoint = mEndpoint;
-  }
-
-  void
-  GetKey(JSContext* cx, PushEncryptionKeyName aType,
-         JS::MutableHandle<JSObject*> aP256dhKey);
-
   already_AddRefed<Promise>
-  Unsubscribe(ErrorResult& aRv);
-
-  void
-  ToJSON(PushSubscriptionJSON& aJSON);
-
-protected:
-  ~WorkerPushSubscription();
-
-private:
-  nsString mEndpoint;
-  nsString mScope;
-  nsTArray<uint8_t> mRawP256dhKey;
-  nsTArray<uint8_t> mAuthSecret;
-};
-
-class WorkerPushManager final : public nsISupports
-                              , public nsWrapperCache
-{
-public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WorkerPushManager)
-
-  enum SubscriptionAction {
-    SubscribeAction,
-    GetSubscriptionAction,
-  };
-
-  explicit WorkerPushManager(const nsAString& aScope);
-
-  nsIGlobalObject*
-  GetParentObject() const
-  {
-    return nullptr;
-  }
-
-  JSObject*
-  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
-
-  already_AddRefed<Promise>
-  PerformSubscriptionAction(SubscriptionAction aAction, ErrorResult& aRv);
+  PerformSubscriptionActionFromWorker(SubscriptionAction aAction,
+                                      ErrorResult& aRv);
 
   already_AddRefed<Promise>
   Subscribe(ErrorResult& aRv);
 
   already_AddRefed<Promise>
   GetSubscription(ErrorResult& aRv);
 
   already_AddRefed<Promise>
   PermissionState(ErrorResult& aRv);
 
 protected:
-  ~WorkerPushManager();
+  ~PushManager();
 
 private:
+  // 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
 } // namespace mozilla
 
 #endif // mozilla_dom_PushManager_h
new file mode 100644
--- /dev/null
+++ b/dom/push/PushSubscription.cpp
@@ -0,0 +1,378 @@
+/* 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/PushSubscription.h"
+
+#include "nsIPushService.h"
+#include "nsIScriptObjectPrincipal.h"
+
+#include "mozilla/Base64.h"
+#include "mozilla/unused.h"
+
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseWorkerProxy.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/dom/workers/Workers.h"
+
+namespace mozilla {
+namespace dom {
+
+using namespace workers;
+
+namespace {
+
+class UnsubscribeResultCallback final : public nsIUnsubscribeResultCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  explicit UnsubscribeResultCallback(Promise* aPromise)
+    : mPromise(aPromise)
+  {
+    AssertIsOnMainThread();
+  }
+
+  NS_IMETHOD
+  OnUnsubscribe(nsresult aStatus, bool aSuccess) override
+  {
+    if (NS_SUCCEEDED(aStatus)) {
+      mPromise->MaybeResolve(aSuccess);
+    } else {
+      mPromise->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE);
+    }
+
+    return NS_OK;
+  }
+
+private:
+  ~UnsubscribeResultCallback()
+  {}
+
+  RefPtr<Promise> mPromise;
+};
+
+NS_IMPL_ISUPPORTS(UnsubscribeResultCallback, nsIUnsubscribeResultCallback)
+
+class UnsubscribeResultRunnable final : public WorkerRunnable
+{
+public:
+  UnsubscribeResultRunnable(WorkerPrivate* aWorkerPrivate,
+                            already_AddRefed<PromiseWorkerProxy>&& aProxy,
+                            nsresult aStatus,
+                            bool aSuccess)
+    : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
+    , mProxy(Move(aProxy))
+    , mStatus(aStatus)
+    , mSuccess(aSuccess)
+  {
+    AssertIsOnMainThread();
+  }
+
+  bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+  {
+    MOZ_ASSERT(aWorkerPrivate);
+    aWorkerPrivate->AssertIsOnWorkerThread();
+
+    RefPtr<Promise> promise = mProxy->WorkerPromise();
+    if (NS_SUCCEEDED(mStatus)) {
+      promise->MaybeResolve(mSuccess);
+    } else {
+      promise->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE);
+    }
+
+    mProxy->CleanUp();
+
+    return true;
+  }
+private:
+  ~UnsubscribeResultRunnable()
+  {}
+
+  RefPtr<PromiseWorkerProxy> mProxy;
+  nsresult mStatus;
+  bool mSuccess;
+};
+
+class WorkerUnsubscribeResultCallback final : public nsIUnsubscribeResultCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  explicit WorkerUnsubscribeResultCallback(PromiseWorkerProxy* aProxy)
+    : mProxy(aProxy)
+  {
+    AssertIsOnMainThread();
+  }
+
+  NS_IMETHOD
+  OnUnsubscribe(nsresult aStatus, bool aSuccess) override
+  {
+    AssertIsOnMainThread();
+    MOZ_ASSERT(mProxy, "OnUnsubscribe() called twice?");
+
+    MutexAutoLock lock(mProxy->Lock());
+    if (mProxy->CleanedUp()) {
+      return NS_OK;
+    }
+
+    WorkerPrivate* worker = mProxy->GetWorkerPrivate();
+    RefPtr<UnsubscribeResultRunnable> r =
+      new UnsubscribeResultRunnable(worker, mProxy.forget(), aStatus, aSuccess);
+    MOZ_ALWAYS_TRUE(r->Dispatch());
+
+    return NS_OK;
+  }
+
+private:
+  ~WorkerUnsubscribeResultCallback()
+  {
+  }
+
+  RefPtr<PromiseWorkerProxy> mProxy;
+};
+
+NS_IMPL_ISUPPORTS(WorkerUnsubscribeResultCallback, nsIUnsubscribeResultCallback)
+
+class UnsubscribeRunnable final : public nsRunnable
+{
+public:
+  UnsubscribeRunnable(PromiseWorkerProxy* aProxy,
+                      const nsAString& aScope)
+    : mProxy(aProxy)
+    , mScope(aScope)
+  {
+    MOZ_ASSERT(aProxy);
+    MOZ_ASSERT(!aScope.IsEmpty());
+  }
+
+  NS_IMETHOD
+  Run() override
+  {
+    AssertIsOnMainThread();
+
+    nsCOMPtr<nsIPrincipal> principal;
+
+    {
+      MutexAutoLock lock(mProxy->Lock());
+      if (mProxy->CleanedUp()) {
+        return NS_OK;
+      }
+      principal = mProxy->GetWorkerPrivate()->GetPrincipal();
+    }
+
+    MOZ_ASSERT(principal);
+
+    RefPtr<WorkerUnsubscribeResultCallback> callback =
+      new WorkerUnsubscribeResultCallback(mProxy);
+
+    nsCOMPtr<nsIPushService> service =
+      do_GetService("@mozilla.org/push/Service;1");
+    if (NS_WARN_IF(!service)) {
+      callback->OnUnsubscribe(NS_ERROR_FAILURE, false);
+      return NS_OK;
+    }
+
+    if (NS_WARN_IF(NS_FAILED(service->Unsubscribe(mScope, principal, callback)))) {
+      callback->OnUnsubscribe(NS_ERROR_FAILURE, false);
+      return NS_OK;
+    }
+
+    return NS_OK;
+  }
+
+private:
+  ~UnsubscribeRunnable()
+  {}
+
+  RefPtr<PromiseWorkerProxy> mProxy;
+  nsString mScope;
+};
+
+bool
+CopyArrayBufferToArray(const ArrayBuffer& aBuffer,
+                       nsTArray<uint8_t>& aArray)
+{
+  aBuffer.ComputeLengthAndData();
+  if (!aArray.SetLength(aBuffer.Length(), fallible) ||
+      !aArray.ReplaceElementsAt(0, aBuffer.Length(), aBuffer.Data(),
+                                aBuffer.Length(), fallible)) {
+    return false;
+  }
+  return true;
+}
+
+} // anonymous namespace
+
+PushSubscription::PushSubscription(nsIGlobalObject* aGlobal,
+                                   const nsAString& aEndpoint,
+                                   const nsAString& aScope,
+                                   nsTArray<uint8_t>&& aRawP256dhKey,
+                                   nsTArray<uint8_t>&& aAuthSecret)
+  : mEndpoint(aEndpoint)
+  , mScope(aScope)
+  , mRawP256dhKey(Move(aRawP256dhKey))
+  , mAuthSecret(Move(aAuthSecret))
+{
+  if (NS_IsMainThread()) {
+    mGlobal = aGlobal;
+  } else {
+#ifdef DEBUG
+    // There's only one global on a worker, so we don't need to pass a global
+    // object to the constructor.
+    WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+    MOZ_ASSERT(worker);
+    worker->AssertIsOnWorkerThread();
+#endif
+  }
+}
+
+PushSubscription::~PushSubscription()
+{}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushSubscription, mGlobal)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PushSubscription)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PushSubscription)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushSubscription)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject*
+PushSubscription::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return PushSubscriptionBinding::Wrap(aCx, this, aGivenProto);
+}
+
+// static
+already_AddRefed<PushSubscription>
+PushSubscription::Constructor(GlobalObject& aGlobal,
+                              const nsAString& aEndpoint,
+                              const nsAString& aScope,
+                              const Nullable<ArrayBuffer>& aP256dhKey,
+                              const Nullable<ArrayBuffer>& aAuthSecret,
+                              ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+
+  nsTArray<uint8_t> rawKey, authSecret;
+  if ((!aP256dhKey.IsNull() && !CopyArrayBufferToArray(aP256dhKey.Value(),
+                                                       rawKey)) ||
+      (!aAuthSecret.IsNull() && !CopyArrayBufferToArray(aAuthSecret.Value(),
+                                                        authSecret))) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return nullptr;
+  }
+
+  RefPtr<PushSubscription> sub = new PushSubscription(global,
+                                                      aEndpoint,
+                                                      aScope,
+                                                      Move(rawKey),
+                                                      Move(authSecret));
+
+  return sub.forget();
+}
+
+already_AddRefed<Promise>
+PushSubscription::Unsubscribe(ErrorResult& aRv)
+{
+  if (!NS_IsMainThread()) {
+    RefPtr<Promise> p = UnsubscribeFromWorker(aRv);
+    return p.forget();
+  }
+
+  MOZ_ASSERT(mGlobal);
+
+  nsCOMPtr<nsIPushService> service =
+    do_GetService("@mozilla.org/push/Service;1");
+  if (NS_WARN_IF(!service)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(mGlobal);
+  if (!sop) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  RefPtr<Promise> p = Promise::Create(mGlobal, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  RefPtr<UnsubscribeResultCallback> callback =
+    new UnsubscribeResultCallback(p);
+  Unused << NS_WARN_IF(NS_FAILED(
+    service->Unsubscribe(mScope, sop->GetPrincipal(), callback)));
+
+  return p.forget();
+}
+
+void
+PushSubscription::GetKey(JSContext* aCx,
+                         PushEncryptionKeyName aType,
+                         JS::MutableHandle<JSObject*> aKey)
+{
+  if (aType == PushEncryptionKeyName::P256dh && !mRawP256dhKey.IsEmpty()) {
+    aKey.set(ArrayBuffer::Create(aCx,
+                                 mRawP256dhKey.Length(),
+                                 mRawP256dhKey.Elements()));
+  } else if (aType == PushEncryptionKeyName::Auth && !mAuthSecret.IsEmpty()) {
+    aKey.set(ArrayBuffer::Create(aCx,
+                                 mAuthSecret.Length(),
+                                 mAuthSecret.Elements()));
+  } else {
+    aKey.set(nullptr);
+  }
+}
+
+void
+PushSubscription::ToJSON(PushSubscriptionJSON& aJSON)
+{
+  aJSON.mEndpoint.Construct();
+  aJSON.mEndpoint.Value() = mEndpoint;
+
+  aJSON.mKeys.mP256dh.Construct();
+  nsresult rv = Base64URLEncode(mRawP256dhKey.Length(),
+                                mRawP256dhKey.Elements(),
+                                aJSON.mKeys.mP256dh.Value());
+  Unused << NS_WARN_IF(NS_FAILED(rv));
+
+  aJSON.mKeys.mAuth.Construct();
+  rv = Base64URLEncode(mAuthSecret.Length(), mAuthSecret.Elements(),
+                       aJSON.mKeys.mAuth.Value());
+  Unused << NS_WARN_IF(NS_FAILED(rv));
+}
+
+already_AddRefed<Promise>
+PushSubscription::UnsubscribeFromWorker(ErrorResult& aRv)
+{
+  WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+  MOZ_ASSERT(worker);
+  worker->AssertIsOnWorkerThread();
+
+  nsCOMPtr<nsIGlobalObject> global = worker->GlobalScope();
+  RefPtr<Promise> p = Promise::Create(global, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  RefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(worker, p);
+  if (!proxy) {
+    p->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE);
+    return p.forget();
+  }
+
+  RefPtr<UnsubscribeRunnable> r =
+    new UnsubscribeRunnable(proxy, mScope);
+  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r));
+
+  return p.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/push/PushSubscription.h
@@ -0,0 +1,95 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PushSubscription_h
+#define mozilla_dom_PushSubscription_h
+
+#include "jsapi.h"
+#include "nsCOMPtr.h"
+#include "nsWrapperCache.h"
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/RefPtr.h"
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/PushSubscriptionBinding.h"
+#include "mozilla/dom/TypedArray.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+namespace dom {
+
+namespace workers {
+class WorkerPrivate;
+}
+
+class Promise;
+
+class PushSubscription final : public nsISupports
+                             , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PushSubscription)
+
+  PushSubscription(nsIGlobalObject* aGlobal,
+                   const nsAString& aEndpoint,
+                   const nsAString& aScope,
+                   nsTArray<uint8_t>&& aP256dhKey,
+                   nsTArray<uint8_t>&& aAuthSecret);
+
+  JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  nsIGlobalObject*
+  GetParentObject() const
+  {
+    return mGlobal;
+  }
+
+  void
+  GetEndpoint(nsAString& aEndpoint) const
+  {
+    aEndpoint = mEndpoint;
+  }
+
+  void
+  GetKey(JSContext* cx,
+         PushEncryptionKeyName aType,
+         JS::MutableHandle<JSObject*> aKey);
+
+  static already_AddRefed<PushSubscription>
+  Constructor(GlobalObject& aGlobal,
+              const nsAString& aEndpoint,
+              const nsAString& aScope,
+              const Nullable<ArrayBuffer>& aP256dhKey,
+              const Nullable<ArrayBuffer>& aAuthSecret,
+              ErrorResult& aRv);
+
+  already_AddRefed<Promise>
+  Unsubscribe(ErrorResult& aRv);
+
+  void
+  ToJSON(PushSubscriptionJSON& aJSON);
+
+protected:
+  ~PushSubscription();
+
+private:
+  already_AddRefed<Promise>
+  UnsubscribeFromWorker(ErrorResult& aRv);
+
+  nsString mEndpoint;
+  nsString mScope;
+  nsTArray<uint8_t> mRawP256dhKey;
+  nsTArray<uint8_t> mAuthSecret;
+  nsCOMPtr<nsIGlobalObject> mGlobal;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PushSubscription_h
--- a/dom/push/moz.build
+++ b/dom/push/moz.build
@@ -34,21 +34,23 @@ MOCHITEST_MANIFESTS += [
 
 XPCSHELL_TESTS_MANIFESTS += [
     'test/xpcshell/xpcshell.ini',
 ]
 
 EXPORTS.mozilla.dom += [
     'PushManager.h',
     'PushNotifier.h',
+    'PushSubscription.h',
 ]
 
 UNIFIED_SOURCES += [
     'PushManager.cpp',
     'PushNotifier.cpp',
+    'PushSubscription.cpp',
 ]
 
 TEST_DIRS += ['test/xpcshell']
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 if CONFIG['GNU_CXX']:
     CXXFLAGS += ['-Wshadow']
--- a/dom/svg/nsSVGElement.cpp
+++ b/dom/svg/nsSVGElement.cpp
@@ -990,24 +990,22 @@ nsSVGElement::sGraphicsMap[] = {
 // PresentationAttributes-TextContentElements
 /* static */ const Element::MappedAttributeEntry
 nsSVGElement::sTextContentElementsMap[] = {
   // Properties that we don't support are commented out.
   // { &nsGkAtoms::alignment_baseline },
   // { &nsGkAtoms::baseline_shift },
   { &nsGkAtoms::direction },
   { &nsGkAtoms::dominant_baseline },
-  // { &nsGkAtoms::glyph_orientation_horizontal },
-  // { &nsGkAtoms::glyph_orientation_vertical },
-  // { &nsGkAtoms::kerning },
   { &nsGkAtoms::letter_spacing },
   { &nsGkAtoms::text_anchor },
   { &nsGkAtoms::text_decoration },
   { &nsGkAtoms::unicode_bidi },
   { &nsGkAtoms::word_spacing },
+  { &nsGkAtoms::writing_mode },
   { nullptr }
 };
 
 // PresentationAttributes-FontSpecification
 /* static */ const Element::MappedAttributeEntry
 nsSVGElement::sFontSpecificationMap[] = {
   { &nsGkAtoms::font_family },
   { &nsGkAtoms::font_size },
--- a/dom/webidl/PushManager.webidl
+++ b/dom/webidl/PushManager.webidl
@@ -2,40 +2,33 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * The origin of this IDL file is
 * https://w3c.github.io/push-api/
 */
 
-// Please see comments in dom/push/PushManager.h for the split between
-// PushManagerImpl and PushManager.
+// 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",
- NoInterfaceObject]
+ ChromeOnly, Constructor(DOMString scope)]
 interface PushManagerImpl {
-    Promise<PushSubscription>     subscribe();
-    Promise<PushSubscription?>    getSubscription();
-    Promise<PushPermissionState> permissionState();
-
-    // We need a setter in the bindings so that the C++ can use it,
-    // but we don't want it exposed to client JS.  WebPushMethodHider
-    // always returns false.
-    [Func="ServiceWorkerRegistration::WebPushMethodHider"] void setScope(DOMString scope);
+  Promise<PushSubscription>    subscribe();
+  Promise<PushSubscription?>   getSubscription();
+  Promise<PushPermissionState> permissionState();
 };
 
-[Exposed=(Window,Worker), Func="nsContentUtils::PushEnabled"]
+[Exposed=(Window,Worker), Func="nsContentUtils::PushEnabled",
+ ChromeConstructor(DOMString scope)]
 interface PushManager {
-  [ChromeOnly, Throws, Exposed=Window]
-  void setPushManagerImpl(PushManagerImpl store);
-
   [Throws, UseCounter]
-  Promise<PushSubscription>     subscribe();
+  Promise<PushSubscription>    subscribe();
   [Throws]
-  Promise<PushSubscription?>    getSubscription();
+  Promise<PushSubscription?>   getSubscription();
   [Throws]
   Promise<PushPermissionState> permissionState();
 };
 
 enum PushPermissionState
 {
     "granted",
     "denied",
--- a/dom/webidl/PushSubscription.webidl
+++ b/dom/webidl/PushSubscription.webidl
@@ -34,13 +34,9 @@ interface PushSubscription
 {
     readonly attribute USVString endpoint;
     ArrayBuffer? getKey(PushEncryptionKeyName name);
     [Throws, UseCounter]
     Promise<boolean> unsubscribe();
 
     // Implements the custom serializer specified in Push API, section 9.
     PushSubscriptionJSON toJSON();
-
-    // Used to set the principal from the JS implemented PushManager.
-    [Exposed=Window,ChromeOnly]
-    void setPrincipal(Principal principal);
 };
--- a/dom/workers/ServiceWorkerRegistration.cpp
+++ b/dom/workers/ServiceWorkerRegistration.cpp
@@ -747,58 +747,44 @@ ServiceWorkerRegistrationMainThread::Get
   if (NS_WARN_IF(!window)) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
   return Notification::Get(window, aOptions, mScope, aRv);
 }
 
 already_AddRefed<PushManager>
-ServiceWorkerRegistrationMainThread::GetPushManager(ErrorResult& aRv)
+ServiceWorkerRegistrationMainThread::GetPushManager(JSContext* aCx,
+                                                    ErrorResult& aRv)
 {
   AssertIsOnMainThread();
 
 #ifdef MOZ_SIMPLEPUSH
   return nullptr;
 #else
 
   if (!mPushManager) {
     nsCOMPtr<nsIGlobalObject> globalObject = do_QueryInterface(GetOwner());
 
     if (!globalObject) {
       aRv.Throw(NS_ERROR_FAILURE);
       return nullptr;
     }
 
-    // TODO: bug 1148117.  This will fail when swr is exposed on workers
-    JS::Rooted<JSObject*> jsImplObj(nsContentUtils::RootingCxForThread());
-    ConstructJSImplementation("@mozilla.org/push/PushManager;1",
-                              globalObject, &jsImplObj, aRv);
+    GlobalObject global(aCx, globalObject->GetGlobalJSObject());
+    mPushManager = PushManager::Constructor(global, mScope, aRv);
     if (aRv.Failed()) {
       return nullptr;
     }
-    mPushManager = new PushManager(globalObject, mScope);
-
-    RefPtr<PushManagerImpl> impl = new PushManagerImpl(jsImplObj, globalObject);
-    impl->SetScope(mScope, aRv);
-    if (aRv.Failed()) {
-      mPushManager = nullptr;
-      return nullptr;
-    }
-    mPushManager->SetPushManagerImpl(*impl, aRv);
-    if (aRv.Failed()) {
-      mPushManager = nullptr;
-      return nullptr;
-    }
   }
 
   RefPtr<PushManager> ret = mPushManager;
   return ret.forget();
 
-  #endif /* ! MOZ_SIMPLEPUSH */
+#endif /* ! MOZ_SIMPLEPUSH */
 }
 
 ////////////////////////////////////////////////////
 // Worker Thread implementation
 class WorkerListener final : public ServiceWorkerRegistrationListener
 {
   // Accessed on the main thread.
   WorkerPrivate* mWorkerPrivate;
@@ -1212,27 +1198,27 @@ ServiceWorkerRegistrationWorkerThread::S
 }
 
 already_AddRefed<Promise>
 ServiceWorkerRegistrationWorkerThread::GetNotifications(const GetNotificationOptions& aOptions, ErrorResult& aRv)
 {
   return Notification::WorkerGet(mWorkerPrivate, aOptions, mScope, aRv);
 }
 
-already_AddRefed<WorkerPushManager>
+already_AddRefed<PushManager>
 ServiceWorkerRegistrationWorkerThread::GetPushManager(ErrorResult& aRv)
 {
 #ifdef MOZ_SIMPLEPUSH
   return nullptr;
 #else
 
   if (!mPushManager) {
-    mPushManager = new WorkerPushManager(mScope);
+    mPushManager = new PushManager(mScope);
   }
 
-  RefPtr<WorkerPushManager> ret = mPushManager;
+  RefPtr<PushManager> ret = mPushManager;
   return ret.forget();
 
-  #endif /* ! MOZ_SIMPLEPUSH */
+#endif /* ! MOZ_SIMPLEPUSH */
 }
 
 } // dom namespace
 } // mozilla namespace
--- a/dom/workers/ServiceWorkerRegistration.h
+++ b/dom/workers/ServiceWorkerRegistration.h
@@ -17,45 +17,29 @@
 
 class nsPIDOMWindowInner;
 
 namespace mozilla {
 namespace dom {
 
 class Promise;
 class PushManager;
-class WorkerPushManager;
 class WorkerListener;
 
 namespace workers {
 class ServiceWorker;
 class WorkerPrivate;
 } // namespace workers
 
 bool
 ServiceWorkerRegistrationVisible(JSContext* aCx, JSObject* aObj);
 
 bool
 ServiceWorkerNotificationAPIVisible(JSContext* aCx, JSObject* aObj);
 
-// This class exists solely so that we can satisfy some WebIDL Func= attribute
-// constraints. Func= converts the function name to a header file to include, in
-// this case "ServiceWorkerRegistration.h".
-class ServiceWorkerRegistration final
-{
-public:
-  // Something that we can feed into the Func webidl property to ensure that
-  // SetScope is never exposed to the user.
-  static bool
-  WebPushMethodHider(JSContext* unusedContext, JSObject* unusedObject) {
-    return false;
-  }
-
-};
-
 // Used by ServiceWorkerManager to notify ServiceWorkerRegistrations of
 // updatefound event and invalidating ServiceWorker instances.
 class ServiceWorkerRegistrationListener
 {
 public:
   NS_IMETHOD_(MozExternalRefCountType) AddRef() = 0;
   NS_IMETHOD_(MozExternalRefCountType) Release() = 0;
 
@@ -134,17 +118,17 @@ public:
 
   already_AddRefed<workers::ServiceWorker>
   GetWaiting() override;
 
   already_AddRefed<workers::ServiceWorker>
   GetActive() override;
 
   already_AddRefed<PushManager>
-  GetPushManager(ErrorResult& aRv);
+  GetPushManager(JSContext* aCx, ErrorResult& aRv);
 
   // DOMEventTargethelper
   void DisconnectFromOwner() override
   {
     StopListeningForEvents();
     ServiceWorkerRegistrationBase::DisconnectFromOwner();
   }
 
@@ -236,17 +220,17 @@ public:
   GetScope(nsAString& aScope) const
   {
     aScope = mScope;
   }
 
   bool
   Notify(workers::Status aStatus) override;
 
-  already_AddRefed<WorkerPushManager>
+  already_AddRefed<PushManager>
   GetPushManager(ErrorResult& aRv);
 
 private:
   enum Reason
   {
     RegistrationIsGoingAway = 0,
     WorkerIsGoingAway,
   };
@@ -258,16 +242,16 @@ private:
 
   void
   ReleaseListener(Reason aReason);
 
   workers::WorkerPrivate* mWorkerPrivate;
   RefPtr<WorkerListener> mListener;
 
 #ifndef MOZ_SIMPLEPUSH
-  RefPtr<WorkerPushManager> mPushManager;
+  RefPtr<PushManager> mPushManager;
 #endif
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_ServiceWorkerRegistration_h */
--- a/gfx/ipc/GfxMessageUtils.h
+++ b/gfx/ipc/GfxMessageUtils.h
@@ -698,18 +698,16 @@ struct ParamTraits<mozilla::layers::Fram
     WriteParam(aMsg, aParam.mDisplayPortMargins);
     WriteParam(aMsg, aParam.mPresShellId);
     WriteParam(aMsg, aParam.mViewport);
     WriteParam(aMsg, aParam.mExtraResolution);
     WriteParam(aMsg, aParam.mBackgroundColor);
     WriteParam(aMsg, aParam.GetContentDescription());
     WriteParam(aMsg, aParam.mLineScrollAmount);
     WriteParam(aMsg, aParam.mPageScrollAmount);
-    WriteParam(aMsg, aParam.mClipRect);
-    WriteParam(aMsg, aParam.mMaskLayerIndex);
     WriteParam(aMsg, aParam.mPaintRequestTime);
     WriteParam(aMsg, aParam.mIsRootContent);
     WriteParam(aMsg, aParam.mHasScrollgrab);
     WriteParam(aMsg, aParam.mUpdateScrollOffset);
     WriteParam(aMsg, aParam.mDoSmoothScroll);
     WriteParam(aMsg, aParam.mUseDisplayPortMargins);
     WriteParam(aMsg, aParam.mAllowVerticalScrollWithWheel);
     WriteParam(aMsg, aParam.mIsLayersIdRoot);
@@ -760,31 +758,77 @@ struct ParamTraits<mozilla::layers::Fram
             ReadParam(aMsg, aIter, &aResult->mDisplayPortMargins) &&
             ReadParam(aMsg, aIter, &aResult->mPresShellId) &&
             ReadParam(aMsg, aIter, &aResult->mViewport) &&
             ReadParam(aMsg, aIter, &aResult->mExtraResolution) &&
             ReadParam(aMsg, aIter, &aResult->mBackgroundColor) &&
             ReadContentDescription(aMsg, aIter, aResult) &&
             ReadParam(aMsg, aIter, &aResult->mLineScrollAmount) &&
             ReadParam(aMsg, aIter, &aResult->mPageScrollAmount) &&
-            ReadParam(aMsg, aIter, &aResult->mClipRect) &&
-            ReadParam(aMsg, aIter, &aResult->mMaskLayerIndex) &&
             ReadParam(aMsg, aIter, &aResult->mPaintRequestTime) &&
             ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetIsRootContent) &&
             ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetHasScrollgrab) &&
             ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetUpdateScrollOffset) &&
             ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetDoSmoothScroll) &&
             ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetUseDisplayPortMargins) &&
             ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetAllowVerticalScrollWithWheel) &&
             ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetIsLayersIdRoot) &&
             ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetUsesContainerScrolling) &&
             ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetIsScrollInfoLayer));
   }
 };
 
+template <>
+struct ParamTraits<mozilla::layers::ScrollSnapInfo>
+{
+  typedef mozilla::layers::ScrollSnapInfo paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mScrollSnapTypeX);
+    WriteParam(aMsg, aParam.mScrollSnapTypeY);
+    WriteParam(aMsg, aParam.mScrollSnapIntervalX);
+    WriteParam(aMsg, aParam.mScrollSnapIntervalY);
+    WriteParam(aMsg, aParam.mScrollSnapDestination);
+    WriteParam(aMsg, aParam.mScrollSnapCoordinates);
+  }
+
+  static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
+  {
+    return (ReadParam(aMsg, aIter, &aResult->mScrollSnapTypeX) &&
+            ReadParam(aMsg, aIter, &aResult->mScrollSnapTypeY) &&
+            ReadParam(aMsg, aIter, &aResult->mScrollSnapIntervalX) &&
+            ReadParam(aMsg, aIter, &aResult->mScrollSnapIntervalY) &&
+            ReadParam(aMsg, aIter, &aResult->mScrollSnapDestination) &&
+            ReadParam(aMsg, aIter, &aResult->mScrollSnapCoordinates));
+  }
+};
+
+template <>
+struct ParamTraits<mozilla::layers::ScrollMetadata>
+{
+  typedef mozilla::layers::ScrollMetadata paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mMetrics);
+    WriteParam(aMsg, aParam.mSnapInfo);
+    WriteParam(aMsg, aParam.mMaskLayerIndex);
+    WriteParam(aMsg, aParam.mClipRect);
+  }
+
+  static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
+  {
+    return (ReadParam(aMsg, aIter, &aResult->mMetrics) &&
+            ReadParam(aMsg, aIter, &aResult->mSnapInfo) &&
+            ReadParam(aMsg, aIter, &aResult->mMaskLayerIndex) &&
+            ReadParam(aMsg, aIter, &aResult->mClipRect));
+  }
+};
+
 template<>
 struct ParamTraits<mozilla::layers::TextureFactoryIdentifier>
 {
   typedef mozilla::layers::TextureFactoryIdentifier paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
     WriteParam(aMsg, aParam.mParentBackend);
--- a/gfx/layers/FrameMetrics.cpp
+++ b/gfx/layers/FrameMetrics.cpp
@@ -5,18 +5,19 @@
 
 #include "FrameMetrics.h"
 #include "gfxPrefs.h"
 
 namespace mozilla {
 namespace layers {
 
 const FrameMetrics::ViewID FrameMetrics::NULL_SCROLL_ID = 0;
-const FrameMetrics FrameMetrics::sNullMetrics;
 
 void
 FrameMetrics::SetUsesContainerScrolling(bool aValue) {
   MOZ_ASSERT_IF(aValue, gfxPrefs::LayoutUseContainersForRootFrames());
   mUsesContainerScrolling = aValue;
 }
 
+StaticAutoPtr<const ScrollMetadata> ScrollMetadata::sNullMetadata;
+
 }
-}
\ No newline at end of file
+}
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -9,18 +9,20 @@
 #include <stdint.h>                     // for uint32_t, uint64_t
 #include "Units.h"                      // for CSSRect, CSSPixel, etc
 #include "mozilla/HashFunctions.h"      // for HashGeneric
 #include "mozilla/Maybe.h"
 #include "mozilla/gfx/BasePoint.h"      // for BasePoint
 #include "mozilla/gfx/Rect.h"           // for RoundedIn
 #include "mozilla/gfx/ScaleFactor.h"    // for ScaleFactor
 #include "mozilla/gfx/Logging.h"        // for Log
+#include "mozilla/StaticPtr.h"          // for StaticAutoPtr
 #include "mozilla/TimeStamp.h"          // for TimeStamp
 #include "nsString.h"
+#include "nsStyleCoord.h"               // for nsStyleCoord
 
 namespace IPC {
 template <typename T> struct ParamTraits;
 } // namespace IPC
 
 namespace mozilla {
 namespace layers {
 
@@ -33,17 +35,16 @@ namespace layers {
 struct FrameMetrics {
   friend struct IPC::ParamTraits<mozilla::layers::FrameMetrics>;
 public:
   // We use IDs to identify frames across processes.
   typedef uint64_t ViewID;
   static const ViewID NULL_SCROLL_ID;   // This container layer does not scroll.
   static const ViewID START_SCROLL_ID = 2;  // This is the ID that scrolling subframes
                                         // will begin at.
-  static const FrameMetrics sNullMetrics;   // We often need an empty metrics
 
   FrameMetrics()
     : mScrollId(NULL_SCROLL_ID)
     , mScrollParentId(NULL_SCROLL_ID)
     , mPresShellResolution(1)
     , mCompositionBounds(0, 0, 0, 0)
     , mDisplayPort(0, 0, 0, 0)
     , mCriticalDisplayPort(0, 0, 0, 0)
@@ -58,18 +59,16 @@ public:
     , mDisplayPortMargins(0, 0, 0, 0)
     , mPresShellId(-1)
     , mViewport(0, 0, 0, 0)
     , mExtraResolution()
     , mBackgroundColor()
     , mContentDescription()
     , mLineScrollAmount(0, 0)
     , mPageScrollAmount(0, 0)
-    , mClipRect()
-    , mMaskLayerIndex()
     , mPaintRequestTime()
     , mIsRootContent(false)
     , mHasScrollgrab(false)
     , mUpdateScrollOffset(false)
     , mDoSmoothScroll(false)
     , mUseDisplayPortMargins(false)
     , mAllowVerticalScrollWithWheel(false)
     , mIsLayersIdRoot(false)
@@ -100,18 +99,16 @@ public:
            mDisplayPortMargins == aOther.mDisplayPortMargins &&
            mPresShellId == aOther.mPresShellId &&
            mViewport.IsEqualEdges(aOther.mViewport) &&
            mExtraResolution == aOther.mExtraResolution &&
            mBackgroundColor == aOther.mBackgroundColor &&
            // don't compare mContentDescription
            mLineScrollAmount == aOther.mLineScrollAmount &&
            mPageScrollAmount == aOther.mPageScrollAmount &&
-           mClipRect == aOther.mClipRect &&
-           mMaskLayerIndex == aOther.mMaskLayerIndex &&
            mPaintRequestTime == aOther.mPaintRequestTime &&
            mIsRootContent == aOther.mIsRootContent &&
            mHasScrollgrab == aOther.mHasScrollgrab &&
            mUpdateScrollOffset == aOther.mUpdateScrollOffset &&
            mDoSmoothScroll == aOther.mDoSmoothScroll &&
            mUseDisplayPortMargins == aOther.mUseDisplayPortMargins &&
            mAllowVerticalScrollWithWheel == aOther.mAllowVerticalScrollWithWheel &&
            mIsLayersIdRoot == aOther.mIsLayersIdRoot &&
@@ -119,24 +116,16 @@ public:
            mIsScrollInfoLayer == aOther.mIsScrollInfoLayer;
   }
 
   bool operator!=(const FrameMetrics& aOther) const
   {
     return !operator==(aOther);
   }
 
-  bool IsDefault() const
-  {
-    FrameMetrics def;
-
-    def.mPresShellId = mPresShellId;
-    return (def == *this);
-  }
-
   bool IsScrollable() const
   {
     return mScrollId != NULL_SCROLL_ID;
   }
 
   CSSToScreenScale2D DisplayportPixelsPerCSSPixel() const
   {
     // Note: use 'mZoom * ParentLayerToLayerScale(1.0f)' as the CSS-to-Layer scale
@@ -200,16 +189,25 @@ public:
   CSSSize CalculateBoundedCompositedSizeInCssPixels() const
   {
     CSSSize size = CalculateCompositedSizeInCssPixels();
     size.width = std::min(size.width, mRootCompositionSize.width);
     size.height = std::min(size.height, mRootCompositionSize.height);
     return size;
   }
 
+  CSSRect CalculateScrollRange() const
+  {
+    CSSSize scrollPortSize = CalculateCompositedSizeInCssPixels();
+    CSSRect scrollRange = mScrollableRect;
+    scrollRange.width = std::max(scrollRange.width - scrollPortSize.width, 0.0f);
+    scrollRange.height = std::max(scrollRange.height - scrollPortSize.height, 0.0f);
+    return scrollRange;
+  }
+
   void ScrollBy(const CSSPoint& aPoint)
   {
     mScrollOffset += aPoint;
   }
 
   void ZoomBy(float aScale)
   {
     ZoomBy(gfxSize(aScale, aScale));
@@ -523,38 +521,16 @@ public:
     return mAllowVerticalScrollWithWheel;
   }
 
   void SetAllowVerticalScrollWithWheel(bool aValue)
   {
     mAllowVerticalScrollWithWheel = aValue;
   }
 
-  void SetClipRect(const Maybe<ParentLayerIntRect>& aClipRect)
-  {
-    mClipRect = aClipRect;
-  }
-  const Maybe<ParentLayerIntRect>& GetClipRect() const
-  {
-    return mClipRect;
-  }
-  bool HasClipRect() const {
-    return mClipRect.isSome();
-  }
-  const ParentLayerIntRect& ClipRect() const {
-    return mClipRect.ref();
-  }
-
-  void SetMaskLayerIndex(const Maybe<size_t>& aIndex) {
-    mMaskLayerIndex = aIndex;
-  }
-  const Maybe<size_t>& GetMaskLayerIndex() const {
-    return mMaskLayerIndex;
-  }
-
   void SetPaintRequestTime(const TimeStamp& aTime) {
     mPaintRequestTime = aTime;
   }
   const TimeStamp& GetPaintRequestTime() const {
     return mPaintRequestTime;
   }
 
   void SetIsLayersIdRoot(bool aValue) {
@@ -726,24 +702,16 @@ private:
   nsCString mContentDescription;
 
   // The value of GetLineScrollAmount(), for scroll frames.
   LayoutDeviceIntSize mLineScrollAmount;
 
   // The value of GetPageScrollAmount(), for scroll frames.
   LayoutDeviceIntSize mPageScrollAmount;
 
-  // The clip rect to use when compositing a layer with this FrameMetrics.
-  Maybe<ParentLayerIntRect> mClipRect;
-
-  // An extra clip mask layer to use when compositing a layer with this
-  // FrameMetrics. This is an index into the MetricsMaskLayers array on
-  // the Layer.
-  Maybe<size_t> mMaskLayerIndex;
-
   // The time at which the APZC last requested a repaint for this scrollframe.
   TimeStamp mPaintRequestTime;
 
   // Whether or not this is the root scroll frame for the root content document.
   bool mIsRootContent:1;
 
   // Whether or not this frame is for an element marked 'scrollgrab'.
   bool mHasScrollgrab:1;
@@ -789,16 +757,124 @@ private:
   void SetUpdateScrollOffset(bool aValue) {
     mUpdateScrollOffset = aValue;
   }
   void SetDoSmoothScroll(bool aValue) {
     mDoSmoothScroll = aValue;
   }
 };
 
+struct ScrollSnapInfo {
+  ScrollSnapInfo()
+    : mScrollSnapTypeX(NS_STYLE_SCROLL_SNAP_TYPE_NONE)
+    , mScrollSnapTypeY(NS_STYLE_SCROLL_SNAP_TYPE_NONE)
+  {}
+
+  // The scroll frame's scroll-snap-type.
+  // One of NS_STYLE_SCROLL_SNAP_{NONE, MANDATORY, PROXIMITY}.
+  uint8_t mScrollSnapTypeX;
+  uint8_t mScrollSnapTypeY;
+
+  // The intervals derived from the scroll frame's scroll-snap-points.
+  Maybe<nscoord> mScrollSnapIntervalX;
+  Maybe<nscoord> mScrollSnapIntervalY;
+
+  // The scroll frame's scroll-snap-destination, in cooked form (to avoid
+  // shipping the raw nsStyleCoord::CalcValue over IPC).
+  nsPoint mScrollSnapDestination;
+
+  // The scroll-snap-coordinates of any descendant frames of the scroll frame,
+  // relative to the origin of the scrolled frame.
+  nsTArray<nsPoint> mScrollSnapCoordinates;
+};
+
+/**
+ * Metadata about a scroll frame that's stored in the layer tree for use by
+ * the compositor (including APZ). This includes the scroll frame's FrameMetrics,
+ * as well as other metadata. We don't put the other metadata into FrameMetrics
+ * to avoid FrameMetrics becoming too bloated (as a FrameMetrics is e.g. sent
+ * over IPC for every repaint request for every active scroll frame).
+ */
+struct ScrollMetadata {
+  friend struct IPC::ParamTraits<mozilla::layers::ScrollMetadata>;
+public:
+  static StaticAutoPtr<const ScrollMetadata> sNullMetadata;   // We sometimes need an empty metadata
+
+  ScrollMetadata()
+    : mMetrics()
+    , mSnapInfo()
+    , mMaskLayerIndex()
+    , mClipRect()
+  {}
+
+  bool operator==(const ScrollMetadata& aOther) const
+  {
+    // TODO(botond): Should we include mSnapInfo in the comparison?
+    return mMetrics == aOther.mMetrics &&
+           mMaskLayerIndex == aOther.mMaskLayerIndex &&
+           mClipRect == aOther.mClipRect;
+  }
+
+  bool operator!=(const ScrollMetadata& aOther) const
+  {
+    return !operator==(aOther);
+  }
+
+  bool IsDefault() const
+  {
+    ScrollMetadata def;
+
+    def.mMetrics.SetPresShellId(mMetrics.GetPresShellId());
+    return (def == *this);
+  }
+
+  FrameMetrics& GetMetrics() { return mMetrics; }
+  const FrameMetrics& GetMetrics() const { return mMetrics; }
+
+  void SetSnapInfo(ScrollSnapInfo&& aSnapInfo) {
+    mSnapInfo = Move(aSnapInfo);
+  }
+  const ScrollSnapInfo& GetSnapInfo() const { return mSnapInfo; }
+
+  void SetMaskLayerIndex(const Maybe<size_t>& aIndex) {
+    mMaskLayerIndex = aIndex;
+  }
+  const Maybe<size_t>& GetMaskLayerIndex() const {
+    return mMaskLayerIndex;
+  }
+
+  void SetClipRect(const Maybe<ParentLayerIntRect>& aClipRect)
+  {
+    mClipRect = aClipRect;
+  }
+  const Maybe<ParentLayerIntRect>& GetClipRect() const
+  {
+    return mClipRect;
+  }
+  bool HasClipRect() const {
+    return mClipRect.isSome();
+  }
+  const ParentLayerIntRect& ClipRect() const {
+    return mClipRect.ref();
+  }
+private:
+  FrameMetrics mMetrics;
+
+  // Information used to determine where to snap to for a given scroll.
+  ScrollSnapInfo mSnapInfo;
+
+  // An extra clip mask layer to use when compositing a layer with this
+  // FrameMetrics. This is an index into the MetricsMaskLayers array on
+  // the Layer.
+  Maybe<size_t> mMaskLayerIndex;
+
+  // The clip rect to use when compositing a layer with this FrameMetrics.
+  Maybe<ParentLayerIntRect> mClipRect;
+};
+
 /**
  * This class allows us to uniquely identify a scrollable layer. The
  * mLayersId identifies the layer tree (corresponding to a child process
  * and/or tab) that the scrollable layer belongs to. The mPresShellId
  * is a temporal identifier (corresponding to the document loaded that
  * contains the scrollable layer, which may change over time). The
  * mScrollId corresponds to the actual frame that is scrollable.
  */
--- a/gfx/layers/LayerMetricsWrapper.h
+++ b/gfx/layers/LayerMetricsWrapper.h
@@ -137,17 +137,17 @@ public:
     , mIndex(0)
   {
     if (!mLayer) {
       return;
     }
 
     switch (aStart) {
       case StartAt::TOP:
-        mIndex = mLayer->GetFrameMetricsCount();
+        mIndex = mLayer->GetScrollMetadataCount();
         if (mIndex > 0) {
           mIndex--;
         }
         break;
       case StartAt::BOTTOM:
         mIndex = 0;
         break;
       default:
@@ -156,17 +156,17 @@ public:
     }
   }
 
   explicit LayerMetricsWrapper(Layer* aLayer, uint32_t aMetricsIndex)
     : mLayer(aLayer)
     , mIndex(aMetricsIndex)
   {
     MOZ_ASSERT(mLayer);
-    MOZ_ASSERT(mIndex == 0 || mIndex < mLayer->GetFrameMetricsCount());
+    MOZ_ASSERT(mIndex == 0 || mIndex < mLayer->GetScrollMetadataCount());
   }
 
   bool IsValid() const
   {
     return mLayer != nullptr;
   }
 
   MOZ_EXPLICIT_CONVERSION operator bool() const
@@ -235,46 +235,51 @@ public:
     MOZ_ASSERT(IsValid());
 
     if (AtTopLayer()) {
       return LayerMetricsWrapper(mLayer->GetNextSibling());
     }
     return LayerMetricsWrapper(nullptr);
   }
 
-  const FrameMetrics& Metrics() const
+  const ScrollMetadata& Metadata() const
   {
     MOZ_ASSERT(IsValid());
 
-    if (mIndex >= mLayer->GetFrameMetricsCount()) {
-      return FrameMetrics::sNullMetrics;
+    if (mIndex >= mLayer->GetScrollMetadataCount()) {
+      return *ScrollMetadata::sNullMetadata;
     }
-    return mLayer->GetFrameMetrics(mIndex);
+    return mLayer->GetScrollMetadata(mIndex);
+  }
+
+  const FrameMetrics& Metrics() const
+  {
+    return Metadata().GetMetrics();
   }
 
   AsyncPanZoomController* GetApzc() const
   {
     MOZ_ASSERT(IsValid());
 
-    if (mIndex >= mLayer->GetFrameMetricsCount()) {
+    if (mIndex >= mLayer->GetScrollMetadataCount()) {
       return nullptr;
     }
     return mLayer->GetAsyncPanZoomController(mIndex);
   }
 
   void SetApzc(AsyncPanZoomController* aApzc) const
   {
     MOZ_ASSERT(IsValid());
 
-    if (mLayer->GetFrameMetricsCount() == 0) {
+    if (mLayer->GetScrollMetadataCount() == 0) {
       MOZ_ASSERT(mIndex == 0);
       MOZ_ASSERT(aApzc == nullptr);
       return;
     }
-    MOZ_ASSERT(mIndex < mLayer->GetFrameMetricsCount());
+    MOZ_ASSERT(mIndex < mLayer->GetScrollMetadataCount());
     mLayer->SetAsyncPanZoomController(mIndex, aApzc);
   }
 
   const char* Name() const
   {
     MOZ_ASSERT(IsValid());
 
     if (AtBottomLayer()) {
@@ -436,51 +441,51 @@ public:
 
   bool operator!=(const LayerMetricsWrapper& aOther) const
   {
     return !(*this == aOther);
   }
 
   static const FrameMetrics& TopmostScrollableMetrics(Layer* aLayer)
   {
-    for (uint32_t i = aLayer->GetFrameMetricsCount(); i > 0; i--) {
+    for (uint32_t i = aLayer->GetScrollMetadataCount(); i > 0; i--) {
       if (aLayer->GetFrameMetrics(i - 1).IsScrollable()) {
         return aLayer->GetFrameMetrics(i - 1);
       }
     }
-    return FrameMetrics::sNullMetrics;
+    return ScrollMetadata::sNullMetadata->GetMetrics();
   }
 
   static const FrameMetrics& BottommostScrollableMetrics(Layer* aLayer)
   {
-    for (uint32_t i = 0; i < aLayer->GetFrameMetricsCount(); i++) {
+    for (uint32_t i = 0; i < aLayer->GetScrollMetadataCount(); i++) {
       if (aLayer->GetFrameMetrics(i).IsScrollable()) {
         return aLayer->GetFrameMetrics(i);
       }
     }
-    return FrameMetrics::sNullMetrics;
+    return ScrollMetadata::sNullMetadata->GetMetrics();
   }
 
   static const FrameMetrics& BottommostMetrics(Layer* aLayer)
   {
-    if (aLayer->GetFrameMetricsCount() > 0) {
+    if (aLayer->GetScrollMetadataCount() > 0) {
       return aLayer->GetFrameMetrics(0);
     }
-    return FrameMetrics::sNullMetrics;
+    return ScrollMetadata::sNullMetadata->GetMetrics();
   }
 
 private:
   bool AtBottomLayer() const
   {
     return mIndex == 0;
   }
 
   bool AtTopLayer() const
   {
-    return mLayer->GetFrameMetricsCount() == 0 || mIndex == mLayer->GetFrameMetricsCount() - 1;
+    return mLayer->GetScrollMetadataCount() == 0 || mIndex == mLayer->GetScrollMetadataCount() - 1;
   }
 
 private:
   Layer* mLayer;
   uint32_t mIndex;
 };
 
 } // namespace layers
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -533,36 +533,36 @@ Layer::StartPendingAnimations(const Time
   for (Layer* child = GetFirstChild(); child; child = child->GetNextSibling()) {
     child->StartPendingAnimations(aReadyTime);
   }
 }
 
 void
 Layer::SetAsyncPanZoomController(uint32_t aIndex, AsyncPanZoomController *controller)
 {
-  MOZ_ASSERT(aIndex < GetFrameMetricsCount());
+  MOZ_ASSERT(aIndex < GetScrollMetadataCount());
   mApzcs[aIndex] = controller;
 }
 
 AsyncPanZoomController*
 Layer::GetAsyncPanZoomController(uint32_t aIndex) const
 {
-  MOZ_ASSERT(aIndex < GetFrameMetricsCount());
+  MOZ_ASSERT(aIndex < GetScrollMetadataCount());
 #ifdef DEBUG
   if (mApzcs[aIndex]) {
     MOZ_ASSERT(GetFrameMetrics(aIndex).IsScrollable());
   }
 #endif
   return mApzcs[aIndex];
 }
 
 void
-Layer::FrameMetricsChanged()
+Layer::ScrollMetadataChanged()
 {
-  mApzcs.SetLength(GetFrameMetricsCount());
+  mApzcs.SetLength(GetScrollMetadataCount());
 }
 
 void
 Layer::ApplyPendingUpdatesToSubtree()
 {
   ApplyPendingUpdatesForThisTransaction();
   for (Layer* child = GetFirstChild(); child; child = child->GetNextSibling()) {
     child->ApplyPendingUpdatesToSubtree();
@@ -841,27 +841,33 @@ Layer::CalculateScissorRect(const Render
   }
 
   if (container) {
     scissor.MoveBy(-container->GetIntermediateSurfaceRect().TopLeft());
   }
   return currentClip.Intersect(scissor);
 }
 
+const ScrollMetadata&
+Layer::GetScrollMetadata(uint32_t aIndex) const
+{
+  MOZ_ASSERT(aIndex < GetScrollMetadataCount());
+  return mScrollMetadata[aIndex];
+}
+
 const FrameMetrics&
 Layer::GetFrameMetrics(uint32_t aIndex) const
 {
-  MOZ_ASSERT(aIndex < GetFrameMetricsCount());
-  return mFrameMetrics[aIndex];
+  return GetScrollMetadata(aIndex).GetMetrics();
 }
 
 bool
 Layer::HasScrollableFrameMetrics() const
 {
-  for (uint32_t i = 0; i < GetFrameMetricsCount(); i++) {
+  for (uint32_t i = 0; i < GetScrollMetadataCount(); i++) {
     if (GetFrameMetrics(i).IsScrollable()) {
       return true;
     }
   }
   return false;
 }
 
 bool
@@ -1083,22 +1089,22 @@ Layer::GetVisibleRegionRelativeToRootLay
   return true;
 }
 
 Maybe<ParentLayerIntRect>
 Layer::GetCombinedClipRect() const
 {
   Maybe<ParentLayerIntRect> clip = GetClipRect();
 
-  for (size_t i = 0; i < mFrameMetrics.Length(); i++) {
-    if (!mFrameMetrics[i].HasClipRect()) {
+  for (size_t i = 0; i < mScrollMetadata.Length(); i++) {
+    if (!mScrollMetadata[i].HasClipRect()) {
       continue;
     }
 
-    const ParentLayerIntRect& other = mFrameMetrics[i].ClipRect();
+    const ParentLayerIntRect& other = mScrollMetadata[i].ClipRect();
     if (clip) {
       clip = Some(clip.value().Intersect(other));
     } else {
       clip = Some(other);
     }
   }
 
   return clip;
@@ -1963,20 +1969,20 @@ Layer::PrintInfo(std::stringstream& aStr
                      mStickyPositionData->mOuter.x, mStickyPositionData->mOuter.y,
                      mStickyPositionData->mOuter.width, mStickyPositionData->mOuter.height,
                      mStickyPositionData->mInner.x, mStickyPositionData->mInner.y,
                      mStickyPositionData->mInner.width, mStickyPositionData->mInner.height).get();
   }
   if (mMaskLayer) {
     aStream << nsPrintfCString(" [mMaskLayer=%p]", mMaskLayer.get()).get();
   }
-  for (uint32_t i = 0; i < mFrameMetrics.Length(); i++) {
-    if (!mFrameMetrics[i].IsDefault()) {
+  for (uint32_t i = 0; i < mScrollMetadata.Length(); i++) {
+    if (!mScrollMetadata[i].IsDefault()) {
       aStream << nsPrintfCString(" [metrics%d=", i).get();
-      AppendToString(aStream, mFrameMetrics[i], "", "]");
+      AppendToString(aStream, mScrollMetadata[i], "", "]");
     }
   }
 }
 
 // The static helper function sets the transform matrix into the packet
 static void
 DumpTransform(layerscope::LayersPacket::Layer::Matrix* aLayerMatrix, const Matrix4x4& aMatrix)
 {
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -850,49 +850,49 @@ public:
   /**
    * CONSTRUCTION PHASE ONLY
    * Set the (sub)document metrics used to render the Layer subtree
    * rooted at this. Note that a layer may have multiple FrameMetrics
    * objects; calling this function will remove all of them and replace
    * them with the provided FrameMetrics. See the documentation for
    * SetFrameMetrics(const nsTArray<FrameMetrics>&) for more details.
    */
-  void SetFrameMetrics(const FrameMetrics& aFrameMetrics)
+  void SetScrollMetadata(const ScrollMetadata& aScrollMetadata)
   {
-    if (mFrameMetrics.Length() != 1 || mFrameMetrics[0] != aFrameMetrics) {
+    if (mScrollMetadata.Length() != 1 || mScrollMetadata[0] != aScrollMetadata) {
       MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) FrameMetrics", this));
-      mFrameMetrics.ReplaceElementsAt(0, mFrameMetrics.Length(), aFrameMetrics);
-      FrameMetricsChanged();
+      mScrollMetadata.ReplaceElementsAt(0, mScrollMetadata.Length(), aScrollMetadata);
+      ScrollMetadataChanged();
       Mutated();
     }
   }
 
   /**
    * CONSTRUCTION PHASE ONLY
    * Set the (sub)document metrics used to render the Layer subtree
    * rooted at this. There might be multiple metrics on this layer
    * because the layer may, for example, be contained inside multiple
    * nested scrolling subdocuments. In general a Layer having multiple
-   * FrameMetrics objects is conceptually equivalent to having a stack
+   * ScrollMetadata objects is conceptually equivalent to having a stack
    * of ContainerLayers that have been flattened into this Layer.
    * See the documentation in LayerMetricsWrapper.h for a more detailed
    * explanation of this conceptual equivalence.
    *
    * Note also that there is actually a many-to-many relationship between
-   * Layers and FrameMetrics, because multiple Layers may have identical
-   * FrameMetrics objects. This happens when those layers belong to the
+   * Layers and ScrollMetadata, because multiple Layers may have identical
+   * ScrollMetadata objects. This happens when those layers belong to the
    * same scrolling subdocument and therefore end up with the same async
    * transform when they are scrolled by the APZ code.
    */
-  void SetFrameMetrics(const nsTArray<FrameMetrics>& aMetricsArray)
+  void SetScrollMetadata(const nsTArray<ScrollMetadata>& aMetadataArray)
   {
-    if (mFrameMetrics != aMetricsArray) {
+    if (mScrollMetadata != aMetadataArray) {
       MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) FrameMetrics", this));
-      mFrameMetrics = aMetricsArray;
-      FrameMetricsChanged();
+      mScrollMetadata = aMetadataArray;
+      ScrollMetadataChanged();
       Mutated();
     }
   }
 
   /*
    * Compositor event handling
    * =========================
    * When a touch-start event (or similar) is sent to the AsyncPanZoomController,
@@ -1249,19 +1249,20 @@ public:
 
   // These getters can be used anytime.
   float GetOpacity() { return mOpacity; }
   gfx::CompositionOp GetMixBlendMode() const { return mMixBlendMode; }
   const Maybe<ParentLayerIntRect>& GetClipRect() const { return mClipRect; }
   uint32_t GetContentFlags() { return mContentFlags; }
   const gfx::IntRect& GetLayerBounds() const { return mLayerBounds; }
   const LayerIntRegion& GetVisibleRegion() const { return mVisibleRegion; }
+  const ScrollMetadata& GetScrollMetadata(uint32_t aIndex) const;
   const FrameMetrics& GetFrameMetrics(uint32_t aIndex) const;
-  uint32_t GetFrameMetricsCount() const { return mFrameMetrics.Length(); }
-  const nsTArray<FrameMetrics>& GetAllFrameMetrics() { return mFrameMetrics; }
+  uint32_t GetScrollMetadataCount() const { return mScrollMetadata.Length(); }
+  const nsTArray<ScrollMetadata>& GetAllScrollMetadata() { return mScrollMetadata; }
   bool HasScrollableFrameMetrics() const;
   bool IsScrollInfoLayer() const;
   const EventRegions& GetEventRegions() const { return mEventRegions; }
   ContainerLayer* GetParent() { return mParent; }
   Layer* GetNextSibling() { return mNextSibling; }
   const Layer* GetNextSibling() const { return mNextSibling; }
   Layer* GetPrevSibling() { return mPrevSibling; }
   const Layer* GetPrevSibling() const { return mPrevSibling; }
@@ -1662,20 +1663,20 @@ public:
 
   // These functions allow attaching an AsyncPanZoomController to this layer,
   // and can be used anytime.
   // A layer has an APZC at index aIndex only-if GetFrameMetrics(aIndex).IsScrollable();
   // attempting to get an APZC for a non-scrollable metrics will return null.
   // The aIndex for these functions must be less than GetFrameMetricsCount().
   void SetAsyncPanZoomController(uint32_t aIndex, AsyncPanZoomController *controller);
   AsyncPanZoomController* GetAsyncPanZoomController(uint32_t aIndex) const;
-  // The FrameMetricsChanged function is used internally to ensure the APZC array length
+  // The ScrollMetadataChanged function is used internally to ensure the APZC array length
   // matches the frame metrics array length.
 private:
-  void FrameMetricsChanged();
+  void ScrollMetadataChanged();
 public:
 
   void ApplyPendingUpdatesForThisTransaction();
 
 #ifdef DEBUG
   void SetDebugColorIndex(uint32_t aIndex) { mDebugColorIndex = aIndex; }
   uint32_t GetDebugColorIndex() { return mDebugColorIndex; }
 #endif
@@ -1786,17 +1787,17 @@ protected:
   Layer* mNextSibling;
   Layer* mPrevSibling;
   void* mImplData;
   RefPtr<Layer> mMaskLayer;
   nsTArray<RefPtr<Layer>> mAncestorMaskLayers;
   gfx::UserData mUserData;
   gfx::IntRect mLayerBounds;
   LayerIntRegion mVisibleRegion;
-  nsTArray<FrameMetrics> mFrameMetrics;
+  nsTArray<ScrollMetadata> mScrollMetadata;
   EventRegions mEventRegions;
   gfx::Matrix4x4 mTransform;
   // A mutation of |mTransform| that we've queued to be applied at the
   // end of the next transaction (if nothing else overrides it in the
   // meantime).
   nsAutoPtr<gfx::Matrix4x4> mPendingTransform;
   float mPostXScale;
   float mPostYScale;
@@ -2493,18 +2494,16 @@ private:
   { MOZ_CRASH(); return false; }
 
   virtual bool RemoveChild(Layer* aChild) override
   { MOZ_CRASH(); return false; }
 
   virtual bool RepositionChild(Layer* aChild, Layer* aAfter) override
   { MOZ_CRASH(); return false; }
 
-  using Layer::SetFrameMetrics;
-
 public:
   /**
    * CONSTRUCTION PHASE ONLY
    * Set the ID of the layer's referent.
    */
   void SetReferentId(uint64_t aId)
   {
     MOZ_ASSERT(aId != 0);
--- a/gfx/layers/LayersLogging.cpp
+++ b/gfx/layers/LayersLogging.cpp
@@ -140,16 +140,28 @@ AppendToString(std::stringstream& aStrea
   }
   if (!e.mVerticalPanRegion.IsEmpty()) {
     AppendToString(aStream, e.mVerticalPanRegion, " VerticalPanRegion=", "");
   }
   aStream << "}" << sfx;
 }
 
 void
+AppendToString(std::stringstream& aStream, const ScrollMetadata& m,
+               const char* pfx, const char* sfx)
+{
+  aStream << pfx;
+  AppendToString(aStream, m.GetMetrics(), "{ [metrics=", "]");
+  if (m.HasClipRect()) {
+    AppendToString(aStream, m.ClipRect(), " [clip=", "]");
+  }
+  aStream << "}" << sfx;
+}
+
+void
 AppendToString(std::stringstream& aStream, const FrameMetrics& m,
                const char* pfx, const char* sfx, bool detailed)
 {
   aStream << pfx;
   AppendToString(aStream, m.GetCompositionBounds(), "{ [cb=");
   AppendToString(aStream, m.GetScrollableRect(), "] [sr=");
   AppendToString(aStream, m.GetScrollOffset(), "] [s=");
   if (m.GetDoSmoothScroll()) {
@@ -161,19 +173,16 @@ AppendToString(std::stringstream& aStrea
   if (!detailed) {
     AppendToString(aStream, m.GetScrollId(), "] [scrollId=");
     if (m.GetScrollParentId() != FrameMetrics::NULL_SCROLL_ID) {
       AppendToString(aStream, m.GetScrollParentId(), "] [scrollParent=");
     }
     if (m.IsRootContent()) {
       aStream << "] [rcd";
     }
-    if (m.HasClipRect()) {
-      AppendToString(aStream, m.ClipRect(), "] [clip=");
-    }
     AppendToString(aStream, m.GetZoom(), "] [z=", "] }");
   } else {
     AppendToString(aStream, m.GetDisplayPortMargins(), " [dpm=");
     aStream << nsPrintfCString("] um=%d", m.GetUseDisplayPortMargins()).get();
     AppendToString(aStream, m.GetRootCompositionSize(), "] [rcs=");
     AppendToString(aStream, m.GetViewport(), "] [v=");
     aStream << nsPrintfCString("] [z=(ld=%.3f r=%.3f",
             m.GetDevPixelsPerCSSPixel().scale,
--- a/gfx/layers/LayersLogging.h
+++ b/gfx/layers/LayersLogging.h
@@ -110,16 +110,20 @@ AppendToString(std::stringstream& aStrea
   aStream << sfx;
 }
 
 void
 AppendToString(std::stringstream& aStream, const EventRegions& e,
                const char* pfx="", const char* sfx="");
 
 void
+AppendToString(std::stringstream& aStream, const ScrollMetadata& m,
+               const char* pfx="", const char* sfx="");
+
+void
 AppendToString(std::stringstream& aStream, const FrameMetrics& m,
                const char* pfx="", const char* sfx="", bool detailed = false);
 
 void
 AppendToString(std::stringstream& aStream, const ScrollableLayerGuid& s,
                const char* pfx="", const char* sfx="");
 
 void
--- a/gfx/layers/apz/public/GeckoContentController.h
+++ b/gfx/layers/apz/public/GeckoContentController.h
@@ -32,24 +32,16 @@ public:
   /**
    * Requests a paint of the given FrameMetrics |aFrameMetrics| from Gecko.
    * Implementations per-platform are responsible for actually handling this.
    * This method will always be called on the Gecko main thread.
    */
   virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) = 0;
 
   /**
-   * Requests handling of a scroll snapping at the end of a fling gesture for
-   * the scrollable frame with the given scroll id. aDestination specifies the
-   * expected landing position of the fling if no snapping were to be performed.
-   */
-  virtual void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
-                                const mozilla::CSSPoint& aDestination) = 0;
-
-  /**
    * Acknowledges the recipt of a scroll offset update for the scrollable
    * frame with the given scroll id. This is used to maintain consistency
    * between APZ and other sources of scroll changes.
    */
   virtual void AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId,
                                        const uint32_t& aScrollGeneration) = 0;
 
   /**
@@ -147,17 +139,18 @@ public:
   {}
 
   /**
    * Notify content that the repaint requests have been flushed.
    */
   virtual void NotifyFlushComplete() = 0;
 
   virtual void UpdateOverscrollVelocity(const float aX, const float aY) {}
-  virtual void UpdateOverscrollOffset(const float aX,const  float aY) {}
+  virtual void UpdateOverscrollOffset(const float aX, const float aY) {}
+  virtual void SetScrollingRootContent(const bool isRootContent) {}
 
   GeckoContentController() {}
   virtual void ChildAdopted() {}
   /**
    * Needs to be called on the main thread.
    */
   virtual void Destroy() {}
 
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -452,17 +452,17 @@ APZCTreeManager::PrepareNodeForLayer(con
       // to be destroyed, because it's going to remain active.
       aState.mNodesToDestroy.RemoveElement(node);
       node->SetPrevSibling(nullptr);
       node->SetLastChild(nullptr);
     }
 
     APZCTM_LOG("Using APZC %p for layer %p with identifiers %" PRId64 " %" PRId64 "\n", apzc, aLayer.GetLayer(), aLayersId, aMetrics.GetScrollId());
 
-    apzc->NotifyLayersUpdated(aMetrics, aState.mIsFirstPaint,
+    apzc->NotifyLayersUpdated(aLayer.Metadata(), aState.mIsFirstPaint,
         aLayersId == aState.mOriginatingLayersId);
 
     // Since this is the first time we are encountering an APZC with this guid,
     // the node holding it must be the primary holder. It may be newly-created
     // or not, depending on whether it went through the newApzc branch above.
     MOZ_ASSERT(node->IsPrimaryHolder() && node->GetApzc() && node->GetApzc()->Matches(guid));
 
     ParentLayerIntRegion clipRegion = ComputeClipRegion(state->mController, aLayer);
--- a/gfx/layers/apz/src/APZUtils.h
+++ b/gfx/layers/apz/src/APZUtils.h
@@ -20,17 +20,17 @@ enum HitTestResult {
   HitNothing,
   HitLayer,
   HitDispatchToContentRegion,
 };
 
 enum CancelAnimationFlags : uint32_t {
   Default = 0x0,            /* Cancel all animations */
   ExcludeOverscroll = 0x1,  /* Don't clear overscroll */
-  RequestSnap = 0x2         /* Request snapping to snap points */
+  ScrollSnap = 0x2          /* Snap to snap points */
 };
 
 inline CancelAnimationFlags
 operator|(CancelAnimationFlags a, CancelAnimationFlags b)
 {
   return static_cast<CancelAnimationFlags>(static_cast<int>(a)
                                          | static_cast<int>(b));
 }
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -65,16 +65,17 @@
 #include "nsMathUtils.h"                // for NS_hypot
 #include "nsPoint.h"                    // for nsIntPoint
 #include "nsStyleConsts.h"
 #include "nsStyleStruct.h"              // for nsTimingFunction
 #include "nsTArray.h"                   // for nsTArray, nsTArray_Impl, etc
 #include "nsThreadUtils.h"              // for NS_IsMainThread
 #include "prsystem.h"                   // for PR_GetPhysicalMemorySize
 #include "SharedMemoryBasic.h"          // for SharedMemoryBasic
+#include "ScrollSnap.h"                 // for ScrollSnapUtils
 #include "WheelScrollAnimation.h"
 
 #define ENABLE_APZC_LOGGING 0
 // #define ENABLE_APZC_LOGGING 1
 
 #if ENABLE_APZC_LOGGING
 #  define APZC_LOG(...) printf_stderr("APZC: " __VA_ARGS__)
 #  define APZC_LOG_FM(fm, prefix, ...) \
@@ -686,17 +687,23 @@ public:
     if (!continueX && !continueY) {
       // If we got into overscroll from a fling, that fling did not request a
       // fling snap to avoid a resulting scrollTo from cancelling the overscroll
       // animation too early. We do still want to request a fling snap, though,
       // in case the end of the axis at which we're overscrolled is not a valid
       // snap point, so we request one now. If there are no snap points, this will
       // do nothing. If there are snap points, we'll get a scrollTo that snaps us
       // back to the nearest valid snap point.
-      mApzc.RequestSnap();
+      // The scroll snapping is done in a deferred task, otherwise the state
+      // change to NOTHING caused by the overscroll animation ending would
+      // clobber a possible state change to SMOOTH_SCROLL in ScrollSnap().
+      if (!mDeferredTasks.append(NewRunnableMethod(&mApzc,
+                                                   &AsyncPanZoomController::ScrollSnap))) {
+        MOZ_CRASH();
+      }
       return false;
     }
     return true;
   }
 
   virtual bool WantsRepaints() override
   {
     return false;
@@ -704,17 +711,16 @@ public:
 
 private:
   AsyncPanZoomController& mApzc;
 };
 
 class SmoothScrollAnimation : public AsyncPanZoomAnimation {
 public:
   SmoothScrollAnimation(AsyncPanZoomController& aApzc,
-                        ScrollSource aSource,
                         const nsPoint &aInitialPosition,
                         const nsPoint &aInitialVelocity,
                         const nsPoint& aDestination, double aSpringConstant,
                         double aDampingRatio)
    : mApzc(aApzc)
    , mXAxisModel(aInitialPosition.x, aDestination.x, aInitialVelocity.x,
                  aSpringConstant, aDampingRatio)
    , mYAxisModel(aInitialPosition.y, aDestination.y, aInitialVelocity.y,
@@ -855,16 +861,17 @@ AsyncPanZoomController::AsyncPanZoomCont
                                                GeckoContentController* aGeckoContentController,
                                                GestureBehavior aGestures)
   :  mLayersId(aLayersId),
      mGeckoContentController(aGeckoContentController),
      mRefPtrMonitor("RefPtrMonitor"),
      // mTreeManager must be initialized before GetFrameTime() is called
      mTreeManager(aTreeManager),
      mSharingFrameMetricsAcrossProcesses(false),
+     mFrameMetrics(mScrollMetadata.GetMetrics()),
      mMonitor("AsyncPanZoomController"),
      mX(this),
      mY(this),
      mPanDirRestricted(false),
      mZoomConstraints(false, false, MIN_ZOOM, MAX_ZOOM),
      mLastSampleTime(GetFrameTime()),
      mLastCheckerboardReport(GetFrameTime()),
      mState(NOTHING),
@@ -919,17 +926,17 @@ AsyncPanZoomController::GetInputQueue() 
   return mInputQueue;
 }
 
 void
 AsyncPanZoomController::Destroy()
 {
   APZThreadUtils::AssertOnCompositorThread();
 
-  CancelAnimation(CancelAnimationFlags::RequestSnap);
+  CancelAnimation(CancelAnimationFlags::ScrollSnap);
 
   { // scope the lock
     MonitorAutoLock lock(mRefPtrMonitor);
     mGeckoContentController = nullptr;
     mGestureEventListener = nullptr;
   }
   mParent = nullptr;
   mTreeManager = nullptr;
@@ -1314,16 +1321,21 @@ nsEventStatus AsyncPanZoomController::On
   }
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::OnTouchEnd(const MultiTouchInput& aEvent) {
   APZC_LOG("%p got a touch-end in state %d\n", this, mState);
 
+  RefPtr<GeckoContentController> controller = GetGeckoContentController();
+  if (controller) {
+    controller->SetScrollingRootContent(false);
+  }
+
   OnTouchEndOrCancel();
 
   // In case no touch behavior triggered previously we can avoid sending
   // scroll events or requesting content repaint. This condition is added
   // to make tests consistent - in case touch-action is NONE (and therefore
   // no pans/zooms can be performed) we expected neither scroll or repaint
   // events.
   if (mState != NOTHING) {
@@ -1540,46 +1552,47 @@ nsEventStatus AsyncPanZoomController::On
   if (HasReadyTouchBlock() && !CurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
     return nsEventStatus_eIgnore;
   }
 
   SetState(NOTHING);
 
   {
     ReentrantMonitorAutoEnter lock(mMonitor);
+    ScheduleComposite();
+    RequestContentRepaint();
+    UpdateSharedCompositorFrameMetrics();
+  }
+
+  // Non-negative focus point would indicate that one finger is still down
+  if (aEvent.mFocusPoint.x != -1 && aEvent.mFocusPoint.y != -1) {
+    mPanDirRestricted = false;
+    mX.StartTouch(aEvent.mFocusPoint.x, aEvent.mTime);
+    mY.StartTouch(aEvent.mFocusPoint.y, aEvent.mTime);
+    SetState(TOUCHING);
+  } else {
+    // Otherwise, handle the fingers being lifted.
+    ReentrantMonitorAutoEnter lock(mMonitor);
 
     // We can get into a situation where we are overscrolled at the end of a
     // pinch if we go into overscroll with a two-finger pan, and then turn
     // that into a pinch by increasing the span sufficiently. In such a case,
     // there is no snap-back animation to get us out of overscroll, so we need
     // to get out of it somehow.
     // Moreover, in cases of scroll handoff, the overscroll can be on an APZC
     // further up in the handoff chain rather than on the current APZC, so
     // we need to clear overscroll along the entire handoff chain.
     if (HasReadyTouchBlock()) {
       CurrentTouchBlock()->GetOverscrollHandoffChain()->ClearOverscroll();
     } else {
       ClearOverscroll();
     }
     // Along with clearing the overscroll, we also want to snap to the nearest
-    // snap point as appropriate, so ask the main thread (which knows about such
-    // things) to handle it.
-    RequestSnap();
-
-    ScheduleComposite();
-    RequestContentRepaint();
-    UpdateSharedCompositorFrameMetrics();
-  }
-
-  // Non-negative focus point would indicate that one finger is still down
-  if (aEvent.mFocusPoint.x != -1 && aEvent.mFocusPoint.y != -1) {
-    mPanDirRestricted = false;
-    mX.StartTouch(aEvent.mFocusPoint.x, aEvent.mTime);
-    mY.StartTouch(aEvent.mFocusPoint.y, aEvent.mTime);
-    SetState(TOUCHING);
+    // snap point as appropriate.
+    ScrollSnap();
   }
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
 bool
 AsyncPanZoomController::ConvertToGecko(const ScreenIntPoint& aPoint, CSSPoint* aOut)
 {
@@ -1993,32 +2006,32 @@ nsEventStatus AsyncPanZoomController::On
   if (!overscrollHandoffChain->CanScrollInDirection(this, Layer::VERTICAL)) {
     mY.SetVelocity(0);
   }
 
   SetState(NOTHING);
   RequestContentRepaint();
 
   if (!aEvent.mFollowedByMomentum) {
-    RequestSnap();
+    ScrollSnap();
   }
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::OnPanMomentumStart(const PanGestureInput& aEvent) {
   APZC_LOG("%p got a pan-momentumstart in state %d\n", this, mState);
 
   if (mState == SMOOTH_SCROLL) {
     // SMOOTH_SCROLL scrolls are cancelled by pan gestures.
     CancelAnimation();
   }
 
   SetState(PAN_MOMENTUM);
-  RequestSnapToDestination();
+  ScrollSnapToDestination();
 
   // Call into OnPan in order to process any delta included in this event.
   OnPan(aEvent, false);
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::OnPanMomentumEnd(const PanGestureInput& aEvent) {
@@ -2355,21 +2368,21 @@ bool AsyncPanZoomController::AttemptScro
   ParentLayerPoint displacement = aStartPoint - aEndPoint;
 
   ParentLayerPoint overscroll;  // will be used outside monitor block
 
   // If the direction of panning is reversed within the same input block,
   // a later event in the block could potentially scroll an APZC earlier
   // in the handoff chain, than an earlier event in the block (because
   // the earlier APZC was scrolled to its extent in the original direction).
-  // If immediate handoff is disallowed, we want to disallow this (to
-  // preserve the property that a single input block only scrolls one APZC),
-  // so we skip the earlier APZC.
-  bool scrollThisApzc = gfxPrefs::APZAllowImmediateHandoff() ||
-      (CurrentInputBlock() && (!CurrentInputBlock()->GetScrolledApzc() || this == CurrentInputBlock()->GetScrolledApzc()));
+  // We want to disallow this.
+  bool scrollThisApzc = false;
+  if (InputBlockState* block = CurrentInputBlock()) {
+    scrollThisApzc = !block->GetScrolledApzc() || block->IsDownchainOfScrolledApzc(this);
+  }
 
   if (scrollThisApzc) {
     ReentrantMonitorAutoEnter lock(mMonitor);
 
     ParentLayerPoint adjustedDisplacement;
     bool forceVerticalOverscroll =
       (aOverscrollHandoffState.mScrollSource == ScrollSource::Wheel &&
        !mFrameMetrics.AllowVerticalScrollWithWheel());
@@ -2378,31 +2391,35 @@ bool AsyncPanZoomController::AttemptScro
     bool xChanged = mX.AdjustDisplacement(displacement.x, adjustedDisplacement.x, overscroll.x);
 
     if (xChanged || yChanged) {
       ScheduleComposite();
     }
 
     if (!IsZero(adjustedDisplacement)) {
       ScrollBy(adjustedDisplacement / mFrameMetrics.GetZoom());
-      if (!gfxPrefs::APZAllowImmediateHandoff()) {
-        if (InputBlockState* block = CurrentInputBlock()) {
-          block->SetScrolledApzc(this);
+      if (CancelableBlockState* block = CurrentInputBlock()) {
+        if (block->AsTouchBlock() && (block->GetScrolledApzc() != this)) {
+          RefPtr<GeckoContentController> controller = GetGeckoContentController();
+          if (controller) {
+            controller->SetScrollingRootContent(IsRootContent());
+          }
         }
+        block->SetScrolledApzc(this);
       }
       ScheduleCompositeAndMaybeRepaint();
       UpdateSharedCompositorFrameMetrics();
     }
+
+    // Adjust the start point to reflect the consumed portion of the scroll.
+    aStartPoint = aEndPoint + overscroll;
   } else {
     overscroll = displacement;
   }
 
-  // Adjust the start point to reflect the consumed portion of the scroll.
-  aStartPoint = aEndPoint + overscroll;
-
   // If we consumed the entire displacement as a normal scroll, great.
   if (IsZero(overscroll)) {
     return true;
   }
 
   if (AllowScrollHandoffInCurrentBlock()) {
     // If there is overscroll, first try to hand it off to an APZC later
     // in the handoff chain to consume (either as a normal scroll or as
@@ -2505,61 +2522,28 @@ void AsyncPanZoomController::AcceptFling
   if (mX.CanScroll()) {
     mX.SetVelocity(mX.GetVelocity() + aHandoffState.mVelocity.x);
     aHandoffState.mVelocity.x = 0;
   }
   if (mY.CanScroll()) {
     mY.SetVelocity(mY.GetVelocity() + aHandoffState.mVelocity.y);
     aHandoffState.mVelocity.y = 0;
   }
-  SetState(FLING);
-  FlingAnimation *fling = new FlingAnimation(*this,
-      aHandoffState.mChain,
-      !aHandoffState.mIsHandoff,  // only apply acceleration if this is an initial fling
-      aHandoffState.mScrolledApzc);
-  RequestSnapToDestination();
-  StartAnimation(fling);
-}
-
-void
-AsyncPanZoomController::RequestSnapToDestination()
-{
-  ReentrantMonitorAutoEnter lock(mMonitor);
-
-  float friction = gfxPrefs::APZFlingFriction();
-  ParentLayerPoint velocity(mX.GetVelocity(), mY.GetVelocity());
-  ParentLayerPoint predictedDelta;
-  // "-velocity / log(1.0 - friction)" is the integral of the deceleration
-  // curve modeled for flings in the "Axis" class.
-  if (velocity.x != 0.0f) {
-    predictedDelta.x = -velocity.x / log(1.0 - friction);
-  }
-  if (velocity.y != 0.0f) {
-    predictedDelta.y = -velocity.y / log(1.0 - friction);
-  }
-  CSSPoint predictedDestination = mFrameMetrics.GetScrollOffset() + predictedDelta / mFrameMetrics.GetZoom();
-
-  // If the fling will overscroll, don't request a fling snap, because the
-  // resulting content scrollTo() would unnecessarily cancel the overscroll
-  // animation.
-  bool flingWillOverscroll = IsOverscrolled() && ((velocity.x * mX.GetOverscroll() >= 0) ||
-                                                  (velocity.y * mY.GetOverscroll() >= 0));
-  if (!flingWillOverscroll) {
-    RefPtr<GeckoContentController> controller = GetGeckoContentController();
-    if (controller) {
-      APZC_LOG("%p fling snapping.  friction: %f velocity: %f, %f "
-               "predictedDelta: %f, %f position: %f, %f "
-               "predictedDestination: %f, %f\n",
-               this, friction, velocity.x, velocity.y, (float)predictedDelta.x,
-               (float)predictedDelta.y, (float)mFrameMetrics.GetScrollOffset().x,
-               (float)mFrameMetrics.GetScrollOffset().y,
-               (float)predictedDestination.x, (float)predictedDestination.y);
-      controller->RequestFlingSnap(mFrameMetrics.GetScrollId(),
-                                   predictedDestination);
-    }
+
+  // If there's a scroll snap point near the predicted fling destination,
+  // scroll there using a smooth scroll animation. Otherwise, start a
+  // fling animation.
+  ScrollSnapToDestination();
+  if (mState != SMOOTH_SCROLL) {
+    SetState(FLING);
+    FlingAnimation *fling = new FlingAnimation(*this,
+        aHandoffState.mChain,
+        !aHandoffState.mIsHandoff,  // only apply acceleration if this is an initial fling
+        aHandoffState.mScrolledApzc);
+    StartAnimation(fling);
   }
 }
 
 bool AsyncPanZoomController::AttemptFling(FlingHandoffState& aHandoffState) {
   // If we are pannable, take over the fling ourselves.
   if (IsPannable()) {
     AcceptFling(aHandoffState);
     return true;
@@ -2592,31 +2576,38 @@ void AsyncPanZoomController::HandleFling
 }
 
 void AsyncPanZoomController::HandleSmoothScrollOverscroll(const ParentLayerPoint& aVelocity) {
   // We must call BuildOverscrollHandoffChain from this deferred callback
   // function in order to avoid a deadlock when acquiring the tree lock.
   HandleFlingOverscroll(aVelocity, BuildOverscrollHandoffChain(), nullptr);
 }
 
-void AsyncPanZoomController::StartSmoothScroll(ScrollSource aSource) {
-  SetState(SMOOTH_SCROLL);
-  nsPoint initialPosition = CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset());
-  // Cast velocity from ParentLayerPoints/ms to CSSPoints/ms then convert to
-  // appunits/second
-  nsPoint initialVelocity = CSSPoint::ToAppUnits(CSSPoint(mX.GetVelocity(),
-                                                          mY.GetVelocity())) * 1000.0f;
-  nsPoint destination = CSSPoint::ToAppUnits(mFrameMetrics.GetSmoothScrollOffset());
-
-  StartAnimation(new SmoothScrollAnimation(*this,
-                                           aSource,
-                                           initialPosition, initialVelocity,
-                                           destination,
-                                           gfxPrefs::ScrollBehaviorSpringConstant(),
-                                           gfxPrefs::ScrollBehaviorDampingRatio()));
+void AsyncPanZoomController::SmoothScrollTo(const CSSPoint& aDestination) {
+  if (mState == SMOOTH_SCROLL && mAnimation) {
+    APZC_LOG("%p updating destination on existing animation\n", this);
+    RefPtr<SmoothScrollAnimation> animation(
+      static_cast<SmoothScrollAnimation*>(mAnimation.get()));
+    animation->SetDestination(CSSPoint::ToAppUnits(aDestination));
+  } else {
+    CancelAnimation();
+    SetState(SMOOTH_SCROLL);
+    nsPoint initialPosition = CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset());
+    // Cast velocity from ParentLayerPoints/ms to CSSPoints/ms then convert to
+    // appunits/second
+    nsPoint initialVelocity = CSSPoint::ToAppUnits(CSSPoint(mX.GetVelocity(),
+                                                            mY.GetVelocity())) * 1000.0f;
+    nsPoint destination = CSSPoint::ToAppUnits(aDestination);
+
+    StartAnimation(new SmoothScrollAnimation(*this,
+                                             initialPosition, initialVelocity,
+                                             destination,
+                                             gfxPrefs::ScrollBehaviorSpringConstant(),
+                                             gfxPrefs::ScrollBehaviorDampingRatio()));
+  }
 }
 
 void AsyncPanZoomController::StartOverscrollAnimation(const ParentLayerPoint& aVelocity) {
   SetState(OVERSCROLL_ANIMATION);
   StartAnimation(new OverscrollAnimation(*this, aVelocity));
 }
 
 void AsyncPanZoomController::CallDispatchScroll(ParentLayerPoint& aStartPoint,
@@ -2687,19 +2678,19 @@ void AsyncPanZoomController::CancelAnima
   // Setting the state to nothing and cancelling the animation can
   // preempt normal mechanisms for relieving overscroll, so we need to clear
   // overscroll here.
   if (!(aFlags & ExcludeOverscroll) && IsOverscrolled()) {
     ClearOverscroll();
     repaint = true;
   }
   // Similar to relieving overscroll, we also need to snap to any snap points
-  // if appropriate, so ask the main thread to do that.
-  if (aFlags & CancelAnimationFlags::RequestSnap) {
-    RequestSnap();
+  // if appropriate.
+  if (aFlags & CancelAnimationFlags::ScrollSnap) {
+    ScrollSnap();
   }
   if (repaint) {
     RequestContentRepaint();
     ScheduleComposite();
     UpdateSharedCompositorFrameMetrics();
   }
 }
 
@@ -2889,17 +2880,17 @@ bool AsyncPanZoomController::SnapBackIfO
     APZC_LOG("%p is overscrolled, starting snap-back\n", this);
     StartOverscrollAnimation(ParentLayerPoint(0, 0));
     return true;
   }
   // If we don't kick off an overscroll animation, we still need to ask the
   // main thread to snap to any nearby snap points, assuming we haven't already
   // done so when we started this fling
   if (mState != FLING) {
-    RequestSnap();
+    ScrollSnap();
   }
   return false;
 }
 
 bool AsyncPanZoomController::IsFlingingFast() const {
   ReentrantMonitorAutoEnter lock(mMonitor);
   if (mState == FLING &&
       GetVelocityVector().Length() > gfxPrefs::APZFlingStopOnTapThreshold()) {
@@ -3284,24 +3275,26 @@ bool AsyncPanZoomController::IsCurrently
   if (painted.Contains(visible)) {
     return false;
   }
   APZC_LOG_FM(mFrameMetrics, "%p is currently checkerboarding (painted %s visble %s)",
     this, Stringify(painted).c_str(), Stringify(visible).c_str());
   return true;
 }
 
-void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aLayerMetrics,
+void AsyncPanZoomController::NotifyLayersUpdated(const ScrollMetadata& aScrollMetadata,
                                                  bool aIsFirstPaint,
                                                  bool aThisLayerTreeUpdated)
 {
   APZThreadUtils::AssertOnCompositorThread();
 
   ReentrantMonitorAutoEnter lock(mMonitor);
-  bool isDefault = mFrameMetrics.IsDefault();
+  bool isDefault = mScrollMetadata.IsDefault();
+
+  const FrameMetrics& aLayerMetrics = aScrollMetadata.GetMetrics();
 
   if ((aLayerMetrics == mLastContentPaintMetrics) && !isDefault) {
     // No new information here, skip it. Note that this is not just an
     // optimization; it's correctness too. In the case where we get one of these
     // stale aLayerMetrics *after* a call to NotifyScrollUpdated, processing the
     // stale aLayerMetrics would clobber the more up-to-date information from
     // NotifyScrollUpdated.
     APZC_LOG("%p NotifyLayersUpdated short-circuit\n", this);
@@ -3376,17 +3369,17 @@ void AsyncPanZoomController::NotifyLayer
   // TODO if we're in a drag and scrollOffsetUpdated is set then we want to
   // ignore it
 
   if ((aIsFirstPaint && aThisLayerTreeUpdated) || isDefault) {
     // Initialize our internal state to something sane when the content
     // that was just painted is something we knew nothing about previously
     CancelAnimation();
 
-    mFrameMetrics = aLayerMetrics;
+    mScrollMetadata = aScrollMetadata;
     if (scrollOffsetUpdated) {
       AcknowledgeScrollUpdate();
     }
     mExpectedGeckoMetrics = aLayerMetrics;
     ShareCompositorFrameMetrics();
 
     if (mFrameMetrics.GetDisplayPortMargins() != ScreenMargin()) {
       // A non-zero display port margin here indicates a displayport has
@@ -3432,18 +3425,19 @@ void AsyncPanZoomController::NotifyLayer
     }
     mFrameMetrics.SetCompositionBounds(aLayerMetrics.GetCompositionBounds());
     mFrameMetrics.SetRootCompositionSize(aLayerMetrics.GetRootCompositionSize());
     mFrameMetrics.SetPresShellResolution(aLayerMetrics.GetPresShellResolution());
     mFrameMetrics.SetCumulativeResolution(aLayerMetrics.GetCumulativeResolution());
     mFrameMetrics.SetHasScrollgrab(aLayerMetrics.GetHasScrollgrab());
     mFrameMetrics.SetLineScrollAmount(aLayerMetrics.GetLineScrollAmount());
     mFrameMetrics.SetPageScrollAmount(aLayerMetrics.GetPageScrollAmount());
-    mFrameMetrics.SetClipRect(aLayerMetrics.GetClipRect());
-    mFrameMetrics.SetMaskLayerIndex(aLayerMetrics.GetMaskLayerIndex());
+    mScrollMetadata.SetSnapInfo(ScrollSnapInfo(aScrollMetadata.GetSnapInfo()));
+    mScrollMetadata.SetClipRect(aScrollMetadata.GetClipRect());
+    mScrollMetadata.SetMaskLayerIndex(aScrollMetadata.GetMaskLayerIndex());
     mFrameMetrics.SetIsLayersIdRoot(aLayerMetrics.IsLayersIdRoot());
     mFrameMetrics.SetUsesContainerScrolling(aLayerMetrics.UsesContainerScrolling());
     mFrameMetrics.SetIsScrollInfoLayer(aLayerMetrics.IsScrollInfoLayer());
 
     if (scrollOffsetUpdated) {
       APZC_LOG("%p updating scroll offset from %s to %s\n", this,
         ToString(mFrameMetrics.GetScrollOffset()).c_str(),
         ToString(aLayerMetrics.GetScrollOffset()).c_str());
@@ -3486,26 +3480,17 @@ void AsyncPanZoomController::NotifyLayer
       mState);
 
     // See comment on the similar code in the |if (scrollOffsetUpdated)| block
     // above.
     mFrameMetrics.CopySmoothScrollInfoFrom(aLayerMetrics);
     AcknowledgeScrollUpdate();
     mExpectedGeckoMetrics = aLayerMetrics;
 
-    if (mState == SMOOTH_SCROLL && mAnimation) {
-      APZC_LOG("%p updating destination on existing animation\n", this);
-      RefPtr<SmoothScrollAnimation> animation(
-        static_cast<SmoothScrollAnimation*>(mAnimation.get()));
-      animation->SetDestination(
-        CSSPoint::ToAppUnits(aLayerMetrics.GetSmoothScrollOffset()));
-    } else {
-      CancelAnimation();
-      StartSmoothScroll(ScrollSource::DOM);
-    }
+    SmoothScrollTo(mFrameMetrics.GetSmoothScrollOffset());
   }
 
   if (needContentRepaint) {
     RequestContentRepaint();
   }
   UpdateSharedCompositorFrameMetrics();
 }
 
@@ -3737,17 +3722,17 @@ AsyncPanZoomController::ResetTouchInputS
   }
 }
 
 void
 AsyncPanZoomController::CancelAnimationAndGestureState()
 {
   mX.CancelGesture();
   mY.CancelGesture();
-  CancelAnimation(CancelAnimationFlags::RequestSnap);
+  CancelAnimation(CancelAnimationFlags::ScrollSnap);
 }
 
 bool
 AsyncPanZoomController::HasReadyTouchBlock() const
 {
   return GetInputQueue()->HasReadyTouchBlock();
 }
 
@@ -3914,19 +3899,69 @@ void AsyncPanZoomController::ShareCompos
       // so the content process know which APZC sent this shared FrameMetrics.
       if (!compositor->SendSharedCompositorFrameMetrics(mem, handle, mLayersId, mAPZCId)) {
         APZC_LOG("%p failed to share FrameMetrics with content process.", this);
       }
     }
   }
 }
 
-void AsyncPanZoomController::RequestSnap() {
-  if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
-    APZC_LOG("%p requesting snap near %s\n", this,
-        Stringify(mFrameMetrics.GetScrollOffset()).c_str());
-    controller->RequestFlingSnap(mFrameMetrics.GetScrollId(),
-                                 mFrameMetrics.GetScrollOffset());
+void AsyncPanZoomController::ScrollSnapNear(const CSSPoint& aDestination) {
+  mMonitor.AssertCurrentThreadIn();
+  APZC_LOG("%p scroll snapping near %s\n", this, Stringify(aDestination).c_str());
+  CSSRect scrollRange = mFrameMetrics.CalculateScrollRange();
+  if (Maybe<nsPoint> snapPoint = ScrollSnapUtils::GetSnapPointForDestination(
+          mScrollMetadata.GetSnapInfo(),
+          nsIScrollableFrame::DEVICE_PIXELS,
+          CSSSize::ToAppUnits(mFrameMetrics.CalculateCompositedSizeInCssPixels()),
+          CSSRect::ToAppUnits(scrollRange),
+          CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset()),
+          CSSPoint::ToAppUnits(aDestination))) {
+    CSSPoint cssSnapPoint = CSSPoint::FromAppUnits(snapPoint.ref());
+    // GetSnapPointForDestination() can produce a destination that's outside
+    // of the scroll frame's scroll range. Clamp it here (this matches the
+    // behaviour of the main-thread code path, which clamps it in
+    // nsGfxScrollFrame::ScrollTo()).
+    cssSnapPoint = scrollRange.ClampPoint(cssSnapPoint);
+    SmoothScrollTo(cssSnapPoint);
+  }
+}
+
+void AsyncPanZoomController::ScrollSnap() {
+  ReentrantMonitorAutoEnter lock(mMonitor);
+  ScrollSnapNear(mFrameMetrics.GetScrollOffset());
+}
+
+void AsyncPanZoomController::ScrollSnapToDestination() {
+  ReentrantMonitorAutoEnter lock(mMonitor);
+
+  float friction = gfxPrefs::APZFlingFriction();
+  ParentLayerPoint velocity(mX.GetVelocity(), mY.GetVelocity());
+  ParentLayerPoint predictedDelta;
+  // "-velocity / log(1.0 - friction)" is the integral of the deceleration
+  // curve modeled for flings in the "Axis" class.
+  if (velocity.x != 0.0f) {
+    predictedDelta.x = -velocity.x / log(1.0 - friction);
+  }
+  if (velocity.y != 0.0f) {
+    predictedDelta.y = -velocity.y / log(1.0 - friction);
+  }
+  CSSPoint predictedDestination = mFrameMetrics.GetScrollOffset() + predictedDelta / mFrameMetrics.GetZoom();
+
+  // If the fling will overscroll, don't scroll snap, because then the user
+  // user would not see any overscroll animation.
+  bool flingWillOverscroll = IsOverscrolled() && ((velocity.x * mX.GetOverscroll() >= 0) ||
+                                                  (velocity.y * mY.GetOverscroll() >= 0));
+  if (!flingWillOverscroll) {
+    APZC_LOG("%p fling snapping.  friction: %f velocity: %f, %f "
+             "predictedDelta: %f, %f position: %f, %f "
+             "predictedDestination: %f, %f\n",
+             this, friction, velocity.x, velocity.y, (float)predictedDelta.x,
+             (float)predictedDelta.y, (float)mFrameMetrics.GetScrollOffset().x,
+             (float)mFrameMetrics.GetScrollOffset().y,
+             (float)predictedDestination.x, (float)predictedDestination.y);
+
+    ScrollSnapNear(predictedDestination);
   }
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -173,23 +173,23 @@ public:
 
   /**
    * Return a visual effect that reflects this apzc's
    * overscrolled state, if any.
    */
   AsyncTransformComponentMatrix GetOverscrollTransform() const;
 
   /**
-   * A shadow layer update has arrived. |aLayerMetrics| is the new FrameMetrics
+   * A shadow layer update has arrived. |aScrollMetdata| is the new ScrollMetadata
    * for the container layer corresponding to this APZC.
    * |aIsFirstPaint| is a flag passed from the shadow
-   * layers code indicating that the frame metrics being sent with this call are
-   * the initial metrics and the initial paint of the frame has just happened.
+   * layers code indicating that the scroll metadata being sent with this call are
+   * the initial metadata and the initial paint of the frame has just happened.
    */
-  void NotifyLayersUpdated(const FrameMetrics& aLayerMetrics, bool aIsFirstPaint,
+  void NotifyLayersUpdated(const ScrollMetadata& aScrollMetadata, bool aIsFirstPaint,
                            bool aThisLayerTreeUpdated);
 
   /**
    * A lightweight version of NotifyLayersUpdated that allows just the scroll
    * offset and scroll generation from the main thread to be propagated to APZ.
    */
   void NotifyScrollUpdated(uint32_t aScrollGeneration,
                            const CSSPoint& aScrollOffset);
@@ -638,23 +638,24 @@ protected:
   static AxisLockMode GetAxisLockMode();
 
   // Helper function for OnSingleTapUp() and OnSingleTapConfirmed().
   nsEventStatus GenerateSingleTap(const ScreenIntPoint& aPoint, mozilla::Modifiers aModifiers);
 
   // Common processing at the end of a touch block.
   void OnTouchEndOrCancel();
 
-  // This is called to request that the main thread snap the scroll position
-  // to a nearby snap position if appropriate. The current scroll position is
-  // used as the final destination.
-  void RequestSnap();
-  // Same as above, but takes into account the current velocity to find a
-  // predicted destination.
-  void RequestSnapToDestination();
+  // Snap to a snap position nearby the current scroll position, if appropriate.
+  void ScrollSnap();
+  // Snap to a snap position nearby the destination predicted based on the
+  // current velocity, if appropriate.
+  void ScrollSnapToDestination();
+
+  // Helper function for ScrollSnap() and ScrollSnapToDestination().
+  void ScrollSnapNear(const CSSPoint& aDestination);
 
   uint64_t mLayersId;
   RefPtr<CompositorBridgeParent> mCompositorBridgeParent;
 
   /* Access to the following two fields is protected by the mRefPtrMonitor,
      since they are accessed on the UI thread but can be cleared on the
      compositor thread. */
   RefPtr<GeckoContentController> mGeckoContentController;
@@ -676,17 +677,18 @@ protected:
   bool mSharingFrameMetricsAcrossProcesses;
   /* Utility function to get the Compositor with which we share the FrameMetrics.
      This function is only callable from the compositor thread. */
   PCompositorBridgeParent* GetSharedFrameMetricsCompositor();
 
 protected:
   // Both |mFrameMetrics| and |mLastContentPaintMetrics| are protected by the
   // monitor. Do not read from or modify either of them without locking.
-  FrameMetrics mFrameMetrics;
+  ScrollMetadata mScrollMetadata;
+  FrameMetrics& mFrameMetrics;  // for convenience, refers to mScrollMetadata.mMetrics
 
   // Protects |mFrameMetrics|, |mLastContentPaintMetrics|, and |mState|.
   // Before manipulating |mFrameMetrics| or |mLastContentPaintMetrics|, the
   // monitor should be held. When setting |mState|, either the SetState()
   // function can be used, or the monitor can be held and then |mState| updated.
   // IMPORTANT: See the note about lock ordering at the top of APZCTreeManager.h.
   // This is mutable to allow entering it from 'const' methods; doing otherwise
   // would significantly limit what methods could be 'const'.
@@ -873,17 +875,17 @@ private:
   void HandleSmoothScrollOverscroll(const ParentLayerPoint& aVelocity);
 
   // Helper function used by AttemptFling().
   void AcceptFling(FlingHandoffState& aHandoffState);
 
   // Start an overscroll animation with the given initial velocity.
   void StartOverscrollAnimation(const ParentLayerPoint& aVelocity);
 
-  void StartSmoothScroll(ScrollSource aSource);
+  void SmoothScrollTo(const CSSPoint& aDestination);
 
   // Returns whether overscroll is allowed during an event.
   bool AllowScrollHandoffInCurrentBlock() const;
 
   void AcknowledgeScrollUpdate() const;
 
   /* ===================================================================
    * The functions and members in this section are used to make ancestor chains
--- a/gfx/layers/apz/src/InputBlockState.cpp
+++ b/gfx/layers/apz/src/InputBlockState.cpp
@@ -83,17 +83,17 @@ InputBlockState::GetBlockId() const
 
 bool
 InputBlockState::IsTargetConfirmed() const
 {
   return mTargetConfirmed;
 }
 
 bool
-InputBlockState::IsAncestorOf(AsyncPanZoomController* aA, AsyncPanZoomController* aB)
+InputBlockState::IsDownchainOf(AsyncPanZoomController* aA, AsyncPanZoomController* aB) const
 {
   if (aA == aB) {
     return true;
   }
 
   bool seenA = false;
   for (size_t i = 0; i < mOverscrollHandoffChain->Length(); ++i) {
     AsyncPanZoomController* apzc = mOverscrollHandoffChain->GetApzcAtIndex(i);
@@ -107,27 +107,35 @@ InputBlockState::IsAncestorOf(AsyncPanZo
   return false;
 }
 
 
 void
 InputBlockState::SetScrolledApzc(AsyncPanZoomController* aApzc)
 {
   // An input block should only have one scrolled APZC.
-  MOZ_ASSERT(!mScrolledApzc || mScrolledApzc == aApzc);
+  MOZ_ASSERT(!mScrolledApzc || (gfxPrefs::APZAllowImmediateHandoff() ? IsDownchainOf(mScrolledApzc, aApzc) : mScrolledApzc == aApzc));
 
   mScrolledApzc = aApzc;
 }
 
 AsyncPanZoomController*
 InputBlockState::GetScrolledApzc() const
 {
   return mScrolledApzc;
 }
 
+bool
+InputBlockState::IsDownchainOfScrolledApzc(AsyncPanZoomController* aApzc) const
+{
+  MOZ_ASSERT(aApzc && mScrolledApzc);
+
+  return IsDownchainOf(mScrolledApzc, aApzc);
+}
+
 CancelableBlockState::CancelableBlockState(const RefPtr<AsyncPanZoomController>& aTargetApzc,
                                            bool aTargetConfirmed)
   : InputBlockState(aTargetApzc, aTargetConfirmed)
   , mPreventDefault(false)
   , mContentResponded(false)
   , mContentResponseTimerExpired(false)
 {
 }
--- a/gfx/layers/apz/src/InputBlockState.h
+++ b/gfx/layers/apz/src/InputBlockState.h
@@ -47,24 +47,25 @@ public:
   const RefPtr<AsyncPanZoomController>& GetTargetApzc() const;
   const RefPtr<const OverscrollHandoffChain>& GetOverscrollHandoffChain() const;
   uint64_t GetBlockId() const;
 
   bool IsTargetConfirmed() const;
 
   void SetScrolledApzc(AsyncPanZoomController* aApzc);
   AsyncPanZoomController* GetScrolledApzc() const;
+  bool IsDownchainOfScrolledApzc(AsyncPanZoomController* aApzc) const;
 
 protected:
   virtual void UpdateTargetApzc(const RefPtr<AsyncPanZoomController>& aTargetApzc);
 
 private:
   // Checks whether |aA| is an ancestor of |aB| (or the same as |aB|) in
   // |mOverscrollHandoffChain|.
-  bool IsAncestorOf(AsyncPanZoomController* aA, AsyncPanZoomController* aB);
+  bool IsDownchainOf(AsyncPanZoomController* aA, AsyncPanZoomController* aB) const;
 
 private:
   RefPtr<AsyncPanZoomController> mTargetApzc;
   bool mTargetConfirmed;
   const uint64_t mBlockId;
 
   // The APZC that was actually scrolled by events in this input block.
   // This is used in configurations where a single input block is only
--- a/gfx/layers/apz/test/gtest/APZCTreeManagerTester.h
+++ b/gfx/layers/apz/test/gtest/APZCTreeManagerTester.h
@@ -8,21 +8,23 @@
 #define mozilla_layers_APZCTreeManagerTester_h
 
 /**
  * Defines a test fixture used for testing multiple APZCs interacting in
  * an APZCTreeManager.
  */
 
 #include "APZTestCommon.h"
+#include "gfxPlatform.h"
 
 class APZCTreeManagerTester : public ::testing::Test {
 protected:
   virtual void SetUp() {
     gfxPrefs::GetSingleton();
+    gfxPlatform::GetPlatform();
     APZThreadUtils::SetThreadAssertionsEnabled(false);
     APZThreadUtils::SetControllerThread(MessageLoop::current());
 
     mcc = new NiceMock<MockContentControllerDelayed>();
     manager = new TestAPZCTreeManager(mcc);
   }
 
   virtual void TearDown() {
@@ -52,50 +54,51 @@ protected:
   RefPtr<LayerManager> lm;
   RefPtr<Layer> root;
 
   RefPtr<TestAPZCTreeManager> manager;
 
 protected:
   static void SetScrollableFrameMetrics(Layer* aLayer, FrameMetrics::ViewID aScrollId,
                                         CSSRect aScrollableRect = CSSRect(-1, -1, -1, -1)) {
-    FrameMetrics metrics;
+    ScrollMetadata metadata;
+    FrameMetrics& metrics = metadata.GetMetrics();
     metrics.SetScrollId(aScrollId);
     // By convention in this test file, START_SCROLL_ID is the root, so mark it as such.
     if (aScrollId == FrameMetrics::START_SCROLL_ID) {
       metrics.SetIsLayersIdRoot(true);
     }
     IntRect layerBound = aLayer->GetVisibleRegion().ToUnknownRegion().GetBounds();
     metrics.SetCompositionBounds(ParentLayerRect(layerBound.x, layerBound.y,
                                                  layerBound.width, layerBound.height));
     metrics.SetScrollableRect(aScrollableRect);
     metrics.SetScrollOffset(CSSPoint(0, 0));
     metrics.SetPageScrollAmount(LayoutDeviceIntSize(50, 100));
     metrics.SetAllowVerticalScrollWithWheel(true);
-    aLayer->SetFrameMetrics(metrics);
+    aLayer->SetScrollMetadata(metadata);
     aLayer->SetClipRect(Some(ViewAs<ParentLayerPixel>(layerBound)));
     if (!aScrollableRect.IsEqualEdges(CSSRect(-1, -1, -1, -1))) {
       // The purpose of this is to roughly mimic what layout would do in the
       // case of a scrollable frame with the event regions and clip. This lets
       // us exercise the hit-testing code in APZCTreeManager
       EventRegions er = aLayer->GetEventRegions();
       IntRect scrollRect = RoundedToInt(aScrollableRect * metrics.LayersPixelsPerCSSPixel()).ToUnknownRect();
       er.mHitRegion = nsIntRegion(IntRect(layerBound.TopLeft(), scrollRect.Size()));
       aLayer->SetEventRegions(er);
     }
   }
 
   void SetScrollHandoff(Layer* aChild, Layer* aParent) {
-    FrameMetrics metrics = aChild->GetFrameMetrics(0);
-    metrics.SetScrollParentId(aParent->GetFrameMetrics(0).GetScrollId());
-    aChild->SetFrameMetrics(metrics);
+    ScrollMetadata metadata = aChild->GetScrollMetadata(0);
+    metadata.GetMetrics().SetScrollParentId(aParent->GetFrameMetrics(0).GetScrollId());
+    aChild->SetScrollMetadata(metadata);
   }
 
   static TestAsyncPanZoomController* ApzcOf(Layer* aLayer) {
-    EXPECT_EQ(1u, aLayer->GetFrameMetricsCount());
+    EXPECT_EQ(1u, aLayer->GetScrollMetadataCount());
     return (TestAsyncPanZoomController*)aLayer->GetAsyncPanZoomController(0);
   }
 
   void CreateSimpleScrollingLayer() {
     const char* layerTreeSyntax = "t";
     nsIntRegion layerVisibleRegion[] = {
       nsIntRegion(IntRect(0,0,200,200)),
     };
@@ -146,19 +149,19 @@ protected:
       nsIntRegion(IntRect(0,0,100,100)),
       nsIntRegion(IntRect(0,0,100,100)),
     };
     root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
     SetScrollableFrameMetrics(layers[0], FrameMetrics::START_SCROLL_ID);
     SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
 
     // Make layers[1] the root content
-    FrameMetrics childMetrics = layers[1]->GetFrameMetrics(0);
-    childMetrics.SetIsRootContent(true);
-    layers[1]->SetFrameMetrics(childMetrics);
+    ScrollMetadata childMetadata = layers[1]->GetScrollMetadata(0);
+    childMetadata.GetMetrics().SetIsRootContent(true);
+    layers[1]->SetScrollMetadata(childMetadata);
 
     // Both layers are fully dispatch-to-content
     EventRegions regions;
     regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 100));
     regions.mDispatchToContentHitRegion = regions.mHitRegion;
     layers[0]->SetEventRegions(regions);
     layers[1]->SetEventRegions(regions);
   }
--- a/gfx/layers/apz/test/gtest/TestBasic.cpp
+++ b/gfx/layers/apz/test/gtest/TestBasic.cpp
@@ -73,48 +73,50 @@ TEST_F(APZCBasicTester, ComplexTransform
   };
   transforms[0].PostScale(0.5f, 0.5f, 1.0f); // this results from the 2.0 resolution on the root layer
   transforms[1].PostScale(2.0f, 1.0f, 1.0f); // this is the 2.0 x-axis CSS transform on the child layer
 
   nsTArray<RefPtr<Layer> > layers;
   RefPtr<LayerManager> lm;
   RefPtr<Layer> root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, lm, layers);
 
-  FrameMetrics metrics;
+  ScrollMetadata metadata;
+  FrameMetrics& metrics = metadata.GetMetrics();
   metrics.SetCompositionBounds(ParentLayerRect(0, 0, 24, 24));
   metrics.SetDisplayPort(CSSRect(-1, -1, 6, 6));
   metrics.SetScrollOffset(CSSPoint(10, 10));
   metrics.SetScrollableRect(CSSRect(0, 0, 50, 50));
   metrics.SetCumulativeResolution(LayoutDeviceToLayerScale2D(2, 2));
   metrics.SetPresShellResolution(2.0f);
   metrics.SetZoom(CSSToParentLayerScale2D(6, 6));
   metrics.SetDevPixelsPerCSSPixel(CSSToLayoutDeviceScale(3));
   metrics.SetScrollId(FrameMetrics::START_SCROLL_ID);
 
-  FrameMetrics childMetrics = metrics;
+  ScrollMetadata childMetadata = metadata;
+  FrameMetrics& childMetrics = childMetadata.GetMetrics();
   childMetrics.SetScrollId(FrameMetrics::START_SCROLL_ID + 1);
 
-  layers[0]->SetFrameMetrics(metrics);
-  layers[1]->SetFrameMetrics(childMetrics);
+  layers[0]->SetScrollMetadata(metadata);
+  layers[1]->SetScrollMetadata(childMetadata);
 
   ParentLayerPoint pointOut;
   AsyncTransform viewTransformOut;
 
   // Both the parent and child layer should behave exactly the same here, because
   // the CSS transform on the child layer does not affect the SampleContentTransformForFrame code
 
   // initial transform
   apzc->SetFrameMetrics(metrics);
-  apzc->NotifyLayersUpdated(metrics, true, true);
+  apzc->NotifyLayersUpdated(metadata, true, true);
   apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
   EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()), viewTransformOut);
   EXPECT_EQ(ParentLayerPoint(60, 60), pointOut);
 
   childApzc->SetFrameMetrics(childMetrics);
-  childApzc->NotifyLayersUpdated(childMetrics, true, true);
+  childApzc->NotifyLayersUpdated(childMetadata, true, true);
   childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
   EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()), viewTransformOut);
   EXPECT_EQ(ParentLayerPoint(60, 60), pointOut);
 
   // do an async scroll by 5 pixels and check the transform
   metrics.ScrollBy(CSSPoint(5, 0));
   apzc->SetFrameMetrics(metrics);
   apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -326,56 +326,16 @@ APZCCallbackHelper::InitializeRootDispla
     // nsRootBoxFrame::BuildDisplayList.
     nsLayoutUtils::SetDisplayPortMargins(content, aPresShell, ScreenMargin(), 0,
         nsLayoutUtils::RepaintMode::DoNotRepaint);
     nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
         content->GetPrimaryFrame(), nsLayoutUtils::RepaintMode::DoNotRepaint);
   }
 }
 
-class FlingSnapEvent : public nsRunnable
-{
-    typedef mozilla::layers::FrameMetrics::ViewID ViewID;
-
-public:
-    FlingSnapEvent(const ViewID& aScrollId,
-                   const mozilla::CSSPoint& aDestination)
-        : mScrollId(aScrollId)
-        , mDestination(aDestination)
-    {
-    }
-
-    NS_IMETHOD Run() {
-        MOZ_ASSERT(NS_IsMainThread());
-
-        nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(mScrollId);
-        if (sf) {
-            sf->FlingSnap(mDestination);
-        }
-
-        return NS_OK;
-    }
-
-protected:
-    ViewID mScrollId;
-    mozilla::CSSPoint mDestination;
-};
-
-void
-APZCCallbackHelper::RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
-                                     const mozilla::CSSPoint& aDestination)
-{
-    nsCOMPtr<nsIRunnable> r1 = new FlingSnapEvent(aScrollId, aDestination);
-    if (!NS_IsMainThread()) {
-        NS_DispatchToMainThread(r1);
-    } else {
-        r1->Run();
-    }
-}
-
 class AcknowledgeScrollUpdateEvent : public nsRunnable
 {
     typedef mozilla::layers::FrameMetrics::ViewID ViewID;
 
 public:
     AcknowledgeScrollUpdateEvent(const ViewID& aScrollId, const uint32_t& aScrollGeneration)
         : mScrollId(aScrollId)
         , mScrollGeneration(aScrollGeneration)
--- a/gfx/layers/apz/util/APZCCallbackHelper.h
+++ b/gfx/layers/apz/util/APZCCallbackHelper.h
@@ -61,24 +61,16 @@ public:
     static bool GetOrCreateScrollIdentifiers(nsIContent* aContent,
                                              uint32_t* aPresShellIdOut,
                                              FrameMetrics::ViewID* aViewIdOut);
 
     /* Initialize a zero-margin displayport on the root document element of the
        given presShell. */
     static void InitializeRootDisplayport(nsIPresShell* aPresShell);
 
-    /* Tell layout to perform scroll snapping for the scrollable frame with the
-     * given scroll id. aDestination specifies the expected landing position of
-     * a current fling or scrolling animation that should be used to select
-     * the scroll snap point.
-     */
-    static void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
-                                 const mozilla::CSSPoint& aDestination);
-
     /* Tell layout that we received the scroll offset update for the given view ID, so
        that it accepts future scroll offset updates from APZ. */
     static void AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId,
                                         const uint32_t& aScrollGeneration);
 
     /* Get the pres shell associated with the root content document enclosing |aContent|. */
     static nsIPresShell* GetRootContentDocumentPresShellForContent(nsIContent* aContent);
 
--- a/gfx/layers/apz/util/ChromeProcessController.cpp
+++ b/gfx/layers/apz/util/ChromeProcessController.cpp
@@ -64,23 +64,16 @@ ChromeProcessController::RequestContentR
 
 void
 ChromeProcessController::PostDelayedTask(Task* aTask, int aDelayMs)
 {
   MessageLoop::current()->PostDelayedTask(FROM_HERE, aTask, aDelayMs);
 }
 
 void
-ChromeProcessController::RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
-                                          const mozilla::CSSPoint& aDestination)
-{
-  APZCCallbackHelper::RequestFlingSnap(aScrollId, aDestination);
-}
-
-void
 ChromeProcessController::AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId,
                                                  const uint32_t& aScrollGeneration)
 {
   APZCCallbackHelper::AcknowledgeScrollUpdate(aScrollId, aScrollGeneration);
 }
 
 void
 ChromeProcessController::Destroy()
--- a/gfx/layers/apz/util/ChromeProcessController.h
+++ b/gfx/layers/apz/util/ChromeProcessController.h
@@ -35,18 +35,16 @@ protected:
 public:
   explicit ChromeProcessController(nsIWidget* aWidget, APZEventState* aAPZEventState, APZCTreeManager* aAPZCTreeManager);
   ~ChromeProcessController();
   virtual void Destroy() override;
 
   // GeckoContentController interface
   virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) override;
   virtual void PostDelayedTask(Task* aTask, int aDelayMs) override;
-  virtual void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
-                                const mozilla::CSSPoint& aDestination) override;
   virtual void AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId,
                                        const uint32_t& aScrollGeneration) override;
 
   virtual void HandleDoubleTap(const mozilla::CSSPoint& aPoint, Modifiers aModifiers,
                                const ScrollableLayerGuid& aGuid) override;
   virtual void HandleSingleTap(const mozilla::CSSPoint& aPoint, Modifiers aModifiers,
                                const ScrollableLayerGuid& aGuid) override;
   virtual void HandleLongTap(const mozilla::CSSPoint& aPoint, Modifiers aModifiers,
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -325,17 +325,17 @@ IntervalOverlap(gfxFloat aTranslation, g
 /**
  * Finds the metrics on |aLayer| with scroll id |aScrollId|, and returns a
  * LayerMetricsWrapper representing the (layer, metrics) pair, or the null
  * LayerMetricsWrapper if no matching metrics could be found.
  */
 static LayerMetricsWrapper
 FindMetricsWithScrollId(Layer* aLayer, FrameMetrics::ViewID aScrollId)
 {
-  for (uint64_t i = 0; i < aLayer->GetFrameMetricsCount(); ++i) {
+  for (uint64_t i = 0; i < aLayer->GetScrollMetadataCount(); ++i) {
     if (aLayer->GetFrameMetrics(i).GetScrollId() == aScrollId) {
       return LayerMetricsWrapper(aLayer, i);
     }
   }
   return LayerMetricsWrapper();
 }
 
 /**
@@ -682,17 +682,17 @@ AsyncCompositionManager::RecordShadowTra
   MOZ_ASSERT(gfxPrefs::CollectScrollTransforms());
   MOZ_ASSERT(CompositorBridgeParent::IsInCompositorThread());
 
   for (Layer* child = aLayer->GetFirstChild();
       child; child = child->GetNextSibling()) {
       RecordShadowTransforms(child);
   }
 
-  for (uint32_t i = 0; i < aLayer->GetFrameMetricsCount(); i++) {
+  for (uint32_t i = 0; i < aLayer->GetScrollMetadataCount(); i++) {
     AsyncPanZoomController* apzc = aLayer->GetAsyncPanZoomController(i);
     if (!apzc) {
       continue;
     }
     gfx::Matrix4x4 shadowTransform = aLayer->AsLayerComposite()->GetShadowBaseTransform();
     if (!shadowTransform.Is2D()) {
       continue;
     }
@@ -832,17 +832,17 @@ AsyncCompositionManager::ApplyAsyncConte
   // its ancestor scroll frames. A scroll frame mask layer only needs to be
   // async transformed for async scrolls of this scroll frame's ancestor
   // scroll frames, not for async scrolls of this scroll frame itself.
   // In the loop below, we iterate over scroll frames from inside to outside.
   // At each iteration, this array contains the layer's ancestor mask layers
   // of all scroll frames inside the current one.
   nsTArray<Layer*> ancestorMaskLayers;
 
-  for (uint32_t i = 0; i < aLayer->GetFrameMetricsCount(); i++) {
+  for (uint32_t i = 0; i < aLayer->GetScrollMetadataCount(); i++) {
     AsyncPanZoomController* controller = aLayer->GetAsyncPanZoomController(i);
     if (!controller) {
       continue;
     }
 
     hasAsyncTransform = true;
 
     AsyncTransform asyncTransformWithoutOverscroll;
@@ -853,29 +853,30 @@ AsyncCompositionManager::ApplyAsyncConte
     AsyncTransformComponentMatrix asyncTransform =
         AsyncTransformComponentMatrix(asyncTransformWithoutOverscroll)
       * overscrollTransform;
 
     if (!aLayer->IsScrollInfoLayer()) {
       controller->MarkAsyncTransformAppliedToContent();
     }
 
-    const FrameMetrics& metrics = aLayer->GetFrameMetrics(i);
+    const ScrollMetadata& scrollMetadata = aLayer->GetScrollMetadata(i);
+    const FrameMetrics& metrics = scrollMetadata.GetMetrics();
 
 #if defined(MOZ_ANDROID_APZ)
     // If we find a metrics which is the root content doc, use that. If not, use
     // the root layer. Since this function recurses on children first we should
     // only end up using the root layer if the entire tree was devoid of a
     // root content metrics. This is a temporary solution; in the long term we
     // should not need the root content metrics at all. See bug 1201529 comment
     // 6 for details.
     if (!(*aOutFoundRoot)) {
       *aOutFoundRoot = metrics.IsRootContent() ||       /* RCD */
             (aLayer->GetParent() == nullptr &&          /* rootmost metrics */
-             i + 1 >= aLayer->GetFrameMetricsCount());
+             i + 1 >= aLayer->GetScrollMetadataCount());
       if (*aOutFoundRoot) {
         mRootScrollableId = metrics.GetScrollId();
         CSSToLayerScale geckoZoom = metrics.LayersPixelsPerCSSPixel().ToScaleFactor();
         if (mIsFirstPaint) {
           LayerIntPoint scrollOffsetLayerPixels = RoundedToInt(metrics.GetScrollOffset() * geckoZoom);
           mContentRect = metrics.GetScrollableRect();
           SetFirstPaintViewport(scrollOffsetLayerPixels,
                                 geckoZoom,
@@ -934,18 +935,18 @@ AsyncCompositionManager::ApplyAsyncConte
 
     // AlignFixedAndStickyLayers may have changed the clip rect, so we have to
     // read it from the layer again.
     asyncClip = aLayer->AsLayerComposite()->GetShadowClipRect();
 
     // Combine the local clip with the ancestor scrollframe clip. This is not
     // included in the async transform above, since the ancestor clip should not
     // move with this APZC.
-    if (metrics.HasClipRect()) {
-      ParentLayerIntRect clip = metrics.ClipRect();
+    if (scrollMetadata.HasClipRect()) {
+      ParentLayerIntRect clip = scrollMetadata.ClipRect();
       if (aLayer->GetParent() && aLayer->GetParent()->GetTransformIsPerspective()) {
         // If our parent layer has a perspective transform, we want to apply
         // our scroll clip to it instead of to this layer (see bug 1168263).
         // A layer with a perspective transform shouldn't have multiple
         // children with FrameMetrics, nor a child with multiple FrameMetrics.
         MOZ_ASSERT(!aClipDeferredToParent);
         aClipDeferredToParent = Some(clip);
       } else {
@@ -957,18 +958,18 @@ AsyncCompositionManager::ApplyAsyncConte
     // the ancestor mask layers for scroll frames *inside* the current scroll
     // frame, so these are the ones we need to shift by our async transform.
     for (Layer* ancestorMaskLayer : ancestorMaskLayers) {
       SetShadowTransform(ancestorMaskLayer,
           ancestorMaskLayer->GetLocalTransformTyped() * asyncTransform);
     }
 
     // Append the ancestor mask layer for this scroll frame to ancestorMaskLayers.
-    if (metrics.GetMaskLayerIndex()) {
-      size_t maskLayerIndex = metrics.GetMaskLayerIndex().value();
+    if (scrollMetadata.GetMaskLayerIndex()) {
+      size_t maskLayerIndex = scrollMetadata.GetMaskLayerIndex().value();
       Layer* ancestorMaskLayer = aLayer->GetAncestorMaskLayerAt(maskLayerIndex);
       ancestorMaskLayers.AppendElement(ancestorMaskLayer);
     }
   }
 
   if (hasAsyncTransform || clipDeferredFromChildren) {
     aLayer->AsLayerComposite()->SetShadowClipRect(
         IntersectMaybeRects(asyncClip, clipDeferredFromChildren));
--- a/gfx/layers/composite/ContainerLayerComposite.cpp
+++ b/gfx/layers/composite/ContainerLayerComposite.cpp
@@ -355,17 +355,18 @@ ContainerRenderVR(ContainerT* aContainer
                                       scaleTransform);
 
   DUMP("<<< ContainerRenderVR [%p]\n", aContainer);
 }
 
 static bool
 NeedToDrawCheckerboardingForLayer(Layer* aLayer, Color* aOutCheckerboardingColor)
 {
-  return (aLayer->GetContentFlags() & Layer::CONTENT_OPAQUE) &&
+  return (aLayer->Manager()->AsyncPanZoomEnabled() &&
+         aLayer->GetContentFlags() & Layer::CONTENT_OPAQUE) &&
          aLayer->IsOpaqueForVisibility() &&
          LayerHasCheckerboardingAPZC(aLayer, aOutCheckerboardingColor);
 }
 
 /* all of the prepared data that we need in RenderLayer() */
 struct PreparedData
 {
   RefPtr<CompositingRenderTarget> mTmpTarget;
@@ -473,17 +474,17 @@ ContainerPrepare(ContainerT* aContainer,
 }
 
 template<class ContainerT> void
 RenderMinimap(ContainerT* aContainer, LayerManagerComposite* aManager,
                    const RenderTargetIntRect& aClipRect, Layer* aLayer)
 {
   Compositor* compositor = aManager->GetCompositor();
 
-  if (aLayer->GetFrameMetricsCount() < 1) {
+  if (aLayer->GetScrollMetadataCount() < 1) {
     return;
   }
 
   AsyncPanZoomController* controller = aLayer->GetAsyncPanZoomController(0);
   if (!controller) {
     return;
   }
 
@@ -649,17 +650,17 @@ RenderLayers(ContainerT* aContainer,
     // Draw a border around scrollable layers.
     // A layer can be scrolled by multiple scroll frames. Draw a border
     // for each.
     // Within the list of scroll frames for a layer, the layer border for a
     // scroll frame lower down is affected by the async transforms on scroll
     // frames higher up, so loop from the top down, and accumulate an async
     // transform as we go along.
     Matrix4x4 asyncTransform;
-    for (uint32_t i = layer->GetFrameMetricsCount(); i > 0; --i) {
+    for (uint32_t i = layer->GetScrollMetadataCount(); i > 0; --i) {
       if (layer->GetFrameMetrics(i - 1).IsScrollable()) {
         // Since the composition bounds are in the parent layer's coordinates,
         // use the parent's effective transform rather than the layer's own.
         ParentLayerRect compositionBounds = layer->GetFrameMetrics(i - 1).GetCompositionBounds();
         aManager->GetCompositor()->DrawDiagnostics(DiagnosticFlags::CONTAINER,
                                                    compositionBounds.ToUnknownRect(),
                                                    gfx::Rect(aClipRect.ToUnknownRect()),
                                                    asyncTransform * aContainer->GetEffectiveTransform());
--- a/gfx/layers/ipc/APZChild.cpp
+++ b/gfx/layers/ipc/APZChild.cpp
@@ -90,24 +90,16 @@ APZChild::~APZChild()
 
 bool
 APZChild::RecvUpdateFrame(const FrameMetrics& aFrameMetrics)
 {
   return mBrowser->UpdateFrame(aFrameMetrics);
 }
 
 bool
-APZChild::RecvRequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
-                               const mozilla::CSSPoint& aDestination)
-{
-  APZCCallbackHelper::RequestFlingSnap(aScrollId, aDestination);
-  return true;
-}
-
-bool
 APZChild::RecvAcknowledgeScrollUpdate(const ViewID& aScrollId,
                                       const uint32_t& aScrollGeneration)
 {
   APZCCallbackHelper::AcknowledgeScrollUpdate(aScrollId, aScrollGeneration);
   return true;
 }
 
 bool
--- a/gfx/layers/ipc/APZChild.h
+++ b/gfx/layers/ipc/APZChild.h
@@ -23,19 +23,16 @@ class APZChild final : public PAPZChild
 {
 public:
   static APZChild* Create(const dom::TabId& aTabId);
 
   ~APZChild();
 
   virtual bool RecvUpdateFrame(const FrameMetrics& frame) override;
 
-  virtual bool RecvRequestFlingSnap(const ViewID& aScrollID,
-                                    const CSSPoint& aDestination) override;
-
   virtual bool RecvAcknowledgeScrollUpdate(const ViewID& aScrollId,
                                            const uint32_t& aScrollGeneration) override;
 
   virtual bool RecvHandleDoubleTap(const CSSPoint& aPoint,
                                    const Modifiers& aModifiers,
                                    const ScrollableLayerGuid& aGuid) override;
 
   virtual bool RecvHandleSingleTap(const CSSPoint& aPoint,
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -362,17 +362,17 @@ LayerTransactionParent::RecvUpdate(Infal
       layer->SetMixBlendMode((gfx::CompositionOp)common.mixBlendMode());
       layer->SetForceIsolatedGroup(common.forceIsolatedGroup());
       if (PLayerParent* maskLayer = common.maskLayerParent()) {
         layer->SetMaskLayer(cast(maskLayer)->AsLayer());
       } else {
         layer->SetMaskLayer(nullptr);
       }
       layer->SetAnimations(common.animations());
-      layer->SetFrameMetrics(common.metrics());
+      layer->SetScrollMetadata(common.scrollMetadata());
       layer->SetDisplayListLog(common.displayListLog().get());
 
       // The updated invalid region is added to the existing one, since we can
       // update multiple times before the next composite.
       layer->AddInvalidRegion(common.invalidRegion());
 
       nsTArray<RefPtr<Layer>> maskLayers;
       for (size_t i = 0; i < common.ancestorMaskLayersParent().Length(); i++) {
@@ -787,17 +787,17 @@ LayerTransactionParent::RecvGetAnimation
 
   *aTransform = transform;
   return true;
 }
 
 static AsyncPanZoomController*
 GetAPZCForViewID(Layer* aLayer, FrameMetrics::ViewID aScrollID)
 {
-  for (uint32_t i = 0; i < aLayer->GetFrameMetricsCount(); i++) {
+  for (uint32_t i = 0; i < aLayer->GetScrollMetadataCount(); i++) {
     if (aLayer->GetFrameMetrics(i).GetScrollId() == aScrollID) {
       return aLayer->GetAsyncPanZoomController(i);
     }
   }
   ContainerLayer* container = aLayer->AsContainerLayer();
   if (container) {
     for (Layer* l = container->GetFirstChild(); l; l = l->GetNextSibling()) {
       AsyncPanZoomController* c = GetAPZCForViewID(l, aScrollID);
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -36,17 +36,17 @@ using mozilla::LayerPoint from "Units.h"
 using mozilla::LayerRect from "Units.h";
 using mozilla::LayerIntRegion from "Units.h";
 using mozilla::ParentLayerIntRect from "Units.h";
 using mozilla::LayoutDeviceIntRect from "Units.h";
 using mozilla::layers::ScaleMode from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::EventRegions from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::EventRegionsOverride from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::DiagnosticTypes from "mozilla/layers/CompositorTypes.h";
-using struct mozilla::layers::FrameMetrics from "FrameMetrics.h";
+using struct mozilla::layers::ScrollMetadata from "FrameMetrics.h";
 using mozilla::layers::FrameMetrics::ViewID from "FrameMetrics.h";
 using struct mozilla::layers::FenceHandle from "mozilla/layers/FenceUtils.h";
 using mozilla::layers::LayersBackend from "mozilla/layers/LayersTypes.h";
 
 namespace mozilla {
 namespace layers {
 
 struct TargetConfig {
@@ -232,17 +232,17 @@ struct CommonLayerAttributes {
   float scrollbarThumbRatio;
   int8_t mixBlendMode;
   bool forceIsolatedGroup;
   nullable PLayer maskLayer;
   PLayer[] ancestorMaskLayers;
   // Animated colors will only honored for ColorLayers.
   Animation[] animations;
   nsIntRegion invalidRegion;
-  FrameMetrics[] metrics;
+  ScrollMetadata[] scrollMetadata;
   nsCString displayListLog;
 };
 
 struct PaintedLayerAttributes {
   nsIntRegion validRegion;
 };
 struct ContainerLayerAttributes {
   float preXScale;
--- a/gfx/layers/ipc/PAPZ.ipdl
+++ b/gfx/layers/ipc/PAPZ.ipdl
@@ -84,17 +84,16 @@ parent:
                               MaybeZoomConstraints aConstraints);
 
 child:
   async UpdateFrame(FrameMetrics frame);
 
   // The following methods correspond to functions on the GeckoContentController
   // interface in gfx/layers/apz/public/GeckoContentController.h. Refer to documentation
   // in that file for these functions.
-  async RequestFlingSnap(ViewID aScrollID, CSSPoint aDestination);
   async AcknowledgeScrollUpdate(ViewID aScrollId, uint32_t aScrollGeneration);
   async HandleDoubleTap(CSSPoint aPoint, Modifiers aModifiers, ScrollableLayerGuid aGuid);
   async HandleSingleTap(CSSPoint aPoint, Modifiers aModifiers, ScrollableLayerGuid aGuid, bool aCallTakeFocusForClickFromTap);
   async HandleLongTap(CSSPoint point, Modifiers aModifiers, ScrollableLayerGuid aGuid, uint64_t aInputBlockId);
   async NotifyAPZStateChange(ViewID aViewId, APZStateChange aChange, int aArg);
   async NotifyFlushComplete();
 
   async __delete__();
--- a/gfx/layers/ipc/RemoteContentController.cpp
+++ b/gfx/layers/ipc/RemoteContentController.cpp
@@ -47,34 +47,16 @@ RemoteContentController::RequestContentR
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (CanSend()) {
     Unused << SendUpdateFrame(aFrameMetrics);
   }
 }
 
 void
-RemoteContentController::RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
-                                          const mozilla::CSSPoint& aDestination)
-{
-  if (MessageLoop::current() != mUILoop) {
-    // We have to send this message from the "UI thread" (main
-    // thread).
-    mUILoop->PostTask(
-      FROM_HERE,
-      NewRunnableMethod(this, &RemoteContentController::RequestFlingSnap,
-                        aScrollId, aDestination));
-    return;
-  }
-  if (CanSend()) {
-    Unused << SendRequestFlingSnap(aScrollId, aDestination);
-  }
-}
-
-void
 RemoteContentController::AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId,
                                                  const uint32_t& aScrollGeneration)
 {
   if (MessageLoop::current() != mUILoop) {
     // We have to send this message from the "UI thread" (main
     // thread).
     mUILoop->PostTask(
       FROM_HERE,
--- a/gfx/layers/ipc/RemoteContentController.h
+++ b/gfx/layers/ipc/RemoteContentController.h
@@ -37,19 +37,16 @@ public:
   explicit RemoteContentController(uint64_t aLayersId,
                                    dom::TabParent* aBrowserParent);
 
   virtual ~RemoteContentController();
 
   // Needs to be called on the main thread.
   virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) override;
 
-  virtual void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
-                                const mozilla::CSSPoint& aDestination) override;
-
   virtual void AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId,
                                        const uint32_t& aScrollGeneration) override;
 
   virtual void HandleDoubleTap(const CSSPoint& aPoint,
                                Modifiers aModifiers,
                                const ScrollableLayerGuid& aGuid) override;
 
   virtual void HandleSingleTap(const CSSPoint& aPoint,
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -839,17 +839,17 @@ ShadowLayerForwarder::EndTransaction(Inf
     if (Layer* maskLayer = mutant->GetMaskLayer()) {
       common.maskLayerChild() = Shadow(maskLayer->AsShadowableLayer());
     } else {
       common.maskLayerChild() = nullptr;
     }
     common.maskLayerParent() = nullptr;
     common.animations() = mutant->GetAnimations();
     common.invalidRegion() = mutant->GetInvalidRegion();
-    common.metrics() = mutant->GetAllFrameMetrics();
+    common.scrollMetadata() = mutant->GetAllScrollMetadata();
     for (size_t i = 0; i < mutant->GetAncestorMaskLayerCount(); i++) {
       auto layer = Shadow(mutant->GetAncestorMaskLayerAt(i)->AsShadowableLayer());
       common.ancestorMaskLayersChild().AppendElement(layer);
     }
     nsCString log;
     mutant->GetDisplayListLog(log);
     common.displayListLog() = log;
 
--- a/gfx/src/nsDeviceContext.cpp
+++ b/gfx/src/nsDeviceContext.cpp
@@ -139,17 +139,17 @@ nsFontCache::GetMetricsFor(const nsFont&
             fm->Language() == language &&
             fm->Orientation() == aParams.orientation) {
             if (i != n) {
                 // promote it to the end of the cache
                 mFontMetrics.RemoveElementAt(i);
                 mFontMetrics.AppendElement(fm);
             }
             fm->GetThebesFontGroup()->UpdateUserFonts();
-            return do_AddRef(Move(fm));
+            return do_AddRef(fm);
         }
     }
 
     // It's not in the cache. Get font metrics and then cache them.
 
     nsFontMetrics::Params params = aParams;
     params.language = language;
     RefPtr<nsFontMetrics> fm = new nsFontMetrics(aFont, params, mContext);
--- a/gfx/tests/gtest/TestLayers.cpp
+++ b/gfx/tests/gtest/TestLayers.cpp
@@ -347,17 +347,25 @@ TEST(Layers, RepositionChild) {
 
   //   0
   // 3 2 1
   ASSERT_EQ(layers[2], layers[3]->GetNextSibling());
   ASSERT_EQ(layers[1], layers[2]->GetNextSibling());
   ASSERT_EQ(nullptr, layers[1]->GetNextSibling());
 }
 
-TEST(LayerMetricsWrapper, SimpleTree) {
+class LayerMetricsWrapperTester : public ::testing::Test {
+protected:
+  virtual void SetUp() {
+    // This ensures ScrollMetadata::sNullMetadata is initialized.
+    gfxPlatform::GetPlatform();
+  }
+};
+
+TEST_F(LayerMetricsWrapperTester, SimpleTree) {
   nsTArray<RefPtr<Layer> > layers;
   RefPtr<LayerManager> lm;
   RefPtr<Layer> root = CreateLayerTree("c(c(c(tt)c(t)))", nullptr, nullptr, lm, layers);
   LayerMetricsWrapper wrapper(root);
 
   ASSERT_EQ(root.get(), wrapper.GetLayer());
   wrapper = wrapper.GetFirstChild();
   ASSERT_EQ(layers[1].get(), wrapper.GetLayer());
@@ -384,53 +392,53 @@ TEST(LayerMetricsWrapper, SimpleTree) {
   ASSERT_EQ(layers[2].get(), wrapper.GetLayer());
   wrapper = wrapper.GetParent();
   ASSERT_EQ(layers[1].get(), wrapper.GetLayer());
   ASSERT_TRUE(layer5 == wrapper.GetLastChild());
   LayerMetricsWrapper rootWrapper(root);
   ASSERT_TRUE(rootWrapper == wrapper.GetParent());
 }
 
-static FrameMetrics
-MakeMetrics(FrameMetrics::ViewID aId) {
-  FrameMetrics metrics;
-  metrics.SetScrollId(aId);
-  return metrics;
+static ScrollMetadata
+MakeMetadata(FrameMetrics::ViewID aId) {
+  ScrollMetadata metadata;
+  metadata.GetMetrics().SetScrollId(aId);
+  return metadata;
 }
 
-TEST(LayerMetricsWrapper, MultiFramemetricsTree) {
+TEST_F(LayerMetricsWrapperTester, MultiFramemetricsTree) {
   nsTArray<RefPtr<Layer> > layers;
   RefPtr<LayerManager> lm;
   RefPtr<Layer> root = CreateLayerTree("c(c(c(tt)c(t)))", nullptr, nullptr, lm, layers);
 
-  nsTArray<FrameMetrics> metrics;
-  metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 0)); // topmost of root layer
-  metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::NULL_SCROLL_ID));
-  metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 1));
-  metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 2));
-  metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::NULL_SCROLL_ID));
-  metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::NULL_SCROLL_ID));      // bottom of root layer
-  root->SetFrameMetrics(metrics);
+  nsTArray<ScrollMetadata> metadata;
+  metadata.InsertElementAt(0, MakeMetadata(FrameMetrics::START_SCROLL_ID + 0)); // topmost of root layer
+  metadata.InsertElementAt(0, MakeMetadata(FrameMetrics::NULL_SCROLL_ID));
+  metadata.InsertElementAt(0, MakeMetadata(FrameMetrics::START_SCROLL_ID + 1));
+  metadata.InsertElementAt(0, MakeMetadata(FrameMetrics::START_SCROLL_ID + 2));
+  metadata.InsertElementAt(0, MakeMetadata(FrameMetrics::NULL_SCROLL_ID));
+  metadata.InsertElementAt(0, MakeMetadata(FrameMetrics::NULL_SCROLL_ID));      // bottom of root layer
+  root->SetScrollMetadata(metadata);
 
-  metrics.Clear();
-  metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 3));
-  layers[1]->SetFrameMetrics(metrics);
+  metadata.Clear();
+  metadata.InsertElementAt(0, MakeMetadata(FrameMetrics::START_SCROLL_ID + 3));
+  layers[1]->SetScrollMetadata(metadata);
 
-  metrics.Clear();
-  metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::NULL_SCROLL_ID));
-  metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 4));
-  layers[2]->SetFrameMetrics(metrics);
+  metadata.Clear();
+  metadata.InsertElementAt(0, MakeMetadata(FrameMetrics::NULL_SCROLL_ID));
+  metadata.InsertElementAt(0, MakeMetadata(FrameMetrics::START_SCROLL_ID + 4));
+  layers[2]->SetScrollMetadata(metadata);
 
-  metrics.Clear();
-  metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 5));
-  layers[4]->SetFrameMetrics(metrics);
+  metadata.Clear();
+  metadata.InsertElementAt(0, MakeMetadata(FrameMetrics::START_SCROLL_ID + 5));
+  layers[4]->SetScrollMetadata(metadata);
 
-  metrics.Clear();
-  metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 6));
-  layers[5]->SetFrameMetrics(metrics);
+  metadata.Clear();
+  metadata.InsertElementAt(0, MakeMetadata(FrameMetrics::START_SCROLL_ID + 6));
+  layers[5]->SetScrollMetadata(metadata);
 
   LayerMetricsWrapper wrapper(root, LayerMetricsWrapper::StartAt::TOP);
   nsTArray<Layer*> expectedLayers;
   expectedLayers.AppendElement(layers[0].get());
   expectedLayers.AppendElement(layers[0].get());
   expectedLayers.AppendElement(layers[0].get());
   expectedLayers.AppendElement(layers[0].get());
   expectedLayers.AppendElement(layers[0].get());
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/layers/AsyncTransactionTracker.h" // for AsyncTransactionTracker
 #include "mozilla/layers/CompositorBridgeChild.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/layers/SharedBufferManagerChild.h"
 #include "mozilla/layers/ISurfaceAllocator.h"     // for GfxMemoryImageReporter
+#include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 
 #include "mozilla/Logging.h"
 #include "mozilla/Services.h"
 #include "prprf.h"
 
 #include "gfxCrashReporterUtils.h"
@@ -62,16 +63,17 @@
 #include "gfxUtils.h" // for NextPowerOfTwo
 
 #include "nsUnicodeRange.h"
 #include "nsServiceManagerUtils.h"
 #include "nsTArray.h"
 #include "nsILocaleService.h"
 #include "nsIObserverService.h"
 #include "nsIScreenManager.h"
+#include "FrameMetrics.h"
 #include "MainThreadUtils.h"
 #ifdef MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"
 #endif
 
 #include "nsWeakReference.h"
 
 #include "cairo.h"
@@ -752,16 +754,19 @@ gfxPlatform::Init()
     }
 
 #ifdef USE_SKIA
     uint32_t skiaCacheSize = GetSkiaGlyphCacheSize();
     if (skiaCacheSize != kDefaultGlyphCacheSize) {
       SkGraphics::SetFontCacheLimit(skiaCacheSize);
     }
 #endif
+
+    ScrollMetadata::sNullMetadata = new ScrollMetadata();
+    ClearOnShutdown(&ScrollMetadata::sNullMetadata);
 }
 
 static bool sLayersIPCIsUp = false;
 
 void
 gfxPlatform::Shutdown()
 {
     if (!gPlatform) {
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -393,16 +393,17 @@ private:
   DECL_GFX_PREF(Once, "layers.use-image-offscreen-surfaces",   UseImageOffscreenSurfaces, bool, true);
   DECL_GFX_PREF(Live, "layers.single-tile.enabled",            LayersSingleTileEnabled, bool, true);
 
   DECL_GFX_PREF(Live, "layout.css.scroll-behavior.damping-ratio", ScrollBehaviorDampingRatio, float, 1.0f);
   DECL_GFX_PREF(Live, "layout.css.scroll-behavior.enabled",    ScrollBehaviorEnabled, bool, false);
   DECL_GFX_PREF(Live, "layout.css.scroll-behavior.spring-constant", ScrollBehaviorSpringConstant, float, 250.0f);
   DECL_GFX_PREF(Live, "layout.css.scroll-snap.prediction-max-velocity", ScrollSnapPredictionMaxVelocity, int32_t, 2000);
   DECL_GFX_PREF(Live, "layout.css.scroll-snap.prediction-sensitivity", ScrollSnapPredictionSensitivity, float, 0.750f);
+  DECL_GFX_PREF(Live, "layout.css.scroll-snap.proximity-threshold", ScrollSnapProximityThreshold, int32_t, 200);
   DECL_GFX_PREF(Once, "layout.css.touch_action.enabled",       TouchActionEnabled, bool, false);
   DECL_GFX_PREF(Live, "layout.display-list.dump",              LayoutDumpDisplayList, bool, false);
   DECL_GFX_PREF(Live, "layout.event-regions.enabled",          LayoutEventRegionsEnabledDoNotUseDirectly, bool, false);
   DECL_GFX_PREF(Once, "layout.frame_rate",                     LayoutFrameRate, int32_t, -1);
   DECL_GFX_PREF(Once, "layout.paint_rects_separately",         LayoutPaintRectsSeparately, bool, true);
 
   // This and code dependent on it should be removed once containerless scrolling looks stable.
   DECL_GFX_PREF(Once, "layout.scroll.root-frame-containers",   LayoutUseContainersForRootFrames, bool, true);
--- a/ipc/chromium/src/base/message_loop.cc
+++ b/ipc/chromium/src/base/message_loop.cc
@@ -198,22 +198,16 @@ void MessageLoop::RemoveDestructionObser
   destruction_observers_.RemoveObserver(obs);
 }
 
 void MessageLoop::Run() {
   AutoRunState save_state(this);
   RunHandler();
 }
 
-void MessageLoop::RunAllPending() {
-  AutoRunState save_state(this);
-  state_->quit_received = true;  // Means run until we would otherwise block.
-  RunHandler();
-}
-
 // Runs the loop in two different SEH modes:
 // enable_SEH_restoration_ = false : any unhandled exception goes to the last
 // one that calls SetUnhandledExceptionFilter().
 // enable_SEH_restoration_ = true : any unhandled exception goes to the filter
 // that was existed before the loop was run.
 void MessageLoop::RunHandler() {
 #if defined(OS_WIN)
   if (exception_restoration_) {
@@ -261,32 +255,22 @@ void MessageLoop::Quit() {
     state_->quit_received = true;
   } else {
     NOTREACHED() << "Must be inside Run to call Quit";
   }
 }
 
 void MessageLoop::PostTask(
     const tracked_objects::Location& from_here, Task* task) {
-  PostTask_Helper(from_here, task, 0, true);
+  PostTask_Helper(from_here, task, 0);
 }
 
 void MessageLoop::PostDelayedTask(
     const tracked_objects::Location& from_here, Task* task, int delay_ms) {
-  PostTask_Helper(from_here, task, delay_ms, true);
-}
-
-void MessageLoop::PostNonNestableTask(
-    const tracked_objects::Location& from_here, Task* task) {
-  PostTask_Helper(from_here, task, 0, false);
-}
-
-void MessageLoop::PostNonNestableDelayedTask(
-    const tracked_objects::Location& from_here, Task* task, int delay_ms) {
-  PostTask_Helper(from_here, task, delay_ms, false);
+  PostTask_Helper(from_here, task, delay_ms);
 }
 
 void MessageLoop::PostIdleTask(
     const tracked_objects::Location& from_here, Task* task) {
   DCHECK(current() == this);
 
 #ifdef MOZ_TASK_TRACER
   task = mozilla::tasktracer::CreateTracedTask(task);
@@ -295,27 +279,26 @@ void MessageLoop::PostIdleTask(
 
   task->SetBirthPlace(from_here);
   PendingTask pending_task(task, false);
   deferred_non_nestable_work_queue_.push(pending_task);
 }
 
 // Possibly called on a background thread!
 void MessageLoop::PostTask_Helper(
-    const tracked_objects::Location& from_here, Task* task, int delay_ms,
-    bool nestable) {
+    const tracked_objects::Location& from_here, Task* task, int delay_ms) {
 
 #ifdef MOZ_TASK_TRACER
   task = mozilla::tasktracer::CreateTracedTask(task);
   (static_cast<mozilla::tasktracer::TracedTask*>(task))->DispatchTask(delay_ms);
 #endif
 
   task->SetBirthPlace(from_here);
 
-  PendingTask pending_task(task, nestable);
+  PendingTask pending_task(task, true);
 
   if (delay_ms > 0) {
     pending_task.delayed_run_time =
         TimeTicks::Now() + TimeDelta::FromMilliseconds(delay_ms);
   } else {
     DCHECK(delay_ms == 0) << "delay should not be negative";
   }
 
--- a/ipc/chromium/src/base/message_loop.h
+++ b/ipc/chromium/src/base/message_loop.h
@@ -112,62 +112,23 @@ public:
   // on the thread that executes MessageLoop::Run().
 
   B2G_ACL_EXPORT void PostTask(
       const tracked_objects::Location& from_here, Task* task);
 
   void PostDelayedTask(
       const tracked_objects::Location& from_here, Task* task, int delay_ms);
 
-  void PostNonNestableTask(
-      const tracked_objects::Location& from_here, Task* task);
-
-  void PostNonNestableDelayedTask(
-      const tracked_objects::Location& from_here, Task* task, int delay_ms);
-
   // PostIdleTask is not thread safe and should be called on this thread
   void PostIdleTask(
       const tracked_objects::Location& from_here, Task* task);
 
-  // A variant on PostTask that deletes the given object.  This is useful
-  // if the object needs to live until the next run of the MessageLoop (for
-  // example, deleting a RenderProcessHost from within an IPC callback is not
-  // good).
-  //
-  // NOTE: This method may be called on any thread.  The object will be deleted
-  // on the thread that executes MessageLoop::Run().  If this is not the same
-  // as the thread that calls PostDelayedTask(FROM_HERE, ), then T MUST inherit
-  // from RefCountedThreadSafe<T>!
-  template <class T>
-  void DeleteSoon(const tracked_objects::Location& from_here, T* object) {
-    PostNonNestableTask(from_here, new DeleteTask<T>(object));
-  }
-
-  // A variant on PostTask that releases the given reference counted object
-  // (by calling its Release method).  This is useful if the object needs to
-  // live until the next run of the MessageLoop, or if the object needs to be
-  // released on a particular thread.
-  //
-  // NOTE: This method may be called on any thread.  The object will be
-  // released (and thus possibly deleted) on the thread that executes
-  // MessageLoop::Run().  If this is not the same as the thread that calls
-  // PostDelayedTask(FROM_HERE, ), then T MUST inherit from
-  // RefCountedThreadSafe<T>!
-  template <class T>
-  void ReleaseSoon(const tracked_objects::Location& from_here, T* object) {
-    PostNonNestableTask(from_here, new ReleaseTask<T>(object));
-  }
-
   // Run the message loop.
   void Run();
 
-  // Process all pending tasks, windows messages, etc., but don't wait/sleep.
-  // Return as soon as all items that can be run are taken care of.
-  void RunAllPending();
-
   // Signals the Run method to return after it is done processing all pending
   // messages.  This method may only be called on the same thread that called
   // Run, and Run must still be on the call stack.
   //
   // Use QuitTask if you need to Quit another thread's MessageLoop, but note
   // that doing so is fairly dangerous if the target thread makes nested calls
   // to MessageLoop::Run.  The problem being that you won't know which nested
   // run loop you are quiting, so be careful!
@@ -390,17 +351,17 @@ public:
 
   // Delete tasks that haven't run yet without running them.  Used in the
   // destructor to make sure all the task's destructors get called.  Returns
   // true if some work was done.
   bool DeletePendingTasks();
 
   // Post a task to our incomming queue.
   void PostTask_Helper(const tracked_objects::Location& from_here, Task* task,
-                       int delay_ms, bool nestable);
+                       int delay_ms);
 
   // base::MessagePump::Delegate methods:
   virtual bool DoWork() override;
   virtual bool DoDelayedWork(base::TimeTicks* next_delayed_work_time) override;
   virtual bool DoIdleWork() override;
 
   Type type_;
   int32_t id_;
--- a/ipc/chromium/src/base/task.h
+++ b/ipc/chromium/src/base/task.h
@@ -209,34 +209,16 @@ class DeleteTask : public CancelableTask
   virtual void Cancel() {
     obj_ = NULL;
   }
  private:
   T* MOZ_UNSAFE_REF("The validity of this pointer must be enforced by "
                     "external factors.") obj_;
 };
 
-// Task to Release() an object
-template<class T>
-class ReleaseTask : public CancelableTask {
- public:
-  explicit ReleaseTask(T* obj) : obj_(obj) {
-  }
-  virtual void Run() {
-    if (obj_)
-      obj_->Release();
-  }
-  virtual void Cancel() {
-    obj_ = NULL;
-  }
- private:
-  T* MOZ_UNSAFE_REF("The validity of this pointer must be enforced by "
-                    "external factors.") obj_;
-};
-
 // RunnableMethodTraits --------------------------------------------------------
 //
 // This traits-class is used by RunnableMethod to manage the lifetime of the
 // callee object.  By default, it is assumed that the callee supports AddRef
 // and Release methods.  A particular class can specialize this template to
 // define other lifetime management.  For example, if the callee is known to
 // live longer than the RunnableMethod object, then a RunnableMethodTraits
 // struct could be defined with empty RetainCallee and ReleaseCallee methods.
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -113,16 +113,18 @@ struct RunnableMethodTraits<mozilla::ipc
             DebugAbort(__FILE__, __LINE__, #_cond,## __VA_ARGS__);  \
     } while (0)
 
 static MessageChannel* gParentProcessBlocker;
 
 namespace mozilla {
 namespace ipc {
 
+static const int kMinTelemetryMessageSize = 8192;
+
 const int32_t MessageChannel::kNoTimeout = INT32_MIN;
 
 // static
 bool MessageChannel::sIsPumpingMessages = false;
 
 enum Direction
 {
     IN_MESSAGE,
@@ -744,16 +746,20 @@ MessageChannel::Echo(Message* aMsg)
 
     mLink->EchoMessage(msg.forget());
     return true;
 }
 
 bool
 MessageChannel::Send(Message* aMsg)
 {
+    if (aMsg->size() >= kMinTelemetryMessageSize) {
+        Telemetry::Accumulate(Telemetry::IPC_MESSAGE_SIZE, nsCString(aMsg->name()), aMsg->size());
+    }
+
     CxxStackFrame frame(*this, OUT_MESSAGE, aMsg);
 
     nsAutoPtr<Message> msg(aMsg);
     AssertWorkerThread();
     mMonitor->AssertNotCurrentThreadOwns();
     if (MSG_ROUTING_NONE == msg->routing_id()) {
         ReportMessageRouteError("MessageChannel::Send");
         return false;
@@ -1040,16 +1046,20 @@ MessageChannel::ProcessPendingRequests(A
             ProcessPendingRequest(*it);
         }
     }
 }
 
 bool
 MessageChannel::Send(Message* aMsg, Message* aReply)
 {
+    if (aMsg->size() >= kMinTelemetryMessageSize) {
+        Telemetry::Accumulate(Telemetry::IPC_MESSAGE_SIZE, nsCString(aMsg->name()), aMsg->size());
+    }
+
     nsAutoPtr<Message> msg(aMsg);
 
     // Sanity checks.
     AssertWorkerThread();
     mMonitor->AssertNotCurrentThreadOwns();
 
 #ifdef OS_WIN
     SyncStackFrame frame(this, false);
--- a/ipc/glue/ProtocolUtils.cpp
+++ b/ipc/glue/ProtocolUtils.cpp
@@ -311,16 +311,34 @@ void AnnotateSystemError()
   if (error) {
     CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("SystemError"),
                                        nsPrintfCString("%lld", error));
   }
 }
 #endif
 
 void
+LogMessageForProtocol(const char* aTopLevelProtocol, base::ProcessId aOtherPid,
+                      const char* aContextDescription,
+                      const char* aMessageDescription,
+                      MessageDirection aDirection)
+{
+  nsPrintfCString logMessage("[time: %" PRId64 "][%d%s%d] [%s] %s %s\n",
+                             PR_Now(), base::GetCurrentProcId(),
+                             aDirection == MessageDirection::eReceiving ? "<-" : "->",
+                             aOtherPid, aTopLevelProtocol,
+                             aContextDescription,
+                             aMessageDescription);
+#ifdef ANDROID
+  __android_log_write(ANDROID_LOG_INFO, "GeckoIPC", logMessage.get());
+#endif
+  fputs(logMessage.get(), stderr);
+}
+
+void
 ProtocolErrorBreakpoint(const char* aMsg)
 {
     // Bugs that generate these error messages can be tough to
     // reproduce.  Log always in the hope that someone finds the error
     // message.
     printf_stderr("IPDL protocol error: %s\n", aMsg);
 }
 
--- a/ipc/glue/ProtocolUtils.h
+++ b/ipc/glue/ProtocolUtils.h
@@ -291,16 +291,27 @@ LoggingEnabledFor(const char *aTopLevelP
         return false;
     }
     return strcmp(filter, "1") == 0 || strcmp(filter, aTopLevelProtocol) == 0;
 #else
     return false;
 #endif
 }
 
+enum class MessageDirection {
+    eSending,
+    eReceiving,
+};
+
+MOZ_NEVER_INLINE void
+LogMessageForProtocol(const char* aTopLevelProtocol, base::ProcessId aOtherPid,
+                      const char* aContextDescription,
+                      const char* aMessageDescription,
+                      MessageDirection aDirection);
+
 MOZ_NEVER_INLINE void
 ProtocolErrorBreakpoint(const char* aMsg);
 
 MOZ_NEVER_INLINE void
 FatalError(const char* aProtocolName, const char* aMsg,
            base::ProcessId aOtherPid, bool aIsParent);
 
 struct PrivateIPDLInterface {};
--- a/ipc/ipdl/ipdl/builtin.py
+++ b/ipc/ipdl/ipdl/builtin.py
@@ -38,17 +38,16 @@ Types = (
     'nsCString',
     'mozilla::ipc::Shmem',
     'mozilla::ipc::FileDescriptor'
 )
 
 
 HeaderIncludes = (
     'mozilla/Attributes.h',
-    'prtime.h',
     'IPCMessageStart.h',
     'ipc/IPCMessageUtils.h',
     'mozilla/RefPtr.h',
     'nsStringGlue.h',
     'nsTArray.h',
     'mozilla/ipc/ProtocolUtils.h',
     'nsTHashtable.h',
 )
--- a/ipc/ipdl/ipdl/lower.py
+++ b/ipc/ipdl/ipdl/lower.py
@@ -919,36 +919,26 @@ class MessageDecl(ipdl.ast.MessageDecl):
         return 'Msg_%s'% (self.decl.progname)
 
     def prettyMsgName(self, pfx=''):
         return pfx + self.msgClass()
 
     def pqMsgClass(self):
         return '%s::%s'% (self.namespace, self.msgClass())
 
-    def msgCast(self, msgexpr):
-        return ExprCast(msgexpr, self.msgCxxType(const=1, ptr=1), static=1)
-
-    def msgCxxType(self, const=0, ref=0, ptr=0):
-        return Type(self.pqMsgClass(), const=const, ref=ref, ptr=ptr)
-
     def msgId(self):  return self.msgClass()+ '__ID'
     def pqMsgId(self):
         return '%s::%s'% (self.namespace, self.msgId())
 
     def replyClass(self):
         return 'Reply_%s'% (self.decl.progname)
 
     def pqReplyClass(self):
         return '%s::%s'% (self.namespace, self.replyClass())
 
-    def replyCast(self, replyexpr):
-        return ExprCast(replyexpr, Type(self.pqReplyClass(), const=1, ptr=1),
-                        static=1)
-
     def replyId(self):  return self.replyClass()+ '__ID'
     def pqReplyId(self):
         return '%s::%s'% (self.namespace, self.replyId())
 
     def prettyReplyName(self, pfx=''):
         return pfx + self.replyClass()
 
     def actorDecl(self):
@@ -1657,30 +1647,38 @@ class _GenerateProtocolCode(ipdl.ast.Vis
         msgenum.addId(self.protocol.name +'End')
         ns.addstmts([ StmtDecl(Decl(msgenum, '')), Whitespace.NL ])
 
         tfDecl, tfDefn = _splitFuncDeclDefn(self.genTransitionFunc())
         ns.addstmts([ tfDecl, Whitespace.NL ])
         self.funcDefns.append(tfDefn)
 
         for md in p.messageDecls:
-            ns.addstmts([
-                _generateMessageClass(md.msgClass(), md.msgId(),
-                                      md.decl.type.priority,
-                                      md.prettyMsgName(p.name+'::'),
-                                      md.decl.type.compress),
-                Whitespace.NL ])
+            decls = []
+
+            mfDecl, mfDefn = _splitFuncDeclDefn(
+                _generateMessageConstructor(md.msgClass(), md.msgId(),
+                                            md.decl.type.priority,
+                                            md.prettyMsgName(p.name+'::'),
+                                            md.decl.type.compress))
+            decls.append(mfDecl)
+            self.funcDefns.append(mfDefn)
+
             if md.hasReply():
-                ns.addstmts([
-                    _generateMessageClass(
+                rfDecl, rfDefn = _splitFuncDeclDefn(
+                    _generateMessageConstructor(
                         md.replyClass(), md.replyId(),
                         md.decl.type.priority,
                         md.prettyReplyName(p.name+'::'),
-                        md.decl.type.compress),
-                    Whitespace.NL ])
+                        md.decl.type.compress))
+                decls.append(rfDecl)
+                self.funcDefns.append(rfDefn)
+
+            decls.append(Whitespace.NL)
+            ns.addstmts(decls)
 
         ns.addstmts([ Whitespace.NL, Whitespace.NL ])
 
 
     def genBridgeFunc(self, bridge):
         p = self.protocol
         parentHandleType = _cxxBareType(ActorType(bridge.parent.ptype),
                                         _otherSide(bridge.parent.side),
@@ -1892,107 +1890,48 @@ class _GenerateProtocolCode(ipdl.ast.Vis
                 StmtExpr(ExprAssn(ExprDeref(nextvar), _errorState())),
                 StmtReturn(ExprLiteral.FALSE),
             ])
 
         return transitionfunc
 
 ##--------------------------------------------------
 
-def _generateMessageClass(clsname, msgid, priority, prettyName, compress):
-    cls = Class(name=clsname, inherits=[ Inherit(Type('IPC::Message')) ])
-    cls.addstmt(Label.PUBLIC)
-
-    idenum = TypeEnum()
-    idenum.addId('ID', msgid)
-    cls.addstmt(StmtDecl(Decl(idenum, '')))
-
-    # make the message constructor
+def _generateMessageConstructor(clsname, msgid, priority, prettyName, compress):
+    routingId = ExprVar('routingId')
+
+    func = FunctionDefn(FunctionDecl(
+        clsname,
+        params=[ Decl(Type('int32_t'), routingId.name) ],
+        ret=Type('IPC::Message', ptr=1)))
+
     if compress == 'compress':
-        compression = ExprVar('COMPRESSION_ENABLED')
+        compression = ExprVar('IPC::Message::COMPRESSION_ENABLED')
     elif compress:
         assert compress == 'compressall'
-        compression = ExprVar('COMPRESSION_ALL')
+        compression = ExprVar('IPC::Message::COMPRESSION_ALL')
     else:
-        compression = ExprVar('COMPRESSION_NONE')
+        compression = ExprVar('IPC::Message::COMPRESSION_NONE')
     if priority == ipdl.ast.NORMAL_PRIORITY:
         priorityEnum = 'IPC::Message::PRIORITY_NORMAL'
     elif priority == ipdl.ast.HIGH_PRIORITY:
         priorityEnum = 'IPC::Message::PRIORITY_HIGH'
     else:
         assert priority == ipdl.ast.URGENT_PRIORITY
         priorityEnum = 'IPC::Message::PRIORITY_URGENT'
-    routingId = ExprVar('routingId')
-    ctor = ConstructorDefn(
-        ConstructorDecl(clsname, params=[ Decl(Type('int32_t'), routingId.name) ]),
-        memberinits=[ ExprMemberInit(ExprVar('IPC::Message'),
-                                     [ routingId,
-                                       ExprVar('ID'),
-                                       ExprVar(priorityEnum),
-                                       compression,
-                                       ExprLiteral.String(prettyName) ]) ])
-    cls.addstmts([ ctor, Whitespace.NL ])
-
-    # generate a logging function
-    # 'pfx' will be something like "[FooParent] sent"
-    pfxvar = ExprVar('pfx__')
-    otherpid = ExprVar('otherPid__')
-    receiving = ExprVar('receiving__')
-    logger = MethodDefn(MethodDecl(
-        'Log',
-        params=([ Decl(Type('std::string', const=1, ref=1), pfxvar.name),
-                  Decl(Type('base::ProcessId'), otherpid.name),
-                  Decl(Type('bool'), receiving.name) ]),
-        const=1))
-    # TODO/cjones: allow selecting what information is printed to
-    # the log
-    msgvar = ExprVar('logmsg__')
-    logger.addstmt(StmtDecl(Decl(Type('std::string'), msgvar.name)))
-
-    def appendToMsg(thing):
-        return StmtExpr(ExprCall(ExprSelect(msgvar, '.', 'append'),
-                                 args=[ thing ]))
-    logger.addstmts([
-        StmtExpr(ExprCall(
-            ExprVar('StringAppendF'),
-            args=[ ExprAddrOf(msgvar),
-                   ExprLiteral.String('[time:%" PRId64 "][%d%s%d]'),
-                   ExprCall(ExprVar('PR_Now')),
-                   ExprCall(ExprVar('base::GetCurrentProcId')),
-                   ExprConditional(receiving, ExprLiteral.String('<-'),
-                                   ExprLiteral.String('->')),
-                   otherpid ])),
-        appendToMsg(pfxvar),
-        appendToMsg(ExprLiteral.String(clsname +'(')),
-        Whitespace.NL
-    ])
-
-    # TODO turn this back on when string stuff is sorted
-
-    logger.addstmt(appendToMsg(ExprLiteral.String('[TODO])\\n')))
-
-    logger.addstmts([
-        CppDirective('ifdef', 'ANDROID'),
-        StmtExpr(ExprCall(
-            ExprVar('__android_log_write'),
-            args=[ ExprVar('ANDROID_LOG_INFO'),
-                   ExprLiteral.String('GeckoIPC'),
-                   ExprCall(ExprSelect(msgvar, '.', 'c_str')) ])),
-        CppDirective('endif')
-    ])
-
-    # and actually print the log message
-    logger.addstmt(StmtExpr(ExprCall(
-        ExprVar('fputs'),
-        args=[ ExprCall(ExprSelect(msgvar, '.', 'c_str')),
-               ExprVar('stderr') ])))
-
-    cls.addstmt(logger)
-
-    return cls
+
+    func.addstmt(
+        StmtReturn(ExprNew(Type('IPC::Message'),
+                           args=[ routingId,
+                                  ExprVar(msgid),
+                                  ExprVar(priorityEnum),
+                                  compression,
+                                  ExprLiteral.String(prettyName) ])))
+
+    return func
 
 ##--------------------------------------------------
 
 class _ComputeTypeDeps(TypeVisitor):
     '''Pass that gathers the C++ types that a particular IPDL type
 (recursively) depends on.  There are two kinds of dependencies: (i)
 types that need forward declaration; (ii) types that need a |using|
 stmt.  Some types generate both kinds.'''
@@ -5163,19 +5102,19 @@ class _GenerateProtocolActorCode(ipdl.as
                  StmtExpr(ExprAssn(_actorId(actorexpr), _FREED_ACTOR_ID)) ]
 
     def makeMessage(self, md, errfn, fromActor=None):
         msgvar = self.msgvar
         routingId = self.protocol.routingId(fromActor)
         this = None
         if md.decl.type.isDtor():  this = md.actorDecl().var()
 
-        stmts = ([ StmtDecl(Decl(Type(md.pqMsgClass(), ptr=1), msgvar.name),
-                            init=ExprNew(Type(md.pqMsgClass()),
-                                         args=[ routingId ])) ]
+        stmts = ([ StmtDecl(Decl(Type('IPC::Message', ptr=1), msgvar.name),
+                            init=ExprCall(ExprVar(md.pqMsgClass()),
+                                          args=[ routingId ])) ]
                  + [ Whitespace.NL ]
                  + [ StmtExpr(self.write(p.ipdltype, p.var(), msgvar, this))
                      for p in md.params ]
                  + [ Whitespace.NL ]
                  + self.setMessageFlags(md, msgvar, reply=0))
         return msgvar, stmts
 
 
@@ -5184,22 +5123,22 @@ class _GenerateProtocolActorCode(ipdl.as
             routingId = self.protocol.routingId()
         # TODO special cases for async ctor/dtor replies
         if not md.decl.type.hasReply():
             return [ ]
 
         replyvar = self.replyvar
         return (
             [ StmtExpr(ExprAssn(
-                replyvar, ExprNew(Type(md.pqReplyClass()), args=[ routingId ]))),
+                replyvar, ExprCall(ExprVar(md.pqReplyClass()), args=[ routingId ]))),
               Whitespace.NL ]
             + [ StmtExpr(self.write(r.ipdltype, r.var(), replyvar))
                 for r in md.returns ]
             + self.setMessageFlags(md, replyvar, reply=1)
-            + [ self.logMessage(md, md.replyCast(replyvar), 'Sending reply ') ])
+            + [ self.logMessage(md, replyvar, 'Sending reply ') ])
 
 
     def setMessageFlags(self, md, var, reply):
         stmts = [ ]
 
         if md.decl.type.isSync():
             stmts.append(StmtExpr(ExprCall(
                 ExprSelect(var, '->', 'set_sync'))))
@@ -5224,17 +5163,17 @@ class _GenerateProtocolActorCode(ipdl.as
             # are forwarding the message name (yuck) or making the
             # IPDL|*Channel abstraction leak more
             StmtExpr(ExprCall(
                 ExprSelect(
                     ExprCast(msgvar, Type('Message', ref=1), const=1),
                     '.', 'set_name'),
                 args=[ ExprLiteral.String(md.prettyMsgName(self.protocol.name
                                                            +'::')) ])),
-            self.logMessage(md, md.msgCast(msgexpr), 'Received ',
+            self.logMessage(md, msgexpr, 'Received ',
                             receiving=True),
             self.profilerLabel('Recv', md.decl.progname),
             Whitespace.NL
         ])
 
         if 0 == len(md.params):
             return stmts
 
@@ -5262,17 +5201,17 @@ class _GenerateProtocolActorCode(ipdl.as
                         for p in md.params[start:] ]
             + [ self.endRead(msgvar, itervar) ]))
 
         return stmts
 
 
     def deserializeReply(self, md, replyexpr, side, errfn, actor=None):
         stmts = [ Whitespace.NL,
-                   self.logMessage(md, md.replyCast(replyexpr),
+                   self.logMessage(md, replyexpr,
                                    'Received reply ', actor, receiving=True) ]
         if 0 == len(md.returns):
             return stmts
 
         itervar = self.itervar
         stmts.extend(
             [ Whitespace.NL,
               StmtDecl(Decl(Type.VOIDPTR, itervar.name),
@@ -5375,22 +5314,26 @@ class _GenerateProtocolActorCode(ipdl.as
             ret=Type.BOOL)
         if md.decl.type.isCtor():
             decl.ret = md.actorDecl().bareType(self.side)
         return decl
 
     def logMessage(self, md, msgptr, pfx, actor=None, receiving=False):
         actorname = _actorName(self.protocol.name, self.side)
 
-        topLevel = self.protocol.decl.type.toplevel().name()
-        return _ifLogging(ExprLiteral.String(topLevel), [ StmtExpr(ExprCall(
-            ExprSelect(msgptr, '->', 'Log'),
-            args=[ ExprLiteral.String('['+ actorname +'] '+ pfx),
-                   self.protocol.callOtherPid(actor),
-                   ExprLiteral.TRUE if receiving else ExprLiteral.FALSE ])) ])
+        return _ifLogging(ExprLiteral.String(actorname),
+                          [ StmtExpr(ExprCall(
+                              ExprVar('mozilla::ipc::LogMessageForProtocol'),
+                              args=[ ExprLiteral.String(actorname),
+                                     self.protocol.callOtherPid(actor),
+                                     ExprLiteral.String(pfx),
+                                     ExprCall(ExprSelect(msgptr, '->', 'name')),
+                                     ExprVar('mozilla::ipc::MessageDirection::eReceiving'
+                                             if receiving
+                                             else 'mozilla::ipc::MessageDirection::eSending') ])) ])
 
     def profilerLabel(self, tag, msgname):
         return StmtExpr(ExprCall(ExprVar('PROFILER_LABEL'),
                                  [ ExprLiteral.String('IPDL::' + self.protocol.name),
                                    ExprLiteral.String(tag + msgname),
                                    ExprVar('js::ProfileEntry::Category::OTHER') ]))
 
     def saveActorId(self, md):
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -681,19 +681,19 @@ struct NewLayerEntry {
     , mIsCaret(false)
     , mIsPerspectiveItem(false)
   {}
   // mLayer is null if the previous entry is for a PaintedLayer that hasn't
   // been optimized to some other form (yet).
   RefPtr<Layer> mLayer;
   AnimatedGeometryRoot* mAnimatedGeometryRoot;
   const DisplayItemScrollClip* mScrollClip;
-  // If non-null, this FrameMetrics is set to the be the first FrameMetrics
+  // If non-null, this ScrollMetadata is set to the be the first ScrollMetadata
   // on the layer.
-  UniquePtr<FrameMetrics> mBaseFrameMetrics;
+  UniquePtr<ScrollMetadata> mBaseScrollMetadata;
   // The following are only used for retained layers (for occlusion
   // culling of those layers). These regions are all relative to the
   // container reference frame.
   nsIntRegion mVisibleRegion;
   nsIntRegion mOpaqueRegion;
   // This rect is in the layer's own coordinate space. The computed visible
   // region for the layer cannot extend beyond this rect.
   nsIntRect mLayerContentsVisibleRect;
@@ -4155,25 +4155,25 @@ ContainerState::ProcessDisplayItems(nsDi
 
         SetOuterVisibleRegionForLayer(ownLayer, visible,
             layerContentsVisibleRect.width >= 0 ? &layerContentsVisibleRect : nullptr,
             useChildrenVisible);
       }
       if (itemType == nsDisplayItem::TYPE_SCROLL_INFO_LAYER) {
         nsDisplayScrollInfoLayer* scrollItem = static_cast<nsDisplayScrollInfoLayer*>(item);
         newLayerEntry->mOpaqueForAnimatedGeometryRootParent = false;
-        newLayerEntry->mBaseFrameMetrics =
-            scrollItem->ComputeFrameMetrics(ownLayer, mParameters);
+        newLayerEntry->mBaseScrollMetadata =
+            scrollItem->ComputeScrollMetadata(ownLayer, mParameters);
       } else if ((itemType == nsDisplayItem::TYPE_SUBDOCUMENT ||
                   itemType == nsDisplayItem::TYPE_ZOOM ||
                   itemType == nsDisplayItem::TYPE_RESOLUTION) &&
                  gfxPrefs::LayoutUseContainersForRootFrames())
       {
-        newLayerEntry->mBaseFrameMetrics =
-          static_cast<nsDisplaySubDocument*>(item)->ComputeFrameMetrics(ownLayer, mParameters);
+        newLayerEntry->mBaseScrollMetadata =
+          static_cast<nsDisplaySubDocument*>(item)->ComputeScrollMetadata(ownLayer, mParameters);
       }
 
       /**
        * No need to allocate geometry for items that aren't
        * part of a PaintedLayer.
        */
       mLayerBuilder->AddLayerDisplayItem(ownLayer, item, layerState, nullptr);
     } else {
@@ -4709,23 +4709,23 @@ ContainerState::SetupScrollingMetadata(N
   }
 
   if (!mBuilder->IsPaintingToWindow()) {
     // async scrolling not possible, and async scrolling info not computed
     // for this paint.
     return;
   }
 
-  AutoTArray<FrameMetrics,2> metricsArray;
-  if (aEntry->mBaseFrameMetrics) {
-    metricsArray.AppendElement(*aEntry->mBaseFrameMetrics);
+  AutoTArray<ScrollMetadata,2> metricsArray;
+  if (aEntry->mBaseScrollMetadata) {
+    metricsArray.AppendElement(*aEntry->mBaseScrollMetadata);
 
     // The base FrameMetrics was not computed by the nsIScrollableframe, so it
     // should not have a mask layer.
-    MOZ_ASSERT(!aEntry->mBaseFrameMetrics->GetMaskLayerIndex());
+    MOZ_ASSERT(!aEntry->mBaseScrollMetadata->GetMaskLayerIndex());
   }
 
   // Any extra mask layers we need to attach to FrameMetrics.
   nsTArray<RefPtr<Layer>> maskLayers;
 
   for (const DisplayItemScrollClip* scrollClip = aEntry->mScrollClip;
        scrollClip && scrollClip != mContainerScrollClip;
        scrollClip = scrollClip->mParent) {
@@ -4734,44 +4734,44 @@ ContainerState::SetupScrollingMetadata(N
       // whether it needs to be async scrollable for scroll handoff. It was
       // not activated, so we don't need to create a frame metrics for it.
       continue;
     }
 
     nsIScrollableFrame* scrollFrame = scrollClip->mScrollableFrame;
     const DisplayItemClip* clip = scrollClip->mClip;
 
-    Maybe<FrameMetrics> metrics =
-      scrollFrame->ComputeFrameMetrics(aEntry->mLayer, mContainerReferenceFrame, mParameters, clip);
-    if (!metrics) {
+    Maybe<ScrollMetadata> metadata =
+      scrollFrame->ComputeScrollMetadata(aEntry->mLayer, mContainerReferenceFrame, mParameters, clip);
+    if (!metadata) {
       continue;
     }
 
     if (clip &&
         clip->HasClip() &&
         clip->GetRoundedRectCount() > 0)
     {
       // The clip in between this scrollframe and its ancestor scrollframe
       // requires a mask layer. Since this mask layer should not move with
       // the APZC associated with this FrameMetrics, we attach the mask
       // layer as an additional, separate clip.
       Maybe<size_t> nextIndex = Some(maskLayers.Length());
       RefPtr<Layer> maskLayer =
         CreateMaskLayer(aEntry->mLayer, *clip, nextIndex, clip->GetRoundedRectCount());
       if (maskLayer) {
-        metrics->SetMaskLayerIndex(nextIndex);
+        metadata->SetMaskLayerIndex(nextIndex);
         maskLayers.AppendElement(maskLayer);
       }
     }
 
-    metricsArray.AppendElement(*metrics);
+    metricsArray.AppendElement(*metadata);
   }
 
   // Watch out for FrameMetrics copies in profiles
-  aEntry->mLayer->SetFrameMetrics(metricsArray);
+  aEntry->mLayer->SetScrollMetadata(metricsArray);
   aEntry->mLayer->SetAncestorMaskLayers(maskLayers);
 }
 
 static void
 InvalidateVisibleBoundsChangesForScrolledLayer(PaintedLayer* aLayer)
 {
   PaintedDisplayItemLayerUserData* data =
     static_cast<PaintedDisplayItemLayerUserData*>(aLayer->GetUserData(&gPaintedDisplayItemLayerUserData));
@@ -4801,18 +4801,18 @@ InvalidateVisibleBoundsChangesForScrolle
     }
     data->mIgnoreInvalidationsOutsideRect = Nothing();
   }
 }
 
 static inline const Maybe<ParentLayerIntRect>&
 GetStationaryClipInContainer(Layer* aLayer)
 {
-  if (size_t metricsCount = aLayer->GetFrameMetricsCount()) {
-    return aLayer->GetFrameMetrics(metricsCount - 1).GetClipRect();
+  if (size_t metricsCount = aLayer->GetScrollMetadataCount()) {
+    return aLayer->GetScrollMetadata(metricsCount - 1).GetClipRect();
   }
   return aLayer->GetClipRect();
 }
 
 void
 ContainerState::PostprocessRetainedLayers(nsIntRegion* aOpaqueRegionForContainer)
 {
   AutoTArray<OpaqueRegionEntry,4> opaqueRegions;
--- a/layout/base/Units.h
+++ b/layout/base/Units.h
@@ -243,16 +243,21 @@ struct CSSPixel {
                    NSToCoordRoundWithClamp(aPoint.y * float(AppUnitsPerCSSPixel())));
   }
 
   static nsPoint ToAppUnits(const CSSIntPoint& aPoint) {
     return nsPoint(NSToCoordRoundWithClamp(float(aPoint.x) * float(AppUnitsPerCSSPixel())),
                    NSToCoordRoundWithClamp(float(aPoint.y) * float(AppUnitsPerCSSPixel())));
   }
 
+  static nsSize ToAppUnits(const CSSSize& aSize) {
+    return nsSize(NSToCoordRoundWithClamp(aSize.width * float(AppUnitsPerCSSPixel())),
+                  NSToCoordRoundWithClamp(aSize.height * float(AppUnitsPerCSSPixel())));
+  }
+
   static nsSize ToAppUnits(const CSSIntSize& aSize) {
     return nsSize(NSToCoordRoundWithClamp(float(aSize.width)  * float(AppUnitsPerCSSPixel())),
                   NSToCoordRoundWithClamp(float(aSize.height) * float(AppUnitsPerCSSPixel())));
   }
 
   static nsRect ToAppUnits(const CSSRect& aRect) {
     return nsRect(NSToCoordRoundWithClamp(aRect.x * float(AppUnitsPerCSSPixel())),
                   NSToCoordRoundWithClamp(aRect.y * float(AppUnitsPerCSSPixel())),
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -2361,17 +2361,17 @@ nsCSSFrameConstructor::ConstructDocEleme
 
   SetUpDocElementContainingBlock(aDocElement);
 
   NS_ASSERTION(mDocElementContainingBlock, "Should have parent by now");
 
   nsFrameConstructorState state(mPresShell,
                                 GetAbsoluteContainingBlock(mDocElementContainingBlock, FIXED_POS),
                                 nullptr,
-                                nullptr, do_AddRef(Move(aFrameState)));
+                                nullptr, do_AddRef(aFrameState));
   // Initialize the ancestor filter with null for now; we'll push
   // aDocElement once we finish resolving style for it.
   state.mTreeMatchContext.InitAncestors(nullptr);
 
   // XXXbz why, exactly?
   if (!mTempFrameTreeState)
     state.mPresShell->CaptureHistoryState(getter_AddRefs(mTempFrameTreeState));
 
@@ -7774,17 +7774,17 @@ nsCSSFrameConstructor::ContentRangeInser
     LAYOUT_PHASE_TEMP_REENTER();
     return rv;
   }
 
   nsFrameConstructorState state(mPresShell,
                                 GetAbsoluteContainingBlock(insertion.mParentFrame, FIXED_POS),
                                 GetAbsoluteContainingBlock(insertion.mParentFrame, ABS_POS),
                                 GetFloatContainingBlock(insertion.mParentFrame),
-                                do_AddRef(Move(aFrameState)));
+                                do_AddRef(aFrameState));
   state.mTreeMatchContext.InitAncestors(aContainer ?
                                           aContainer->AsElement() :
                                           nullptr);
 
   // Recover state for the containing block - we need to know if
   // it has :first-letter or :first-line style applied to it. The
   // reason we care is that the internal structure in these cases
   // is not the normal structure and requires custom updating
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -1686,21 +1686,21 @@ already_AddRefed<LayerManager> nsDisplay
                              (!layerManager->IsCompositingCheap() && layerManager->NeedsWidgetInvalidation())) &&
                             widgetTransaction;
 
   UniquePtr<LayerProperties> props;
   if (computeInvalidRect) {
     props = Move(LayerProperties::CloneFrom(layerManager->GetRoot()));
   }
 
-  // Clear any FrameMetrics that may have been set on the root layer on a
+  // Clear any ScrollMetadata that may have been set on the root layer on a
   // previous paint. This paint will set new metrics if necessary, and if we
   // don't clear the old one here, we may be left with extra metrics.
   if (Layer* root = layerManager->GetRoot()) {
-      root->SetFrameMetrics(nsTArray<FrameMetrics>());
+      root->SetScrollMetadata(nsTArray<ScrollMetadata>());
   }
 
   ContainerLayerParameters containerParameters
     (presShell->GetResolution(), presShell->GetResolution());
   RefPtr<ContainerLayer> root = layerBuilder->
     BuildContainerLayerFor(aBuilder, layerManager, frame, nullptr, this,
                            containerParameters, nullptr);
 
@@ -1762,18 +1762,18 @@ already_AddRefed<LayerManager> nsDisplay
     }
   }
 
   if (addMetrics || ensureMetricsForRootId) {
     bool isRootContent = presContext->IsRootContentDocument();
 
     nsRect viewport(aBuilder->ToReferenceFrame(frame), frame->GetSize());
 
-    root->SetFrameMetrics(
-      nsLayoutUtils::ComputeFrameMetrics(frame,
+    root->SetScrollMetadata(
+      nsLayoutUtils::ComputeScrollMetadata(frame,
                          rootScrollFrame, content,
                          aBuilder->FindReferenceFrameFor(frame),
                          root, FrameMetrics::NULL_SCROLL_ID, viewport, Nothing(),
                          isRootContent, containerParameters));
   }
 
   // NS_WARNING is debug-only, so don't even bother checking the conditions in
   // a release build.
@@ -4575,22 +4575,22 @@ nsDisplaySubDocument::BuildLayer(nsDispl
 
   RefPtr<Layer> layer = nsDisplayOwnLayer::BuildLayer(aBuilder, aManager, params);
   layer->AsContainerLayer()->SetEventRegionsOverride(mForceDispatchToContentRegion
     ? EventRegionsOverride::ForceDispatchToContent
     : EventRegionsOverride::NoOverride);
   return layer.forget();
 }
 
-UniquePtr<FrameMetrics>
-nsDisplaySubDocument::ComputeFrameMetrics(Layer* aLayer,
-                                          const ContainerLayerParameters& aContainerParameters)
+UniquePtr<ScrollMetadata>
+nsDisplaySubDocument::ComputeScrollMetadata(Layer* aLayer,
+                                            const ContainerLayerParameters& aContainerParameters)
 {
   if (!(mFlags & GENERATE_SCROLLABLE_LAYER)) {
-    return UniquePtr<FrameMetrics>(nullptr);
+    return UniquePtr<ScrollMetadata>(nullptr);
   }
 
   nsPresContext* presContext = mFrame->PresContext();
   nsIFrame* rootScrollFrame = presContext->PresShell()->GetRootScrollFrame();
   bool isRootContentDocument = presContext->IsRootContentDocument();
   nsIPresShell* presShell = presContext->PresShell();
   ContainerLayerParameters params(
       aContainerParameters.mXScale * presShell->GetResolution(),
@@ -4601,18 +4601,18 @@ nsDisplaySubDocument::ComputeFrameMetric
       nsLayoutUtils::HasCriticalDisplayPort(rootScrollFrame->GetContent())) {
     params.mInLowPrecisionDisplayPort = true;
   }
 
   nsRect viewport = mFrame->GetRect() -
                     mFrame->GetPosition() +
                     mFrame->GetOffsetToCrossDoc(ReferenceFrame());
 
-  return MakeUnique<FrameMetrics>(
-    nsLayoutUtils::ComputeFrameMetrics(
+  return MakeUnique<ScrollMetadata>(
+    nsLayoutUtils::ComputeScrollMetadata(
       mFrame, rootScrollFrame, rootScrollFrame->GetContent(), ReferenceFrame(),
       aLayer, mScrollParentId, viewport, Nothing(),
       isRootContentDocument, params));
 }
 
 static bool
 UseDisplayPortForViewport(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
 {
@@ -4984,37 +4984,37 @@ nsDisplayScrollInfoLayer::BuildLayer(nsD
 LayerState
 nsDisplayScrollInfoLayer::GetLayerState(nsDisplayListBuilder* aBuilder,
                                     LayerManager* aManager,
                                     const ContainerLayerParameters& aParameters)
 {
   return LAYER_ACTIVE_EMPTY;
 }
 
-UniquePtr<FrameMetrics>
-nsDisplayScrollInfoLayer::ComputeFrameMetrics(Layer* aLayer,
-                                              const ContainerLayerParameters& aContainerParameters)
+UniquePtr<ScrollMetadata>
+nsDisplayScrollInfoLayer::ComputeScrollMetadata(Layer* aLayer,
+                                                const ContainerLayerParameters& aContainerParameters)
 {
   ContainerLayerParameters params = aContainerParameters;
   if (mScrolledFrame->GetContent() &&
       nsLayoutUtils::HasCriticalDisplayPort(mScrolledFrame->GetContent())) {
     params.mInLowPrecisionDisplayPort = true;
   }
 
   nsRect viewport = mScrollFrame->GetRect() -
                     mScrollFrame->GetPosition() +
                     mScrollFrame->GetOffsetToCrossDoc(ReferenceFrame());
 
-  FrameMetrics metrics = nsLayoutUtils::ComputeFrameMetrics(
+  ScrollMetadata metadata = nsLayoutUtils::ComputeScrollMetadata(
       mScrolledFrame, mScrollFrame, mScrollFrame->GetContent(),
       ReferenceFrame(), aLayer,
       mScrollParentId, viewport, Nothing(), false, params);
-  metrics.SetIsScrollInfoLayer(true);
-
-  return UniquePtr<FrameMetrics>(new FrameMetrics(metrics));
+  metadata.GetMetrics().SetIsScrollInfoLayer(true);
+
+  return UniquePtr<ScrollMetadata>(new ScrollMetadata(metadata));
 }
 
 
 
 void
 nsDisplayScrollInfoLayer::WriteDebugInfo(std::stringstream& aStream)
 {
   aStream << " (scrollframe " << mScrollFrame
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -1303,16 +1303,17 @@ protected:
  * move from one list to another).
  */
 class nsDisplayItem : public nsDisplayItemLink {
 public:
   typedef mozilla::ContainerLayerParameters ContainerLayerParameters;
   typedef mozilla::DisplayItemClip DisplayItemClip;
   typedef mozilla::DisplayItemScrollClip DisplayItemScrollClip;
   typedef mozilla::layers::FrameMetrics FrameMetrics;
+  typedef mozilla::layers::ScrollMetadata ScrollMetadata;
   typedef mozilla::layers::FrameMetrics::ViewID ViewID;
   typedef mozilla::layers::Layer Layer;
   typedef mozilla::layers::LayerManager LayerManager;
   typedef mozilla::LayerState LayerState;
 
   // This is never instantiated directly (it has pure virtual methods), so no
   // need to count constructors and destructors.
   nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame);
@@ -3545,18 +3546,18 @@ public:
                                  nsRegion* aVisibleRegion) override;
 
   virtual bool ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder) override;
 
   virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
 
   NS_DISPLAY_DECL_NAME("SubDocument", TYPE_SUBDOCUMENT)
 
-  mozilla::UniquePtr<FrameMetrics> ComputeFrameMetrics(Layer* aLayer,
-                                                       const ContainerLayerParameters& aContainerParameters);
+  mozilla::UniquePtr<ScrollMetadata> ComputeScrollMetadata(Layer* aLayer,
+                                                           const ContainerLayerParameters& aContainerParameters);
 
 protected:
   ViewID mScrollParentId;
   bool mForceDispatchToContentRegion;
 };
 
 /**
  * A display item for subdocuments to capture the resolution from the presShell
@@ -3686,18 +3687,18 @@ public:
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override;
 
   virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override
   { return false; }
 
   virtual void WriteDebugInfo(std::stringstream& aStream) override;
 
-  mozilla::UniquePtr<FrameMetrics> ComputeFrameMetrics(Layer* aLayer,
-                                                       const ContainerLayerParameters& aContainerParameters);
+  mozilla::UniquePtr<ScrollMetadata> ComputeScrollMetadata(Layer* aLayer,
+                                                           const ContainerLayerParameters& aContainerParameters);
 
 protected:
   nsIFrame* mScrollFrame;
   nsIFrame* mScrolledFrame;
   ViewID mScrollParentId;
 };
 
 /**
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -8578,33 +8578,34 @@ nsLayoutUtils::SetScrollPositionClamping
 /* static */ bool
 nsLayoutUtils::CanScrollOriginClobberApz(nsIAtom* aScrollOrigin)
 {
   return aScrollOrigin != nullptr
       && aScrollOrigin != nsGkAtoms::apz
       && aScrollOrigin != nsGkAtoms::restore;
 }
 
-/* static */ FrameMetrics
-nsLayoutUtils::ComputeFrameMetrics(nsIFrame* aForFrame,
-                                   nsIFrame* aScrollFrame,
-                                   nsIContent* aContent,
-                                   const nsIFrame* aReferenceFrame,
-                                   Layer* aLayer,
-                                   ViewID aScrollParentId,
-                                   const nsRect& aViewport,
-                                   const Maybe<nsRect>& aClipRect,
-                                   bool aIsRootContent,
-                                   const ContainerLayerParameters& aContainerParameters)
+/* static */ ScrollMetadata
+nsLayoutUtils::ComputeScrollMetadata(nsIFrame* aForFrame,
+                                     nsIFrame* aScrollFrame,
+                                     nsIContent* aContent,
+                                     const nsIFrame* aReferenceFrame,
+                                     Layer* aLayer,
+                                     ViewID aScrollParentId,
+                                     const nsRect& aViewport,
+                                     const Maybe<nsRect>& aClipRect,
+                                     bool aIsRootContent,
+                                     const ContainerLayerParameters& aContainerParameters)
 {
   nsPresContext* presContext = aForFrame->PresContext();
   int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel();
 
   nsIPresShell* presShell = presContext->GetPresShell();
-  FrameMetrics metrics;
+  ScrollMetadata metadata;
+  FrameMetrics& metrics = metadata.GetMetrics();
   metrics.SetViewport(CSSRect::FromAppUnits(aViewport));
 
   ViewID scrollId = FrameMetrics::NULL_SCROLL_ID;
   if (aContent) {
     if (void* paintRequestTime = aContent->GetProperty(nsGkAtoms::paintRequestTime)) {
       metrics.SetPaintRequestTime(*static_cast<TimeStamp*>(paintRequestTime));
       aContent->DeleteProperty(nsGkAtoms::paintRequestTime);
     }
@@ -8662,16 +8663,18 @@ nsLayoutUtils::ComputeFrameMetrics(nsIFr
 
     if (!aScrollFrame->GetParent() ||
         EventStateManager::CanVerticallyScrollFrameWithWheel(aScrollFrame->GetParent()))
     {
       metrics.SetAllowVerticalScrollWithWheel(true);
     }
 
     metrics.SetUsesContainerScrolling(scrollableFrame->UsesContainerScrolling());
+
+    metadata.SetSnapInfo(scrollableFrame->GetScrollSnapInfo());
   }
 
   // If we have the scrollparent being the same as the scroll id, the
   // compositor-side code could get into an infinite loop while building the
   // overscroll handoff chain.
   MOZ_ASSERT(aScrollParentId == FrameMetrics::NULL_SCROLL_ID || scrollId != aScrollParentId);
   metrics.SetScrollId(scrollId);
   metrics.SetIsRootContent(aIsRootContent);
@@ -8729,17 +8732,17 @@ nsLayoutUtils::ComputeFrameMetrics(nsIFr
   ParentLayerRect frameBounds = LayoutDeviceRect::FromAppUnits(compositionBounds, auPerDevPixel)
                               * metrics.GetCumulativeResolution()
                               * layerToParentLayerScale;
 
   if (aClipRect) {
     ParentLayerRect rect = LayoutDeviceRect::FromAppUnits(*aClipRect, auPerDevPixel)
                          * metrics.GetCumulativeResolution()
                          * layerToParentLayerScale;
-    metrics.SetClipRect(Some(RoundedToInt(rect)));
+    metadata.SetClipRect(Some(RoundedToInt(rect)));
   }
 
   // For the root scroll frame of the root content document (RCD-RSF), the above calculation
   // will yield the size of the viewport frame as the composition bounds, which
   // doesn't actually correspond to what is visible when
   // nsIDOMWindowUtils::setCSSViewport has been called to modify the visible area of
   // the prescontext that the viewport frame is reflowed into. In that case if our
   // document has a widget then the widget's bounds will correspond to what is
@@ -8793,23 +8796,23 @@ nsLayoutUtils::ComputeFrameMetrics(nsIFr
       nsStyleContext* backgroundStyle;
       if (nsCSSRendering::FindBackground(aScrollFrame, &backgroundStyle)) {
         metrics.SetBackgroundColor(Color::FromABGR(
           backgroundStyle->StyleBackground()->mBackgroundColor));
       }
     }
   }
 
-  return metrics;
+  return metadata;
 }
 
 /* static */ bool
 nsLayoutUtils::ContainsMetricsWithId(const Layer* aLayer, const ViewID& aScrollId)
 {
-  for (uint32_t i = aLayer->GetFrameMetricsCount(); i > 0; i--) {
+  for (uint32_t i = aLayer->GetScrollMetadataCount(); i > 0; i--) {
     if (aLayer->GetFrameMetrics(i-1).GetScrollId() == aScrollId) {
       return true;
     }
   }
   for (Layer* child = aLayer->GetFirstChild(); child; child = child->GetNextSibling()) {
     if (ContainsMetricsWithId(child, aScrollId)) {
       return true;
     }
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -138,16 +138,17 @@ class nsLayoutUtils
   typedef mozilla::gfx::RectDouble RectDouble;
   typedef mozilla::gfx::Matrix4x4 Matrix4x4;
   typedef mozilla::gfx::RectCornerRadii RectCornerRadii;
   typedef mozilla::gfx::StrokeOptions StrokeOptions;
   typedef mozilla::image::DrawResult DrawResult;
 
 public:
   typedef mozilla::layers::FrameMetrics FrameMetrics;
+  typedef mozilla::layers::ScrollMetadata ScrollMetadata;
   typedef FrameMetrics::ViewID ViewID;
   typedef mozilla::CSSPoint CSSPoint;
   typedef mozilla::CSSSize CSSSize;
   typedef mozilla::CSSIntSize CSSIntSize;
   typedef mozilla::CSSRect CSSRect;
   typedef mozilla::ScreenMargin ScreenMargin;
   typedef mozilla::LayoutDeviceIntSize LayoutDeviceIntSize;
 
@@ -2716,26 +2717,26 @@ public:
    * Returns true if the given scroll origin is "higher priority" than APZ.
    * In general any content programmatic scrolls (e.g. scrollTo calls) are
    * higher priority, and take precedence over APZ scrolling. This function
    * returns true for those, and returns false for other origins like APZ
    * itself, or scroll position updates from the history restore code.
    */
   static bool CanScrollOriginClobberApz(nsIAtom* aScrollOrigin);
 
-  static FrameMetrics ComputeFrameMetrics(nsIFrame* aForFrame,
-                                          nsIFrame* aScrollFrame,
-                                          nsIContent* aContent,
-                                          const nsIFrame* aReferenceFrame,
-                                          Layer* aLayer,
-                                          ViewID aScrollParentId,
-                                          const nsRect& aViewport,
-                                          const mozilla::Maybe<nsRect>& aClipRect,
-                                          bool aIsRoot,
-                                          const ContainerLayerParameters& aContainerParameters);
+  static ScrollMetadata ComputeScrollMetadata(nsIFrame* aForFrame,
+                                              nsIFrame* aScrollFrame,
+                                              nsIContent* aContent,
+                                              const nsIFrame* aReferenceFrame,
+                                              Layer* aLayer,
+                                              ViewID aScrollParentId,
+                                              const nsRect& aViewport,
+                                              const mozilla::Maybe<nsRect>& aClipRect,
+                                              bool aIsRoot,
+                                              const ContainerLayerParameters& aContainerParameters);
 
   /**
    * If the given scroll frame needs an area excluded from its composition
    * bounds due to scrollbars, return that area, otherwise return an empty
    * margin.
    * There is no need to exclude scrollbars in the following cases:
    *   - If the scroll frame is not the RCD-RSF; in that case, the composition
    *     bounds is calculated based on the scroll port which already excludes
--- a/layout/forms/nsHTMLButtonControlFrame.cpp
+++ b/layout/forms/nsHTMLButtonControlFrame.cpp
@@ -84,16 +84,21 @@ nsHTMLButtonControlFrame::HandleEvent(ns
     return NS_OK;
   }
 
   // mouse clicks are handled by content
   // we don't want our children to get any events. So just pass it to frame.
   return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
 }
 
+bool
+nsHTMLButtonControlFrame::ShouldClipPaintingToBorderBox()
+{
+  return IsInput() || StyleDisplay()->mOverflowX != NS_STYLE_OVERFLOW_VISIBLE;
+}
 
 void
 nsHTMLButtonControlFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                            const nsRect&           aDirtyRect,
                                            const nsDisplayListSet& aLists)
 {
   // Clip to our border area for event hit testing.
   Maybe<DisplayListClipState::AutoSaveRestore> eventClipState;
@@ -112,17 +117,17 @@ nsHTMLButtonControlFrame::BuildDisplayLi
   }
 
   nsDisplayListCollection set;
 
   // Do not allow the child subtree to receive events.
   if (!isForEventDelivery) {
     DisplayListClipState::AutoSaveRestore clipState(aBuilder);
 
-    if (IsInput() || StyleDisplay()->mOverflowX != NS_STYLE_OVERFLOW_VISIBLE) {
+    if (ShouldClipPaintingToBorderBox()) {
       nsMargin border = StyleBorder()->GetComputedBorder();
       nsRect rect(aBuilder->ToReferenceFrame(this), GetSize());
       rect.Deflate(border);
       nscoord radii[8];
       bool hasRadii = GetPaddingBoxBorderRadii(radii);
       clipState.ClipContainingBlockDescendants(rect, hasRadii ? radii : nullptr);
     }
 
@@ -209,17 +214,21 @@ nsHTMLButtonControlFrame::Reflow(nsPresC
   // !NS_SUBTREE_DIRTY(firstKid).
   // We'd need to cache our ascent for that, of course.
 
   // Reflow the contents of the button.
   // (This populates our aDesiredSize, too.)
   ReflowButtonContents(aPresContext, aDesiredSize,
                        aReflowState, firstKid);
 
-  ConsiderChildOverflow(aDesiredSize.mOverflowAreas, firstKid);
+  if (!ShouldClipPaintingToBorderBox()) {
+    ConsiderChildOverflow(aDesiredSize.mOverflowAreas, firstKid);
+  }
+  // else, we ignore child overflow -- anything that overflows beyond our
+  // own border-box will get clipped when painting.
 
   aStatus = NS_FRAME_COMPLETE;
   FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize,
                                  aReflowState, aStatus);
 
   // We're always complete and we don't support overflow containers
   // so we shouldn't have a next-in-flow ever.
   aStatus = NS_FRAME_COMPLETE;
--- a/layout/forms/nsHTMLButtonControlFrame.h
+++ b/layout/forms/nsHTMLButtonControlFrame.h
@@ -87,16 +87,22 @@ public:
   virtual bool IsFrameOfType(uint32_t aFlags) const override
   {
     return nsContainerFrame::IsFrameOfType(aFlags &
       ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
   }
 
 protected:
   virtual bool IsInput() { return false; }
+
+  // Indicates whether we should clip our children's painting to our
+  // border-box (either because of "overflow" or because of legacy reasons
+  // about how <input>-flavored buttons work).
+  bool ShouldClipPaintingToBorderBox();
+
   // Reflows the button's sole child frame, and computes the desired size
   // of the button itself from the results.
   void ReflowButtonContents(nsPresContext* aPresContext,
                             nsHTMLReflowMetrics& aButtonDesiredSize,
                             const nsHTMLReflowState& aButtonReflowState,
                             nsIFrame* aFirstKid);
 
   nsButtonFrameRenderer mRenderer;
new file mode 100644
--- /dev/null
+++ b/layout/generic/ScrollSnap.cpp
@@ -0,0 +1,311 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "FrameMetrics.h"
+#include "ScrollSnap.h"
+#include "gfxPrefs.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Preferences.h"
+#include "nsLineLayout.h"
+
+namespace mozilla {
+
+using layers::ScrollSnapInfo;
+
+/**
+ * Stores candidate snapping edges.
+ */
+class SnappingEdgeCallback {
+public:
+  virtual void AddHorizontalEdge(nscoord aEdge) = 0;
+  virtual void AddVerticalEdge(nscoord aEdge) = 0;
+  virtual void AddHorizontalEdgeInterval(const nsRect &aScrollRange,
+                                         nscoord aInterval,
+                                         nscoord aOffset) = 0;
+  virtual void AddVerticalEdgeInterval(const nsRect &aScrollRange,
+                                       nscoord aInterval,
+                                       nscoord aOffset) = 0;
+};
+
+/**
+ * Keeps track of the current best edge to snap to. The criteria for
+ * adding an edge depends on the scrolling unit.
+ */
+class CalcSnapPoints : public SnappingEdgeCallback {
+public:
+  CalcSnapPoints(nsIScrollableFrame::ScrollUnit aUnit,
+                 const nsPoint& aDestination,
+                 const nsPoint& aStartPos);
+  virtual void AddHorizontalEdge(nscoord aEdge) override;
+  virtual void AddVerticalEdge(nscoord aEdge) override;
+  virtual void AddHorizontalEdgeInterval(const nsRect &aScrollRange,
+                                         nscoord aInterval, nscoord aOffset)
+                                         override;
+  virtual void AddVerticalEdgeInterval(const nsRect &aScrollRange,
+                                       nscoord aInterval, nscoord aOffset)
+                                       override;
+  void AddEdge(nscoord aEdge,
+               nscoord aDestination,
+               nscoord aStartPos,
+               nscoord aScrollingDirection,
+               nscoord* aBestEdge,
+               bool* aEdgeFound);
+  void AddEdgeInterval(nscoord aInterval,
+                       nscoord aMinPos,
+                       nscoord aMaxPos,
+                       nscoord aOffset,
+                       nscoord aDestination,
+                       nscoord aStartPos,
+                       nscoord aScrollingDirection,
+                       nscoord* aBestEdge,
+                       bool* aEdgeFound);
+  nsPoint GetBestEdge() const;
+protected:
+  nsIScrollableFrame::ScrollUnit mUnit;
+  nsPoint mDestination;            // gives the position after scrolling but before snapping
+  nsPoint mStartPos;               // gives the position before scrolling
+  nsIntPoint mScrollingDirection;  // always -1, 0, or 1
+  nsPoint mBestEdge;               // keeps track of the position of the current best edge
+  bool mHorizontalEdgeFound;       // true if mBestEdge.x is storing a valid horizontal edge
+  bool mVerticalEdgeFound;         // true if mBestEdge.y is storing a valid vertical edge
+};
+
+CalcSnapPoints::CalcSnapPoints(nsIScrollableFrame::ScrollUnit aUnit,
+                               const nsPoint& aDestination,
+                               const nsPoint& aStartPos)
+{
+  mUnit = aUnit;
+  mDestination = aDestination;
+  mStartPos = aStartPos;
+
+  nsPoint direction = aDestination - aStartPos;
+  mScrollingDirection = nsIntPoint(0,0);
+  if (direction.x < 0) {
+    mScrollingDirection.x = -1;
+  }
+  if (direction.x > 0) {
+    mScrollingDirection.x = 1;
+  }
+  if (direction.y < 0) {
+    mScrollingDirection.y = -1;
+  }
+  if (direction.y > 0) {
+    mScrollingDirection.y = 1;
+  }
+  mBestEdge = aDestination;
+  mHorizontalEdgeFound = false;
+  mVerticalEdgeFound = false;
+}
+
+nsPoint
+CalcSnapPoints::GetBestEdge() const
+{
+  return nsPoint(mVerticalEdgeFound ? mBestEdge.x : mStartPos.x,
+                 mHorizontalEdgeFound ? mBestEdge.y : mStartPos.y);
+}
+
+void
+CalcSnapPoints::AddHorizontalEdge(nscoord aEdge)
+{
+  AddEdge(aEdge, mDestination.y, mStartPos.y, mScrollingDirection.y, &mBestEdge.y,
+          &mHorizontalEdgeFound);
+}
+
+void
+CalcSnapPoints::AddVerticalEdge(nscoord aEdge)
+{
+  AddEdge(aEdge, mDestination.x, mStartPos.x, mScrollingDirection.x, &mBestEdge.x,
+          &mVerticalEdgeFound);
+}
+
+void
+CalcSnapPoints::AddHorizontalEdgeInterval(const nsRect &aScrollRange,
+                                          nscoord aInterval, nscoord aOffset)
+{
+  AddEdgeInterval(aInterval, aScrollRange.y, aScrollRange.YMost(), aOffset,
+                  mDestination.y, mStartPos.y, mScrollingDirection.y,
+                  &mBestEdge.y, &mHorizontalEdgeFound);
+}
+
+void
+CalcSnapPoints::AddVerticalEdgeInterval(const nsRect &aScrollRange,
+                                        nscoord aInterval, nscoord aOffset)
+{
+  AddEdgeInterval(aInterval, aScrollRange.x, aScrollRange.XMost(), aOffset,
+                  mDestination.x, mStartPos.x, mScrollingDirection.x,
+                  &mBestEdge.x, &mVerticalEdgeFound);
+}
+
+void
+CalcSnapPoints::AddEdge(nscoord aEdge, nscoord aDestination, nscoord aStartPos,
+                        nscoord aScrollingDirection, nscoord* aBestEdge,
+                        bool *aEdgeFound)
+{
+  // nsIScrollableFrame::DEVICE_PIXELS indicates that we are releasing a drag
+  // gesture or any other user input event that sets an absolute scroll
+  // position.  In this case, scroll snapping is expected to travel in any
+  // direction.  Otherwise, we will restrict the direction of the scroll
+  // snapping movement based on aScrollingDirection.
+  if (mUnit != nsIScrollableFrame::DEVICE_PIXELS) {
+    // Unless DEVICE_PIXELS, we only want to snap to points ahead of the
+    // direction we are scrolling
+    if (aScrollingDirection == 0) {
+      // The scroll direction is neutral - will not hit a snap point.
+      return;
+    }
+    // nsIScrollableFrame::WHOLE indicates that we are navigating to "home" or
+    // "end".  In this case, we will always select the first or last snap point
+    // regardless of the direction of the scroll.  Otherwise, we will select
+    // scroll snapping points only in the direction specified by
+    // aScrollingDirection.
+    if (mUnit != nsIScrollableFrame::WHOLE) {
+      // Direction of the edge from the current position (before scrolling) in
+      // the direction of scrolling
+      nscoord direction = (aEdge - aStartPos) * aScrollingDirection;
+      if (direction <= 0) {
+        // The edge is not in the direction we are scrolling, skip it.
+        return;
+      }
+    }
+  }
+  if (!*aEdgeFound) {
+    *aBestEdge = aEdge;
+    *aEdgeFound = true;
+    return;
+  }
+  if (mUnit == nsIScrollableFrame::DEVICE_PIXELS ||
+      mUnit == nsIScrollableFrame::LINES) {
+    if (std::abs(aEdge - aDestination) < std::abs(*aBestEdge - aDestination)) {
+      *aBestEdge = aEdge;
+    }
+  } else if (mUnit == nsIScrollableFrame::PAGES) {
+    // distance to the edge from the scrolling destination in the direction of scrolling
+    nscoord overshoot = (aEdge - aDestination) * aScrollingDirection;
+    // distance to the current best edge from the scrolling destination in the direction of scrolling
+    nscoord curOvershoot = (*aBestEdge - aDestination) * aScrollingDirection;
+
+    // edges between the current position and the scrolling destination are favoured
+    // to preserve context
+    if (overshoot < 0 && (overshoot > curOvershoot || curOvershoot >= 0)) {
+      *aBestEdge = aEdge;
+    }
+    // if there are no edges between the current position and the scrolling destination
+    // the closest edge beyond the destination is used
+    if (overshoot > 0 && overshoot < curOvershoot) {
+      *aBestEdge = aEdge;
+    }
+  } else if (mUnit == nsIScrollableFrame::WHOLE) {
+    // the edge closest to the top/bottom/left/right is used, depending on scrolling direction
+    if (aScrollingDirection > 0 && aEdge > *aBestEdge) {
+      *aBestEdge = aEdge;
+    } else if (aScrollingDirection < 0 && aEdge < *aBestEdge) {
+      *aBestEdge = aEdge;
+    }
+  } else {
+    NS_ERROR("Invalid scroll mode");
+    return;
+  }
+}
+
+void
+CalcSnapPoints::AddEdgeInterval(nscoord aInterval, nscoord aMinPos,
+                                nscoord aMaxPos, nscoord aOffset,
+                                nscoord aDestination, nscoord aStartPos,
+                                nscoord aScrollingDirection,
+                                nscoord* aBestEdge, bool *aEdgeFound)
+{
+  if (aInterval == 0) {
+    // When interval is 0, there are no scroll snap points.
+    // Avoid division by zero and bail.
+    return;
+  }
+
+  // The only possible candidate interval snap points are the edges immediately
+  // surrounding aDestination.
+
+  // aDestination must be clamped to the scroll
+  // range in order to handle cases where the best matching snap point would
+  // result in scrolling out of bounds.  This clamping must be prior to
+  // selecting the two interval edges.
+  nscoord clamped = std::max(std::min(aDestination, aMaxPos), aMinPos);
+
+  // Add each edge in the interval immediately before aTarget and after aTarget
+  // Do not add edges that are out of range.
+  nscoord r = (clamped + aOffset) % aInterval;
+  if (r < aMinPos) {
+    r += aInterval;
+  }
+  nscoord edge = clamped - r;
+  if (edge >= aMinPos && edge <= aMaxPos) {
+    AddEdge(edge, aDestination, aStartPos, aScrollingDirection, aBestEdge,
+            aEdgeFound);
+  }
+  edge += aInterval;
+  if (edge >= aMinPos && edge <= aMaxPos) {
+    AddEdge(edge, aDestination, aStartPos, aScrollingDirection, aBestEdge,
+            aEdgeFound);
+  }
+}
+
+static void
+ProcessScrollSnapCoordinates(SnappingEdgeCallback& aCallback,
+                             const nsTArray<nsPoint>& aScrollSnapCoordinates,
+                             const nsPoint& aScrollSnapDestination) {
+  for (nsPoint snapCoords : aScrollSnapCoordinates) {
+    // Make them relative to the scroll snap destination.
+    snapCoords -= aScrollSnapDestination;
+
+    aCallback.AddVerticalEdge(snapCoords.x);
+    aCallback.AddHorizontalEdge(snapCoords.y);
+  }
+}
+
+Maybe<nsPoint> ScrollSnapUtils::GetSnapPointForDestination(
+    const ScrollSnapInfo& aSnapInfo,
+    nsIScrollableFrame::ScrollUnit aUnit,
+    const nsSize& aScrollPortSize,
+    const nsRect& aScrollRange,
+    const nsPoint& aStartPos,
+    const nsPoint& aDestination)
+{
+  if (aSnapInfo.mScrollSnapTypeY == NS_STYLE_SCROLL_SNAP_TYPE_NONE &&
+      aSnapInfo.mScrollSnapTypeX == NS_STYLE_SCROLL_SNAP_TYPE_NONE) {
+    return Nothing();
+  }
+
+  nsPoint destPos = aSnapInfo.mScrollSnapDestination;
+
+  CalcSnapPoints calcSnapPoints(aUnit, aDestination, aStartPos);
+
+  if (aSnapInfo.mScrollSnapIntervalX.isSome()) {
+    nscoord interval = aSnapInfo.mScrollSnapIntervalX.value();
+    calcSnapPoints.AddVerticalEdgeInterval(aScrollRange, interval, destPos.x);
+  }
+  if (aSnapInfo.mScrollSnapIntervalY.isSome()) {
+    nscoord interval = aSnapInfo.mScrollSnapIntervalY.value();
+    calcSnapPoints.AddHorizontalEdgeInterval(aScrollRange, interval, destPos.y);
+  }
+
+  ProcessScrollSnapCoordinates(calcSnapPoints, aSnapInfo.mScrollSnapCoordinates, destPos);
+  bool snapped = false;
+  nsPoint finalPos = calcSnapPoints.GetBestEdge();
+  nscoord proximityThreshold = gfxPrefs::ScrollSnapProximityThreshold();
+  proximityThreshold = nsPresContext::CSSPixelsToAppUnits(proximityThreshold);
+  if (aSnapInfo.mScrollSnapTypeY == NS_STYLE_SCROLL_SNAP_TYPE_PROXIMITY &&
+      std::abs(aDestination.y - finalPos.y) > proximityThreshold) {
+    finalPos.y = aDestination.y;
+  } else {
+    snapped = true;
+  }
+  if (aSnapInfo.mScrollSnapTypeX == NS_STYLE_SCROLL_SNAP_TYPE_PROXIMITY &&
+      std::abs(aDestination.x - finalPos.x) > proximityThreshold) {
+    finalPos.x = aDestination.x;
+  } else {
+    snapped = true;
+  }
+  return snapped ? Some(finalPos) : Nothing();
+}
+
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/layout/generic/ScrollSnap.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layout_ScrollSnap_h_
+#define mozilla_layout_ScrollSnap_h_
+
+namespace mozilla {
+
+namespace layers {
+struct ScrollSnapInfo;
+}
+
+struct ScrollSnapUtils {
+  /**
+   * GetSnapPointForDestination determines which point to snap to after
+   * scrolling. |aStartPos| gives the position before scrolling and
+   * |aDestination| gives the position after scrolling, with no snapping.
+   * Behaviour is dependent on the value of |aUnit|.
+   * |aSnapInfo|, |aScrollPortSize|, and |aScrollRange| are characteristics
+   * of the scroll frame for which snapping is being performed.
+   * If a suitable snap point could be found, it is returned. Otherwise, an
+   * empty Maybe is returned.
+   * IMPORTANT NOTE: This function is designed to be called both on and off
+   *                 the main thread. If modifying its implementation, be sure
+   *                 not to touch main-thread-only data structures without
+   *                 appropriate locking.
+   */
+  static Maybe<nsPoint> GetSnapPointForDestination(
+      const layers::ScrollSnapInfo& aSnapInfo,
+      nsIScrollableFrame::ScrollUnit aUnit,
+      const nsSize& aScrollPortSize,
+      const nsRect& aScrollRange,
+      const nsPoint& aStartPos,
+      const nsPoint& aDestination);
+};
+
+} // namespace mozilla
+
+#endif // mozilla_layout_ScrollSnap_h_
--- a/layout/generic/moz.build
+++ b/layout/generic/moz.build
@@ -91,16 +91,17 @@ EXPORTS += [
     'nsRubyFrame.h',
     'nsRubyTextContainerFrame.h',
     'nsRubyTextFrame.h',
     'nsSplittableFrame.h',
     'nsSubDocumentFrame.h',
     'nsTextRunTransformations.h',
     'RubyUtils.h',
     'ScrollbarActivity.h',
+    'ScrollSnap.h',
     'Visibility.h',
 ]
 
 EXPORTS.mozilla += [
     'WritingModes.h',
 ]
 
 EXPORTS.mozilla.dom += [
@@ -161,16 +162,17 @@ UNIFIED_SOURCES += [
     'nsSubDocumentFrame.cpp',
     'nsTextFrame.cpp',
     'nsTextFrameUtils.cpp',
     'nsTextRunTransformations.cpp',
     'nsVideoFrame.cpp',
     'nsViewportFrame.cpp',
     'RubyUtils.cpp',
     'ScrollbarActivity.cpp',
+    'ScrollSnap.cpp',
     'ScrollVelocityQueue.cpp',
     'StickyScrollContainer.cpp',
     'SummaryFrame.cpp',
     'TextOverflow.cpp',
 ]
 
 # nsLineLayout.cpp needs to be built separately because it uses plarena.h.
 # nsPluginFrame.cpp needs to be built separately because of name clashes in the OS X headers.
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -5469,17 +5469,17 @@ nsIFrame::InvalidateFrameWithRect(const 
   *rect = rect->Union(aRect);
 }
 
 /*static*/ uint8_t nsIFrame::sLayerIsPrerenderedDataKey;
 
 static bool
 DoesLayerHaveOutOfDateFrameMetrics(Layer* aLayer)
 {
-  for (uint32_t i = 0; i < aLayer->GetFrameMetricsCount(); i++) {
+  for (uint32_t i = 0; i < aLayer->GetScrollMetadataCount(); i++) {
     const FrameMetrics& metrics = aLayer->GetFrameMetrics(i);
     if (!metrics.IsScrollable()) {
       continue;
     }
     nsIScrollableFrame* scrollableFrame =
       nsLayoutUtils::FindScrollableFrameFor(metrics.GetScrollId());
     if (!scrollableFrame) {
       // This shouldn't happen, so let's do the safe thing and trigger a full
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -54,16 +54,17 @@
 #include "nsThemeConstants.h"
 #include "nsSVGIntegrationUtils.h"
 #include "nsIScrollPositionListener.h"
 #include "StickyScrollContainer.h"
 #include "nsIFrameInlines.h"
 #include "gfxPlatform.h"
 #include "gfxPrefs.h"
 #include "AsyncScrollBase.h"
+#include "ScrollSnap.h"
 #include "UnitTransforms.h"
 #include "nsPluginFrame.h"
 #include <mozilla/layers/AxisPhysicsModel.h>
 #include <mozilla/layers/AxisPhysicsMSDModel.h>
 #include "mozilla/layers/LayerTransactionChild.h"
 #include "mozilla/layers/ScrollLinkedEffectDetector.h"
 #include "mozilla/layers/ShadowLayers.h"
 #include "mozilla/unused.h"
@@ -3548,21 +3549,21 @@ ScrollFrameHelper::DecideScrollableLayer
   if (gfxPrefs::LayoutUseContainersForRootFrames() && mWillBuildScrollableLayer && mIsRoot) {
     mIsScrollableLayerInRootContainer = true;
   }
 
   return mWillBuildScrollableLayer;
 }
 
 
-Maybe<FrameMetrics>
-ScrollFrameHelper::ComputeFrameMetrics(Layer* aLayer,
-                                       nsIFrame* aContainerReferenceFrame,
-                                       const ContainerLayerParameters& aParameters,
-                                       const DisplayItemClip* aClip) const
+Maybe<ScrollMetadata>
+ScrollFrameHelper::ComputeScrollMetadata(Layer* aLayer,
+                                         nsIFrame* aContainerReferenceFrame,
+                                         const ContainerLayerParameters& aParameters,
+                                         const DisplayItemClip* aClip) const
 {
   if (!mWillBuildScrollableLayer || mIsScrollableLayerInRootContainer) {
     return Nothing();
   }
 
   nsPoint toReferenceFrame = mOuter->GetOffsetToCrossDoc(aContainerReferenceFrame);
 
   Maybe<nsRect> parentLayerClip;
@@ -3604,17 +3605,17 @@ ScrollFrameHelper::ComputeFrameMetrics(L
     // Return early, since if we don't use APZ we don't need FrameMetrics.
     return Nothing();
   }
 
   MOZ_ASSERT(mScrolledFrame->GetContent());
 
   nsRect scrollport = mScrollPort + toReferenceFrame;
 
-  return Some(nsLayoutUtils::ComputeFrameMetrics(
+  return Some(nsLayoutUtils::ComputeScrollMetadata(
     mScrolledFrame, mOuter, mOuter->GetContent(),
     aContainerReferenceFrame, aLayer, mScrollParentID,
     scrollport, parentLayerClip, isRootContent, aParameters));
 }
 
 bool
 ScrollFrameHelper::IsRectNearlyVisible(const nsRect& aRect) const
 {
@@ -3910,22 +3911,16 @@ ScrollFrameHelper::ScrollSnap(nsIScrolla
                                     velocity.y * flingSensitivity);
   predictedOffset.Clamp(maxOffset);
   nsPoint pos = GetScrollPosition();
   nsPoint destinationPos = pos + predictedOffset;
   ScrollSnap(destinationPos, aMode);
 }
 
 void
-ScrollFrameHelper::FlingSnap(const mozilla::CSSPoint& aDestination)
-{
-  ScrollSnap(CSSPoint::ToAppUnits(aDestination));
-}
-
-void
 ScrollFrameHelper::ScrollSnap(const nsPoint &aDestination,
                               nsIScrollableFrame::ScrollMode aMode)
 {
   nsRect scrollRange = GetScrollRangeForClamping();
   nsPoint pos = GetScrollPosition();
   nsPoint snapDestination = scrollRange.ClampPoint(aDestination);
   if (GetSnapPointForDestination(nsIScrollableFrame::DEVICE_PIXELS,
                                                  pos,
@@ -5770,353 +5765,127 @@ nsIScrollableFrame::GetPerceivedScrollin
   }
   if (scrollRange.height >= oneDevPixel) {
     directions |= VERTICAL;
   }
   return directions;
 }
 
 /**
- * Stores candidate snapping edges.
- */
-class SnappingEdgeCallback {
-public:
-  virtual void AddHorizontalEdge(nscoord aEdge) = 0;
-  virtual void AddVerticalEdge(nscoord aEdge) = 0;
-  virtual void AddHorizontalEdgeInterval(const nsRect &aScrollRange,
-                                         nscoord aInterval,
-                                         nscoord aOffset) = 0;
-  virtual void AddVerticalEdgeInterval(const nsRect &aScrollRange,
-                                       nscoord aInterval,
-                                       nscoord aOffset) = 0;
-};
-
-/**
- * Keeps track of the current best edge to snap to. The criteria for
- * adding an edge depends on the scrolling unit.
+ * Collect the scroll-snap-coordinates of frames in the subtree rooted at
+ * |aFrame|, relative to |aScrolledFrame|, into |aOutCoords|.
  */
-class CalcSnapPoints : public SnappingEdgeCallback {
-public:
-  CalcSnapPoints(nsIScrollableFrame::ScrollUnit aUnit,
-                 const nsPoint& aDestination,
-                 const nsPoint& aStartPos);
-  virtual void AddHorizontalEdge(nscoord aEdge) override;
-  virtual void AddVerticalEdge(nscoord aEdge) override;
-  virtual void AddHorizontalEdgeInterval(const nsRect &aScrollRange,
-                                         nscoord aInterval, nscoord aOffset)
-                                         override;
-  virtual void AddVerticalEdgeInterval(const nsRect &aScrollRange,
-                                       nscoord aInterval, nscoord aOffset)
-                                       override;
-  void AddEdge(nscoord aEdge,
-               nscoord aDestination,
-               nscoord aStartPos,
-               nscoord aScrollingDirection,
-               nscoord* aBestEdge,
-               bool* aEdgeFound);
-  void AddEdgeInterval(nscoord aInterval,
-                       nscoord aMinPos,
-                       nscoord aMaxPos,
-                       nscoord aOffset,
-                       nscoord aDestination,
-                       nscoord aStartPos,
-                       nscoord aScrollingDirection,
-                       nscoord* aBestEdge,
-                       bool* aEdgeFound);
-  nsPoint GetBestEdge() const;
-protected:
-  nsIScrollableFrame::ScrollUnit mUnit;
-  nsPoint mDestination;            // gives the position after scrolling but before snapping
-  nsPoint mStartPos;               // gives the position before scrolling
-  nsIntPoint mScrollingDirection;  // always -1, 0, or 1
-  nsPoint mBestEdge;               // keeps track of the position of the current best edge
-  bool mHorizontalEdgeFound;       // true if mBestEdge.x is storing a valid horizontal edge
-  bool mVerticalEdgeFound;         // true if mBestEdge.y is storing a valid vertical edge
-};
-
-CalcSnapPoints::CalcSnapPoints(nsIScrollableFrame::ScrollUnit aUnit,
-                               const nsPoint& aDestination,
-                               const nsPoint& aStartPos)
-{
-  mUnit = aUnit;
-  mDestination = aDestination;
-  mStartPos = aStartPos;
-
-  nsPoint direction = aDestination - aStartPos;
-  mScrollingDirection = nsIntPoint(0,0);
-  if (direction.x < 0) {
-    mScrollingDirection.x = -1;
-  }
-  if (direction.x > 0) {
-    mScrollingDirection.x = 1;
-  }
-  if (direction.y < 0) {
-    mScrollingDirection.y = -1;
-  }
-  if (direction.y > 0) {
-    mScrollingDirection.y = 1;
-  }
-  mBestEdge = aDestination;
-  mHorizontalEdgeFound = false;
-  mVerticalEdgeFound = false;
-}
-
-nsPoint
-CalcSnapPoints::GetBestEdge() const
-{
-  return nsPoint(mVerticalEdgeFound ? mBestEdge.x : mStartPos.x,
-                 mHorizontalEdgeFound ? mBestEdge.y : mStartPos.y);
-}
-
-void
-CalcSnapPoints::AddHorizontalEdge(nscoord aEdge)
-{
-  AddEdge(aEdge, mDestination.y, mStartPos.y, mScrollingDirection.y, &mBestEdge.y,
-          &mHorizontalEdgeFound);
-}
-
-void
-CalcSnapPoints::AddVerticalEdge(nscoord aEdge)
-{
-  AddEdge(aEdge, mDestination.x, mStartPos.x, mScrollingDirection.x, &mBestEdge.x,
-          &mVerticalEdgeFound);
-}
-
-void
-CalcSnapPoints::AddHorizontalEdgeInterval(const nsRect &aScrollRange,
-                                          nscoord aInterval, nscoord aOffset)
-{
-  AddEdgeInterval(aInterval, aScrollRange.y, aScrollRange.YMost(), aOffset,
-                  mDestination.y, mStartPos.y, mScrollingDirection.y,
-                  &mBestEdge.y, &mHorizontalEdgeFound);
-}
-
 void
-CalcSnapPoints::AddVerticalEdgeInterval(const nsRect &aScrollRange,
-                                        nscoord aInterval, nscoord aOffset)
-{
-  AddEdgeInterval(aInterval, aScrollRange.x, aScrollRange.XMost(), aOffset,
-                  mDestination.x, mStartPos.x, mScrollingDirection.x,
-                  &mBestEdge.x, &mVerticalEdgeFound);
-}
-
-void
-CalcSnapPoints::AddEdge(nscoord aEdge, nscoord aDestination, nscoord aStartPos,
-                        nscoord aScrollingDirection, nscoord* aBestEdge,
-                        bool *aEdgeFound)
-{
-  // nsIScrollableFrame::DEVICE_PIXELS indicates that we are releasing a drag
-  // gesture or any other user input event that sets an absolute scroll
-  // position.  In this case, scroll snapping is expected to travel in any
-  // direction.  Otherwise, we will restrict the direction of the scroll
-  // snapping movement based on aScrollingDirection.
-  if (mUnit != nsIScrollableFrame::DEVICE_PIXELS) {
-    // Unless DEVICE_PIXELS, we only want to snap to points ahead of the
-    // direction we are scrolling
-    if (aScrollingDirection == 0) {
-      // The scroll direction is neutral - will not hit a snap point.
-      return;
-    }
-    // nsIScrollableFrame::WHOLE indicates that we are navigating to "home" or
-    // "end".  In this case, we will always select the first or last snap point
-    // regardless of the direction of the scroll.  Otherwise, we will select
-    // scroll snapping points only in the direction specified by
-    // aScrollingDirection.
-    if (mUnit != nsIScrollableFrame::WHOLE) {
-      // Direction of the edge from the current position (before scrolling) in
-      // the direction of scrolling
-      nscoord direction = (aEdge - aStartPos) * aScrollingDirection;
-      if (direction <= 0) {
-        // The edge is not in the direction we are scrolling, skip it.
-        return;
-      }
-    }
-  }
-  if (!*aEdgeFound) {
-    *aBestEdge = aEdge;
-    *aEdgeFound = true;
-    return;
-  }
-  if (mUnit == nsIScrollableFrame::DEVICE_PIXELS ||
-      mUnit == nsIScrollableFrame::LINES) {
-    if (std::abs(aEdge - aDestination) < std::abs(*aBestEdge - aDestination)) {
-      *aBestEdge = aEdge;
-    }
-  } else if (mUnit == nsIScrollableFrame::PAGES) {
-    // distance to the edge from the scrolling destination in the direction of scrolling
-    nscoord overshoot = (aEdge - aDestination) * aScrollingDirection;
-    // distance to the current best edge from the scrolling destination in the direction of scrolling
-    nscoord curOvershoot = (*aBestEdge - aDestination) * aScrollingDirection;
-
-    // edges between the current position and the scrolling destination are favoured
-    // to preserve context
-    if (overshoot < 0 && (overshoot > curOvershoot || curOvershoot >= 0)) {
-      *aBestEdge = aEdge;
-    }
-    // if there are no edges between the current position and the scrolling destination
-    // the closest edge beyond the destination is used
-    if (overshoot > 0 && overshoot < curOvershoot) {
-      *aBestEdge = aEdge;
-    }
-  } else if (mUnit == nsIScrollableFrame::WHOLE) {
-    // the edge closest to the top/bottom/left/right is used, depending on scrolling direction
-    if (aScrollingDirection > 0 && aEdge > *aBestEdge) {
-      *aBestEdge = aEdge;
-    } else if (aScrollingDirection < 0 && aEdge < *aBestEdge) {
-      *aBestEdge = aEdge;
-    }
-  } else {
-    NS_ERROR("Invalid scroll mode");
-    return;
-  }
-}
-
-void
-CalcSnapPoints::AddEdgeInterval(nscoord aInterval, nscoord aMinPos,
-                                nscoord aMaxPos, nscoord aOffset,
-                                nscoord aDestination, nscoord aStartPos,
-                                nscoord aScrollingDirection,
-                                nscoord* aBestEdge, bool *aEdgeFound)
-{
-  if (aInterval == 0) {
-    // When interval is 0, there are no scroll snap points.
-    // Avoid division by zero and bail.
-    return;
-  }
-
-  // The only possible candidate interval snap points are the edges immediately
-  // surrounding aDestination.
-
-  // aDestination must be clamped to the scroll
-  // range in order to handle cases where the best matching snap point would
-  // result in scrolling out of bounds.  This clamping must be prior to
-  // selecting the two interval edges.
-  nscoord clamped = std::max(std::min(aDestination, aMaxPos), aMinPos);
-
-  // Add each edge in the interval immediately before aTarget and after aTarget
-  // Do not add edges that are out of range.
-  nscoord r = (clamped + aOffset) % aInterval;
-  if (r < aMinPos) {
-    r += aInterval;
-  }
-  nscoord edge = clamped - r;
-  if (edge >= aMinPos && edge <= aMaxPos) {
-    AddEdge(edge, aDestination, aStartPos, aScrollingDirection, aBestEdge,
-            aEdgeFound);
-  }
-  edge += aInterval;
-  if (edge >= aMinPos && edge <= aMaxPos) {
-    AddEdge(edge, aDestination, aStartPos, aScrollingDirection, aBestEdge,
-            aEdgeFound);
-  }
-}
-
-static void
-ScrollSnapHelper(SnappingEdgeCallback& aCallback, nsIFrame* aFrame,
-                 nsIFrame* aScrolledFrame,
-                 const nsPoint &aScrollSnapDestination) {
+CollectScrollSnapCoordinates(nsIFrame* aFrame, nsIFrame* aScrolledFrame,
+                             nsTArray<nsPoint>& aOutCoords)
+{
   nsIFrame::ChildListIterator childLists(aFrame);
   for (; !childLists.IsDone(); childLists.Next()) {
     nsFrameList::Enumerator childFrames(childLists.CurrentList());
     for (; !childFrames.AtEnd(); childFrames.Next()) {
       nsIFrame* f = childFrames.get();
 
       const nsStyleDisplay* styleDisplay = f->StyleDisplay();
       size_t coordCount = styleDisplay->mScrollSnapCoordinate.Length();
 
       if (coordCount) {
         nsRect frameRect = f->GetRect();
         nsPoint offset = f->GetOffsetTo(aScrolledFrame);
         nsRect edgesRect = nsRect(offset, frameRect.Size());
         for (size_t coordNum = 0; coordNum < coordCount; coordNum++) {
           const nsStyleImageLayers::Position &coordPosition =
             f->StyleDisplay()->mScrollSnapCoordinate[coordNum];
-          nsPoint coordPoint = edgesRect.TopLeft() - aScrollSnapDestination;
+          nsPoint coordPoint = edgesRect.TopLeft();
           coordPoint += nsPoint(coordPosition.mXPosition.mLength,
                                 coordPosition.mYPosition.mLength);
           if (coordPosition.mXPosition.mHasPercent) {
             coordPoint.x += NSToCoordRound(coordPosition.mXPosition.mPercent *
                                            frameRect.width);
           }
           if (coordPosition.mYPosition.mHasPercent) {
             coordPoint.y += NSToCoordRound(coordPosition.mYPosition.mPercent *
                                            frameRect.height);
           }
 
-          aCallback.AddVerticalEdge(coordPoint.x);
-          aCallback.AddHorizontalEdge(coordPoint.y);
+          aOutCoords.AppendElement(coordPoint);
         }
       }
 
-      ScrollSnapHelper(aCallback, f, aScrolledFrame, aScrollSnapDestination);
+      CollectScrollSnapCoordinates(f, aScrolledFrame, aOutCoords);
     }
   }
 }
 
+layers::ScrollSnapInfo
+ComputeScrollSnapInfo(const ScrollFrameHelper& aScrollFrame)
+{
+  ScrollSnapInfo result;
+
+  ScrollbarStyles styles = aScrollFrame.GetScrollbarStylesFromFrame();
+
+  if (styles.mScrollSnapTypeY == NS_STYLE_SCROLL_SNAP_TYPE_NONE &&
+      styles.mScrollSnapTypeX == NS_STYLE_SCROLL_SNAP_TYPE_NONE) {
+    // We won't be snapping, short-circuit the computation.
+    return result;
+  }
+
+  result.mScrollSnapTypeX = styles.mScrollSnapTypeX;
+  result.mScrollSnapTypeY = styles.mScrollSnapTypeY;
+
+  nsSize scrollPortSize = aScrollFrame.GetScrollPortRect().Size();
+
+  result.mScrollSnapDestination = nsPoint(styles.mScrollSnapDestinationX.mLength,
+                                          styles.mScrollSnapDestinationY.mLength);
+  if (styles.mScrollSnapDestinationX.mHasPercent) {
+    result.mScrollSnapDestination.x +=
+        NSToCoordFloorClamped(styles.mScrollSnapDestinationX.mPercent *
+                              scrollPortSize.width);
+  }
+  if (styles.mScrollSnapDestinationY.mHasPercent) {
+    result.mScrollSnapDestination.y +=
+        NSToCoordFloorClamped(styles.mScrollSnapDestinationY.mPercent *
+                              scrollPortSize.height);
+  }
+
+  if (styles.mScrollSnapPointsX.GetUnit() != eStyleUnit_None) {
+    result.mScrollSnapIntervalX = Some(nsRuleNode::ComputeCoordPercentCalc(
+        styles.mScrollSnapPointsX, scrollPortSize.width));
+  }
+  if (styles.mScrollSnapPointsY.GetUnit() != eStyleUnit_None) {
+    result.mScrollSnapIntervalY = Some(nsRuleNode::ComputeCoordPercentCalc(
+        styles.mScrollSnapPointsY, scrollPortSize.height));
+  }
+
+  CollectScrollSnapCoordinates(aScrollFrame.GetScrolledFrame(),
+                               aScrollFrame.GetScrolledFrame(),
+                               result.mScrollSnapCoordinates);
+
+  return result;
+}
+
+layers::ScrollSnapInfo
+ScrollFrameHelper::GetScrollSnapInfo() const
+{
+  // TODO(botond): Should we cache it?
+  return ComputeScrollSnapInfo(*this);
+}
+
 bool
 ScrollFrameHelper::GetSnapPointForDestination(nsIScrollableFrame::ScrollUnit aUnit,
                                               nsPoint aStartPos,
                                               nsPoint &aDestination)
 {
-  ScrollbarStyles styles = GetScrollbarStylesFromFrame();
-  if (styles.mScrollSnapTypeY == NS_STYLE_SCROLL_SNAP_TYPE_NONE &&
-      styles.mScrollSnapTypeX == NS_STYLE_SCROLL_SNAP_TYPE_NONE) {
-    return false;
-  }
-
-  nsSize scrollPortSize = mScrollPort.Size();
-  nsRect scrollRange = GetScrollRangeForClamping();
-
-  nsPoint destPos = nsPoint(styles.mScrollSnapDestinationX.mLength,
-                            styles.mScrollSnapDestinationY.mLength);
-  if (styles.mScrollSnapDestinationX.mHasPercent) {
-    destPos.x += NSToCoordFloorClamped(styles.mScrollSnapDestinationX.mPercent
-                                       * scrollPortSize.width);
-  }
-
-  if (styles.mScrollSnapDestinationY.mHasPercent) {
-    destPos.y += NSToCoordFloorClamped(styles.mScrollSnapDestinationY.mPercent
-                                       * scrollPortSize.height);
-  }
-
-  CalcSnapPoints calcSnapPoints(aUnit, aDestination, aStartPos);
-
-  if (styles.mScrollSnapPointsX.GetUnit() != eStyleUnit_None) {
-    nscoord interval = nsRuleNode::ComputeCoordPercentCalc(styles.mScrollSnapPointsX,
-                                                           scrollPortSize.width);
-    calcSnapPoints.AddVerticalEdgeInterval(scrollRange, interval, destPos.x);
-  }
-  if (styles.mScrollSnapPointsY.GetUnit() != eStyleUnit_None) {
-    nscoord interval = nsRuleNode::ComputeCoordPercentCalc(styles.mScrollSnapPointsY,
-                                                           scrollPortSize.height);
-    calcSnapPoints.AddHorizontalEdgeInterval(scrollRange, interval, destPos.y);
-  }
-
-  ScrollSnapHelper(calcSnapPoints, mScrolledFrame, mScrolledFrame, destPos);
-  bool snapped = false;
-  nsPoint finalPos = calcSnapPoints.GetBestEdge();
-  nscoord proximityThreshold =
-    Preferences::GetInt("layout.css.scroll-snap.proximity-threshold", 0);
-  proximityThreshold = nsPresContext::CSSPixelsToAppUnits(proximityThreshold);
-  if (styles.mScrollSnapTypeY == NS_STYLE_SCROLL_SNAP_TYPE_PROXIMITY &&
-      std::abs(aDestination.y - finalPos.y) > proximityThreshold) {
-    finalPos.y = aDestination.y;
-  } else {
-    snapped = true;
-  }
-  if (styles.mScrollSnapTypeX == NS_STYLE_SCROLL_SNAP_TYPE_PROXIMITY &&
-      std::abs(aDestination.x - finalPos.x) > proximityThreshold) {
-    finalPos.x = aDestination.x;
-  } else {
-    snapped = true;
-  }
-  if (snapped) {
-    aDestination = finalPos;
-  }
-  return snapped;
+  Maybe<nsPoint> snapPoint = ScrollSnapUtils::GetSnapPointForDestination(
+      GetScrollSnapInfo(), aUnit, mScrollPort.Size(),
+      GetScrollRangeForClamping(), aStartPos, aDestination);
+  if (snapPoint) {
+    aDestination = snapPoint.ref();
+    return true;
+  }
+  return false;
 }
 
 bool
 ScrollFrameHelper::UsesContainerScrolling() const
 {
   if (gfxPrefs::LayoutUseContainersForRootFrames()) {
     return mIsRoot;
   }
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -44,16 +44,17 @@ class ScrollbarActivity;
 namespace mozilla {
 
 class ScrollFrameHelper : public nsIReflowCallback {
 public:
   typedef nsIFrame::Sides Sides;
   typedef mozilla::CSSIntPoint CSSIntPoint;
   typedef mozilla::layout::ScrollbarActivity ScrollbarActivity;
   typedef mozilla::layers::FrameMetrics FrameMetrics;
+  typedef mozilla::layers::ScrollSnapInfo ScrollSnapInfo;
   typedef mozilla::layers::Layer Layer;
 
   class AsyncScroll;
   class AsyncSmoothMSDScroll;
 
   ScrollFrameHelper(nsContainerFrame* aOuter, bool aIsRoot);
   ~ScrollFrameHelper();
 
@@ -198,17 +199,16 @@ public:
       mScrollPort.XMost() - mScrolledFrame->GetRect().XMost();
     pt.y = mScrollPort.y - mScrolledFrame->GetPosition().y;
     return pt;
   }
   nsRect GetScrollRange() const;
   // Get the scroll range assuming the scrollport has size (aWidth, aHeight).
   nsRect GetScrollRange(nscoord aWidth, nscoord aHeight) const;
   nsSize GetScrollPositionClampingScrollPortSize() const;
-  void FlingSnap(const mozilla::CSSPoint& aDestination);
   void ScrollSnap(nsIScrollableFrame::ScrollMode aMode = nsIScrollableFrame::SMOOTH_MSD);
   void ScrollSnap(const nsPoint &aDestination,
                   nsIScrollableFrame::ScrollMode aMode = nsIScrollableFrame::SMOOTH_MSD);
 
 protected:
   nsRect GetScrollRangeForClamping() const;
 
 public:
@@ -385,16 +385,18 @@ public:
   bool IsTransformingByAPZ() const {
     return mTransformingByAPZ;
   }
   void SetScrollableByAPZ(bool aScrollable);
   void SetZoomableByAPZ(bool aZoomable);
 
   bool UsesContainerScrolling() const;
 
+  ScrollSnapInfo GetScrollSnapInfo() const;
+
   bool DecideScrollableLayer(nsDisplayListBuilder* aBuilder,
                              nsRect* aDirtyRect,
                              bool aAllowCreateDisplayPort);
   void NotifyApproximateFrameVisibilityUpdate();
   bool GetDisplayPortAtLastApproximateFrameVisibilityUpdate(nsRect* aDisplayPort);
 
   bool AllowDisplayPortExpiration();
   void TriggerDisplayPortExpiration();
@@ -411,17 +413,17 @@ public:
   nsPoint LastScrollDestination() const { return mDestination; }
   void ResetScrollInfoIfGeneration(uint32_t aGeneration) {
     if (aGeneration == mScrollGeneration) {
       mLastScrollOrigin = nullptr;
       mLastSmoothScrollOrigin = nullptr;
     }
   }
   bool WantAsyncScroll() const;
-  Maybe<mozilla::layers::FrameMetrics> ComputeFrameMetrics(
+  Maybe<mozilla::layers::ScrollMetadata> ComputeScrollMetadata(
     Layer* aLayer, nsIFrame* aContainerReferenceFrame,
     const ContainerLayerParameters& aParameters,
     const mozilla::DisplayItemClip* aClip) const;
 
   // nsIScrollbarMediator
   void ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection,
                     nsIScrollbarMediator::ScrollSnapMode aSnap
                       = nsIScrollbarMediator::DISABLE_SNAP);
@@ -833,19 +835,16 @@ public:
   virtual void ScrollBy(nsIntPoint aDelta, ScrollUnit aUnit, ScrollMode aMode,
                         nsIntPoint* aOverflow, nsIAtom* aOrigin = nullptr,
                         nsIScrollableFrame::ScrollMomentum aMomentum = nsIScrollableFrame::NOT_MOMENTUM,
                         nsIScrollbarMediator::ScrollSnapMode aSnap
                           = nsIScrollbarMediator::DISABLE_SNAP)
                         override {
     mHelper.ScrollBy(aDelta, aUnit, aMode, aOverflow, aOrigin, aMomentum, aSnap);
   }
-  virtual void FlingSnap(const mozilla::CSSPoint& aDestination) override {
-    mHelper.FlingSnap(aDestination);
-  }
   virtual void ScrollSnap() override {
     mHelper.ScrollSnap();
   }
   /**
    * @note This method might destroy the frame, pres shell and other objects.
    */
   virtual void ScrollToRestoredPosition() override {
     mHelper.ScrollToRestoredPosition();
@@ -900,22 +899,22 @@ public:
     return mHelper.LastScrollDestination();
   }
   virtual void ResetScrollInfoIfGeneration(uint32_t aGeneration) override {
     mHelper.ResetScrollInfoIfGeneration(aGeneration);
   }
   virtual bool WantAsyncScroll() const override {
     return mHelper.WantAsyncScroll();
   }
-  virtual mozilla::Maybe<mozilla::layers::FrameMetrics> ComputeFrameMetrics(
+  virtual mozilla::Maybe<mozilla::layers::ScrollMetadata> ComputeScrollMetadata(
     Layer* aLayer, nsIFrame* aContainerReferenceFrame,
     const ContainerLayerParameters& aParameters,
     const mozilla::DisplayItemClip* aClip) const override
   {
-    return mHelper.ComputeFrameMetrics(aLayer, aContainerReferenceFrame, aParameters, aClip);
+    return mHelper.ComputeScrollMetadata(aLayer, aContainerReferenceFrame, aParameters, aClip);
   }
   virtual bool IsIgnoringViewportClipping() const override {
     return mHelper.IsIgnoringViewportClipping();
   }
   virtual void MarkScrollbarsDirtyForReflow() const override {
     mHelper.MarkScrollbarsDirtyForReflow();
   }
   virtual bool UsesContainerScrolling() const override {
@@ -1005,16 +1004,20 @@ public:
   }
   void SetScrollableByAPZ(bool aScrollable) override {
     mHelper.SetScrollableByAPZ(aScrollable);
   }
   void SetZoomableByAPZ(bool aZoomable) override {
     mHelper.SetZoomableByAPZ(aZoomable);
   }
   
+  ScrollSnapInfo GetScrollSnapInfo() const override {
+    return mHelper.GetScrollSnapInfo();
+  }
+
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override;
 #endif
 
 #ifdef ACCESSIBILITY
   virtual mozilla::a11y::AccType AccessibleType() override;
 #endif
 
@@ -1244,19 +1247,16 @@ public:
   virtual void ScrollBy(nsIntPoint aDelta, ScrollUnit aUnit, ScrollMode aMode,
                         nsIntPoint* aOverflow, nsIAtom* aOrigin = nullptr,
                         nsIScrollableFrame::ScrollMomentum aMomentum = nsIScrollableFrame::NOT_MOMENTUM,
                         nsIScrollbarMediator::ScrollSnapMode aSnap
                           = nsIScrollbarMediator::DISABLE_SNAP)
                         override {
     mHelper.ScrollBy(aDelta, aUnit, aMode, aOverflow, aOrigin, aMomentum, aSnap);
   }
-  virtual void FlingSnap(const mozilla::CSSPoint& aDestination) override {
-    mHelper.FlingSnap(aDestination);
-  }
   virtual void ScrollSnap() override {
     mHelper.ScrollSnap();
   }
   /**
    * @note This method might destroy the frame, pres shell and other objects.
    */
   virtual void ScrollToRestoredPosition() override {
     mHelper.ScrollToRestoredPosition();
@@ -1311,22 +1311,22 @@ public:
     return mHelper.LastScrollDestination();
   }
   virtual void ResetScrollInfoIfGeneration(uint32_t aGeneration) override {
     mHelper.ResetScrollInfoIfGeneration(aGeneration);
   }
   virtual bool WantAsyncScroll() const override {
     return mHelper.WantAsyncScroll();
   }
-  virtual mozilla::Maybe<mozilla::layers::FrameMetrics> ComputeFrameMetrics(
+  virtual mozilla::Maybe<mozilla::layers::ScrollMetadata> ComputeScrollMetadata(
     Layer* aLayer, nsIFrame* aContainerReferenceFrame,
     const ContainerLayerParameters& aParameters,
     const mozilla::DisplayItemClip* aClip) const override
   {
-    return mHelper.ComputeFrameMetrics(aLayer, aContainerReferenceFrame, aParameters, aClip);
+    return mHelper.ComputeScrollMetadata(aLayer, aContainerReferenceFrame, aParameters, aClip);
   }
   virtual bool IsIgnoringViewportClipping() const override {
     return mHelper.IsIgnoringViewportClipping();
   }
   virtual void MarkScrollbarsDirtyForReflow() const override {
     mHelper.MarkScrollbarsDirtyForReflow();
   }
 
@@ -1424,16 +1424,20 @@ public:
   }
   virtual bool GetDisplayPortAtLastApproximateFrameVisibilityUpdate(nsRect* aDisplayPort) override {
     return mHelper.GetDisplayPortAtLastApproximateFrameVisibilityUpdate(aDisplayPort);
   }
   void TriggerDisplayPortExpiration() override {
     mHelper.TriggerDisplayPortExpiration();
   }
 
+  ScrollSnapInfo GetScrollSnapInfo() const override {
+    return mHelper.GetScrollSnapInfo();
+  }
+
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override;
 #endif
 
 protected:
   nsXULScrollFrame(nsStyleContext* aContext, bool aIsRoot,
                    bool aClipAllDescendants);
 
--- a/layout/generic/nsIScrollableFrame.h
+++ b/layout/generic/nsIScrollableFrame.h
@@ -43,16 +43,17 @@ class Layer;
  * APIs for examining scroll state, observing changes to scroll state,
  * and triggering scrolling.
  */
 class nsIScrollableFrame : public nsIScrollbarMediator {
 public:
   typedef mozilla::CSSIntPoint CSSIntPoint;
   typedef mozilla::ContainerLayerParameters ContainerLayerParameters;
   typedef mozilla::layers::FrameMetrics FrameMetrics;
+  typedef mozilla::layers::ScrollSnapInfo ScrollSnapInfo;
 
   NS_DECL_QUERYFRAME_TARGET(nsIScrollableFrame)
 
   /**
    * Get the frame for the content that we are scrolling within
    * this scrollable frame.
    */
   virtual nsIFrame* GetScrolledFrame() const = 0;
@@ -266,26 +267,16 @@ public:
                         nsIntPoint* aOverflow = nullptr,
                         nsIAtom* aOrigin = nullptr,
                         ScrollMomentum aMomentum = NOT_MOMENTUM,
                         nsIScrollbarMediator::ScrollSnapMode aSnap
                           = nsIScrollbarMediator::DISABLE_SNAP) = 0;
 
   /**
    * Perform scroll snapping, possibly resulting in a smooth scroll to
-   * maintain the scroll snap position constraints.  A predicted landing
-   * position determined by the APZC is used to select the best matching
-   * snap point, allowing touchscreen fling gestures to navigate between
-   * snap points.
-   * @param aDestination The desired landing position of the fling, which
-   * is used to select the best matching snap point.
-   */
-  virtual void FlingSnap(const mozilla::CSSPoint& aDestination) = 0;
-  /**
-   * Perform scroll snapping, possibly resulting in a smooth scroll to
    * maintain the scroll snap position constraints.  Velocity sampled from
    * main thread scrolling is used to determine best matching snap point
    * when called after a fling gesture on a trackpad or mouse wheel.
    */
   virtual void ScrollSnap() = 0;
 
   /**
    * @note This method might destroy the frame, pres shell and other objects.
@@ -395,19 +386,19 @@ public:
   virtual void ResetScrollInfoIfGeneration(uint32_t aGeneration) = 0;
   /**
    * Determine whether it is desirable to be able to asynchronously scroll this
    * scroll frame.
    */
   virtual bool WantAsyncScroll() const = 0;
   /**
    * aLayer's animated geometry root is this frame. If there needs to be a
-   * FrameMetrics contributed by this frame, append it to aOutput.
+   * ScrollMetadata contributed by this frame, append it to aOutput.
    */
-  virtual mozilla::Maybe<mozilla::layers::FrameMetrics> ComputeFrameMetrics(
+  virtual mozilla::Maybe<mozilla::layers::ScrollMetadata> ComputeScrollMetadata(
     mozilla::layers::Layer* aLayer,
     nsIFrame* aContainerReferenceFrame,
     const ContainerLayerParameters& aParameters,
     const mozilla::DisplayItemClip* aClip) const = 0;
 
   /**
    * If this scroll frame is ignoring viewporting clipping
    */
@@ -465,11 +456,16 @@ public:
   virtual bool GetDisplayPortAtLastApproximateFrameVisibilityUpdate(nsRect* aDisplayPort) = 0;
 
   /**
    * This is called when a descendant scrollframe's has its displayport expired.
    * This function will check to see if this scrollframe may safely expire its
    * own displayport and schedule a timer to do that if it is safe.
    */
   virtual void TriggerDisplayPortExpiration() = 0;
+
+  /**
+   * Returns information required to determine where to snap to after a scroll.
+   */
+  virtual ScrollSnapInfo GetScrollSnapInfo() const = 0;
 };
 
 #endif
--- a/layout/generic/nsRubyTextContainerFrame.cpp
+++ b/layout/generic/nsRubyTextContainerFrame.cpp
@@ -5,16 +5,17 @@
  * http://mozilla.org/MPL/2.0/. */
 
 /* rendering object for CSS "display: ruby-text-container" */
 
 #include "nsRubyTextContainerFrame.h"
 
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WritingModes.h"
+#include "nsLineLayout.h"
 #include "nsPresContext.h"
 #include "nsStyleContext.h"
 
 using namespace mozilla;
 
 //----------------------------------------------------------------------
 
 // Frame class boilerplate
--- a/layout/generic/nsSubDocumentFrame.cpp
+++ b/layout/generic/nsSubDocumentFrame.cpp
@@ -130,27 +130,27 @@ nsSubDocumentFrame::Init(nsIContent*    
   // it into the view tree. This happens when we've been reframed, and
   // ensures the presentation persists across reframes. If the frame element
   // has changed documents however, we blow away the presentation.
   RefPtr<nsFrameLoader> frameloader = FrameLoader();
   if (frameloader) {
     nsCOMPtr<nsIDocument> oldContainerDoc;
     nsView* detachedViews =
       frameloader->GetDetachedSubdocView(getter_AddRefs(oldContainerDoc));
+    frameloader->SetDetachedSubdocView(nullptr, nullptr);
     if (detachedViews) {
       if (oldContainerDoc == aContent->OwnerDoc()) {
         // Restore stashed presentation.
         ::InsertViewsInReverseOrder(detachedViews, mInnerView);
         ::EndSwapDocShellsForViews(mInnerView->GetFirstChild());
       } else {
         // Presentation is for a different document, don't restore it.
         frameloader->Hide();
       }
     }
-    frameloader->SetDetachedSubdocView(nullptr, nullptr);
   }
 
   nsContentUtils::AddScriptRunner(new AsyncFrameInit(this));
 }
 
 void
 nsSubDocumentFrame::ShowViewer()
 {
@@ -940,23 +940,26 @@ public:
   NS_IMETHOD Run()
   {
     // Flush frames, to ensure any pending display:none changes are made.
     // Note it can be unsafe to flush if we've destroyed the presentation
     // for some other reason, like if we're shutting down.
     if (!mPresShell->IsDestroying()) {
       mPresShell->FlushPendingNotifications(Flush_Frames);
     }
+
+    // Either the frame has been constructed by now, or it never will be,
+    // either way we want to clear the stashed views.
+    mFrameLoader->SetDetachedSubdocView(nullptr, nullptr);
+
     nsSubDocumentFrame* frame = do_QueryFrame(mFrameElement->GetPrimaryFrame());
     if ((!frame && mHideViewerIfFrameless) ||
         mPresShell->IsDestroying()) {
       // Either the frame element has no nsIFrame or the presshell is being
-      // destroyed. Hide the nsFrameLoader, which destroys the presentation,
-      // and clear our references to the stashed presentation.
-      mFrameLoader->SetDetachedSubdocView(nullptr, nullptr);
+      // destroyed. Hide the nsFrameLoader, which destroys the presentation.
       mFrameLoader->Hide();
     }
     return NS_OK;
   }
 private:
   nsCOMPtr<nsIContent> mFrameElement;
   RefPtr<nsFrameLoader> mFrameLoader;
   nsCOMPtr<nsIPresShell> mPresShell;
@@ -972,26 +975,26 @@ nsSubDocumentFrame::DestroyFrom(nsIFrame
   if (mPostedReflowCallback) {
     PresContext()->PresShell()->CancelReflowCallback(this);
     mPostedReflowCallback = false;
   }
 
   // Detach the subdocument's views and stash them in the frame loader.
   // We can then reattach them if we're being reframed (for example if
   // the frame has been made position:fixed).
-  nsFrameLoader* frameloader = FrameLoader();
+  RefPtr<nsFrameLoader> frameloader = FrameLoader();
   if (frameloader) {
     nsView* detachedViews = ::BeginSwapDocShellsForViews(mInnerView->GetFirstChild());
     frameloader->SetDetachedSubdocView(detachedViews, mContent->OwnerDoc());
 
     // We call nsFrameLoader::HideViewer() in a script runner so that we can
     // safely determine whether the frame is being reframed or destroyed.
     nsContentUtils::AddScriptRunner(
       new nsHideViewer(mContent,
-                       mFrameLoader,
+                       frameloader,
                        PresContext()->PresShell(),
                        (mDidCreateDoc || mCallingShow)));
   }
 
   nsSubDocumentFrameSuper::DestroyFrom(aDestructRoot);
 }
 
 CSSIntSize
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/button/overflow-areas-1-ref.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+  <title>Reference case</title>
+  <style>
+    input, button {
+      border: 0;             /* Combined, these mean the gray area is the */
+      background: lightgray; /* border-box size. */
+
+      outline: 2px solid black; /* The outlined area is the overflow area. */
+      width: 1px;   /* (To attempt to trigger overflow) */
+
+      display: block;     /* Put each button on its own line, w/ some margin, */
+      margin-bottom: 5px; /* so that any overflow doesn't get stomped on.     */
+
+      font: 8px serif; /* (This just lets the testcase fit better on mobile.) */
+    }
+
+    .oh { overflow: hidden }
+  </style>
+</head>
+<body>
+  <!-- For the reference case, we just put "overflow:hidden" on everything. -->
+  <input class="oh" type="reset">
+  <input class="oh" type="submit">
+  <input class="oh" type="button" value="InputTypeButton">
+  <!-- ...with one exception: button with (default) overflow:visible.
+       Such buttons *do* actually allow their contents to overflow. -->
+  <button>ActualButton</button>
+
+  <input  class="oh" type="reset">
+  <input  class="oh" type="submit">
+  <input  class="oh" type="button" value="InputTypeButton">
+  <button class="oh">ActualButton</button>
+
+  <input  class="oh" type="reset">
+  <input  class="oh" type="submit">
+  <input  class="oh" type="button" value="InputTypeButton">
+  <button class="oh">ActualButton</button>
+
+  <input  class="oh" type="reset">
+  <input  class="oh" type="submit">
+  <input  class="oh" type="button" value="InputTypeButton">
+  <button class="oh">ActualButton</button>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/button/overflow-areas-1.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<!-- For buttons whose painting gets clipped to their border-box area,
+     we should *also* clip their overflow area (as exposed via 'outline').
+     This test exposes these areas so they can be visualized, and checks that
+     they match when we expect them to. -->
+<head>
+  <title>Testcase for bug 1261284</title>
+  <style>
+    input, button {
+      border: 0;             /* Combined, these mean the gray area is the */
+      background: lightgray; /* border-box size. */
+
+      outline: 2px solid black; /* The outlined area is the overflow area. */
+      width: 1px;   /* (To attempt to trigger overflow) */
+
+      display: block;     /* Put each button on its own line, w/ some margin, */
+      margin-bottom: 5px; /* so that any overflow doesn't get stomped on.     */
+
+      font: 8px serif; /* (This just lets the testcase fit better on mobile.) */
+    }
+
+    .oh { overflow: hidden }
+    .oa { overflow: auto }
+    .os { overflow: scroll }
+  </style>
+</head>
+<body>
+  <input type="reset">
+  <input type="submit">
+  <input type="button" value="InputTypeButton">
+  <button>ActualButton</button>
+
+  <input  class="oh" type="reset">
+  <input  class="oh" type="submit">
+  <input  class="oh" type="button" value="InputTypeButton">
+  <button class="oh">ActualButton</button>
+
+  <input  class="oa" type="reset">
+  <input  class="oa" type="submit">
+  <input  class="oa" type="button" value="InputTypeButton">
+  <button class="oa">ActualButton</button>
+
+  <input  class="os" type="reset">
+  <input  class="os" type="submit">
+  <input  class="os" type="button" value="InputTypeButton">
+  <button class="os">ActualButton</button>
+</body>
+</html>
--- a/layout/reftests/forms/button/reftest.list
+++ b/layout/reftests/forms/button/reftest.list
@@ -1,12 +1,13 @@
 == first-letter-1.html first-letter-1-ref.html
 != first-letter-1.html first-letter-1-noref.html
 == max-height.html max-height-ref.html
 == min-height.html min-height-ref.html
+== overflow-areas-1.html overflow-areas-1-ref.html
 
 # The buttons in these tests have some fancy shading applied to their corners
 # on B2G, despite their "-moz-appearance: none; background: gray", so they
 # don't quite match the reference case's normal <div>. That's why they're fuzzy.
 fuzzy-if(B2G||Mulet||Android,125,20) == percent-height-child-1.html percent-height-child-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 pref(browser.display.focus_ring_width,1) fuzzy-if(B2G||Mulet||Android,125,80) == percent-height-child-2.html percent-height-child-2-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 fuzzy-if(B2G||Mulet||Android,125,20) == percent-width-child-1.html  percent-width-child-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 pref(browser.display.focus_ring_width,1) fuzzy-if(B2G||Mulet||Android,125,80) == percent-width-child-2.html  percent-width-child-2-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
--- a/layout/reftests/svg/text/reftest.list
+++ b/layout/reftests/svg/text/reftest.list
@@ -125,16 +125,19 @@ fuzzy-if(skiaContent,1,200) == textLengt
 fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\.[12]/.test(http.oscpu),4,17) fuzzy-if(skiaContent,1,100) == textLength-3.svg textLength-3-ref.svg
 == textLength-4.svg textLength-4-ref.svg
 == textLength-5.svg textLength-5-ref.svg
 == textLength-6.svg textLength-6-ref.svg
 
 # text-shadow
 == text-shadow.svg text-shadow-ref.svg
 
+# vertical text
+pref(layout.css.vertical-text.enabled,true) == vertical-01.svg vertical-01-ref.svg
+
 # tests for ignoring various properties
 == ignore-border.svg ignore-prop-ref.svg
 == ignore-display.svg ignore-display-ref.svg
 == ignore-float.svg ignore-prop-ref.svg
 == ignore-float-first-letter.svg ignore-prop-ref.svg
 == ignore-position.svg ignore-position-ref.svg
 == ignore-margin.svg ignore-prop-ref.svg
 == ignore-padding.svg ignore-prop-ref.svg
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/text/vertical-01-ref.svg
@@ -0,0 +1,12 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <defs>
+    <path id="path" d="M 100, 100 v 200" />
+  </defs>
+  <text style="font: 48px sans-serif" text-rendering="geometricPrecision">
+    <textPath xlink:href="#path">A B C</textPath>
+  </text>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/text/vertical-01.svg
@@ -0,0 +1,10 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg">
+  <text x="100" y="100" text-rendering="geometricPrecision"
+        writing-mode="vertical-rl" style="text-orientation: sideways-right;font: 48px sans-serif">
+    A B C
+  </text>
+</svg>
--- a/layout/tools/reftest/reftest-preferences.js
+++ b/layout/tools/reftest/reftest-preferences.js
@@ -104,12 +104,13 @@ user_pref("extensions.autoDisableScopes"
 // Allow unsigned add-ons
 user_pref("xpinstall.signatures.required", false);
 
 // Don't use auto-enabled e10s
 user_pref("browser.tabs.remote.autostart.1", false);
 user_pref("browser.tabs.remote.autostart.2", false);
 
 user_pref("startup.homepage_welcome_url", "");
+user_pref("startup.homepage_welcome_url.additional", "");
 user_pref("startup.homepage_override_url", "");
 user_pref("browser.usedOnWindows10.introURL", "");
 
 user_pref("media.gmp-manager.url.override", "http://localhost/dummy-gmp-manager.xml");
--- a/mfbt/RefPtr.h
+++ b/mfbt/RefPtr.h
@@ -591,17 +591,17 @@ operator!=(decltype(nullptr), const RefP
 {
   return nullptr != aRhs.get();
 }
 
 /*****************************************************************************/
 
 template <class T>
 inline already_AddRefed<T>
-do_AddRef(T*&& aObj)
+do_AddRef(T* aObj)
 {
   RefPtr<T> ref(aObj);
   return ref.forget();
 }
 
 namespace mozilla {
 
 /**
--- a/mfbt/WindowsVersion.h
+++ b/mfbt/WindowsVersion.h
@@ -167,16 +167,22 @@ IsWin7SP1OrLater()
 
 MOZ_ALWAYS_INLINE bool
 IsWin8OrLater()
 {
   return IsWindowsVersionOrLater(0x06020000ul);
 }
 
 MOZ_ALWAYS_INLINE bool
+IsWin8Point1OrLater()
+{
+  return IsWindowsVersionOrLater(0x06030000ul);
+}
+
+MOZ_ALWAYS_INLINE bool
 IsWin10OrLater()
 {
   return IsWindowsVersionOrLater(0x0a000000ul);
 }
 
 MOZ_ALWAYS_INLINE bool
 IsNotWin7PreRTM()
 {
--- a/mobile/android/base/java/org/mozilla/gecko/gfx/DynamicToolbarAnimator.java
+++ b/mobile/android/base/java/org/mozilla/gecko/gfx/DynamicToolbarAnimator.java
@@ -1,15 +1,16 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.gfx;
 
+import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.util.FloatUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.graphics.PointF;
 import android.support.v4.view.ViewCompat;
 import android.util.Log;
 import android.view.animation.DecelerateInterpolator;
@@ -89,30 +90,38 @@ public class DynamicToolbarAnimator {
     /* The task that handles showing/hiding toolbar */
     private DynamicToolbarAnimationTask mAnimationTask;
 
     /* The start point of a drag, used for scroll-based dynamic toolbar
      * behaviour. */
     private PointF mTouchStart;
     private float mLastTouch;
 
+    /* Set to true when root content is being scrolled */
+    private boolean mScrollingRootContent;
+
     public DynamicToolbarAnimator(GeckoLayerClient aTarget) {
         mTarget = aTarget;
         mListeners = new ArrayList<LayerView.DynamicToolbarListener>();
 
         mInterpolator = new DecelerateInterpolator();
 
         // Listen to the dynamic toolbar pref
         mPrefObserver = new PrefsHelper.PrefHandlerBase() {
             @Override
             public void prefValue(String pref, int value) {
                 SCROLL_TOOLBAR_THRESHOLD = value / 100.0f;
             }
         };
         PrefsHelper.addObserver(new String[] { PREF_SCROLL_TOOLBAR_THRESHOLD }, mPrefObserver);
+
+        // JPZ doesn't notify when scrolling root content. This maintains existing behaviour.
+        if (!AppConstants.MOZ_ANDROID_APZ) {
+            mScrollingRootContent = true;
+        }
     }
 
     public void destroy() {
         PrefsHelper.removeObserver(mPrefObserver);
     }
 
     public void addTranslationListener(LayerView.DynamicToolbarListener aListener) {
         mListeners.add(aListener);
@@ -180,16 +189,20 @@ public class DynamicToolbarAnimator {
     public void showToolbar(boolean immediately) {
         animateToolbar(true, immediately);
     }
 
     public void hideToolbar(boolean immediately) {
         animateToolbar(false, immediately);
     }
 
+    public void setScrollingRootContent(boolean isRootContent) {
+        mScrollingRootContent = isRootContent;
+    }
+
     private void animateToolbar(final boolean showToolbar, boolean immediately) {
         ThreadUtils.assertOnUiThread();
 
         if (mAnimationTask != null) {
             mTarget.getView().removeRenderTask(mAnimationTask);
             mAnimationTask = null;
         }
 
@@ -334,21 +347,22 @@ public class DynamicToolbarAnimator {
         if (translation < 0) { // finger moving upwards
             translation = shrinkAbs(translation, aMetrics.getOverscroll().top);
 
             // If the toolbar is in a state between fully hidden and fully shown
             // (i.e. the user is actively translating it), then we want the
             // translation to take effect right away. Or if the user has moved
             // their finger past the required threshold (and is not trying to
             // scroll past the bottom of the page) then also we want the touch
-            // to cause translation.
+            // to cause translation. If the toolbar is fully visible, we only
+            // want the toolbar to hide if the user is scrolling the root content.
             boolean inBetween = (mToolbarTranslation != 0 && mToolbarTranslation != mMaxTranslation);
             boolean reachedThreshold = -aTouchTravelDistance >= exposeThreshold;
             boolean atBottomOfPage = aMetrics.viewportRectBottom() >= aMetrics.pageRectBottom;
-            if (inBetween || (reachedThreshold && !atBottomOfPage)) {
+            if (inBetween || (mScrollingRootContent && reachedThreshold && !atBottomOfPage)) {
                 return translation;
             }
         } else {    // finger moving downwards
             translation = shrinkAbs(translation, aMetrics.getOverscroll().bottom);
 
             // Ditto above comment, but in this case if they reached the top and
             // the toolbar is not shown, then we do want to allow translation
             // right away.
--- a/mobile/android/base/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
+++ b/mobile/android/base/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
@@ -1081,16 +1081,21 @@ class GeckoLayerClient implements LayerV
         // the current Gecko coordinate in CSS pixels.
         PointF layerPoint = new PointF(
                 ((viewPoint.x + origin.x) / zoom) - (geckoOrigin.x / geckoZoom),
                 ((viewPoint.y + origin.y) / zoom) - (geckoOrigin.y / geckoZoom));
 
         return layerPoint;
     }
 
+    @Override
+    public void setScrollingRootContent(boolean isRootContent) {
+        mToolbarAnimator.setScrollingRootContent(isRootContent);
+    }
+
     public void addDrawListener(DrawListener listener) {
         mDrawListeners.add(listener);
     }
 
     public void removeDrawListener(DrawListener listener) {
         mDrawListeners.remove(listener);
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/gfx/NativePanZoomController.java
+++ b/mobile/android/base/java/org/mozilla/gecko/gfx/NativePanZoomController.java
@@ -319,16 +319,21 @@ class NativePanZoomController extends JN
                         mOverscroll.setDistance(x, Overscroll.Axis.X);
                         mOverscroll.setDistance(y, Overscroll.Axis.Y);
                     }
                 });
             }
         }
     }
 
+    @WrapForJNI
+    private void setScrollingRootContent(final boolean isRootContent) {
+        mTarget.setScrollingRootContent(isRootContent);
+    }
+
     /**
      * Active SelectionCaretDrag requires DynamicToolbarAnimator to be pinned
      * to avoid unwanted scroll interactions.
      */
     @WrapForJNI
     private void onSelectionDragState(boolean state) {
         mView.getDynamicToolbarAnimator().setPinned(state, PinReason.CARET_DRAG);
     }
--- a/mobile/android/base/java/org/mozilla/gecko/gfx/PanZoomTarget.java
+++ b/mobile/android/base/java/org/mozilla/gecko/gfx/PanZoomTarget.java
@@ -23,9 +23,10 @@ public interface PanZoomTarget {
     /** This triggers an (asynchronous) viewport update/redraw. */
     public void forceRedraw(DisplayPortMetrics displayPort);
 
     public boolean post(Runnable action);
     public void postRenderTask(RenderTask task);
     public void removeRenderTask(RenderTask task);
     public Object getLock();
     public PointF convertViewPointToLayerPoint(PointF viewPoint);
+    public void setScrollingRootContent(boolean isRootContent);
 }
--- a/netwerk/base/security-prefs.js
+++ b/netwerk/base/security-prefs.js
@@ -44,15 +44,29 @@ pref("security.OCSP.require", false);
 pref("security.OCSP.GET.enabled", false);
 
 pref("security.pki.cert_short_lifetime_in_days", 10);
 // NB: Changes to this pref affect CERT_CHAIN_SHA1_POLICY_STATUS telemetry.
 // See the comment in CertVerifier.cpp.
 // 3 = allow SHA-1 for certificates issued before 2016 or by an imported root.
 pref("security.pki.sha1_enforcement_level", 3);
 
+// security.pki.name_matching_mode controls how the platform matches hostnames
+// to name information in TLS certificates. The possible values are:
+// 0: always fall back to the subject common name if necessary (as in, if the
+//    subject alternative name extension is either not present or does not
+//    contain any DNS names or IP addresses)
+// 1: fall back to the subject common name for certificates valid before 23
+//    August 2016 if necessary
+// 2: only use name information from the subject alternative name extension
+#ifdef RELEASE_BUILD
+pref("security.pki.name_matching_mode", 1);
+#else
+pref("security.pki.name_matching_mode", 2);
+#endif
+
 pref("security.webauth.u2f", false);
 pref("security.webauth.u2f.softtoken", false);
 pref("security.webauth.u2f.usbtoken", false);
 
 pref("security.ssl.errorReporting.enabled", true);
 pref("security.ssl.errorReporting.url", "https://data.mozilla.com/submit/sslreports");
 pref("security.ssl.errorReporting.automatic", false);
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -2692,17 +2692,17 @@ nsCookieService::EnsureReadComplete()
       ("EnsureReadComplete(): corruption detected when creating statement "
        "with rv 0x%x", rv));
     HandleCorruptDB(mDefaultDBState);
     return;
   }
 
   nsCString baseDomain, name, value, host, path;
   bool hasResult;
-  AutoTArray<CookieDomainTuple, kMaxNumberOfCookies> array;
+  nsTArray<CookieDomainTuple> array(kMaxNumberOfCookies);
   while (1) {
     rv = stmt->ExecuteStep(&hasResult);
     if (NS_FAILED(rv)) {
       // Recreate the database.
       COOKIE_LOGSTRING(LogLevel::Debug,
         ("EnsureReadComplete(): corruption detected when reading result "
          "with rv 0x%x", rv));
       HandleCorruptDB(mDefaultDBState);
@@ -4082,18 +4082,18 @@ nsCookieService::PurgeCookies(int64_t aC
   NS_ASSERTION(mDBState->hostTable.Count() > 0, "table is empty");
   EnsureReadComplete();
 
   uint32_t initialCookieCount = mDBState->cookieCount;
   COOKIE_LOGSTRING(LogLevel::Debug,
     ("PurgeCookies(): beginning purge with %ld cookies and %lld oldest age",
      mDBState->cookieCount, aCurrentTimeInUsec - mDBState->cookieOldestTime));
 
-  typedef AutoTArray<nsListIter, kMaxNumberOfCookies> PurgeList;
-  PurgeList purgeList;
+  typedef nsTArray<nsListIter> PurgeList;
+  PurgeList purgeList(kMaxNumberOfCookies);
 
   nsCOMPtr<nsIMutableArray> removedList = do_CreateInstance(NS_ARRAY_CONTRACTID);
 
   // Create a params array to batch the removals. This is OK here because
   // all the removals are in order, and there are no interleaved additions.
   mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
   if (mDBState->dbConn) {
--- a/python/mozboot/mozboot/android.py
+++ b/python/mozboot/mozboot/android.py
@@ -162,21 +162,21 @@ def install_mobile_android_sdk_or_ndk(ur
 
         os.chdir(download_path)
         subprocess.check_call(['wget', '--continue', url])
         file = url.split('/')[-1]
 
         os.chdir(path)
         abspath = os.path.join(download_path, file)
         if file.endswith('.tar.gz') or file.endswith('.tgz'):
-            cmd = ['tar', 'zvxf', abspath]
+            cmd = ['tar', 'zxf', abspath]
         elif file.endswith('.tar.bz2'):
-            cmd = ['tar', 'jvxf', abspath]
+            cmd = ['tar', 'jxf', abspath]
         elif file.endswith('.zip'):
-            cmd = ['unzip', abspath]
+            cmd = ['unzip', '-q', abspath]
         elif file.endswith('.bin'):
             # Execute the .bin file, which unpacks the content.
             mode = os.stat(path).st_mode
             os.chmod(abspath, mode | stat.S_IXUSR)
             cmd = [abspath]
         else:
             raise NotImplementedError("Don't know how to unpack file: %s" % file)
 
new file mode 100644
--- /dev/null
+++ b/security/certverifier/BRNameMatchingPolicy.cpp
@@ -0,0 +1,38 @@
+/* -*- 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 "BRNameMatchingPolicy.h"
+
+#include "mozilla/Assertions.h"
+
+using namespace mozilla::psm;
+using namespace mozilla::pkix;
+
+Result
+BRNameMatchingPolicy::FallBackToCommonName(
+  Time notBefore,
+  /*out*/ FallBackToSearchWithinSubject& fallBackToCommonName)
+{
+  // (new Date("2016-08-23T00:00:00Z")).getTime() / 1000
+  static const Time AUGUST_23_2016 = TimeFromEpochInSeconds(1471910400);
+  switch (mMode)
+  {
+    case Mode::Enforce:
+      fallBackToCommonName = FallBackToSearchWithinSubject::No;
+      break;
+    case Mode::EnforceAfter23August2016:
+      fallBackToCommonName = notBefore > AUGUST_23_2016
+                           ? FallBackToSearchWithinSubject::No
+                           : FallBackToSearchWithinSubject::Yes;
+      break;
+    case Mode::DoNotEnforce:
+      fallBackToCommonName = FallBackToSearchWithinSubject::Yes;
+      break;
+    default:
+      MOZ_CRASH("Unexpected Mode");
+  }
+  return Success;
+}
new file mode 100644
--- /dev/null
+++ b/security/certverifier/BRNameMatchingPolicy.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef BRNameMatchingPolicy_h
+#define BRNameMatchingPolicy_h
+
+#include "pkix/pkixtypes.h"
+
+namespace mozilla { namespace psm {
+
+// According to the Baseline Requirements version 1.3.3 section 7.1.4.2.2.a,
+// the requirements of the subject common name field are as follows:
+// "If present, this field MUST contain a single IP address or Fully‐Qualified
+// Domain Name that is one of the values contained in the Certificate’s
+// subjectAltName extension". Consequently, since any name information present
+// in the common name must be present in the subject alternative name extension,
+// when performing name matching, it should not be necessary to fall back to the
+// common name. Because this consequence has not commonly been enforced, this
+// implementation provides a mechanism to start enforcing it gradually while
+// maintaining some backwards compatibility. If configured with the mode
+// "EnforceAfter23August2016", name matching will only fall back to using the
+// subject common name for certificates where the notBefore field is before 23
+// August 2016.
+// Note that this implementation does not actually directly enforce that if the
+// subject common name is present, its value corresponds to a dNSName or
+// iPAddress entry in the subject alternative name extension.
+
+class BRNameMatchingPolicy : public mozilla::pkix::NameMatchingPolicy
+{
+public:
+  enum class Mode {
+    DoNotEnforce = 0,
+    EnforceAfter23August2016 = 1,
+    Enforce = 2,
+  };
+
+  explicit BRNameMatchingPolicy(Mode mode)
+    : mMode(mode)
+  {
+  }
+
+  virtual mozilla::pkix::Result FallBackToCommonName(
+    mozilla::pkix::Time notBefore,
+    /*out*/ mozilla::pkix::FallBackToSearchWithinSubject& fallBacktoCommonName)
+    override;
+
+private:
+  Mode mMode;
+};
+
+} } // namespace mozilla::psm
+
+#endif // BRNameMatchingPolicy_h
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "CertVerifier.h"
 
 #include <stdint.h>
 
+#include "BRNameMatchingPolicy.h"
 #include "ExtendedValidation.h"
 #include "NSSCertDBTrustDomain.h"
 #include "NSSErrorsService.h"
 #include "cert.h"
 #include "nsNSSComponent.h"
 #include "nsServiceManagerUtils.h"
 #include "pk11pub.h"
 #include "pkix/pkix.h"
@@ -33,23 +34,25 @@ const CertVerifier::Flags CertVerifier::
 const CertVerifier::Flags CertVerifier::FLAG_MUST_BE_EV = 2;
 const CertVerifier::Flags CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST = 4;
 
 CertVerifier::CertVerifier(OcspDownloadConfig odc,
                            OcspStrictConfig osc,
                            OcspGetConfig ogc,
                            uint32_t certShortLifetimeInDays,
                            PinningMode pinningMode,
-                           SHA1Mode sha1Mode)
+                           SHA1Mode sha1Mode,
+                           BRNameMatchingPolicy::Mode nameMatchingMode)
   : mOCSPDownloadConfig(odc)
   , mOCSPStrict(osc == ocspStrict)
   , mOCSPGETEnabled(ogc == ocspGetEnabled)
   , mCertShortLifetimeInDays(certShortLifetimeInDays)
   , mPinningMode(pinningMode)
   , mSHA1Mode(sha1Mode)
+  , mNameMatchingMode(nameMatchingMode)
 {
 }
 
 CertVerifier::~CertVerifier()
 {
 }
 
 void
@@ -711,17 +714,26 @@ CertVerifier::VerifySSLServerCert(CERTCe
   }
 
   Input hostnameInput;
   result = hostnameInput.Init(uint8_t_ptr_cast(hostname), strlen(hostname));
   if (result != Success) {
     PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
     return SECFailure;
   }
-  result = CheckCertHostname(peerCertInput, hostnameInput);
+  bool isBuiltInRoot;
+  result = IsCertChainRootBuiltInRoot(builtChain, isBuiltInRoot);
+  if (result != Success) {
+    PR_SetError(MapResultToPRErrorCode(result), 0);
+    return SECFailure;
+  }
+  BRNameMatchingPolicy nameMatchingPolicy(
+    isBuiltInRoot ? mNameMatchingMode
+                  : BRNameMatchingPolicy::Mode::DoNotEnforce);
+  result = CheckCertHostname(peerCertInput, hostnameInput, nameMatchingPolicy);
   if (result != Success) {
     // Treat malformed name information as a domain mismatch.
     if (result == Result::ERROR_BAD_DER) {
       PR_SetError(SSL_ERROR_BAD_CERT_DOMAIN, 0);
     } else {
       PR_SetError(MapResultToPRErrorCode(result), 0);
     }
     return SECFailure;
--- a/security/certverifier/CertVerifier.h
+++ b/security/certverifier/CertVerifier.h
@@ -2,20 +2,21 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_psm__CertVerifier_h
 #define mozilla_psm__CertVerifier_h
 
+#include "BRNameMatchingPolicy.h"
+#include "OCSPCache.h"
+#include "ScopedNSSTypes.h"
 #include "mozilla/Telemetry.h"
 #include "pkix/pkixtypes.h"
-#include "OCSPCache.h"
-#include "ScopedNSSTypes.h"
 
 namespace mozilla { namespace psm {
 
 // These values correspond to the CERT_CHAIN_KEY_SIZE_STATUS telemetry.
 enum class KeySizeStatus {
   NeverChecked = 0,
   LargeMinimumSucceeded = 1,
   CompatibilityRisk = 2,
@@ -116,27 +117,29 @@ public:
     ocspOn = 1,
     ocspEVOnly = 2
   };
   enum OcspStrictConfig { ocspRelaxed = 0, ocspStrict };
   enum OcspGetConfig { ocspGetDisabled = 0, ocspGetEnabled = 1 };
 
   CertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc,
                OcspGetConfig ogc, uint32_t certShortLifetimeInDays,
-               PinningMode pinningMode, SHA1Mode sha1Mode);
+               PinningMode pinningMode, SHA1Mode sha1Mode,
+               BRNameMatchingPolicy::Mode nameMatchingMode);
   ~CertVerifier();
 
   void ClearOCSPCache() { mOCSPCache.Clear(); }
 
   const OcspDownloadConfig mOCSPDownloadConfig;
   const bool mOCSPStrict;
   const bool mOCSPGETEnabled;
   const uint32_t mCertShortLifetimeInDays;
   const PinningMode mPinningMode;
   const SHA1Mode mSHA1Mode;
+  const BRNameMatchingPolicy::Mode mNameMatchingMode;
 
 private:
   OCSPCache mOCSPCache;
 
   // Returns true if the configured SHA1 mode is more restrictive than the given
   // mode. SHA1Mode::Forbidden is more restrictive than any other mode except
   // Forbidden. Next is Before2016, then ImportedRoot, then Allowed.
   // (A mode is never more restrictive than itself.)
--- a/security/certverifier/moz.build
+++ b/security/certverifier/moz.build
@@ -1,20 +1,22 @@
 # -*- Mode: python; c-basic-offset: 4; 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/.
 
 EXPORTS += [
+    'BRNameMatchingPolicy.h',
     'CertVerifier.h',
     'OCSPCache.h',
 ]
 
 UNIFIED_SOURCES += [
+    'BRNameMatchingPolicy.cpp',
     'CertVerifier.cpp',
     'NSSCertDBTrustDomain.cpp',
     'OCSPCache.cpp',
     'OCSPRequestor.cpp',
     'OCSPVerificationTrustDomain.cpp',
 ]
 
 if not CONFIG['NSS_NO_EV_CERTS']:
--- a/security/manager/ssl/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/SSLServerCertVerification.cpp
@@ -91,51 +91,50 @@
 // an SSL handshake) and the PSM NSS I/O layer are not thread-safe, and because
 // we need the event to interrupt the PR_Poll that may waiting for I/O on the
 // socket for which we are validating the cert.
 
 #include "SSLServerCertVerification.h"
 
 #include <cstring>
 
-#include "pkix/pkix.h"
-#include "pkix/pkixnss.h"
+#include "BRNameMatchingPolicy.h"
 #include "CertVerifier.h"
 #include "CryptoTask.h"
 #include "ExtendedValidation.h"
 #include "NSSCertDBTrustDomain.h"
-#include "nsIBadCertListener2.h"
-#include "nsICertOverrideService.h"
-#include "nsISiteSecurityService.h"
-#include "nsNSSComponent.h"
-#include "nsNSSIOLayer.h"
-#include "nsNSSShutDown.h"
-
+#include "PSMRunnable.h"
+#include "RootCertificateTelemetryUtils.h"
+#include "SharedSSLState.h"
+#include "cert.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/Telemetry.h"
+#include "mozilla/UniquePtr.h"
 #include "mozilla/net/DNS.h"
-#include "mozilla/UniquePtr.h"
 #include "mozilla/unused.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsIBadCertListener2.h"
+#include "nsICertOverrideService.h"
+#include "nsISiteSecurityService.h"
+#include "nsISocketProvider.h"
 #include "nsIThreadPool.h"
-#include "nsISocketProvider.h"
-#include "nsXPCOMCIDInternal.h"
-#include "nsComponentManagerUtils.h"
+#include "nsNSSComponent.h"
+#include "nsNSSIOLayer.h"
+#include "nsNSSShutDown.h"
 #include "nsServiceManagerUtils.h"
-#include "PSMRunnable.h"
-#include "RootCertificateTelemetryUtils.h"
-#include "SharedSSLState.h"
-#include "nsContentUtils.h"
 #include "nsURLHelper.h"
-
-#include "ssl.h"
-#include "cert.h"
+#include "nsXPCOMCIDInternal.h"
+#include "pkix/pkix.h"
+#include "pkix/pkixnss.h"
 #include "secerr.h"
 #include "secoidt.h"
 #include "secport.h"
+#include "ssl.h"
 #include "sslerr.h"
 
 extern mozilla::LazyLogModule gPIPNSSLog;
 
 using namespace mozilla::pkix;
 
 namespace mozilla { namespace psm {
 
@@ -427,23 +426,38 @@ DetermineCertOverrideErrors(CERTCertific
     }
     Input hostnameInput;
     Result result = hostnameInput.Init(uint8_t_ptr_cast(hostName),
                                        strlen(hostName));
     if (result != Success) {
       PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
       return SECFailure;
     }
-    result = CheckCertHostname(certInput, hostnameInput);
+    // Use a lax policy so as to not generate potentially spurious name
+    // mismatch "hints".
+    BRNameMatchingPolicy nameMatchingPolicy(
+      BRNameMatchingPolicy::Mode::DoNotEnforce);
+    // CheckCertHostname expects that its input represents a certificate that
+    // has already been successfully validated by BuildCertChain. This is
+    // obviously not the case, however, because we're in the error path of
+    // certificate verification. Thus, this is problematic. In the future, it
+    // would be nice to remove this optimistic additional error checking and
+    // simply punt to the front-end, which can more easily (and safely) perform
+    // extra checks to give the user hints as to why verification failed.
+    result = CheckCertHostname(certInput, hostnameInput, nameMatchingPolicy);
     // Treat malformed name information as a domain mismatch.
     if (result == Result::ERROR_BAD_DER ||
         result == Result::ERROR_BAD_CERT_DOMAIN) {
       collectedErrors |= nsICertOverrideService::ERROR_MISMATCH;
       errorCodeMismatch = SSL_ERROR_BAD_CERT_DOMAIN;
-    } else if (result != Success) {
+    } else if (IsFatalError(result)) {
+      // Because its input has not been validated by BuildCertChain,
+      // CheckCertHostname can return an error that is less important than the
+      // original certificate verification error. Only return an error result
+      // from this function if we've encountered a fatal error.
       PR_SetError(MapResultToPRErrorCode(result), 0);
       return SECFailure;
     }
   }
 
   return SECSuccess;
 }
 
--- a/security/manager/ssl/SharedCertVerifier.h
+++ b/security/manager/ssl/SharedCertVerifier.h
@@ -16,18 +16,19 @@ class SharedCertVerifier : public mozill
 protected:
   ~SharedCertVerifier();
 
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedCertVerifier)
 
   SharedCertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc,
                      OcspGetConfig ogc, uint32_t certShortLifetimeInDays,
-                     PinningMode pinningMode, SHA1Mode sha1Mode)
+                     PinningMode pinningMode, SHA1Mode sha1Mode,
+                     BRNameMatchingPolicy::Mode nameMatchingMode)
     : mozilla::psm::CertVerifier(odc, osc, ogc, certShortLifetimeInDays,
-                                 pinningMode, sha1Mode)
+                                 pinningMode, sha1Mode, nameMatchingMode)
   {
   }
 };
 
 } } // namespace mozilla::psm
 
 #endif // mozilla_psm__SharedCertVerifier_h
--- a/security/manager/ssl/nsNSSCertificate.cpp
+++ b/security/manager/ssl/nsNSSCertificate.cpp
@@ -508,39 +508,44 @@ nsNSSCertificate::FormatUIStrings(const 
 
 NS_IMETHODIMP
 nsNSSCertificate::GetDbKey(nsACString& aDbKey)
 {
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
+  return GetDbKey(mCert, aDbKey);
+}
 
+nsresult
+nsNSSCertificate::GetDbKey(CERTCertificate* cert, nsACString& aDbKey)
+{
   static_assert(sizeof(uint64_t) == 8, "type size sanity check");
   static_assert(sizeof(uint32_t) == 4, "type size sanity check");
   // The format of the key is the base64 encoding of the following:
   // 4 bytes: {0, 0, 0, 0} (this was intended to be the module ID, but it was
   //                        never implemented)
   // 4 bytes: {0, 0, 0, 0} (this was intended to be the slot ID, but it was
   //                        never implemented)
   // 4 bytes: <serial number length in big-endian order>
   // 4 bytes: <DER-encoded issuer distinguished name length in big-endian order>
   // n bytes: <bytes of serial number>
   // m bytes: <DER-encoded issuer distinguished name>
   nsAutoCString buf;
   const char leadingZeroes[] = {0, 0, 0, 0, 0, 0, 0, 0};
   buf.Append(leadingZeroes, sizeof(leadingZeroes));
-  uint32_t serialNumberLen = htonl(mCert->serialNumber.len);
+  uint32_t serialNumberLen = htonl(cert->serialNumber.len);
   buf.Append(reinterpret_cast<const char*>(&serialNumberLen), sizeof(uint32_t));
-  uint32_t issuerLen = htonl(mCert->derIssuer.len);
+  uint32_t issuerLen = htonl(cert->derIssuer.len);
   buf.Append(reinterpret_cast<const char*>(&issuerLen), sizeof(uint32_t));
-  buf.Append(reinterpret_cast<const char*>(mCert->serialNumber.data),
-             mCert->serialNumber.len);
-  buf.Append(reinterpret_cast<const char*>(mCert->derIssuer.data),
-             mCert->derIssuer.len);
+  buf.Append(reinterpret_cast<const char*>(cert->serialNumber.data),
+             cert->serialNumber.len);
+  buf.Append(reinterpret_cast<const char*>(cert->derIssuer.data),
+             cert->derIssuer.len);
 
   return Base64Encode(buf, aDbKey);
 }
 
 NS_IMETHODIMP
 nsNSSCertificate::GetWindowTitle(nsAString& aWindowTitle)
 {
   nsNSSShutDownPreventionLock locker;
--- a/security/manager/ssl/nsNSSCertificate.h
+++ b/security/manager/ssl/nsNSSCertificate.h
@@ -48,16 +48,20 @@ public:
   nsresult GetIsExtendedValidation(bool* aIsEV);
 
   enum EVStatus {
     ev_status_invalid = 0,
     ev_status_valid = 1,
     ev_status_unknown = 2
   };
 
+  // This is a separate static method so nsNSSComponent can use it during NSS
+  // initialization. Other code should probably not use it.
+  static nsresult GetDbKey(CERTCertificate* cert, nsACString& aDbKey);
+
 private:
   virtual ~nsNSSCertificate();
 
   mozilla::ScopedCERTCertificate mCert;
   bool             mPermDelete;
   uint32_t         mCertType;
   nsresult CreateASN1Struct(nsIASN1Object** aRetVal);
   nsresult CreateTBSCertificateASN1Struct(nsIASN1Sequence** retSequence,
--- a/security/manager/ssl/nsNSSCertificateDB.cpp
+++ b/security/manager/ssl/nsNSSCertificateDB.cpp
@@ -123,42 +123,63 @@ nsNSSCertificateDB::FindCertByNickname(c
       pCert.forget(_rvCert);
       return NS_OK;
     }
   }
   return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
-nsNSSCertificateDB::FindCertByDBKey(const char* aDBkey,nsIX509Cert** _cert)
+nsNSSCertificateDB::FindCertByDBKey(const char* aDBKey,nsIX509Cert** _cert)
 {
-  NS_ENSURE_ARG_POINTER(aDBkey);
-  NS_ENSURE_ARG(aDBkey[0]);
+  NS_ENSURE_ARG_POINTER(aDBKey);
+  NS_ENSURE_ARG(aDBKey[0]);
   NS_ENSURE_ARG_POINTER(_cert);
   *_cert = nullptr;
 
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
+  UniqueCERTCertificate cert;
+  nsresult rv = FindCertByDBKey(aDBKey, cert);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  // If we can't find the certificate, that's not an error. Just return null.
+  if (!cert) {
+    return NS_OK;
+  }
+  nsCOMPtr<nsIX509Cert> nssCert = nsNSSCertificate::Create(cert.get());
+  if (!nssCert) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  nssCert.forget(_cert);
+  return NS_OK;
+}
+
+nsresult
+nsNSSCertificateDB::FindCertByDBKey(const char* aDBKey,
+                                    UniqueCERTCertificate& cert)
+{
   static_assert(sizeof(uint64_t) == 8, "type size sanity check");
   static_assert(sizeof(uint32_t) == 4, "type size sanity check");
   // (From nsNSSCertificate::GetDbKey)
   // The format of the key is the base64 encoding of the following:
   // 4 bytes: {0, 0, 0, 0} (this was intended to be the module ID, but it was
   //                        never implemented)
   // 4 bytes: {0, 0, 0, 0} (this was intended to be the slot ID, but it was
   //                        never implemented)
   // 4 bytes: <serial number length in big-endian order>
   // 4 bytes: <DER-encoded issuer distinguished name length in big-endian order>
   // n bytes: <bytes of serial number>
   // m bytes: <DER-encoded issuer distinguished name>
   nsAutoCString decoded;
-  nsAutoCString tmpDBKey(aDBkey);
+  nsAutoCString tmpDBKey(aDBKey);
   // Filter out any whitespace for backwards compatibility.
   tmpDBKey.StripWhitespace();
   nsresult rv = Base64Decode(tmpDBKey, decoded);
   if (NS_FAILED(rv)) {
     return rv;
   }
   if (decoded.Length() < 16) {
     return NS_ERROR_ILLEGAL_INPUT;
@@ -180,25 +201,17 @@ nsNSSCertificateDB::FindCertByDBKey(cons
   issuerSN.serialNumber.len = serialNumberLen;
   issuerSN.serialNumber.data = (unsigned char*)reader;
   reader += serialNumberLen;
   issuerSN.derIssuer.len = issuerLen;
   issuerSN.derIssuer.data = (unsigned char*)reader;
   reader += issuerLen;
   MOZ_ASSERT(reader == decoded.EndReading());
 
-  ScopedCERTCertificate cert(
-    CERT_FindCertByIssuerAndSN(CERT_GetDefaultCertDB(), &issuerSN));
-  if (cert) {
-    nsCOMPtr<nsIX509Cert> nssCert = nsNSSCertificate::Create(cert.get());
-    if (!nssCert) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-    nssCert.forget(_cert);
-  }
+  cert.reset(CERT_FindCertByIssuerAndSN(CERT_GetDefaultCertDB(), &issuerSN));
   return NS_OK;
 }
 
 SECStatus
 collect_certs(void *arg, SECItem **certs, int numcerts)
 {
   CERTDERCerts *collectArgs;
   SECItem *cert;
--- a/security/manager/ssl/nsNSSCertificateDB.h
+++ b/security/manager/ssl/nsNSSCertificateDB.h
@@ -30,16 +30,21 @@ public:
   get_default_nickname(CERTCertificate *cert, nsIInterfaceRequestor* ctx,
                        nsCString &nickname,
                        const nsNSSShutDownPreventionLock &proofOfLock);
 
   static nsresult 
   ImportValidCACerts(int numCACerts, SECItem *CACerts, nsIInterfaceRequestor *ctx,
                      const nsNSSShutDownPreventionLock &proofOfLock);
 
+  // This is a separate static method so nsNSSComponent can use it during NSS
+  // initialization. Other code should probably not use it.
+  static nsresult
+  FindCertByDBKey(const char* aDBKey, mozilla::UniqueCERTCertificate& cert);
+
 protected:
   virtual ~nsNSSCertificateDB();
 
 private:
 
   static nsresult
   ImportValidCACertsInList(const mozilla::UniqueCERTCertList& filteredCerts,
                            nsIInterfaceRequestor* ctx,
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -1,40 +1,46 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  *
  * 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/. */
 
+#define CERT_AddTempCertToPerm __CERT_AddTempCertToPerm
+
 #include "nsNSSComponent.h"
 
 #include "ExtendedValidation.h"
 #include "NSSCertDBTrustDomain.h"
 #include "SharedSSLState.h"
+#include "cert.h"
+#include "certdb.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/PublicSSL.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/Telemetry.h"
+#include "mozilla/UniquePtr.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsCRT.h"
 #include "nsCertVerificationThread.h"
 #include "nsClientAuthRemember.h"
 #include "nsComponentManagerUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsIBufEntropyCollector.h"
 #include "nsICertOverrideService.h"
 #include "nsIFile.h"
 #include "nsIObserverService.h"
 #include "nsIPrompt.h"
 #include "nsIProperties.h"
 #include "nsISiteSecurityService.h"
 #include "nsITokenPasswordDialogs.h"
 #include "nsIWindowWatcher.h"
 #include "nsIXULRuntime.h"
+#include "nsNSSCertificateDB.h"
 #include "nsNSSHelper.h"
 #include "nsNSSShutDown.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
 #include "nss.h"
 #include "p12plcy.h"
 #include "pkix/pkixnss.h"
@@ -44,17 +50,24 @@
 #include "sslerr.h"
 #include "sslproto.h"
 
 #ifndef MOZ_NO_SMART_CARDS
 #include "nsSmartCardMonitor.h"
 #endif
 
 #ifdef XP_WIN
+#include "mozilla/WindowsVersion.h"
 #include "nsILocalFileWin.h"
+
+#include "windows.h" // this needs to be before the following includes
+#include "Lmcons.h"
+#include "Sddl.h"
+#include "Wincrypt.h"
+#include "nsIWindowsRegKey.h"
 #endif
 
 using namespace mozilla;
 using namespace mozilla::psm;
 
 LazyLogModule gPIPNSSLog("pipnss");
 
 int nsNSSComponent::mInstanceCount = 0;
@@ -389,16 +402,443 @@ nsNSSComponent::ShutdownSmartCardThread(
 void
 nsNSSComponent::ShutdownSmartCardThreads()
 {
   delete mThreadList;
   mThreadList = nullptr;
 }
 #endif // MOZ_NO_SMART_CARDS
 
+#ifdef XP_WIN
+static bool
+GetUserSid(nsAString& sidString)
+{
+  // UNLEN is the maximum user name length (see Lmcons.h). +1 for the null
+  // terminator.
+  WCHAR lpAccountName[UNLEN + 1];
+  DWORD lcAccountName = sizeof(lpAccountName) / sizeof(lpAccountName[0]);
+  BOOL success = GetUserName(lpAccountName, &lcAccountName);
+  if (!success) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("GetUserName failed"));
+    return false;
+  }
+  char sid_buffer[SECURITY_MAX_SID_SIZE];
+  SID* sid = reinterpret_cast<SID*>(sid_buffer);
+  DWORD cbSid = MOZ_ARRAY_LENGTH(sid_buffer);
+  SID_NAME_USE eUse;
+  // There doesn't appear to be a defined maximum length for the domain name
+  // here. To deal with this, we start with a reasonable buffer length and
+  // see if that works. If it fails and the error indicates insufficient length,
+  // we use the indicated required length and try again.
+  DWORD cchReferencedDomainName = 128;
+  auto ReferencedDomainName(MakeUnique<WCHAR[]>(cchReferencedDomainName));
+  success = LookupAccountName(nullptr, lpAccountName, sid, &cbSid,
+                              ReferencedDomainName.get(),
+                              &cchReferencedDomainName, &eUse);
+  if (!success && GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("LookupAccountName failed"));
+    return false;
+  }
+  if (!success) {
+    ReferencedDomainName = MakeUnique<WCHAR[]>(cchReferencedDomainName);
+    success = LookupAccountName(nullptr, lpAccountName, sid, &cbSid,
+                                ReferencedDomainName.get(),
+                                &cchReferencedDomainName, &eUse);
+  }
+  if (!success) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("LookupAccountName failed"));
+    return false;
+  }
+  LPTSTR StringSid;
+  success = ConvertSidToStringSid(sid, &StringSid);
+  if (!success) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("ConvertSidToStringSid failed"));
+    return false;
+  }
+  sidString.Assign(StringSid);
+  LocalFree(StringSid);
+  return true;
+}
+
+// This is a specialized helper function to read the value of a registry key
+// that might not be present. If it is present, returns (via the output
+// parameter) its value. Otherwise, returns the given default value.
+// This function handles one level of nesting. That is, if the desired value
+// is actually in a direct child of the given registry key (where the child
+// and/or the value being sought may not actually be present), this function
+// will handle that. In the normal case, though, optionalChildName will be
+// null.
+static nsresult
+ReadRegKeyValueWithDefault(nsCOMPtr<nsIWindowsRegKey> regKey,
+                           uint32_t flags,
+                           wchar_t* optionalChildName,
+                           wchar_t* valueName,
+                           uint32_t defaultValue,
+                           uint32_t& valueOut)
+{
+  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("ReadRegKeyValueWithDefault"));
+  MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+          ("attempting to read '%S%s%S' with default '%u'",
+           optionalChildName ? optionalChildName : L"",
+           optionalChildName ? "\\" : "", valueName, defaultValue));
+  if (optionalChildName) {
+    nsDependentString childNameString(optionalChildName);
+    bool hasChild;
+    nsresult rv = regKey->HasChild(childNameString, &hasChild);
+    if (NS_FAILED(rv)) {
+      MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+              ("failed to determine if child key is present"));
+      return rv;
+    }
+    if (!hasChild) {
+      valueOut = defaultValue;
+      return NS_OK;
+    }
+    nsCOMPtr<nsIWindowsRegKey> childRegKey;
+    rv = regKey->OpenChild(childNameString, flags,
+                           getter_AddRefs(childRegKey));
+    if (NS_FAILED(rv)) {
+      MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't open child key"));
+      return rv;
+    }
+    return ReadRegKeyValueWithDefault(childRegKey, flags, nullptr, valueName,
+                                      defaultValue, valueOut);
+  }
+  nsDependentString valueNameString(valueName);
+  bool hasValue;
+  nsresult rv = regKey->HasValue(valueNameString, &hasValue);
+  if (NS_FAILED(rv)) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("failed to determine if value is present"));
+    return rv;
+  }
+  if (!hasValue) {
+    valueOut = defaultValue;
+    return NS_OK;
+  }
+  rv = regKey->ReadIntValue(valueNameString, &valueOut);
+  if (NS_FAILED(rv)) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("failed to read value"));
+    return rv;
+  }
+  return NS_OK;
+}
+
+static nsresult
+AccountHasFamilySafetyEnabled(bool& enabled)
+{
+  enabled = false;
+  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("AccountHasFamilySafetyEnabled?"));
+  nsCOMPtr<nsIWindowsRegKey> parentalControlsKey(
+    do_CreateInstance("@mozilla.org/windows-registry-key;1"));
+  if (!parentalControlsKey) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't create nsIWindowsRegKey"));
+    return NS_ERROR_FAILURE;
+  }
+  uint32_t flags = nsIWindowsRegKey::ACCESS_READ | nsIWindowsRegKey::WOW64_64;
+  NS_NAMED_LITERAL_STRING(familySafetyPath,
+    "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Parental Controls");
+  nsresult rv = parentalControlsKey->Open(
+    nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, familySafetyPath, flags);
+  if (NS_FAILED(rv)) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't open parentalControlsKey"));
+    return rv;
+  }
+  NS_NAMED_LITERAL_STRING(usersString, "Users");
+  bool hasUsers;
+  rv = parentalControlsKey->HasChild(usersString, &hasUsers);
+  if (NS_FAILED(rv)) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("HasChild(Users) failed"));
+    return rv;
+  }
+  if (!hasUsers) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("Users subkey not present - Parental Controls not enabled"));
+    return NS_OK;
+  }
+  nsCOMPtr<nsIWindowsRegKey> usersKey;
+  rv = parentalControlsKey->OpenChild(usersString, flags,
+                                      getter_AddRefs(usersKey));
+  if (NS_FAILED(rv)) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("failed to open Users subkey"));
+    return rv;
+  }
+  nsAutoString sid;
+  if (!GetUserSid(sid)) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't get sid"));
+    return NS_ERROR_FAILURE;
+  }
+  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("our sid is '%S'", sid.get()));
+  bool hasSid;
+  rv = usersKey->HasChild(sid, &hasSid);
+  if (NS_FAILED(rv)) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("HasChild(sid) failed"));
+    return rv;
+  }
+  if (!hasSid) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("sid not present in Family Safety Users"));
+    return NS_OK;
+  }
+  nsCOMPtr<nsIWindowsRegKey> sidKey;
+  rv = usersKey->OpenChild(sid, flags, getter_AddRefs(sidKey));
+  if (NS_FAILED(rv)) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't open sid key"));
+    return rv;
+  }
+  // There are three keys we're interested in: "Parental Controls On",
+  // "Logging Required", and "Web\\Filter On". These keys will have value 0
+  // or 1, indicating a particular feature is disabled or enabled,
+  // respectively. So, if "Parental Controls On" is not 1, Family Safety is
+  // disabled and we don't care about anything else. If both "Logging
+  // Required" and "Web\\Filter On" are 0, the proxy will not be running,
+  // so for our purposes we can consider Family Safety disabled in that
+  // case.
+  // By default, "Logging Required" is 1 and "Web\\Filter On" is 0,
+  // reflecting the initial settings when Family Safety is enabled for an
+  // account for the first time, However, these sub-keys are not created
+  // unless they are switched away from the default value.
+  uint32_t parentalControlsOn;
+  rv = sidKey->ReadIntValue(NS_LITERAL_STRING("Parental Controls On"),
+                            &parentalControlsOn);
+  if (NS_FAILED(rv)) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("couldn't read Parental Controls On"));
+    return rv;
+  }
+  MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+          ("Parental Controls On: %u", parentalControlsOn));
+  if (parentalControlsOn != 1) {
+    return NS_OK;
+  }
+  uint32_t loggingRequired;
+  rv = ReadRegKeyValueWithDefault(sidKey, flags, nullptr, L"Logging Required",
+                                  1, loggingRequired);
+  if (NS_FAILED(rv)) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("failed to read value of Logging Required"));
+    return rv;
+  }
+  MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+          ("Logging Required: %u", loggingRequired));
+  uint32_t webFilterOn;
+  rv = ReadRegKeyValueWithDefault(sidKey, flags, L"Web", L"Filter On", 0,
+                                  webFilterOn);
+  if (NS_FAILED(rv)) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("failed to read value of Web\\Filter On"));
+    return rv;
+  }
+  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Web\\Filter On: %u", webFilterOn));
+  enabled = loggingRequired == 1 || webFilterOn == 1;
+  return NS_OK;
+}
+
+const char* kImportedFamilySafetyRootPref =
+  "security.family_safety.imported_root.db_key";
+
+static nsresult
+MaybeImportFamilySafetyRoot(PCCERT_CONTEXT certificate,
+                            bool& wasFamilySafetyRoot)
+{
+  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("MaybeImportFamilySafetyRoot"));
+  wasFamilySafetyRoot = false;
+
+  // It would be convenient to just use nsIX509CertDB here. However, since
+  // nsIX509CertDB depends on nsNSSComponent initialization, we can't use it.
+  // Instead, we can use NSS APIs directly (as long as we're called late enough
+  // in nsNSSComponent initialization such that those APIs are safe to use).
+
+  SECItem derCert = {
+    siBuffer,
+    certificate->pbCertEncoded,
+    certificate->cbCertEncoded
+  };
+  UniqueCERTCertificate nssCertificate(
+    CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &derCert,
+                            nullptr, // nickname unnecessary
+                            false, // not permanent
+                            true)); // copy DER
+  if (!nssCertificate) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't decode certificate"));
+    return NS_ERROR_FAILURE;
+  }
+  // Looking for a certificate with the common name 'Microsoft Family Safety'
+  UniquePtr<char, void(&)(void*)> subjectName(
+    CERT_GetCommonName(&nssCertificate->subject), PORT_Free);
+  MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+          ("subject name is '%s'", subjectName.get()));
+  if (nsCRT::strcmp(subjectName.get(), "Microsoft Family Safety") == 0) {
+    wasFamilySafetyRoot = true;
+    CERTCertTrust trust = {
+      CERTDB_TRUSTED_CA | CERTDB_VALID_CA | CERTDB_USER,
+      0,
+      0
+    };
+    SECStatus srv = __CERT_AddTempCertToPerm(
+      nssCertificate.get(), "Microsoft Family Safety", &trust);
+    if (srv != SECSuccess) {
+      MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+              ("couldn't permanently add certificate"));
+      return NS_ERROR_FAILURE;
+    }
+    nsAutoCString dbKey;
+    nsresult rv = nsNSSCertificate::GetDbKey(nssCertificate.get(), dbKey);
+    if (NS_FAILED(rv)) {
+      MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("GetDbKey failed"));
+      return rv;
+    }
+    Preferences::SetCString(kImportedFamilySafetyRootPref, dbKey);
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("added Family Safety root"));
+  }
+  return NS_OK;
+}
+
+// Because HCERTSTORE is just a typedef void*, we can't use any of the nice
+// scoped pointer templates.
+class ScopedCertStore final
+{
+public:
+  explicit ScopedCertStore(HCERTSTORE certstore) : certstore(certstore) {}
+
+  ~ScopedCertStore()
+  {
+    CertCloseStore(certstore, 0);
+  }
+
+  HCERTSTORE get()
+  {
+    return certstore;
+  }
+
+private:
+  ScopedCertStore(const ScopedCertStore&) = delete;
+  ScopedCertStore& operator=(const ScopedCertStore&) = delete;
+  HCERTSTORE certstore;
+};
+
+static const wchar_t* WindowsDefaultRootStoreName = L"ROOT";
+
+static nsresult
+LoadFamilySafetyRoot()
+{
+  ScopedCertStore certstore(
+    CertOpenSystemStore(0, WindowsDefaultRootStoreName));
+  if (!certstore.get()) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("couldn't get certstore '%S'", WindowsDefaultRootStoreName));
+    return NS_ERROR_FAILURE;
+  }
+  // Any resources held by the certificate are released by the next call to
+  // CertFindCertificateInStore.
+  PCCERT_CONTEXT certificate = nullptr;
+  while (certificate = CertFindCertificateInStore(certstore.get(),
+                                                  X509_ASN_ENCODING, 0,
+                                                  CERT_FIND_ANY, nullptr,
+                                                  certificate)) {
+    bool wasFamilySafetyRoot = false;
+    nsresult rv = MaybeImportFamilySafetyRoot(certificate,
+                                              wasFamilySafetyRoot);
+    if (NS_SUCCEEDED(rv) && wasFamilySafetyRoot) {
+      return NS_OK; // We're done (we're only expecting one root).
+    }
+  }
+  return NS_ERROR_FAILURE;
+}
+
+static void
+UnloadFamilySafetyRoot()
+{
+  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("UnloadFamilySafetyRoot"));
+  nsAdoptingCString dbKey = Preferences::GetCString(
+    kImportedFamilySafetyRootPref);
+  if (!dbKey || dbKey.IsEmpty()) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("Family Safety root wasn't previously imported"));
+    return;
+  }
+  UniqueCERTCertificate cert;
+  nsresult rv = nsNSSCertificateDB::FindCertByDBKey(dbKey, cert);
+  if (NS_FAILED(rv)) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("finding previously-imported Family Safety root failed"));
+    return;
+  }
+  if (!cert) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("previously-imported Family Safety root not found"));
+    return;
+  }
+  SECStatus srv = SEC_DeletePermCertificate(cert.get());
+  if (srv != SECSuccess) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("couldn't delete previously-imported Family Safety root"));
+    return;
+  }
+  MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+          ("deleted previously-imported Family Safety root"));
+  Preferences::ClearUser(kImportedFamilySafetyRootPref);
+}
+
+#endif // XP_WIN
+
+// The supported values of this pref are:
+// 0: disable detecting Family Safety mode and importing the root
+// 1: only attempt to detect Family Safety mode (don't import the root)
+// 2: detect Family Safety mode and import the root
+const char* kFamilySafetyModePref = "security.family_safety.mode";
+
+// The telemetry gathered by this function is as follows:
+// 0-2: the value of the Family Safety mode pref
+// 3: detecting Family Safety mode failed
+// 4: Family Safety was not enabled
+// 5: Family Safety was enabled
+// 6: failed to import the Family Safety root
+// 7: successfully imported the root
+static void
+MaybeEnableFamilySafetyCompatibility()
+{
+#ifdef XP_WIN
+  UnloadFamilySafetyRoot();
+  if (!(IsWin8Point1OrLater() && !IsWin10OrLater())) {
+    return;
+  }
+  // Detect but don't import by default.
+  uint32_t familySafetyMode = Preferences::GetUint(kFamilySafetyModePref, 1);
+  if (familySafetyMode > 2) {
+    familySafetyMode = 0;
+  }
+  Telemetry::Accumulate(Telemetry::FAMILY_SAFETY, familySafetyMode);
+  if (familySafetyMode == 0) {
+    return;
+  }
+  bool familySafetyEnabled;
+  nsresult rv = AccountHasFamilySafetyEnabled(familySafetyEnabled);
+  if (NS_FAILED(rv)) {
+    Telemetry::Accumulate(Telemetry::FAMILY_SAFETY, 3);
+    return;
+  }
+  if (!familySafetyEnabled) {
+    Telemetry::Accumulate(Telemetry::FAMILY_SAFETY, 4);
+    return;
+  }
+  Telemetry::Accumulate(Telemetry::FAMILY_SAFETY, 5);
+  if (familySafetyMode == 2) {
+    rv = LoadFamilySafetyRoot();
+    if (NS_FAILED(rv)) {
+      Telemetry::Accumulate(Telemetry::FAMILY_SAFETY, 6);
+      MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+              ("failed to load Family Safety root"));
+    } else {
+      Telemetry::Accumulate(Telemetry::FAMILY_SAFETY, 7);
+    }
+  }
+#endif // XP_WIN
+}
+
 void
 nsNSSComponent::LoadLoadableRoots()
 {
   nsNSSShutDownPreventionLock locker;
   SECMODModule* RootsModule = nullptr;
 
   // In the past we used SECMOD_AddNewModule to load our module containing
   // root CA certificates. This caused problems, refer to bug 176501.
@@ -867,28 +1307,44 @@ void nsNSSComponent::setValidationOption
   switch (sha1Mode) {
     case CertVerifier::SHA1Mode::Allowed:
     case CertVerifier::SHA1Mode::Forbidden:
     case CertVerifier::SHA1Mode::Before2016:
     case CertVerifier::SHA1Mode::ImportedRoot:
       break;
     default:
       sha1Mode = CertVerifier::SHA1Mode::Allowed;
+      break;
+  }
+
+  BRNameMatchingPolicy::Mode nameMatchingMode =
+    static_cast<BRNameMatchingPolicy::Mode>
+      (Preferences::GetInt("security.pki.name_matching_mode",
+                           static_cast<int32_t>(BRNameMatchingPolicy::Mode::DoNotEnforce)));
+  switch (nameMatchingMode) {
+    case BRNameMatchingPolicy::Mode::Enforce:
+    case BRNameMatchingPolicy::Mode::EnforceAfter23August2016:
+    case BRNameMatchingPolicy::Mode::DoNotEnforce:
+      break;
+    default:
+      nameMatchingMode = BRNameMatchingPolicy::Mode::DoNotEnforce;
+      break;
   }
 
   CertVerifier::OcspDownloadConfig odc;
   CertVerifier::OcspStrictConfig osc;
   CertVerifier::OcspGetConfig ogc;
   uint32_t certShortLifetimeInDays;
 
   GetRevocationBehaviorFromPrefs(&odc, &osc, &ogc, &certShortLifetimeInDays,
                                  lo