merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 12 May 2016 11:54:10 +0200
changeset 338113 c3f5e6079284a7b7053c41f05d0fe06ff031db03
parent 337977 70b53120eb1820043a1739afb2e1574645b92ac3 (current diff)
parent 338112 ea5f22cee4e9144cc6fa918c889d19a08bbb87a4 (diff)
child 338115 6e3f70253eadc6db358f68e7626aa27d76137dbc
child 338176 5386faa586765892790194bb2dfc06bb22a1d454
push id1183
push userraliiev@mozilla.com
push dateMon, 05 Sep 2016 20:01:49 +0000
treeherdermozilla-release@3148731bed45 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone49.0a1
first release with
nightly linux32
c3f5e6079284 / 49.0a1 / 20160512030253 / files
nightly linux64
c3f5e6079284 / 49.0a1 / 20160512030253 / files
nightly mac
c3f5e6079284 / 49.0a1 / 20160512030253 / files
nightly win32
c3f5e6079284 / 49.0a1 / 20160512030253 / files
nightly win64
c3f5e6079284 / 49.0a1 / 20160512030253 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
dom/base/nsIEntropyCollector.idl
dom/canvas/test/mochitest-subsuite-webgl.ini
security/manager/ssl/nsEntropyCollector.cpp
security/manager/ssl/nsEntropyCollector.h
testing/docker/decision/bin/entrypoint
testing/docker/decision/git.env
--- a/accessible/base/nsCoreUtils.cpp
+++ b/accessible/base/nsCoreUtils.cpp
@@ -135,17 +135,17 @@ nsCoreUtils::DispatchMouseEvent(EventMes
                                 nsIContent *aContent, nsIFrame *aFrame,
                                 nsIPresShell *aPresShell, nsIWidget *aRootWidget)
 {
   WidgetMouseEvent event(true, aMessage, aRootWidget,
                          WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
 
   event.mRefPoint = LayoutDeviceIntPoint(aX, aY);
 
-  event.clickCount = 1;
+  event.mClickCount = 1;
   event.button = WidgetMouseEvent::eLeftButton;
   event.mTime = PR_IntervalNow();
   event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
 
   nsEventStatus status = nsEventStatus_eIgnore;
   aPresShell->HandleEventWithTarget(&event, aFrame, aContent, &status);
 }
 
--- a/addon-sdk/source/lib/sdk/url.js
+++ b/addon-sdk/source/lib/sdk/url.js
@@ -65,17 +65,17 @@ var toFilename = exports.toFilename = fu
   var uri = newURI(url);
   if (uri.scheme == "resource")
     uri = newURI(resolveResourceURI(uri));
   if (uri.scheme == "chrome") {
     var channel = ios.newChannelFromURI2(uri,
                                          null,      // aLoadingNode
                                          Services.scriptSecurityManager.getSystemPrincipal(),
                                          null,      // aTriggeringPrincipal
-                                         Ci.nsILoadInfo.SEC_NORMAL,
+                                         Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
                                          Ci.nsIContentPolicy.TYPE_OTHER);
     try {
       channel = channel.QueryInterface(Ci.nsIFileChannel);
       return channel.file.path;
     }
     catch (e) {
       if (e.result == Cr.NS_NOINTERFACE) {
         throw new Error("chrome url isn't on filesystem: " + url);
--- a/addon-sdk/source/test/test-private-browsing.js
+++ b/addon-sdk/source/test/test-private-browsing.js
@@ -12,17 +12,17 @@ const { isWindowPrivate } = winUtils;
 const { isPrivateBrowsingSupported } = require('sdk/self');
 const { is } = require('sdk/system/xul-app');
 const { isPrivate } = require('sdk/private-browsing');
 const { LoaderWithHookedConsole } = require("sdk/test/loader");
 const { getMode, isWindowPBSupported, isTabPBSupported } = require('sdk/private-browsing/utils');
 const { pb } = require('./private-browsing/helper');
 const prefs = require('sdk/preferences/service');
 
-const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
 
 const kAutoStartPref = "browser.privatebrowsing.autostart";
 
 if (isWindowPBSupported) {
   safeMerge(module.exports, require('./private-browsing/windows'));
 
   exports.testPWOnlyOnFirefox = function(assert) {
     assert.ok(is("Firefox"), "isWindowPBSupported is only true on Firefox");
@@ -61,24 +61,20 @@ exports.testWindowDefaults = function(as
 };
 
 exports.testIsPrivateBrowsingFalseDefault = function(assert) {
   assert.equal(isPrivateBrowsingSupported, false,
   	               'isPrivateBrowsingSupported property is false by default');
 };
 
 exports.testNSIPrivateBrowsingChannel = function(assert) {
-  let channel = Services.io.newChannel2("about:blank",
-                                        null,
-                                        null,
-                                        null,      // aLoadingNode
-                                        Services.scriptSecurityManager.getSystemPrincipal(),
-                                        null,      // aTriggeringPrincipal
-                                        Ci.nsILoadInfo.SEC_NORMAL,
-                                        Ci.nsIContentPolicy.TYPE_OTHER);
+  let channel = NetUtil.newChannel({
+    uri: "about:blank",
+    loadUsingSystemPrincipal: true
+  });
   channel.QueryInterface(Ci.nsIPrivateBrowsingChannel);
   assert.equal(isPrivate(channel), false, 'isPrivate detects non-private channels');
   channel.setPrivate(true);
   assert.ok(isPrivate(channel), 'isPrivate detects private channels');
 }
 
 exports.testNewGlobalPBService = function(assert) {
   assert.equal(isPrivate(), false, 'isPrivate() is false by default');
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -21,16 +21,17 @@ support-files =
   browser_web_channel.html
   browser_web_channel_iframe.html
   bug1262648_string_with_newlines.dtd
   bug592338.html
   bug792517-2.html
   bug792517.html
   bug792517.sjs
   bug839103.css
+  clipboard_pastefile.html
   contextmenu_common.js
   ctxmenu-image.png
   discovery.html
   domplate_test.js
   download_page.html
   dummy_page.html
   feed_tab.html
   file_generic_favicon.ico
@@ -272,16 +273,17 @@ tags = mcb
 [browser_mixedContentFramesOnHttp.js]
 tags = mcb
 [browser_bug970746.js]
 [browser_bug1015721.js]
 skip-if = os == 'win'
 [browser_bug1064280_changeUrlInPinnedTab.js]
 [browser_accesskeys.js]
 [browser_clipboard.js]
+[browser_clipboard_pastefile.js]
 [browser_contentAreaClick.js]
 [browser_contextmenu.js]
 tags = fullscreen
 skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558
 [browser_contextmenu_input.js]
 skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558
 [browser_ctrlTab.js]
 [browser_datachoices_notification.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_clipboard_pastefile.js
@@ -0,0 +1,59 @@
+// This test is used to check that pasting files removes all non-file data from
+// event.clipboardData.
+
+add_task(function*() {
+  var searchbar = document.getElementById("searchbar");
+
+  searchbar.focus();
+  searchbar.value = "Text";
+  searchbar.select();
+
+  yield new Promise((resolve, reject) => {
+    searchbar.addEventListener("copy", function copyEvent(event) {
+      searchbar.removeEventListener("copy", copyEvent, true);
+      event.clipboardData.setData("text/plain", "Alternate");
+      // For this test, it doesn't matter that the file isn't actually a file.
+      event.clipboardData.setData("application/x-moz-file", "Sample");
+      event.preventDefault();
+      resolve();
+    }, true)
+
+    EventUtils.synthesizeKey("c", { accelKey: true });
+  });
+
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+              "https://example.com/browser/browser/base/content/test/general/clipboard_pastefile.html");
+  let browser = tab.linkedBrowser;
+
+  yield ContentTask.spawn(browser, { }, function* (arg) {
+    content.document.getElementById("input").focus();
+  });
+
+  yield BrowserTestUtils.synthesizeKey("v", { accelKey: true }, browser);
+
+  let output = yield ContentTask.spawn(browser, { }, function* (arg) {
+    return content.document.getElementById("output").textContent;
+  });
+  is (output, "Passed", "Paste file");
+
+  searchbar.focus();
+
+  yield new Promise((resolve, reject) => {
+    searchbar.addEventListener("paste", function copyEvent(event) {
+      searchbar.removeEventListener("paste", copyEvent, true);
+
+      let dt = event.clipboardData;
+      is(dt.types.length, 3, "number of types");
+      ok(dt.types.contains("text/plain"), "text/plain exists in types");
+      ok(dt.mozTypesAt(0).contains("text/plain"), "text/plain exists in mozTypesAt");
+      is(dt.getData("text/plain"), "Alternate", "text/plain returned in getData");
+      is(dt.mozGetDataAt("text/plain", 0), "Alternate", "text/plain returned in mozGetDataAt");
+
+      resolve();
+    }, true);
+
+    EventUtils.synthesizeKey("v", { accelKey: true });
+  });
+
+  yield BrowserTestUtils.removeTab(tab);
+});
--- a/browser/base/content/test/general/browser_selectpopup.js
+++ b/browser/base/content/test/general/browser_selectpopup.js
@@ -34,26 +34,34 @@ const PAGECONTENT_SMALL =
   "</select><select id='two'>" +
   "  <option value='Three'>Three</option>" +
   "  <option value='Four'>Four</option>" +
   "</select><select id='three'>" +
   "  <option value='Five'>Five</option>" +
   "  <option value='Six'>Six</option>" +
   "</select></body></html>";
 
+const PAGECONTENT_TRANSLATED =
+  "<html><body>" +
+  "<div id='div'>" +
+  "<iframe id='frame' width='320' height='295' style='border: none;'" +
+  "        src='data:text/html,<select id=select autofocus><option>he he he</option><option>boo boo</option><option>baz baz</option></select>'" +
+  "</iframe>" +
+  "</div></body></html>";
+
 function openSelectPopup(selectPopup, withMouse, selector = "select")
 {
   let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
 
   if (withMouse) {
     return Promise.all([popupShownPromise,
                         BrowserTestUtils.synthesizeMouseAtCenter(selector, { }, gBrowser.selectedBrowser)]);
   }
 
-  setTimeout(() => EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true, code: "ArrowDown" }), 1500);
+  EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true, code: "ArrowDown" });
   return popupShownPromise;
 }
 
 function hideSelectPopup(selectPopup, withEscape)
 {
   let popupHiddenPromise = BrowserTestUtils.waitForEvent(selectPopup, "popuphidden");
 
   if (withEscape) {
@@ -197,8 +205,73 @@ add_task(function*() {
   yield openSelectPopup(selectPopup, true, "#one");
 
   popupHiddenPromise = BrowserTestUtils.waitForEvent(selectPopup, "popuphidden");
   yield BrowserTestUtils.removeTab(tab);
   yield popupHiddenPromise;
 
   ok(true, "Popup hidden when tab is closed");
 });
+
+// This test opens a select popup that is isn't a frame and has some translations applied.
+add_task(function*() {
+  const pageUrl = "data:text/html," + escape(PAGECONTENT_TRANSLATED);
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+  let menulist = document.getElementById("ContentSelectDropdown");
+  let selectPopup = menulist.menupopup;
+
+  // First, get the position of the select popup when no translations have been applied.
+  yield openSelectPopup(selectPopup, false);
+
+  let rect = selectPopup.getBoundingClientRect();
+  let expectedX = rect.left;
+  let expectedY = rect.top;
+
+  yield hideSelectPopup(selectPopup);
+
+  // Iterate through a set of steps which each add more translation to the select's expected position.
+  let steps = [
+    [ "div", "transform: translateX(7px) translateY(13px);", 7, 13 ],
+    [ "frame", "border-top: 5px solid green; border-left: 10px solid red; border-right: 35px solid blue;", 10, 5 ],
+    [ "frame", "border: none; padding-left: 6px; padding-right: 12px; padding-top: 2px;", -4, -3 ],
+    [ "select", "margin: 9px; transform: translateY(-3px);", 9, 6 ],
+  ];
+
+  for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) {
+    let step = steps[stepIndex];
+
+    yield ContentTask.spawn(gBrowser.selectedBrowser, step, function*(step) {
+      return new Promise(resolve => {
+        let changedWin = content;
+
+        let elem;
+        if (step[0] == "select") {
+          changedWin = content.document.getElementById("frame").contentWindow;
+          elem = changedWin.document.getElementById("select");
+        }
+        else {
+          elem = content.document.getElementById(step[0]);
+        }
+
+        changedWin.addEventListener("MozAfterPaint", function onPaint() {
+          changedWin.removeEventListener("MozAfterPaint", onPaint);
+          resolve();
+        });
+
+        elem.style = step[1];
+      });
+    });
+
+    yield openSelectPopup(selectPopup, false);
+
+    expectedX += step[2];
+    expectedY += step[3];
+
+    let rect = selectPopup.getBoundingClientRect();
+    is(rect.left, expectedX, "step " + (stepIndex + 1) + " x");
+    is(rect.top, expectedY, "step " + (stepIndex + 1) + " y");
+
+    yield hideSelectPopup(selectPopup);
+  }
+
+  yield BrowserTestUtils.removeTab(tab);
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/clipboard_pastefile.html
@@ -0,0 +1,37 @@
+<html><body>
+<script>
+function checkPaste(event)
+{
+  let output = document.getElementById("output");
+  output.textContent = checkPasteHelper(event);
+}
+
+function checkPasteHelper(event)
+{
+  let dt = event.clipboardData;
+  if (dt.types.length != 2)
+    return "Wrong number of types; got " + dt.types.length;
+
+  for (let type of dt.types) {
+    if (type != "Files" && type != "application/x-moz-file")
+      return "Invalid type for types; got" + type;
+  }
+
+  for (let type of dt.mozTypesAt(0)) {
+    if (type != "Files" && type != "application/x-moz-file")
+      return "Invalid type for mozTypesAt; got" + type;
+  }
+
+  if (dt.getData("text/plain"))
+    return "text/plain found with getData";
+  if (dt.mozGetDataAt("text/plain", 0))
+    return "text/plain found with mozGetDataAt";
+
+  return "Passed";
+}
+</script>
+
+<input id="input" onpaste="checkPaste(event)">
+<div id="output"></div>
+
+</body></html>
--- a/browser/components/contextualidentity/test/browser/browser.ini
+++ b/browser/components/contextualidentity/test/browser/browser.ini
@@ -9,8 +9,9 @@ support-files =
 [browser_aboutURLs.js]
 [browser_usercontext.js]
 [browser_usercontextid_tabdrop.js]
 skip-if = os == "mac" || os == "win" # Intermittent failure - bug 1268276
 [browser_windowName.js]
 [browser_windowOpen.js]
 [browser_serviceworkers.js]
 [browser_broadcastchannel.js]
+[browser_blobUrl.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_blobUrl.js
@@ -0,0 +1,81 @@
+"use strict";
+
+// Here we want to test that blob URLs are not available cross containers.
+
+const BASE_URI = "http://mochi.test:8888/browser/browser/components/"
+  + "contextualidentity/test/browser/empty_file.html";
+
+add_task(function* setup() {
+  yield new Promise((resolve) => {
+    SpecialPowers.pushPrefEnv({"set": [
+      ["privacy.userContext.enabled", true]
+    ]}, resolve);
+  });
+});
+
+
+add_task(function* test() {
+  info("Creating a tab with UCI = 1...");
+  let tab1 = gBrowser.addTab(BASE_URI, {userContextId: 1});
+  is(tab1.getAttribute('usercontextid'), 1, "New tab has UCI equal 1");
+
+  let browser1 = gBrowser.getBrowserForTab(tab1);
+  yield BrowserTestUtils.browserLoaded(browser1);
+
+  let blobURL;
+
+  info("Creating a blob URL...");
+  yield ContentTask.spawn(browser1, null, function() {
+    return Promise.resolve(content.window.URL.createObjectURL(new content.window.Blob([123])));
+  }).then(newURL => { blobURL = newURL });
+
+  info("Blob URL: " + blobURL);
+
+  info("Creating a tab with UCI = 2...");
+  let tab2 = gBrowser.addTab(BASE_URI, {userContextId: 2});
+  is(tab2.getAttribute('usercontextid'), 2, "New tab has UCI equal 2");
+
+  let browser2 = gBrowser.getBrowserForTab(tab2);
+  yield BrowserTestUtils.browserLoaded(browser2);
+
+  yield ContentTask.spawn(browser2, blobURL, function(url) {
+    return new Promise(resolve => {
+      var xhr = new content.window.XMLHttpRequest();
+      xhr.open("GET", url);
+      try {
+        xhr.send();
+        resolve("SendSucceeded");
+      } catch(e) {
+        resolve("SendThrew");
+      }
+    });
+  }).then(status => {
+    is(status, "SendThrew", "Using a blob URI from one user context id in another should not work");
+  });
+
+  info("Creating a tab with UCI = 1...");
+  let tab3 = gBrowser.addTab(BASE_URI, {userContextId: 1});
+  is(tab3.getAttribute('usercontextid'), 1, "New tab has UCI equal 1");
+
+  let browser3 = gBrowser.getBrowserForTab(tab3);
+  yield BrowserTestUtils.browserLoaded(browser3);
+
+  yield ContentTask.spawn(browser3, blobURL, function(url) {
+    return new Promise(resolve => {
+      var xhr = new content.window.XMLHttpRequest();
+      xhr.open("GET", url);
+      try {
+        xhr.send();
+        resolve("SendSucceeded");
+      } catch(e) {
+        resolve("SendThrew");
+      }
+    });
+  }).then(status => {
+    is(status, "SendSucceeded", "Using a blob URI within a single user context id should work");
+  });
+
+  yield BrowserTestUtils.removeTab(tab1);
+  yield BrowserTestUtils.removeTab(tab2);
+  yield BrowserTestUtils.removeTab(tab3);
+});
--- a/browser/modules/FormSubmitObserver.jsm
+++ b/browser/modules/FormSubmitObserver.jsm
@@ -185,17 +185,17 @@ FormSubmitObserver.prototype =
   _showPopup: function (aElement) {
     // Collect positional information and show the popup
     let panelData = {};
 
     panelData.message = this._validationMessage;
 
     // Note, this is relative to the browser and needs to be translated
     // in chrome.
-    panelData.contentRect = this._msgRect(aElement);
+    panelData.contentRect = BrowserUtils.getElementBoundingRect(aElement);
 
     // We want to show the popup at the middle of checkbox and radio buttons
     // and where the content begin for the other elements.
     let offset = 0;
     let position = "";
 
     if (aElement.tagName == 'INPUT' &&
         (aElement.type == 'radio' || aElement.type == 'checkbox')) {
@@ -227,26 +227,10 @@ FormSubmitObserver.prototype =
     if (this._content == null) {
       return true;
     }
     let target = aEvent.originalTarget;
     return (target == this._content.document ||
             (target.ownerDocument && target.ownerDocument == this._content.document));
   },
 
-  /*
-   * Return a message manager rect for the element's bounding client rect
-   * in top level browser coords.
-   */
-  _msgRect: function (aElement) {
-    let domRect = aElement.getBoundingClientRect();
-    let zoomFactor = this._getWindowUtils().fullZoom;
-    let { offsetX, offsetY } = BrowserUtils.offsetToTopLevelWindow(this._content, aElement);
-    return {
-      left: (domRect.left + offsetX) * zoomFactor,
-      top: (domRect.top + offsetY) * zoomFactor,
-      width: domRect.width * zoomFactor,
-      height: domRect.height * zoomFactor
-    };
-  },
-
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver])
 };
--- a/caps/nsPrincipal.cpp
+++ b/caps/nsPrincipal.cpp
@@ -249,18 +249,18 @@ nsPrincipal::MayLoadInternal(nsIURI* aUR
 {
   // See if aURI is something like a Blob URI that is actually associated with
   // a principal.
   nsCOMPtr<nsIURIWithPrincipal> uriWithPrin = do_QueryInterface(aURI);
   nsCOMPtr<nsIPrincipal> uriPrin;
   if (uriWithPrin) {
     uriWithPrin->GetPrincipal(getter_AddRefs(uriPrin));
   }
-  if (uriPrin && nsIPrincipal::Subsumes(uriPrin)) {
-    return true;
+  if (uriPrin) {
+    return nsIPrincipal::Subsumes(uriPrin);
   }
 
   // If this principal is associated with an addon, check whether that addon
   // has been given permission to load from this domain.
   if (AddonAllowsLoad(aURI)) {
     return true;
   }
 
--- a/config/gcc-stl-wrapper.template.h
+++ b/config/gcc-stl-wrapper.template.h
@@ -38,24 +38,22 @@
 #  if !defined(XPCOM_GLUE) && !defined(NS_NO_XPCOM) && !defined(MOZ_NO_MOZALLOC)
 #    include "mozilla/mozalloc.h"
 #  else
 #    error "STL code can only be used with infallible ::operator new()"
 #  endif
 
 #endif
 
-#if defined(DEBUG) && !defined(_GLIBCXX_DEBUG)
+// Don't enable debug mode with the clang plugin; clang doesn't recognize
+// the debug/ versions of the stdlib headers as being system headers, leading
+// to complaints about code that's out of our control.
+#if defined(DEBUG) && !defined(_GLIBCXX_DEBUG) && !defined(MOZ_CLANG_PLUGIN)
 // Enable checked iterators and other goodies
-//
-// FIXME/bug 551254: gcc's debug STL implementation requires -frtti.
-// Figure out how to resolve this with -fno-rtti.  Maybe build with
-// -frtti in DEBUG builds?
-//
-//  # define _GLIBCXX_DEBUG 1
+  # define _GLIBCXX_DEBUG 1
 #endif
 
 #pragma GCC visibility push(default)
 #include_next <${HEADER}>
 #pragma GCC visibility pop
 
 // gcc calls a __throw_*() function from bits/functexcept.h when it
 // wants to "throw an exception".  functexcept exists nominally to
--- a/dom/animation/AnimValuesStyleRule.cpp
+++ b/dom/animation/AnimValuesStyleRule.cpp
@@ -28,26 +28,27 @@ AnimValuesStyleRule::MapRuleInfoInto(nsR
     // will never look at cached structs when we're animating things inside
     // a pseduo-element, so that we don't incorrectly return a struct that
     // is only appropriate for non-pseudo-elements.
     aRuleData->mConditions.SetUncacheable();
     return;
   }
 
   for (uint32_t i = 0, i_end = mPropertyValuePairs.Length(); i < i_end; ++i) {
-    PropertyValuePair &cv = mPropertyValuePairs[i];
+    PropertyStyleAnimationValuePair& pair = mPropertyValuePairs[i];
     if (aRuleData->mSIDs & nsCachedStyleData::GetBitForSID(
-                             nsCSSProps::kSIDTable[cv.mProperty]))
+                             nsCSSProps::kSIDTable[pair.mProperty]))
     {
-      nsCSSValue *prop = aRuleData->ValueFor(cv.mProperty);
+      nsCSSValue *prop = aRuleData->ValueFor(pair.mProperty);
       if (prop->GetUnit() == eCSSUnit_Null) {
 #ifdef DEBUG
         bool ok =
 #endif
-          StyleAnimationValue::UncomputeValue(cv.mProperty, cv.mValue, *prop);
+          StyleAnimationValue::UncomputeValue(pair.mProperty, pair.mValue,
+                                              *prop);
         MOZ_ASSERT(ok, "could not store computed value");
       }
     }
   }
 }
 
 bool
 AnimValuesStyleRule::MightMapInheritedStyleData()
@@ -60,17 +61,17 @@ void
 AnimValuesStyleRule::List(FILE* out, int32_t aIndent) const
 {
   nsAutoCString str;
   for (int32_t index = aIndent; --index >= 0; ) {
     str.AppendLiteral("  ");
   }
   str.AppendLiteral("[anim values] { ");
   for (uint32_t i = 0, i_end = mPropertyValuePairs.Length(); i < i_end; ++i) {
-    const PropertyValuePair &pair = mPropertyValuePairs[i];
+    const PropertyStyleAnimationValuePair& pair = mPropertyValuePairs[i];
     str.Append(nsCSSProps::GetStringValue(pair.mProperty));
     str.AppendLiteral(": ");
     nsAutoString value;
     StyleAnimationValue::UncomputeValue(pair.mProperty, pair.mValue, value);
     AppendUTF16toUTF8(value, str);
     str.AppendLiteral("; ");
   }
   str.AppendLiteral("}\n");
--- a/dom/animation/AnimValuesStyleRule.h
+++ b/dom/animation/AnimValuesStyleRule.h
@@ -33,38 +33,33 @@ public:
   void MapRuleInfoInto(nsRuleData* aRuleData) override;
   bool MightMapInheritedStyleData() override;
 #ifdef DEBUG
   void List(FILE* out = stdout, int32_t aIndent = 0) const override;
 #endif
 
   void AddValue(nsCSSProperty aProperty, const StyleAnimationValue &aStartValue)
   {
-    PropertyValuePair v = { aProperty, aStartValue };
-    mPropertyValuePairs.AppendElement(v);
+    PropertyStyleAnimationValuePair pair = { aProperty, aStartValue };
+    mPropertyValuePairs.AppendElement(pair);
     mStyleBits |=
       nsCachedStyleData::GetBitForSID(nsCSSProps::kSIDTable[aProperty]);
   }
 
   void AddValue(nsCSSProperty aProperty, StyleAnimationValue&& aStartValue)
   {
-    PropertyValuePair *p = mPropertyValuePairs.AppendElement();
-    p->mProperty = aProperty;
-    p->mValue = Move(aStartValue);
+    PropertyStyleAnimationValuePair* pair = mPropertyValuePairs.AppendElement();
+    pair->mProperty = aProperty;
+    pair->mValue = Move(aStartValue);
     mStyleBits |=
       nsCachedStyleData::GetBitForSID(nsCSSProps::kSIDTable[aProperty]);
   }
 
-  struct PropertyValuePair {
-    nsCSSProperty mProperty;
-    StyleAnimationValue mValue;
-  };
-
 private:
   ~AnimValuesStyleRule() {}
 
-  InfallibleTArray<PropertyValuePair> mPropertyValuePairs;
+  InfallibleTArray<PropertyStyleAnimationValuePair> mPropertyValuePairs;
   uint32_t mStyleBits;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_AnimValuesStyleRule_h
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -833,25 +833,16 @@ DumpAnimationProperties(nsTArray<Animati
       printf("  %f..%f: %s..%s\n", s.mFromKey, s.mToKey,
              NS_ConvertUTF16toUTF8(fromValue).get(),
              NS_ConvertUTF16toUTF8(toValue).get());
     }
   }
 }
 #endif
 
-/**
- * A property and StyleAnimationValue pair.
- */
-struct KeyframeValue
-{
-  nsCSSProperty mProperty;
-  StyleAnimationValue mValue;
-};
-
 /* static */ already_AddRefed<KeyframeEffectReadOnly>
 KeyframeEffectReadOnly::Constructor(
     const GlobalObject& aGlobal,
     const Nullable<ElementOrCSSPseudoElement>& aTarget,
     JS::Handle<JSObject*> aFrames,
     const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions,
     ErrorResult& aRv)
 {
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -2084,24 +2084,24 @@ Element::DispatchClickEvent(nsPresContex
   WidgetMouseEvent event(aSourceEvent->IsTrusted(), eMouseClick,
                          aSourceEvent->mWidget, WidgetMouseEvent::eReal);
   event.mRefPoint = aSourceEvent->mRefPoint;
   uint32_t clickCount = 1;
   float pressure = 0;
   uint16_t inputSource = 0;
   WidgetMouseEvent* sourceMouseEvent = aSourceEvent->AsMouseEvent();
   if (sourceMouseEvent) {
-    clickCount = sourceMouseEvent->clickCount;
+    clickCount = sourceMouseEvent->mClickCount;
     pressure = sourceMouseEvent->pressure;
     inputSource = sourceMouseEvent->inputSource;
   } else if (aSourceEvent->mClass == eKeyboardEventClass) {
     inputSource = nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD;
   }
   event.pressure = pressure;
-  event.clickCount = clickCount;
+  event.mClickCount = clickCount;
   event.inputSource = inputSource;
   event.mModifiers = aSourceEvent->mModifiers;
   if (aExtraEventFlags) {
     // Be careful not to overwrite existing flags!
     event.mFlags.Union(*aExtraEventFlags);
   }
 
   return DispatchEvent(aPresContext, &event, aTarget, aFullDispatch, aStatus);
--- a/dom/base/ScreenOrientation.cpp
+++ b/dom/base/ScreenOrientation.cpp
@@ -473,17 +473,17 @@ ScreenOrientation::GetLockOrientationPer
   }
 
   if (Preferences::GetBool("dom.screenorientation.testing.non_fullscreen_lock_allow",
                            false)) {
     return LOCK_ALLOWED;
   }
 
   // Other content must be full-screen in order to lock orientation.
-  return doc->MozFullScreen() ? FULLSCREEN_LOCK_ALLOWED : LOCK_DENIED;
+  return doc->Fullscreen() ? FULLSCREEN_LOCK_ALLOWED : LOCK_DENIED;
 }
 
 nsIDocument*
 ScreenOrientation::GetResponsibleDocument() const
 {
   nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
   if (!owner) {
     return nullptr;
@@ -642,17 +642,17 @@ ScreenOrientation::FullScreenEventListen
   MOZ_ASSERT(target);
 
   nsCOMPtr<nsIDocument> doc = do_QueryInterface(target);
   MOZ_ASSERT(doc);
 
   // We have to make sure that the event we got is the event sent when
   // fullscreen is disabled because we could get one when fullscreen
   // got enabled if the lock call is done at the same moment.
-  if (doc->MozFullScreen()) {
+  if (doc->Fullscreen()) {
     return NS_OK;
   }
 
   hal::UnlockScreenOrientation();
 
   nsresult rv = target->RemoveSystemEventListener(NS_LITERAL_STRING("fullscreenchange"),
                                                   this, true);
   NS_ENSURE_SUCCESS(rv, rv);
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -16,17 +16,16 @@ XPIDL_SOURCES += [
     'nsIDOMDataChannel.idl',
     'nsIDOMDOMCursor.idl',
     'nsIDOMDOMRequest.idl',
     'nsIDOMFileList.idl',
     'nsIDOMFormData.idl',
     'nsIDOMParser.idl',
     'nsIDOMSerializer.idl',
     'nsIDroppedLinkHandler.idl',
-    'nsIEntropyCollector.idl',
     'nsIFrameLoader.idl',
     'nsIImageLoadingContent.idl',
     'nsIMessageManager.idl',
     'nsIObjectLoadingContent.idl',
     'nsIRemoteWindowContext.idl',
     'nsIScriptChannel.idl',
     'nsIScriptLoaderObserver.idl',
     'nsISelection.idl',
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -53,16 +53,17 @@
 #include "mozilla/dom/TextDecoder.h"
 #include "mozilla/dom/TouchEvent.h"
 #include "mozilla/dom/ShadowRoot.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/dom/workers/ServiceWorkerManager.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStateManager.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/InternalMutationEvent.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/Selection.h"
 #include "mozilla/TextEvents.h"
 #include "nsAString.h"
@@ -197,17 +198,16 @@
 #include "xpcprivate.h" // nsXPConnect
 #include "HTMLSplitOnSpacesTokenizer.h"
 #include "nsContentTypeParser.h"
 #include "nsICookiePermission.h"
 #include "mozIThirdPartyUtil.h"
 #include "nsICookieService.h"
 #include "mozilla/EnumSet.h"
 #include "mozilla/BloomFilter.h"
-#include "SourceSurfaceRawData.h"
 
 #include "nsIBidiKeyboard.h"
 
 #if defined(XP_WIN)
 // Undefine LoadImage to prevent naming conflict with Windows.
 #undef LoadImage
 #endif
 
@@ -7129,48 +7129,54 @@ nsContentUtils::GetHostOrIPv6WithBracket
   nsresult rv = GetHostOrIPv6WithBrackets(aURI, hostname);
   if (NS_FAILED(rv)) {
     return rv;
   }
   CopyUTF8toUTF16(hostname, aHost);
   return NS_OK;
 }
 
-void
+bool
 nsContentUtils::CallOnAllRemoteChildren(nsIMessageBroadcaster* aManager,
                                         CallOnRemoteChildFunction aCallback,
                                         void* aArg)
 {
   uint32_t tabChildCount = 0;
   aManager->GetChildCount(&tabChildCount);
   for (uint32_t j = 0; j < tabChildCount; ++j) {
     nsCOMPtr<nsIMessageListenerManager> childMM;
     aManager->GetChildAt(j, getter_AddRefs(childMM));
     if (!childMM) {
       continue;
     }
 
     nsCOMPtr<nsIMessageBroadcaster> nonLeafMM = do_QueryInterface(childMM);
     if (nonLeafMM) {
-      CallOnAllRemoteChildren(nonLeafMM, aCallback, aArg);
+      if (CallOnAllRemoteChildren(nonLeafMM, aCallback, aArg)) {
+        return true;
+      }
       continue;
     }
 
     nsCOMPtr<nsIMessageSender> tabMM = do_QueryInterface(childMM);
 
     mozilla::dom::ipc::MessageManagerCallback* cb =
      static_cast<nsFrameMessageManager*>(tabMM.get())->GetCallback();
     if (cb) {
       nsFrameLoader* fl = static_cast<nsFrameLoader*>(cb);
       TabParent* remote = TabParent::GetFrom(fl);
       if (remote && aCallback) {
-        aCallback(remote, aArg);
+        if (aCallback(remote, aArg)) {
+          return true;
+        }
       }
     }
   }
+
+  return false;
 }
 
 void
 nsContentUtils::CallOnAllRemoteChildren(nsPIDOMWindowOuter* aWindow,
                                         CallOnRemoteChildFunction aCallback,
                                         void* aArg)
 {
   nsCOMPtr<nsIDOMChromeWindow> chromeWindow(do_QueryInterface(aWindow));
@@ -7270,24 +7276,21 @@ nsContentUtils::DataTransferItemToImage(
   const IPCDataTransferImage& imageDetails = aItem.imageDetails();
   const IntSize size(imageDetails.width(), imageDetails.height());
   if (!size.width || !size.height) {
     return NS_ERROR_FAILURE;
   }
 
   const nsCString& text = aItem.data().get_nsCString();
 
-  // InitWrappingData takes a non-const pointer for reading.
-  nsCString& nonConstText = const_cast<nsCString&>(text);
-  RefPtr<SourceSurfaceRawData> image = new SourceSurfaceRawData();
-  image->InitWrappingData(reinterpret_cast<uint8_t*>(nonConstText.BeginWriting()),
-                          size, imageDetails.stride(),
-                          static_cast<SurfaceFormat>(imageDetails.format()),
-                          false);
-  image->GuaranteePersistance();
+  RefPtr<DataSourceSurface> image =
+      CreateDataSourceSurfaceFromData(size,
+                                      static_cast<SurfaceFormat>(imageDetails.format()),
+                                      reinterpret_cast<const uint8_t*>(text.BeginReading()),
+                                      imageDetails.stride());
 
   RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(image, size);
   nsCOMPtr<imgIContainer> imageContainer =
     image::ImageOps::CreateFromDrawable(drawable);
   imageContainer.forget(aContainer);
 
   return NS_OK;
 }
@@ -7779,26 +7782,26 @@ nsContentUtils::SendMouseEvent(nsCOMPtr<
   WidgetMouseEvent event(true, msg, widget, WidgetMouseEvent::eReal,
                          contextMenuKey ? WidgetMouseEvent::eContextMenuKey :
                                           WidgetMouseEvent::eNormal);
   event.mModifiers = GetWidgetModifiers(aModifiers);
   event.button = aButton;
   event.buttons = GetButtonsFlagForButton(aButton);
   event.pressure = aPressure;
   event.inputSource = aInputSourceArg;
-  event.clickCount = aClickCount;
+  event.mClickCount = aClickCount;
   event.mTime = PR_IntervalNow();
   event.mFlags.mIsSynthesizedForTests = aIsSynthesized;
 
   nsPresContext* presContext = aPresShell->GetPresContext();
   if (!presContext)
     return NS_ERROR_FAILURE;
 
   event.mRefPoint = ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
-  event.ignoreRootScrollFrame = aIgnoreRootScrollFrame;
+  event.mIgnoreRootScrollFrame = aIgnoreRootScrollFrame;
 
   nsEventStatus status = nsEventStatus_eIgnore;
   if (aToWindow) {
     nsCOMPtr<nsIPresShell> presShell;
     nsView* view = GetViewToDispatchEvent(presContext, getter_AddRefs(presShell));
     if (!presShell || !view) {
       return NS_ERROR_FAILURE;
     }
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -166,17 +166,17 @@ struct EventNameMapping
   // This holds pointers to nsGkAtoms members, and is therefore safe as a
   // non-owning reference.
   nsIAtom* MOZ_NON_OWNING_REF mAtom;
   int32_t  mType;
   mozilla::EventMessage mMessage;
   mozilla::EventClassID mEventClassID;
 };
 
-typedef void (*CallOnRemoteChildFunction) (mozilla::dom::TabParent* aTabParent,
+typedef bool (*CallOnRemoteChildFunction) (mozilla::dom::TabParent* aTabParent,
                                            void* aArg);
 
 class nsContentUtils
 {
   friend class nsAutoScriptBlockerSuppressNodeRemoved;
   typedef mozilla::dom::Element Element;
   typedef mozilla::TimeDuration TimeDuration;
 
@@ -2374,17 +2374,17 @@ public:
    * If the hostname for aURI is an IPv6 it encloses it in brackets,
    * otherwise it just outputs the hostname in aHost.
    */
   static nsresult GetHostOrIPv6WithBrackets(nsIURI* aURI, nsAString& aHost);
   static nsresult GetHostOrIPv6WithBrackets(nsIURI* aURI, nsCString& aHost);
 
   /*
    * Call the given callback on all remote children of the given top-level
-   * window.
+   * window. Return true from the callback to stop calling further children.
    */
   static void CallOnAllRemoteChildren(nsPIDOMWindowOuter* aWindow,
                                       CallOnRemoteChildFunction aCallback,
                                       void* aArg);
 
   /**
    * Given an nsIFile, attempts to read it into aString.
    *
@@ -2593,17 +2593,17 @@ private:
   static void DestroyClassNameArray(void* aData);
   static void* AllocClassMatchingInfo(nsINode* aRootNode,
                                       const nsString* aClasses);
 
   // Fills in aInfo with the tokens from the supplied autocomplete attribute.
   static AutocompleteAttrState InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrVal,
                                                                       mozilla::dom::AutocompleteInfo& aInfo);
 
-  static void CallOnAllRemoteChildren(nsIMessageBroadcaster* aManager,
+  static bool CallOnAllRemoteChildren(nsIMessageBroadcaster* aManager,
                                       CallOnRemoteChildFunction aCallback,
                                       void* aArg);
 
   /*
    * Checks if storage for a given principal is permitted by the user's
    * preferences. If aWindow is non-null, its principal must be passed as
    * aPrincipal, and the third-party iframe and sandboxing status of the window
    * are also checked.
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -678,28 +678,28 @@ nsDOMWindowUtils::SendPointerEventCommon
   event.pressure = aPressure;
   event.inputSource = aInputSourceArg;
   event.pointerId = aPointerId;
   event.width = aWidth;
   event.height = aHeight;
   event.tiltX = aTiltX;
   event.tiltY = aTiltY;
   event.isPrimary = (nsIDOMMouseEvent::MOZ_SOURCE_MOUSE == aInputSourceArg) ? true : aIsPrimary;
-  event.clickCount = aClickCount;
+  event.mClickCount = aClickCount;
   event.mTime = PR_IntervalNow();
   event.mFlags.mIsSynthesizedForTests = aOptionalArgCount >= 10 ? aIsSynthesized : true;
 
   nsPresContext* presContext = GetPresContext();
   if (!presContext) {
     return NS_ERROR_FAILURE;
   }
 
   event.mRefPoint =
     nsContentUtils::ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
-  event.ignoreRootScrollFrame = aIgnoreRootScrollFrame;
+  event.mIgnoreRootScrollFrame = aIgnoreRootScrollFrame;
 
   nsEventStatus status;
   if (aToWindow) {
     nsCOMPtr<nsIPresShell> presShell;
     nsView* view = nsContentUtils::GetViewToDispatchEvent(presContext, getter_AddRefs(presShell));
     if (!presShell || !view) {
       return NS_ERROR_FAILURE;
     }
--- a/dom/base/nsDeprecatedOperationList.h
+++ b/dom/base/nsDeprecatedOperationList.h
@@ -39,8 +39,9 @@ DEPRECATED_OPERATION(SyncXMLHttpRequest)
 DEPRECATED_OPERATION(DataContainerEvent)
 DEPRECATED_OPERATION(Window_Controllers)
 DEPRECATED_OPERATION(ImportXULIntoContent)
 DEPRECATED_OPERATION(PannerNodeDoppler)
 DEPRECATED_OPERATION(NavigatorGetUserMedia)
 DEPRECATED_OPERATION(WebrtcDeprecatedPrefix)
 DEPRECATED_OPERATION(AppCache)
 DEPRECATED_OPERATION(PrefixedFullscreenAPI)
+DEPRECATED_OPERATION(LenientSetter)
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -12162,17 +12162,17 @@ nsDocument::GetFullscreenElement()
                element->IsFullScreenAncestor(),
     "Fullscreen element should have fullscreen styles applied");
   return element;
 }
 
 NS_IMETHODIMP
 nsDocument::GetMozFullScreen(bool *aFullScreen)
 {
-  *aFullScreen = MozFullScreen();
+  *aFullScreen = Fullscreen();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocument::GetMozFullScreenEnabled(bool *aFullScreen)
 {
   NS_ENSURE_ARG_POINTER(aFullScreen);
   *aFullScreen = FullscreenEnabled();
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -1105,21 +1105,22 @@ nsFocusManager::EnsureCurrentWidgetFocus
         vm->GetRootWidget(getter_AddRefs(widget));
         if (widget)
           widget->SetFocus(false);
       }
     }
   }
 }
 
-void
+bool
 ActivateOrDeactivateChild(TabParent* aParent, void* aArg)
 {
   bool active = static_cast<bool>(aArg);
   Unused << aParent->SendParentActivated(active);
+  return false;
 }
 
 void
 nsFocusManager::ActivateOrDeactivate(nsPIDOMWindowOuter* aWindow, bool aActive)
 {
   if (!aWindow) {
     return;
   }
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -677,16 +677,17 @@ GK_ATOM(objectType, "object-type")
 GK_ATOM(observer, "observer")
 GK_ATOM(observes, "observes")
 GK_ATOM(odd, "odd")
 GK_ATOM(OFF, "OFF")
 GK_ATOM(ol, "ol")
 GK_ATOM(omitXmlDeclaration, "omit-xml-declaration")
 GK_ATOM(ona2dpstatuschanged, "ona2dpstatuschanged")
 GK_ATOM(onabort, "onabort")
+GK_ATOM(onmozaccesskeynotfound, "onmozaccesskeynotfound")
 GK_ATOM(onactivate, "onactivate")
 GK_ATOM(onadapteradded, "onadapteradded")
 GK_ATOM(onadapterremoved, "onadapterremoved")
 GK_ATOM(onafterprint, "onafterprint")
 GK_ATOM(onafterscriptexecute, "onafterscriptexecute")
 GK_ATOM(onalerting, "onalerting")
 GK_ATOM(onanimationend, "onanimationend")
 GK_ATOM(onanimationiteration, "onanimationiteration")
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -111,17 +111,16 @@
 #include "nsIPromptService.h"
 #include "nsIPromptFactory.h"
 #include "nsIWritablePropertyBag2.h"
 #include "nsIWebNavigation.h"
 #include "nsIWebBrowserChrome.h"
 #include "nsIWebBrowserFind.h"  // For window.find()
 #include "nsIWindowMediator.h"  // For window.find()
 #include "nsComputedDOMStyle.h"
-#include "nsIEntropyCollector.h"
 #include "nsDOMCID.h"
 #include "nsDOMWindowUtils.h"
 #include "nsIWindowWatcher.h"
 #include "nsPIWindowWatcher.h"
 #include "nsIContentViewer.h"
 #include "nsIScriptError.h"
 #include "nsIControllers.h"
 #include "nsIControllerContext.h"
@@ -272,17 +271,16 @@ using mozilla::TimeDuration;
 using mozilla::dom::cache::CacheStorage;
 
 static LazyLogModule gDOMLeakPRLog("DOMLeak");
 
 nsGlobalWindow::WindowByIdTable *nsGlobalWindow::sWindowsById = nullptr;
 bool nsGlobalWindow::sWarnedAboutWindowInternal = false;
 bool nsGlobalWindow::sIdleObserversAPIFuzzTimeDisabled = false;
 
-static nsIEntropyCollector *gEntropyCollector          = nullptr;
 static int32_t              gRefCnt                    = 0;
 static int32_t              gOpenPopupSpamCount        = 0;
 static PopupControlState    gPopupControlState         = openAbused;
 static int32_t              gRunningTimeoutDepth       = 0;
 static bool                 gMouseDown                 = false;
 static bool                 gDragServiceDisabled       = false;
 static FILE                *gDumpFile                  = nullptr;
 static uint32_t             gSerialCounter             = 0;
@@ -1333,20 +1331,16 @@ nsGlobalWindow::AssertIsOnMainThread()
 #endif // DEBUG
 
 /* static */
 void
 nsGlobalWindow::Init()
 {
   AssertIsOnMainThread();
 
-  CallGetService(NS_ENTROPYCOLLECTOR_CONTRACTID, &gEntropyCollector);
-  NS_ASSERTION(gEntropyCollector,
-               "gEntropyCollector should have been initialized!");
-
   NS_ASSERTION(gDOMLeakPRLog, "gDOMLeakPRLog should have been initialized!");
 
   sWindowsById = new WindowByIdTable();
 }
 
 nsGlobalWindow::~nsGlobalWindow()
 {
   AssertIsOnMainThread();
@@ -1479,18 +1473,16 @@ nsGlobalWindow::ShutDown()
 {
   AssertIsOnMainThread();
 
   if (gDumpFile && gDumpFile != stdout) {
     fclose(gDumpFile);
   }
   gDumpFile = nullptr;
 
-  NS_IF_RELEASE(gEntropyCollector);
-
   delete sWindowsById;
   sWindowsById = nullptr;
 }
 
 // static
 void
 nsGlobalWindow::CleanupCachedXBLHandlers(nsGlobalWindow* aWindow)
 {
@@ -3312,37 +3304,21 @@ nsGlobalWindow::GetJSContextForEventHand
 {
   return nullptr;
 }
 
 nsresult
 nsGlobalWindow::PreHandleEvent(EventChainPreVisitor& aVisitor)
 {
   NS_PRECONDITION(IsInnerWindow(), "PreHandleEvent is used on outer window!?");
-  static uint32_t count = 0;
   EventMessage msg = aVisitor.mEvent->mMessage;
 
   aVisitor.mCanHandle = true;
   aVisitor.mForceContentDispatch = true; //FIXME! Bug 329119
-  if (msg == eMouseMove && gEntropyCollector) {
-    //Chances are this counter will overflow during the life of the
-    //process, but that's OK for our case. Means we get a little
-    //more entropy.
-    if (count++ % 100 == 0) {
-      //Since the high bits seem to be zero's most of the time,
-      //let's only take the lowest half of the point structure.
-      int16_t myCoord[2];
-
-      myCoord[0] = aVisitor.mEvent->mRefPoint.x;
-      myCoord[1] = aVisitor.mEvent->mRefPoint.y;
-      gEntropyCollector->RandomUpdate((void*)myCoord, sizeof(myCoord));
-      gEntropyCollector->RandomUpdate((void*)&(aVisitor.mEvent->mTime),
-                                      sizeof(uint32_t));
-    }
-  } else if (msg == eResize && aVisitor.mEvent->IsTrusted()) {
+  if (msg == eResize && aVisitor.mEvent->IsTrusted()) {
     // QIing to window so that we can keep the old behavior also in case
     // a child window is handling resize.
     nsCOMPtr<nsPIDOMWindowInner> window =
       do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
     if (window) {
       mIsHandlingResizeEvent = true;
     }
   } else if (msg == eMouseDown && aVisitor.mEvent->IsTrusted()) {
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2555,17 +2555,17 @@ public:
   Element* GetCurrentScript();
   void ReleaseCapture() const;
   virtual void MozSetImageElement(const nsAString& aImageElementId,
                                   Element* aElement) = 0;
   nsIURI* GetDocumentURIObject() const;
   // Not const because all the full-screen goop is not const
   virtual bool FullscreenEnabled() = 0;
   virtual Element* GetFullscreenElement() = 0;
-  bool MozFullScreen()
+  bool Fullscreen()
   {
     return !!GetFullscreenElement();
   }
   void ExitFullscreen();
   Element* GetMozPointerLockElement();
   void MozExitPointerLock()
   {
     UnlockPointer(this);
deleted file mode 100644
--- a/dom/base/nsIEntropyCollector.idl
+++ /dev/null
@@ -1,35 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/* 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 "nsISupports.idl"
-
-/**
- * Interface for an object that wants to gather "random"
- * data to be used for entropy purposes.
- */
-%{C++
-
-/*
- * If anyone wants to collect the entropy distributed by the
- * event handler, they'll have to implement this CONTRACTID
- */
-#define NS_ENTROPYCOLLECTOR_CONTRACTID "@mozilla.org/security/entropy;1"
-%}
-/* Buffer type - for passing random data to the entropy
- * collector.
- */
-[ptr] native buffer(void);
-
-[uuid(6f883680-ab9d-11d4-9978-00b0d02354a0)]
-interface nsIEntropyCollector : nsISupports
-{
-
-  /**
-   * Add the following bytes to the pool of data to be used
-   * in gathering entropy.
-   */
-  void randomUpdate(in buffer entropy, in long bufLen); 
-
-};
--- a/dom/base/nsIImageLoadingContent.idl
+++ b/dom/base/nsIImageLoadingContent.idl
@@ -175,19 +175,21 @@ interface nsIImageLoadingContent : imgIN
    */
   readonly attribute unsigned long    naturalWidth;
   readonly attribute unsigned long    naturalHeight;
 
   /**
    * Called by layout to announce when the frame associated with this content
    * has changed its visibility state.
    *
+   * @param aOldVisibility    The previous visibility state.
    * @param aNewVisibility    The new visibility state.
    * @param aNonvisibleAction A requested action if the frame has become
    *                          nonvisible. If Nothing(), no action is
    *                          requested. If DISCARD_IMAGES is specified, the
    *                          frame is requested to ask any images it's
    *                          associated with to discard their surfaces if
    *                          possible.
    */
-  [noscript, notxpcom] void onVisibilityChange(in Visibility aNewVisibility,
+  [noscript, notxpcom] void onVisibilityChange(in Visibility aOldVisibility,
+                                               in Visibility aNewVisibility,
                                                in MaybeOnNonvisible aNonvisibleAction);
 };
--- a/dom/base/nsImageLoadingContent.cpp
+++ b/dom/base/nsImageLoadingContent.cpp
@@ -286,17 +286,17 @@ nsImageLoadingContent::OnUnlockedDraw()
     return;
   }
 
   nsIPresShell* presShell = presContext->PresShell();
   if (!presShell) {
     return;
   }
 
-  presShell->MarkFrameVisibleInDisplayPort(frame);
+  presShell->MarkFrameVisible(frame, VisibilityCounter::IN_DISPLAYPORT);
 }
 
 nsresult
 nsImageLoadingContent::OnImageIsAnimated(imgIRequest *aRequest)
 {
   bool* requestFlag = GetRegisteredFlagForRequest(aRequest);
   if (requestFlag) {
     nsLayoutUtils::RegisterImageRequest(GetFramePresContext(),
@@ -1420,24 +1420,27 @@ nsImageLoadingContent::UnbindFromTree(bo
   UntrackImage(mCurrentRequest);
   UntrackImage(mPendingRequest);
 
   if (mCurrentRequestFlags & REQUEST_BLOCKS_ONLOAD)
     doc->UnblockOnload(false);
 }
 
 void
-nsImageLoadingContent::OnVisibilityChange(Visibility aNewVisibility,
+nsImageLoadingContent::OnVisibilityChange(Visibility aOldVisibility,
+                                          Visibility aNewVisibility,
                                           const Maybe<OnNonvisible>& aNonvisibleAction)
 {
   switch (aNewVisibility) {
     case Visibility::MAY_BECOME_VISIBLE:
     case Visibility::IN_DISPLAYPORT:
-      TrackImage(mCurrentRequest);
-      TrackImage(mPendingRequest);
+      if (aOldVisibility == Visibility::NONVISIBLE) {
+        TrackImage(mCurrentRequest);
+        TrackImage(mPendingRequest);
+      }
       break;
 
     case Visibility::NONVISIBLE:
       UntrackImage(mCurrentRequest, aNonvisibleAction);
       UntrackImage(mPendingRequest, aNonvisibleAction);
       break;
 
     case Visibility::UNTRACKED:
--- a/dom/base/test/img_referrer_testserver.sjs
+++ b/dom/base/test/img_referrer_testserver.sjs
@@ -1,15 +1,20 @@
 var BASE_URL = 'example.com/tests/dom/base/test/img_referrer_testserver.sjs';
+const IMG_BYTES = atob(
+  "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
+  "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==");
 
-function createTestUrl(aPolicy, aAction, aName) {
+function createTestUrl(aPolicy, aAction, aName, aContent) {
+  var content = aContent || 'text';
   return 'http://' + BASE_URL + '?' +
          'action=' + aAction + '&' +
          'policy=' + aPolicy + '&' +
-         'name=' + aName;
+         'name=' + aName + '&' +
+         'content=' + content;
 }
 
 function createTestPage(aHead, aImgPolicy, aName) {
   var _createTestUrl = createTestUrl.bind(null, aImgPolicy, 'test', aName);
 
   return '<!DOCTYPE HTML>\n\
          <html>'+
             aHead +
@@ -83,16 +88,52 @@ function createTestPage2(aHead, aPolicy,
                  parent.postMessage("childLoadComplete", "http://mochi.test:8888");\n\
                }.bind(window), false);' +
 
              '</script>\n\
            </body>\n\
          </html>';
 }
 
+function createTestPage3(aPolicy, aName) {
+  return '<!DOCTYPE HTML>\n\
+         <html>\n\
+           <body>\n\
+             <script>' +
+               'var image = new Image();\n\
+               image.src = "' + createTestUrl(aPolicy, "test", aName, "image") + '";\n\
+               image.referrerPolicy = "' + aPolicy + '";\n\
+               image.onload = function() {\n\
+                 window.parent.postMessage("childLoadComplete", "http://mochi.test:8888");\n\
+               }\n\
+               document.body.appendChild(image);' +
+
+             '</script>\n\
+           </body>\n\
+         </html>';
+}
+
+function createTestPage4(aPolicy, aName) {
+  return '<!DOCTYPE HTML>\n\
+         <html>\n\
+           <body>\n\
+             <script>' +
+               'var image = new Image();\n\
+               image.referrerPolicy = "' + aPolicy + '";\n\
+               image.src = "' + createTestUrl(aPolicy, "test", aName, "image") + '";\n\
+               image.onload = function() {\n\
+                 window.parent.postMessage("childLoadComplete", "http://mochi.test:8888");\n\
+               }\n\
+               document.body.appendChild(image);' +
+
+             '</script>\n\
+           </body>\n\
+         </html>';
+}
+
 function createTest4(aPolicy, aName) {
   var headString = '<head>';
   headString += '<meta name="referrer" content="' + aPolicy + '">';
   headString += '<script></script>';
 
   return createTestPage2(headString, aPolicy, aName);
 }
 
@@ -114,19 +155,20 @@ function handleRequest(request, response
   if (action === 'resetState') {
     var state = getSharedState(sharedKey);
     state = {};
     setSharedState(sharedKey, JSON.stringify(state));
     response.write("");
     return;
   }
   if (action === 'test') {
-    // ?action=test&policy=origin&name=name
+    // ?action=test&policy=origin&name=name&content=content
     var policy = params[1].split('=')[1];
     var name = params[2].split('=')[1];
+    var content = params[3].split('=')[1];
     var result = getSharedState(sharedKey);
 
     if (result === '') {
       result = {};
     } else {
       result = JSON.parse(result);
     }
 
@@ -148,16 +190,21 @@ function handleRequest(request, response
       test.referrer = '';
     }
     test.policy = referrerLevel;
     test.expected = policy;
 
     result["tests"][name] = test;
 
     setSharedState(sharedKey, JSON.stringify(result));
+
+    if (content === 'image') {
+      response.setHeader("Content-Type", "image/png");
+      response.write(IMG_BYTES);
+    }
     return;
   }
   if (action === 'get-test-results') {
     // ?action=get-result
     response.write(getSharedState(sharedKey));
     return;
   }
   if (action === 'generate-img-policy-test') {
@@ -202,11 +249,29 @@ function handleRequest(request, response
     // ?action=generate-img-policy-test5&policy=b64-encoded-string&name=name
     var policy = unescape(params[1].split('=')[1]);
     var name = unescape(params[2].split('=')[1]);
 
     response.write(createTest5(policy, name));
     return;
   }
 
+  if (action === 'generate-setAttribute-test1') {
+    // ?action=generate-setAttribute-test1&policy=b64-encoded-string&name=name
+    var policy = unescape(params[1].split('=')[1]);
+    var name = unescape(params[2].split('=')[1]);
+
+    response.write(createTestPage3(policy, name));
+    return;
+  }
+
+  if (action === 'generate-setAttribute-test2') {
+    // ?action=generate-setAttribute-test2&policy=b64-encoded-string&name=name
+    var policy = unescape(params[1].split('=')[1]);
+    var name = unescape(params[2].split('=')[1]);
+
+    response.write(createTestPage4(policy, name));
+    return;
+  }
+
   response.write("I don't know action "+action);
   return;
 }
--- a/dom/base/test/test_bug116083.html
+++ b/dom/base/test/test_bug116083.html
@@ -47,42 +47,49 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div data-result="&#10;foo bar&#10;">foo  bar</div>
 </div>
 <script type="application/javascript">
 
 const Cc = SpecialPowers.Cc;
 const Ci = SpecialPowers.Ci;
 
 function hasExpectedFlavors() {
-  var flavors = [
-    "text/plain",
-    "text/html",
-    "application/x-moz-nativehtml",
-  ];
   var cb = Cc["@mozilla.org/widget/clipboard;1"].
            getService(Ci.nsIClipboard);
-  return cb.hasDataMatchingFlavors(flavors, flavors.length,
-                                   cb.kGlobalClipboard);
+
+  ok(cb.hasDataMatchingFlavors(["text/unicode"], 1, cb.kGlobalClipboard),
+     "The clipboard has text/unicode");
+
+  // Android only supports plain text
+  if (navigator.appVersion.indexOf("Android") == -1) {
+    ok(cb.hasDataMatchingFlavors(["text/html"], 1, cb.kGlobalClipboard),
+       "The clipboard has text/html");
+  }
+
+  if (navigator.appVersion.indexOf("Win") >= 0) {
+    ok(cb.hasDataMatchingFlavors(["application/x-moz-nativehtml"], 1, cb.kGlobalClipboard),
+       "The clipboard has application/x-moz-nativehtml");
+  }
 }
 
 function nextTest() {
   var div = document.querySelector("#content>div");
   if (!div) {
     SimpleTest.finish();
     return;
   }
   getSelection().selectAllChildren(div);
   var expected = div.hasAttribute("data-result") ?
                  div.getAttribute("data-result") :
                  div.textContent;
   SimpleTest.waitForClipboard(expected, function() {
     synthesizeKey("C", {accelKey: true});
   }, function() {
     ok(true, div.getAttribute("style") + " passed");
-    ok(hasExpectedFlavors(), "The clipboard has the expected flavors");
+    hasExpectedFlavors();
     div.parentNode.removeChild(div);
     nextTest();
   }, function() {
     ok(false, "failed to copy the expected content to the clipboard");
     SimpleTest.finish();
   });
 }
 
--- a/dom/base/test/test_img_referrer.html
+++ b/dom/base/test/test_img_referrer.html
@@ -16,29 +16,23 @@ Testing that img referrer attribute is h
 https://bugzilla.mozilla.org/show_bug.cgi?id=1166910
 -->
 
 <script type="application/javascript;version=1.7">
 
 SimpleTest.waitForExplicitFinish();
 var advance = function() { tests.next(); };
 
-var mTestResult;
-
 /**
  * Listen for notifications from the child.
  * These are sent in case of error, or when the loads we await have completed.
  */
 window.addEventListener("message", function(event) {
-  if (event.data == "childLoadComplete") {
-    // all loads happen, continue the test.
-    advance();
-  }
-  else if (event.data.contains("childLoadComplete")) {
-    mTestResult = event.data.split(",")[1];
+  if (event.data == "childLoadComplete" ||
+      event.data.contains("childLoadComplete")) {
     advance();
   }
 });
 
 /**
  * helper to perform an XHR.
  */
 function doXHR(aUrl, onSuccess, onFail) {
@@ -58,24 +52,21 @@ function doXHR(aUrl, onSuccess, onFail) 
  * Grabs the results via XHR and passes to checker.
  */
 function checkIndividualResults(aTestname, aExpectedImg, aName) {
   doXHR('/tests/dom/base/test/img_referrer_testserver.sjs?action=get-test-results',
         function(xhr) {
           var results = xhr.response;
           info(JSON.stringify(xhr.response));
 
-          if (aName === 'setAttribute') {
-            is(mTestResult, aExpectedImg, aTestname + ' --- ' + mTestResult);
-          } else {
-            for (i in aName) {
-              ok(aName[i] in results.tests, aName[i] + " tests have to be performed.");
-              is(results.tests[aName[i]].policy, aExpectedImg[i], aTestname + ' --- ' + results.tests[aName[i]].policy + ' (' + results.tests[aName[i]].referrer + ')');
-            }
+          for (i in aName) {
+            ok(aName[i] in results.tests, aName[i] + " tests have to be performed.");
+            is(results.tests[aName[i]].policy, aExpectedImg[i], aTestname + ' --- ' + results.tests[aName[i]].policy + ' (' + results.tests[aName[i]].referrer + ')');
           }
+
           advance();
         },
         function(xhr) {
           ok(false, "Can't get results from the counter server.");
           SimpleTest.finish();
         });
 }
 
@@ -151,16 +142,29 @@ var tests = (function() {
   yield checkIndividualResults("no-referrer in meta (no img referrer policy), speculative load", ["none"], [name]);
 
   yield resetState();
   sjs = "/tests/dom/base/test/img_referrer_testserver.sjs?action=generate-img-policy-test5";
   name = 'regular-load-no-referrer-meta';
   yield iframe.src = sjs + "&policy=" + escape('no-referrer') + "&name=" + name;
   yield checkIndividualResults("no-referrer in meta (no img referrer policy), regular load", ["none"], [name]);
 
+  //test setAttribute
+  yield resetState();
+  sjs = "/tests/dom/base/test/img_referrer_testserver.sjs?action=generate-setAttribute-test1";
+  name = 'set-referrer-policy-attribute-before-src';
+  yield iframe.src = sjs + "&policy=" + escape('no-referrer') + "&name=" + name;
+  yield checkIndividualResults("no-referrer in img", ["none"], [name]);
+
+  yield resetState();
+  sjs = "/tests/dom/base/test/img_referrer_testserver.sjs?action=generate-setAttribute-test2";
+  name = 'set-referrer-policy-attribute-after-src';
+  yield iframe.src = sjs + "&policy=" + escape('no-referrer') + "&name=" + name;
+  yield checkIndividualResults("no-referrer in img", ["none"], [name]);
+
   // complete.  Be sure to yield so we don't call this twice.
   yield SimpleTest.finish();
 })();
 
 </script>
 </head>
 
 <body onload="tests.next();">
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -2543,17 +2543,18 @@ class AttrDefiner(PropertyDefiner):
                 jitinfo = ("&%s_getterinfo" %
                            IDLToCIdentifier(attr.identifier.name))
             return "{ { %s, %s } }" % \
                    (accessor, jitinfo)
 
         def setter(attr):
             if (attr.readonly and
                 attr.getExtendedAttribute("PutForwards") is None and
-                attr.getExtendedAttribute("Replaceable") is None):
+                attr.getExtendedAttribute("Replaceable") is None and
+                attr.getExtendedAttribute("LenientSetter") is None):
                 return "JSNATIVE_WRAPPER(nullptr)"
             if self.static:
                 accessor = 'set_' + IDLToCIdentifier(attr.identifier.name)
                 jitinfo = "nullptr"
             else:
                 if attr.hasLenientThis():
                     accessor = "genericLenientSetter"
                 elif IsCrossOriginWritable(attr, self.descriptor):
@@ -8827,16 +8828,34 @@ class CGSpecializedReplaceableSetter(CGS
     def definition_body(self):
         attrName = self.attr.identifier.name
         # JS_DefineProperty can only deal with ASCII
         assert all(ord(c) < 128 for c in attrName)
         return ('return JS_DefineProperty(cx, obj, "%s", args[0], JSPROP_ENUMERATE);\n' %
                 attrName)
 
 
+class CGSpecializedLenientSetter(CGSpecializedSetter):
+    """
+    A class for generating the code for a specialized attribute setter with
+    LenientSetter that the JIT can call with lower overhead.
+    """
+    def __init__(self, descriptor, attr):
+        CGSpecializedSetter.__init__(self, descriptor, attr)
+
+    def definition_body(self):
+        attrName = self.attr.identifier.name
+        # JS_DefineProperty can only deal with ASCII
+        assert all(ord(c) < 128 for c in attrName)
+        return dedent("""
+            DeprecationWarning(cx, obj, nsIDocument::eLenientSetter);
+            return true;
+            """)
+
+
 def memberReturnsNewObject(member):
     return member.getExtendedAttribute("NewObject") is not None
 
 
 class CGMemberJITInfo(CGThing):
     """
     A class for generating the JITInfo for a property that points to
     our specialized getter and setter.
@@ -8964,17 +8983,18 @@ class CGMemberJITInfo(CGThing):
 
             result = self.defineJitInfo(getterinfo, getter, "Getter",
                                         getterinfal, movable, eliminatable,
                                         aliasSet, isAlwaysInSlot,
                                         isLazilyCachedInSlot, slotIndex,
                                         [self.member.type], None)
             if (not self.member.readonly or
                 self.member.getExtendedAttribute("PutForwards") is not None or
-                self.member.getExtendedAttribute("Replaceable") is not None):
+                self.member.getExtendedAttribute("Replaceable") is not None or
+                self.member.getExtendedAttribute("LenientSetter") is not None):
                 setterinfo = ("%s_setterinfo" %
                               IDLToCIdentifier(self.member.identifier.name))
                 # Actually a JSJitSetterOp, but JSJitGetterOp is first in the
                 # union.
                 setter = ("(JSJitGetterOp)set_%s" %
                           IDLToCIdentifier(self.member.identifier.name))
                 # Setters are always fallible, since they have to do a typed unwrap.
                 result += self.defineJitInfo(setterinfo, setter, "Setter",
@@ -11833,17 +11853,18 @@ def memberProperties(m, descriptor):
                     props.isCrossOriginSetter = True
                 elif descriptor.needsSpecialGenericOps():
                     props.isGenericSetter = True
         elif m.getExtendedAttribute("PutForwards"):
             if IsCrossOriginWritable(m, descriptor):
                 props.isCrossOriginSetter = True
             elif descriptor.needsSpecialGenericOps():
                 props.isGenericSetter = True
-        elif m.getExtendedAttribute("Replaceable"):
+        elif (m.getExtendedAttribute("Replaceable") or
+              m.getExtendedAttribute("LenientSetter")):
             if descriptor.needsSpecialGenericOps():
                 props.isGenericSetter = True
 
     return props
 
 
 class CGDescriptor(CGThing):
     def __init__(self, descriptor):
@@ -11945,16 +11966,18 @@ class CGDescriptor(CGThing):
                         if props.isCrossOriginSetter:
                             crossOriginSetters.add(m.identifier.name)
                 elif m.getExtendedAttribute("PutForwards"):
                     cgThings.append(CGSpecializedForwardingSetter(descriptor, m))
                     if props.isCrossOriginSetter:
                         crossOriginSetters.add(m.identifier.name)
                 elif m.getExtendedAttribute("Replaceable"):
                     cgThings.append(CGSpecializedReplaceableSetter(descriptor, m))
+                elif m.getExtendedAttribute("LenientSetter"):
+                    cgThings.append(CGSpecializedLenientSetter(descriptor, m))
                 if (not m.isStatic() and
                     descriptor.interface.hasInterfacePrototypeObject()):
                     cgThings.append(CGMemberJITInfo(descriptor, m))
 
             hasMethod = hasMethod or props.isGenericMethod
             hasPromiseReturningMethod = (hasPromiseReturningMethod or
                                          props.isPromiseReturningMethod)
             hasGetter = hasGetter or props.isGenericGetter
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -4075,16 +4075,34 @@ class IDLAttribute(IDLInterfaceMember):
                                   "attributes", [attr.location, self.location])
             if self.isStatic():
                 raise WebIDLError("[Replaceable] is only allowed on non-static "
                                   "attributes", [attr.location, self.location])
             if self.getExtendedAttribute("PutForwards") is not None:
                 raise WebIDLError("[PutForwards] and [Replaceable] can't both "
                                   "appear on the same attribute",
                                   [attr.location, self.location])
+        elif identifier == "LenientSetter":
+            if not attr.noArguments():
+                raise WebIDLError("[LenientSetter] must take no arguments",
+                                  [attr.location])
+            if not self.readonly:
+                raise WebIDLError("[LenientSetter] is only allowed on readonly "
+                                  "attributes", [attr.location, self.location])
+            if self.isStatic():
+                raise WebIDLError("[LenientSetter] is only allowed on non-static "
+                                  "attributes", [attr.location, self.location])
+            if self.getExtendedAttribute("PutForwards") is not None:
+                raise WebIDLError("[LenientSetter] and [PutForwards] can't both "
+                                  "appear on the same attribute",
+                                  [attr.location, self.location])
+            if self.getExtendedAttribute("Replaceable") is not None:
+                raise WebIDLError("[LenientSetter] and [Replaceable] can't both "
+                                  "appear on the same attribute",
+                                  [attr.location, self.location])
         elif identifier == "LenientFloat":
             if self.readonly:
                 raise WebIDLError("[LenientFloat] used on a readonly attribute",
                                   [attr.location, self.location])
             if not self.type.includesRestrictedFloat():
                 raise WebIDLError("[LenientFloat] used on an attribute with a "
                                   "non-restricted-float type",
                                   [attr.location, self.location])
@@ -4813,16 +4831,19 @@ class IDLMethod(IDLInterfaceMember, IDLS
             raise WebIDLError("Methods must not be flagged as [SameObject]",
                               [attr.location, self.location])
         elif identifier == "Constant":
             raise WebIDLError("Methods must not be flagged as [Constant]",
                               [attr.location, self.location])
         elif identifier == "PutForwards":
             raise WebIDLError("Only attributes support [PutForwards]",
                               [attr.location, self.location])
+        elif identifier == "LenientSetter":
+            raise WebIDLError("Only attributes support [LenientSetter]",
+                              [attr.location, self.location])
         elif identifier == "LenientFloat":
             # This is called before we've done overload resolution
             assert len(self.signatures()) == 1
             sig = self.signatures()[0]
             if not sig[0].isVoid():
                 raise WebIDLError("[LenientFloat] used on a non-void method",
                                   [attr.location, self.location])
             if not any(arg.type.includesRestrictedFloat() for arg in sig[1]):
@@ -5148,20 +5169,21 @@ class SqueakyCleanLogger(object):
     def __init__(self):
         self.errors = []
 
     def debug(self, msg, *args, **kwargs):
         pass
     info = debug
 
     def warning(self, msg, *args, **kwargs):
-        if msg == "%s:%d: Rule '%s' defined, but not used":
+        if msg == "%s:%d: Rule %r defined, but not used" or \
+           msg == "%s:%d: Rule '%s' defined, but not used":
             # Munge things so we don't have to hardcode filenames and
             # line numbers in our whitelist.
-            whitelistmsg = "Rule '%s' defined, but not used"
+            whitelistmsg = "Rule %r defined, but not used"
             whitelistargs = args[2:]
         else:
             whitelistmsg = msg
             whitelistargs = args
         if (whitelistmsg % whitelistargs) not in SqueakyCleanLogger.errorWhitelist:
             self.errors.append(msg % args)
     error = warning
 
--- a/dom/bindings/parser/runtests.py
+++ b/dom/bindings/parser/runtests.py
@@ -8,70 +8,91 @@ import optparse
 import traceback
 import WebIDL
 
 class TestHarness(object):
     def __init__(self, test, verbose):
         self.test = test
         self.verbose = verbose
         self.printed_intro = False
+        self.passed = 0
+        self.failures = []
 
     def start(self):
         if self.verbose:
             self.maybe_print_intro()
 
     def finish(self):
         if self.verbose or self.printed_intro:
             print "Finished test %s" % self.test
 
     def maybe_print_intro(self):
         if not self.printed_intro:
             print "Starting test %s" % self.test
             self.printed_intro = True
 
     def test_pass(self, msg):
+        self.passed += 1
         if self.verbose:
             print "TEST-PASS | %s" % msg
 
     def test_fail(self, msg):
         self.maybe_print_intro()
+        self.failures.append(msg)
         print "TEST-UNEXPECTED-FAIL | %s" % msg
 
     def ok(self, condition, msg):
         if condition:
             self.test_pass(msg)
         else:
             self.test_fail(msg)
 
     def check(self, a, b, msg):
         if a == b:
             self.test_pass(msg)
         else:
-            self.test_fail(msg)
-            print "\tGot %s expected %s" % (a, b)
+            self.test_fail(msg + " | Got %s expected %s" % (a, b))
 
 def run_tests(tests, verbose):
     testdir = os.path.join(os.path.dirname(__file__), 'tests')
     if not tests:
         tests = glob.iglob(os.path.join(testdir, "*.py"))
     sys.path.append(testdir)
 
+    all_passed = 0
+    failed_tests = []
+
     for test in tests:
         (testpath, ext) = os.path.splitext(os.path.basename(test))
         _test = __import__(testpath, globals(), locals(), ['WebIDLTest'])
 
         harness = TestHarness(test, verbose)
         harness.start()
         try:
             _test.WebIDLTest.__call__(WebIDL.Parser(), harness)
         except Exception, ex:
-            print "TEST-UNEXPECTED-FAIL | Unhandled exception in test %s: %s" % (testpath, ex)
+            harness.test_fail("Unhandled exception in test %s: %s" %
+                              (testpath, ex))
             traceback.print_exc()
         finally:
             harness.finish()
+        all_passed += harness.passed
+        if harness.failures:
+            failed_tests.append((test, harness.failures))
+
+    if verbose or failed_tests:
+        print
+        print 'Result summary:'
+        print 'Successful: %d' % all_passed
+        print 'Unexpected: %d' % \
+                sum(len(failures) for _, failures in failed_tests)
+        for test, failures in failed_tests:
+            print '%s:' % test
+            for failure in failures:
+                print 'TEST-UNEXPECTED-FAIL | %s' % failure
 
 if __name__ == '__main__':
     usage = """%prog [OPTIONS] [TESTS]
                Where TESTS are relative to the tests directory."""
     parser = optparse.OptionParser(usage=usage)
     parser.add_option('-q', '--quiet', action='store_false', dest='verbose', default=True,
                       help="Don't print passing tests.")
     options, tests = parser.parse_args()
new file mode 100644
--- /dev/null
+++ b/dom/bindings/parser/tests/test_lenientSetter.py
@@ -0,0 +1,58 @@
+# 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/.
+
+def should_throw(parser, harness, message, code):
+    parser = parser.reset();
+    threw = False
+    try:
+        parser.parse(code)
+        parser.finish()
+    except:
+        threw = True
+
+    harness.ok(threw, "Should have thrown: %s" % message)
+
+
+def WebIDLTest(parser, harness):
+    # The [LenientSetter] extended attribute MUST take no arguments.
+    should_throw(parser, harness, "no arguments", """
+        interface I {
+          [LenientSetter=X] readonly attribute long A;
+        };
+    """)
+
+    # An attribute with the [LenientSetter] extended attribute MUST NOT
+    # also be declared with the [PutForwards] extended attribute.
+    should_throw(parser, harness, "PutForwards", """
+        interface I {
+          [PutForwards=B, LenientSetter] readonly attribute J A;
+        };
+        interface J {
+          attribute long B;
+        };
+    """)
+
+    # An attribute with the [LenientSetter] extended attribute MUST NOT
+    # also be declared with the [Replaceable] extended attribute.
+    should_throw(parser, harness, "Replaceable", """
+        interface I {
+          [Replaceable, LenientSetter] readonly attribute J A;
+        };
+    """)
+
+    # The [LenientSetter] extended attribute MUST NOT be used on an
+    # attribute that is not read only.
+    should_throw(parser, harness, "writable attribute", """
+        interface I {
+          [LenientSetter] attribute long A;
+        };
+    """)
+
+    # The [LenientSetter] extended attribute MUST NOT be used on a
+    # static attribute.
+    should_throw(parser, harness, "static attribute", """
+        interface I {
+          [LenientSetter] static readonly attribute long A;
+        };
+    """)
--- a/dom/canvas/WebGL2Context.cpp
+++ b/dom/canvas/WebGL2Context.cpp
@@ -91,53 +91,58 @@ static const gl::GLFeature kRequiredFeat
     gl::GLFeature::texture_storage,
     gl::GLFeature::transform_feedback2,
     gl::GLFeature::uniform_buffer_object,
     gl::GLFeature::uniform_matrix_nonsquare,
     gl::GLFeature::vertex_array_object
 };
 
 bool
-WebGLContext::InitWebGL2()
+WebGLContext::InitWebGL2(nsACString* const out_failReason)
 {
     MOZ_ASSERT(IsWebGL2(), "WebGLContext is not a WebGL 2 context!");
 
     // check OpenGL features
     if (!gl->IsSupported(gl::GLFeature::occlusion_query) &&
         !gl->IsSupported(gl::GLFeature::occlusion_query_boolean))
     {
         // On desktop, we fake occlusion_query_boolean with occlusion_query if
         // necessary. (See WebGL2ContextQueries.cpp)
-        GenerateWarning("WebGL 2 unavailable. Requires occlusion queries.");
+        out_failReason->AssignASCII("WebGL 2 requires occlusion query support.");
         return false;
     }
 
     std::vector<gl::GLFeature> missingList;
 
     for (size_t i = 0; i < ArrayLength(kRequiredFeatures); i++) {
-        if (!gl->IsSupported(kRequiredFeatures[i]))
+        if (!gl->IsSupported(kRequiredFeatures[i])) {
             missingList.push_back(kRequiredFeatures[i]);
+        }
     }
 
 #ifdef XP_MACOSX
     // On OSX, GL core profile is used. This requires texture swizzle
     // support to emulate legacy texture formats: ALPHA, LUMINANCE,
     // and LUMINANCE_ALPHA.
-    if (!gl->IsSupported(gl::GLFeature::texture_swizzle))
+    if (!gl->IsSupported(gl::GLFeature::texture_swizzle)) {
         missingList.push_back(gl::GLFeature::texture_swizzle);
+    }
 #endif
 
     if (missingList.size()) {
         nsAutoCString exts;
         for (auto itr = missingList.begin(); itr != missingList.end(); ++itr) {
             exts.AppendLiteral("\n  ");
             exts.Append(gl::GLContext::GetFeatureName(*itr));
         }
-        GenerateWarning("WebGL 2 unavailable. The following required features are"
-                        " unavailible: %s", exts.BeginReading());
+
+        const nsPrintfCString reason("WebGL 2 requires support for the following"
+                                     " features: %s",
+                                     exts.BeginReading());
+        out_failReason->Assign(reason);
         return false;
     }
 
     // we initialise WebGL 2 related stuff.
     gl->GetUIntegerv(LOCAL_GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,
                      &mGLMaxTransformFeedbackSeparateAttribs);
     gl->GetUIntegerv(LOCAL_GL_MAX_UNIFORM_BUFFER_BINDINGS,
                      &mGLMaxUniformBufferBindings);
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -21,16 +21,17 @@
 #include "ImageContainer.h"
 #include "ImageEncoder.h"
 #include "Layers.h"
 #include "LayerUserData.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "mozilla/dom/ImageData.h"
+#include "mozilla/dom/WebGLContextEvent.h"
 #include "mozilla/EnumeratedArrayCycleCollection.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ProcessPriorityManager.h"
 #include "mozilla/Services.h"
 #include "mozilla/Telemetry.h"
 #include "nsContentUtils.h"
 #include "nsDisplayList.h"
 #include "nsError.h"
@@ -571,117 +572,128 @@ BaseCaps(const WebGLContextOptions& opti
 
     return baseCaps;
 }
 
 ////////////////////////////////////////
 
 static already_AddRefed<gl::GLContext>
 CreateGLWithEGL(const gl::SurfaceCaps& caps, gl::CreateContextFlags flags,
-                WebGLContext* webgl)
+                WebGLContext* webgl, nsACString* const out_failReason)
 {
     const gfx::IntSize dummySize(16, 16);
     RefPtr<GLContext> gl = gl::GLContextProviderEGL::CreateOffscreen(dummySize, caps,
                                                                      flags);
-
     if (gl && gl->IsANGLE()) {
         gl = nullptr;
     }
 
     if (!gl) {
-        webgl->GenerateWarning("Error during EGL OpenGL init.");
+        if (out_failReason->Length()) {
+            out_failReason->AppendLiteral("\n");
+        }
+        out_failReason->AppendLiteral("Error during EGL OpenGL init.");
         return nullptr;
     }
 
     return gl.forget();
 }
 
 static already_AddRefed<GLContext>
 CreateGLWithANGLE(const gl::SurfaceCaps& caps, gl::CreateContextFlags flags,
-                  WebGLContext* webgl)
+                  WebGLContext* webgl, nsACString* const out_failReason)
 {
     const gfx::IntSize dummySize(16, 16);
     RefPtr<GLContext> gl = gl::GLContextProviderEGL::CreateOffscreen(dummySize, caps,
                                                                      flags);
-
     if (gl && !gl->IsANGLE()) {
         gl = nullptr;
     }
 
     if (!gl) {
-        webgl->GenerateWarning("Error during ANGLE OpenGL init.");
+        if (out_failReason->Length()) {
+            out_failReason->AppendLiteral("\n");
+        }
+        out_failReason->AppendLiteral("Error during ANGLE OpenGL init.");
         return nullptr;
     }
 
     return gl.forget();
 }
 
 static already_AddRefed<gl::GLContext>
 CreateGLWithDefault(const gl::SurfaceCaps& caps, gl::CreateContextFlags flags,
-                    WebGLContext* webgl)
+                    WebGLContext* webgl, nsACString* const out_failReason)
 {
     nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
 
     if (!(flags & CreateContextFlags::FORCE_ENABLE_HARDWARE) &&
         IsFeatureInBlacklist(gfxInfo, nsIGfxInfo::FEATURE_WEBGL_OPENGL))
     {
-        webgl->GenerateWarning("Refused to create native OpenGL context because of"
-                               " blacklisting.");
+        if (out_failReason->Length()) {
+            out_failReason->AppendASCII("\n");
+        }
+        out_failReason->AppendASCII("Refused to create native OpenGL context because of"
+                                    " blacklisting.");
         return nullptr;
     }
 
     const gfx::IntSize dummySize(16, 16);
     RefPtr<GLContext> gl = gl::GLContextProvider::CreateOffscreen(dummySize, caps, flags);
 
     if (gl && gl->IsANGLE()) {
         gl = nullptr;
     }
 
     if (!gl) {
-        webgl->GenerateWarning("Error during native OpenGL init.");
+        if (out_failReason->Length()) {
+            out_failReason->AppendASCII("\n");
+        }
+        out_failReason->AppendASCII("Error during native OpenGL init.");
         return nullptr;
     }
 
     return gl.forget();
 }
 
 ////////////////////////////////////////
 
 bool
 WebGLContext::CreateAndInitGLWith(FnCreateGL_T fnCreateGL,
                                   const gl::SurfaceCaps& baseCaps,
-                                  gl::CreateContextFlags flags)
+                                  gl::CreateContextFlags flags,
+                                  nsACString* const out_failReason)
 {
     std::queue<gl::SurfaceCaps> fallbackCaps;
     PopulateCapFallbackQueue(baseCaps, &fallbackCaps);
 
     MOZ_RELEASE_ASSERT(!gl);
     gl = nullptr;
     while (!fallbackCaps.empty()) {
         gl::SurfaceCaps& caps = fallbackCaps.front();
 
-        gl = fnCreateGL(caps, flags, this);
+        gl = fnCreateGL(caps, flags, this, out_failReason);
         if (gl)
             break;
 
         fallbackCaps.pop();
     }
     if (!gl)
         return false;
 
-    if (!InitAndValidateGL()) {
+    if (!InitAndValidateGL(out_failReason)) {
         gl = nullptr;
         return false;
     }
 
     return true;
 }
 
 bool
-WebGLContext::CreateAndInitGL(bool forceEnabled)
+WebGLContext::CreateAndInitGL(bool forceEnabled, nsACString* const out_failReason)
 {
     const bool useEGL = PR_GetEnv("MOZ_WEBGL_PREFER_EGL");
 
     bool useANGLE = false;
 #ifdef XP_WIN
     const bool disableANGLE = (gfxPrefs::WebGLDisableANGLE() ||
                                PR_GetEnv("MOZ_WEBGL_FORCE_OPENGL"));
     useANGLE = !disableANGLE;
@@ -690,22 +702,22 @@ WebGLContext::CreateAndInitGL(bool force
     gl::CreateContextFlags flags = gl::CreateContextFlags::NONE;
     if (forceEnabled) flags |= gl::CreateContextFlags::FORCE_ENABLE_HARDWARE;
     if (!IsWebGL2())  flags |= gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE;
     if (IsWebGL2())   flags |= gl::CreateContextFlags::PREFER_ES3;
 
     const gl::SurfaceCaps baseCaps = BaseCaps(mOptions, this);
 
     if (useEGL)
-        return CreateAndInitGLWith(CreateGLWithEGL, baseCaps, flags);
+        return CreateAndInitGLWith(CreateGLWithEGL, baseCaps, flags, out_failReason);
 
     if (useANGLE)
-        return CreateAndInitGLWith(CreateGLWithANGLE, baseCaps, flags);
+        return CreateAndInitGLWith(CreateGLWithANGLE, baseCaps, flags, out_failReason);
 
-    return CreateAndInitGLWith(CreateGLWithDefault, baseCaps, flags);
+    return CreateAndInitGLWith(CreateGLWithDefault, baseCaps, flags, out_failReason);
 }
 
 // Fallback for resizes:
 bool
 WebGLContext::ResizeBackbuffer(uint32_t requestedWidth,
                                uint32_t requestedHeight)
 {
     uint32_t width = requestedWidth;
@@ -740,16 +752,43 @@ WebGLContext::ResizeBackbuffer(uint32_t 
         GenerateWarning("Requested size %dx%d was too large, but resize"
                           " to %dx%d succeeded.",
                         requestedWidth, requestedHeight,
                         width, height);
     }
     return true;
 }
 
+void
+WebGLContext::ThrowEvent_WebGLContextCreationError(const nsACString& text)
+{
+    RefPtr<EventTarget> target = mCanvasElement;
+    if (!target) {
+        target = mOffscreenCanvas;
+    }
+
+    const auto kEventName = NS_LITERAL_STRING("webglcontextcreationerror");
+
+    WebGLContextEventInit eventInit;
+    // eventInit.mCancelable = true; // The spec says this, but it's silly.
+    eventInit.mStatusMessage = NS_ConvertASCIItoUTF16(text);
+
+    const RefPtr<WebGLContextEvent> event = WebGLContextEvent::Constructor(target,
+                                                                           kEventName,
+                                                                           eventInit);
+    event->SetTrusted(true);
+
+    bool didPreventDefault;
+    target->DispatchEvent(event, &didPreventDefault);
+
+    //////
+
+    GenerateWarning("Failed to create WebGL context: %s", text.BeginReading());
+}
+
 NS_IMETHODIMP
 WebGLContext::SetDimensions(int32_t signedWidth, int32_t signedHeight)
 {
     if (signedWidth < 0 || signedHeight < 0) {
         GenerateWarning("Canvas size is too large (seems like a negative value wrapped)");
         return NS_ERROR_OUT_OF_MEMORY;
     }
 
@@ -819,62 +858,71 @@ WebGLContext::SetDimensions(int32_t sign
     // generation is not 0 right now (that is, if this isn't the first
     // context we're creating), we may have to dispatch a context lost
     // event.
 
     // If incrementing the generation would cause overflow,
     // don't allow it.  Allowing this would allow us to use
     // resource handles created from older context generations.
     if (!(mGeneration + 1).isValid()) {
-        GenerateWarning("Too many WebGL contexts created this run.");
-        return NS_ERROR_FAILURE; // exit without changing the value of mGeneration
+        // exit without changing the value of mGeneration
+        const nsLiteralCString text("Too many WebGL contexts created this run.");
+        ThrowEvent_WebGLContextCreationError(text);
+        return NS_ERROR_FAILURE;
     }
 
     // increment the generation number - Do this early because later
     // in CreateOffscreenGL(), "default" objects are created that will
     // pick up the old generation.
     ++mGeneration;
 
     bool disabled = gfxPrefs::WebGLDisabled();
 
     // TODO: When we have software webgl support we should use that instead.
     disabled |= gfxPlatform::InSafeMode();
 
     if (disabled) {
-        GenerateWarning("WebGL creation is disabled, and so disallowed here.");
+        const nsLiteralCString text("WebGL is currently disabled.");
+        ThrowEvent_WebGLContextCreationError(text);
         return NS_ERROR_FAILURE;
     }
 
-    nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
-    bool failIfMajorPerformanceCaveat =
-                    !gfxPrefs::WebGLDisableFailIfMajorPerformanceCaveat() &&
-                    !HasAcceleratedLayers(gfxInfo);
-    if (failIfMajorPerformanceCaveat) {
-        dom::Nullable<dom::WebGLContextAttributes> contextAttributes;
-        this->GetContextAttributes(contextAttributes);
-        if (contextAttributes.Value().mFailIfMajorPerformanceCaveat) {
+    bool failIfPerfCaveat = mOptions.failIfMajorPerformanceCaveat;
+    if (gfxPrefs::WebGLDisableFailIfMajorPerformanceCaveat())
+        failIfPerfCaveat = false;
+
+    if (failIfPerfCaveat) {
+        nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
+        if (!HasAcceleratedLayers(gfxInfo)) {
+            const nsLiteralCString text("failIfMajorPerformanceCaveat: Compositor is not"
+                                        " hardware-accelerated.");
+            ThrowEvent_WebGLContextCreationError(text);
             return NS_ERROR_FAILURE;
         }
     }
 
     // Alright, now let's start trying.
     bool forceEnabled = gfxPrefs::WebGLForceEnabled();
     ScopedGfxFeatureReporter reporter("WebGL", forceEnabled);
 
     MOZ_ASSERT(!gl);
-    if (!CreateAndInitGL(forceEnabled)) {
-        GenerateWarning("WebGL creation failed.");
+    nsCString failReason;
+    if (!CreateAndInitGL(forceEnabled, &failReason)) {
+        const nsPrintfCString text("WebGL creation failed: %s",
+                                   failReason.BeginReading());
+        ThrowEvent_WebGLContextCreationError(text);
         return NS_ERROR_FAILURE;
     }
     MOZ_ASSERT(gl);
 
     MOZ_ASSERT_IF(mOptions.alpha, gl->Caps().alpha);
 
     if (!ResizeBackbuffer(width, height)) {
-        GenerateWarning("Initializing WebGL backbuffer failed.");
+        const nsLiteralCString text("Initializing WebGL backbuffer failed.");
+        ThrowEvent_WebGLContextCreationError(text);
         return NS_ERROR_FAILURE;
     }
 
 #ifdef DEBUG
     if (gl->DebugMode())
         printf_stderr("--- WebGL context created: %p\n", gl.get());
 #endif
 
@@ -1601,31 +1649,33 @@ WebGLContext::UpdateContextLossStatus()
         }
 
         // Fall through.
     }
 
     if (mContextStatus == ContextLostAwaitingEvent) {
         // The context has been lost and we haven't yet triggered the
         // callback, so do that now.
-
+        const auto kEventName = NS_LITERAL_STRING("webglcontextlost");
+        const bool kCanBubble = true;
+        const bool kIsCancelable = true;
         bool useDefaultHandler;
 
         if (mCanvasElement) {
             nsContentUtils::DispatchTrustedEvent(
                 mCanvasElement->OwnerDoc(),
                 static_cast<nsIDOMHTMLCanvasElement*>(mCanvasElement),
-                NS_LITERAL_STRING("webglcontextlost"),
-                true,
-                true,
+                kEventName,
+                kCanBubble,
+                kIsCancelable,
                 &useDefaultHandler);
         } else {
             // OffscreenCanvas case
             RefPtr<Event> event = new Event(mOffscreenCanvas, nullptr, nullptr);
-            event->InitEvent(NS_LITERAL_STRING("webglcontextlost"), true, true);
+            event->InitEvent(kEventName, kCanBubble, kIsCancelable);
             event->SetTrusted(true);
             mOffscreenCanvas->DispatchEvent(event, &useDefaultHandler);
         }
 
         // We sent the callback, so we're just 'regular lost' now.
         mContextStatus = ContextLost;
         // If we're told to use the default handler, it means the script
         // didn't bother to handle the event. In this case, we shouldn't
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -1196,31 +1196,34 @@ protected:
     nsTArray<GLenum> mCompressedTextureFormats;
 
     // -------------------------------------------------------------------------
     // WebGL 2 specifics (implemented in WebGL2Context.cpp)
 public:
     virtual bool IsWebGL2() const = 0;
 
 protected:
-    bool InitWebGL2();
+    bool InitWebGL2(nsACString* const out_failReason);
 
-    bool CreateAndInitGL(bool forceEnabled);
+    bool CreateAndInitGL(bool forceEnabled, nsACString* const out_failReason);
     bool ResizeBackbuffer(uint32_t width, uint32_t height);
 
     typedef already_AddRefed<gl::GLContext> FnCreateGL_T(const gl::SurfaceCaps& caps,
                                                          gl::CreateContextFlags flags,
-                                                         WebGLContext* webgl);
+                                                         WebGLContext* webgl,
+                                                         nsACString* const out_failReason);
 
     bool CreateAndInitGLWith(FnCreateGL_T fnCreateGL, const gl::SurfaceCaps& baseCaps,
-                             gl::CreateContextFlags flags);
+                             gl::CreateContextFlags flags,
+                             nsACString* const out_failReason);
+    void ThrowEvent_WebGLContextCreationError(const nsACString& text);
 
     // -------------------------------------------------------------------------
     // Validation functions (implemented in WebGLContextValidate.cpp)
-    bool InitAndValidateGL();
+    bool InitAndValidateGL(nsACString* const out_failReason);
     bool ValidateBlendEquationEnum(GLenum cap, const char* info);
     bool ValidateBlendFuncDstEnum(GLenum mode, const char* info);
     bool ValidateBlendFuncSrcEnum(GLenum mode, const char* info);
     bool ValidateBlendFuncEnumsCompatibility(GLenum sfactor, GLenum dfactor,
                                              const char* info);
     bool ValidateDataOffsetSize(WebGLintptr offset, WebGLsizeiptr size, WebGLsizeiptr bufferSize, const char* info);
     bool ValidateDataRanges(WebGLintptr readOffset, WebGLintptr writeOffset, WebGLsizeiptr size, const char* info);
     bool ValidateTextureTargetEnum(GLenum target, const char* info);
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -640,32 +640,35 @@ FloorPOT(int32_t x)
         if (x < pot*2)
             break;
         pot *= 2;
     }
     return pot;
 }
 
 bool
-WebGLContext::InitAndValidateGL()
+WebGLContext::InitAndValidateGL(nsACString* const out_failReason)
 {
-    if (!gl)
-        return false;
+    MOZ_RELEASE_ASSERT(gl);
 
     // Unconditionally create a new format usage authority. This is
     // important when restoring contexts and extensions need to add
     // formats back into the authority.
     mFormatUsage = CreateFormatUsage(gl);
-    if (!mFormatUsage)
+    if (!mFormatUsage) {
+        out_failReason->AssignLiteral("Failed to create mFormatUsage.");
         return false;
+    }
 
     GLenum error = gl->fGetError();
     if (error != LOCAL_GL_NO_ERROR) {
-        GenerateWarning("GL error 0x%x occurred during OpenGL context"
-                        " initialization, before WebGL initialization!", error);
+        const nsPrintfCString reason("GL error 0x%x occurred during OpenGL context"
+                                     " initialization, before WebGL initialization!",
+                                     error);
+        out_failReason->Assign(reason);
         return false;
     }
 
     mMinCapability = gfxPrefs::WebGLMinCapabilityMode();
     mDisableExtensions = gfxPrefs::WebGLDisableExtensions();
     mLoseContextOnMemoryPressure = gfxPrefs::WebGLLoseContextOnMemoryPressure();
     mCanLoseContextInForeground = gfxPrefs::WebGLCanLoseContextInForeground();
     mRestoreWhenVisible = gfxPrefs::WebGLRestoreWhenVisible();
@@ -744,32 +747,34 @@ WebGLContext::InitAndValidateGL()
         gl->fEnableVertexAttribArray(0);
 
     if (MinCapabilityMode())
         mGLMaxVertexAttribs = MINVALUE_GL_MAX_VERTEX_ATTRIBS;
     else
         gl->fGetIntegerv(LOCAL_GL_MAX_VERTEX_ATTRIBS, &mGLMaxVertexAttribs);
 
     if (mGLMaxVertexAttribs < 8) {
-        GenerateWarning("GL_MAX_VERTEX_ATTRIBS: %d is < 8!",
-                        mGLMaxVertexAttribs);
+        const nsPrintfCString reason("GL_MAX_VERTEX_ATTRIBS: %d is < 8!",
+                                     mGLMaxVertexAttribs);
+        out_failReason->Assign(reason);
         return false;
     }
 
     // Note: GL_MAX_TEXTURE_UNITS is fixed at 4 for most desktop hardware,
     // even though the hardware supports much more.  The
     // GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS value is the accurate value.
     if (MinCapabilityMode())
         mGLMaxTextureUnits = MINVALUE_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS;
     else
         gl->fGetIntegerv(LOCAL_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &mGLMaxTextureUnits);
 
     if (mGLMaxTextureUnits < 8) {
-        GenerateWarning("GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: %d is < 8!",
-                        mGLMaxTextureUnits);
+        const nsPrintfCString reason("GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: %d is < 8!",
+                                     mGLMaxTextureUnits);
+        out_failReason->Assign(reason);
         return false;
     }
 
     mBound2DTextures.SetLength(mGLMaxTextureUnits);
     mBoundCubeMapTextures.SetLength(mGLMaxTextureUnits);
     mBound3DTextures.SetLength(mGLMaxTextureUnits);
     mBound2DArrayTextures.SetLength(mGLMaxTextureUnits);
     mBoundSamplers.SetLength(mGLMaxTextureUnits);
@@ -915,37 +920,39 @@ WebGLContext::InitAndValidateGL()
         gl->fEnable(LOCAL_GL_TEXTURE_CUBE_MAP_SEAMLESS);
     }
 
     // Check the shader validator pref
     mBypassShaderValidation = gfxPrefs::WebGLBypassShaderValidator();
 
     // initialize shader translator
     if (!ShInitialize()) {
-        GenerateWarning("GLSL translator initialization failed!");
+        out_failReason->AssignLiteral("GLSL translator initialization failed!");
         return false;
     }
 
     // Mesa can only be detected with the GL_VERSION string, of the form
     // "2.1 Mesa 7.11.0"
     const char* versionStr = (const char*)(gl->fGetString(LOCAL_GL_VERSION));
     mIsMesa = strstr(versionStr, "Mesa");
 
     // Notice that the point of calling fGetError here is not only to check for
     // errors, but also to reset the error flags so that a subsequent WebGL
     // getError call will give the correct result.
     error = gl->fGetError();
     if (error != LOCAL_GL_NO_ERROR) {
-        GenerateWarning("GL error 0x%x occurred during WebGL context"
-                        " initialization!", error);
+        const nsPrintfCString reason("GL error 0x%x occurred during WebGL context"
+                                     " initialization!",
+                                     error);
+        out_failReason->Assign(reason);
         return false;
     }
 
     if (IsWebGL2() &&
-        !InitWebGL2())
+        !InitWebGL2(out_failReason))
     {
         // Todo: Bug 898404: Only allow WebGL2 on GL>=3.0 on desktop GL.
         return false;
     }
 
     // Default value for all disabled vertex attributes is [0, 0, 0, 1]
     mVertexAttribType = MakeUnique<GLenum[]>(mGLMaxVertexAttribs);
     for (int32_t index = 0; index < mGLMaxVertexAttribs; ++index) {
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -1,22 +1,23 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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 "WebGLTexture.h"
 
 #include <algorithm>
+
 #include "CanvasUtils.h"
 #include "GLBlitHelper.h"
 #include "GLContext.h"
+#include "mozilla/gfx/2D.h"
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "mozilla/dom/ImageData.h"
-#include "mozilla/gfx/SourceSurfaceRawData.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/Scoped.h"
 #include "mozilla/unused.h"
 #include "ScopedGLHelpers.h"
 #include "TexUnpackBlob.h"
 #include "WebGLContext.h"
 #include "WebGLContextUtils.h"
 #include "WebGLFramebuffer.h"
@@ -178,24 +179,26 @@ UnpackBlobFromImageData(WebGLContext* we
 
     scopedArr->ComputeLengthAndData();
     const DebugOnly<size_t> dataSize = scopedArr->Length();
     const void* const data = scopedArr->Data();
 
     const gfx::IntSize size(imageData->Width(), imageData->Height());
     const size_t stride = size.width * 4;
     const gfx::SurfaceFormat surfFormat = gfx::SurfaceFormat::R8G8B8A8;
-    const bool ownsData = false;
 
     MOZ_ASSERT(dataSize == stride * size.height);
 
-    const RefPtr<gfx::SourceSurfaceRawData> surf = new gfx::SourceSurfaceRawData;
+    uint8_t* wrappableData = (uint8_t*)data;
 
-    uint8_t* wrappableData = (uint8_t*)data;
-    surf->InitWrappingData(wrappableData, size, stride, surfFormat, ownsData);
+    const RefPtr<gfx::SourceSurface> surf =
+        gfx::Factory::CreateWrappingDataSourceSurface(wrappableData,
+                                                      stride,
+                                                      size,
+                                                      surfFormat);
 
     // WhatWG "HTML Living Standard" (30 October 2015):
     // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
     //  non-premultiplied alpha values."
     const bool surfIsAlphaPremult = false;
 
     UniquePtr<webgl::TexUnpackBlob> ret;
     ret.reset(new webgl::TexUnpackSurface(surf, surfIsAlphaPremult));
--- a/dom/canvas/moz.build
+++ b/dom/canvas/moz.build
@@ -1,18 +1,18 @@
 # -*- 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/.
 
 TEST_DIRS += ['compiledtest']
 
-# Number changes to this file to avoid bug 1081323 (clobber after changing a manifest):
-# 12
+# Change the following line to avoid bug 1081323 (clobber after changing a manifest):
+# Add test for webglcontextcreationerror.
 
 MOCHITEST_MANIFESTS += [
     'test/crash/mochitest.ini',
     'test/crossorigin/mochitest.ini',
     'test/mochitest.ini',
     'test/webgl-conf/generated-mochitest.ini',
     'test/webgl-mochitest/mochitest.ini',
 ]
deleted file mode 100644
--- a/dom/canvas/test/mochitest-subsuite-webgl.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[DEFAULT]
-subsuite = webgl
-
-[include:_webgl-conformance.ini]
-[include:webgl-mochitest.ini]
--- a/dom/canvas/test/webgl-mochitest/mochitest.ini
+++ b/dom/canvas/test/webgl-mochitest/mochitest.ini
@@ -89,8 +89,9 @@ skip-if = toolkit == 'android' #bug 8654
 skip-if = toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests pass except for one on armv6 tests
 [test_webgl2_not_exposed.html]
 skip-if = toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests pass except for one on armv6 tests
 [test_webgl2_invalidate_framebuffer.html]
 skip-if = toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests pass except for one on armv6 tests
 [test_webgl2_alpha_luminance.html]
 skip-if = toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests pass except for one on armv6 tests
 [test_fuzzing_bugs.html]
+[test_webglcontextcreationerror.html]
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/test_webglcontextcreationerror.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset='UTF-8'>
+  <script src='/tests/SimpleTest/SimpleTest.js'></script>
+  <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+</head>
+<body>
+<script>
+'use strict';
+
+function RunWithPrefs(prefPairList, func) {
+  var prefEnv = {'set': prefPairList};
+  try {
+    SpecialPowers.pushPrefEnv(prefEnv, func);
+  } catch (e) {
+    console.log('Warning: Failed to set prefs: ' + JSON.stringify(prefPairList));
+    func();
+  }
+}
+
+////////////////////////////////////////
+
+function Check(expr, text) {
+  ok(expr, text);
+  return expr;
+}
+
+function TestWhenDisabled() {
+  var c = document.createElement('canvas');
+
+  var generatedEvent = null;
+  var f = function(event) { generatedEvent = event; };
+  c.addEventListener('webglcontextcreationerror', f);
+
+  var gl = c.getContext('webgl'); // Should fail.
+
+  do {
+    if (!Check(!gl, 'When disabled, context creation should fail.'))
+      break;
+
+    if (!Check(generatedEvent, 'Context creation failure should generate an event.'))
+      break;
+
+    var reason = generatedEvent.statusMessage;
+    if (!Check(reason !== undefined, 'generatedEvent.statusMessage should be defined.'))
+      break;
+
+    ok(reason.length, 'statusMessage should be non-empty.');
+  } while (false);
+
+  SimpleTest.finish();
+}
+
+////////////////////////////////////////
+
+SimpleTest.waitForExplicitFinish();
+
+var prefPairList = [
+  ['webgl.disabled', true],
+];
+RunWithPrefs(prefPairList, TestWhenDisabled);
+
+</script>
+</body>
+</html>
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -373,35 +373,18 @@ DataTransfer::GetFiles(nsIDOMFileList** 
   NS_IF_ADDREF(*aFileList =
     GetFileListInternal(rv, nsContentUtils::GetSystemPrincipal()));
   return rv.StealNSResult();
 }
 
 already_AddRefed<DOMStringList>
 DataTransfer::Types() const
 {
-  RefPtr<DOMStringList> types = new DOMStringList();
-  if (mItems.Length()) {
-    bool addFile = false;
-    const nsTArray<TransferItem>& item = mItems[0];
-    for (uint32_t i = 0; i < item.Length(); i++) {
-      const nsString& format = item[i].mFormat;
-      types->Add(format);
-      if (!addFile) {
-        addFile = format.EqualsASCII(kFileMime) ||
-                  format.EqualsASCII("application/x-moz-file-promise");
-      }
-    }
-
-    if (addFile) {
-      types->Add(NS_LITERAL_STRING("Files"));
-    }
-  }
-
-  return types.forget();
+  ErrorResult rv;
+  return MozTypesAt(0, rv);
 }
 
 NS_IMETHODIMP
 DataTransfer::GetTypes(nsISupports** aTypes)
 {
   RefPtr<DOMStringList> types = Types();
   types.forget(aTypes);
 
@@ -565,32 +548,49 @@ DataTransfer::GetMozSourceNode(nsIDOMNod
     *aSourceNode = nullptr;
     return NS_OK;
   }
 
   return CallQueryInterface(sourceNode, aSourceNode);
 }
 
 already_AddRefed<DOMStringList>
-DataTransfer::MozTypesAt(uint32_t aIndex, ErrorResult& aRv)
+DataTransfer::MozTypesAt(uint32_t aIndex, ErrorResult& aRv) const
 {
   // Only the first item is valid for clipboard events
   if (aIndex > 0 &&
       (mEventMessage == eCut || mEventMessage == eCopy ||
        mEventMessage == ePaste)) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return nullptr;
   }
 
   RefPtr<DOMStringList> types = new DOMStringList();
   if (aIndex < mItems.Length()) {
+    bool addFile = false;
     // note that you can retrieve the types regardless of their principal
-    nsTArray<TransferItem>& item = mItems[aIndex];
+    const nsTArray<TransferItem>& item = mItems[aIndex];
     for (uint32_t i = 0; i < item.Length(); i++) {
-      types->Add(item[i].mFormat);
+      const nsString& format = item[i].mFormat;
+      types->Add(format);
+      if (!addFile) {
+        addFile = format.EqualsASCII(kFileMime);
+      }
+    }
+
+    if (addFile) {
+      // If this is a content caller, and a file is in the data transfer, remove
+      // the non-file types. This prevents alternate text forms of the file
+      // from being returned.
+      if (!nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+        types->Clear();
+        types->Add(NS_LITERAL_STRING(kFileMime));
+      }
+
+      types->Add(NS_LITERAL_STRING("Files"));
     }
   }
 
   return types.forget();
 }
 
 NS_IMETHODIMP
 DataTransfer::MozTypesAt(uint32_t aIndex, nsISupports** aTypes)
@@ -627,22 +627,33 @@ DataTransfer::GetDataAtInternal(const ns
 
   // Only the first item is valid for clipboard events
   if (aIndex > 0 &&
       (mEventMessage == eCut || mEventMessage == eCopy ||
        mEventMessage == ePaste)) {
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
 
-
   nsAutoString format;
   GetRealFormat(aFormat, format);
 
   nsTArray<TransferItem>& item = mItems[aIndex];
 
+  // If this is a content caller, and a file is in the data transfer, only
+  // return the file type.
+  if (!format.EqualsLiteral(kFileMime) &&
+      !nsContentUtils::IsSystemPrincipal(aSubjectPrincipal)) {
+    uint32_t count = item.Length();
+    for (uint32_t i = 0; i < count; i++) {
+      if (item[i].mFormat.EqualsLiteral(kFileMime)) {
+        return NS_OK;
+      }
+    }
+  }
+
   // Check if the caller is allowed to access the drag data. Callers with
   // chrome privileges can always read the data. During the
   // drop event, allow retrieving the data except in the case where the
   // source of the drag is in a child frame of the caller. In that case,
   // we only allow access to data of the same principal. During other events,
   // only allow access to the data with the same principal.
   bool checkFormatItemPrincipal = mIsCrossDomainSubFrameDrop ||
       (mEventMessage != eDrop && mEventMessage != eLegacyDragDrop &&
@@ -741,18 +752,18 @@ DataTransfer::SetDataAtInternal(const ns
   // Don't allow the custom type to be assigned.
   if (aFormat.EqualsLiteral(kCustomTypesMime)) {
     return NS_ERROR_TYPE_ERR;
   }
 
   // Don't allow non-chrome to add non-string or file data. We'll block file
   // promises as well which are used internally for drags to the desktop.
   if (!nsContentUtils::IsSystemPrincipal(aSubjectPrincipal)) {
-    if (aFormat.EqualsLiteral("application/x-moz-file-promise") ||
-        aFormat.EqualsLiteral("application/x-moz-file")) {
+    if (aFormat.EqualsLiteral(kFilePromiseMime) ||
+        aFormat.EqualsLiteral(kFileMime)) {
       return NS_ERROR_DOM_SECURITY_ERR;
     }
 
     uint16_t type;
     aData->GetDataType(&type);
     if (type == nsIDataType::VTYPE_INTERFACE ||
         type == nsIDataType::VTYPE_INTERFACE_IS) {
       return NS_ERROR_DOM_SECURITY_ERR;
--- a/dom/events/DataTransfer.h
+++ b/dom/events/DataTransfer.h
@@ -175,17 +175,17 @@ public:
     if (mCursorState) {
       aCursor.AssignLiteral("default");
     } else {
       aCursor.AssignLiteral("auto");
     }
   }
 
   already_AddRefed<DOMStringList> MozTypesAt(uint32_t aIndex,
-                                             mozilla::ErrorResult& aRv);
+                                             mozilla::ErrorResult& aRv) const;
 
   void MozClearDataAt(const nsAString& aFormat, uint32_t aIndex,
                       mozilla::ErrorResult& aRv);
 
   void MozSetDataAt(JSContext* aCx, const nsAString& aFormat,
                     JS::Handle<JS::Value> aData, uint32_t aIndex,
                     mozilla::ErrorResult& aRv);
 
--- a/dom/events/EventNameList.h
+++ b/dom/events/EventNameList.h
@@ -265,16 +265,20 @@ NON_IDL_EVENT(mozbrowserafterkeydown,
 NON_IDL_EVENT(mozbrowserbeforekeyup,
               eBeforeKeyUp,
               EventNameType_None,
               eBeforeAfterKeyboardEventClass)
 NON_IDL_EVENT(mozbrowserafterkeyup,
               eAfterKeyUp,
               EventNameType_None,
               eBeforeAfterKeyboardEventClass)
+NON_IDL_EVENT(mozaccesskeynotfound,
+              eAccessKeyNotFound,
+              EventNameType_None,
+              eKeyboardEventClass)
 EVENT(loadeddata,
       eLoadedData,
       EventNameType_HTML,
       eBasicEventClass)
 EVENT(loadedmetadata,
       eLoadedMetaData,
       EventNameType_HTML,
       eBasicEventClass)
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -601,26 +601,26 @@ EventStateManager::PreHandleEvent(nsPres
     if (sIsPointerLocked) {
       return NS_ERROR_DOM_INVALID_STATE_ERR;
     }
     break;
   case eMouseDown: {
     switch (mouseEvent->button) {
     case WidgetMouseEvent::eLeftButton:
       BeginTrackingDragGesture(aPresContext, mouseEvent, aTargetFrame);
-      mLClickCount = mouseEvent->clickCount;
+      mLClickCount = mouseEvent->mClickCount;
       SetClickCount(mouseEvent, aStatus);
       sNormalLMouseEventInProcess = true;
       break;
     case WidgetMouseEvent::eMiddleButton:
-      mMClickCount = mouseEvent->clickCount;
+      mMClickCount = mouseEvent->mClickCount;
       SetClickCount(mouseEvent, aStatus);
       break;
     case WidgetMouseEvent::eRightButton:
-      mRClickCount = mouseEvent->clickCount;
+      mRClickCount = mouseEvent->mClickCount;
       SetClickCount(mouseEvent, aStatus);
       break;
     }
     break;
   }
   case eMouseUp: {
     switch (mouseEvent->button) {
       case WidgetMouseEvent::eLeftButton:
@@ -662,22 +662,22 @@ EventStateManager::PreHandleEvent(nsPres
 
     // Flag helps to suppress double event sending into process of content.
     // For more information see comment above, at eMouseEnterIntoWidget case.
     aEvent->StopCrossProcessForwarding();
 
     // If the event is not a top-level window exit, then it's not
     // really an exit --- we may have traversed widget boundaries but
     // we're still in our toplevel window.
-    if (mouseEvent->exit != WidgetMouseEvent::eTopLevel) {
+    if (mouseEvent->mExitFrom != WidgetMouseEvent::eTopLevel) {
       // Treat it as a synthetic move so we don't generate spurious
       // "exit" or "move" events.  Any necessary "out" or "over" events
       // will be generated by GenerateMouseEnterExit
       mouseEvent->mMessage = eMouseMove;
-      mouseEvent->reason = WidgetMouseEvent::eSynthesized;
+      mouseEvent->mReason = WidgetMouseEvent::eSynthesized;
       // then fall through...
     } else {
       if (sPointerEventEnabled) {
         // We should synthetize corresponding pointer events
         GeneratePointerEnterExit(ePointerLeave, mouseEvent);
       }
       GenerateMouseEnterExit(mouseEvent);
       //This is a window level mouse exit event and should stop here
@@ -727,25 +727,28 @@ EventStateManager::PreHandleEvent(nsPres
       if (keyEvent->IsAlt())
         modifierMask |= NS_MODIFIER_ALT;
       if (keyEvent->IsMeta())
         modifierMask |= NS_MODIFIER_META;
       if (keyEvent->IsOS())
         modifierMask |= NS_MODIFIER_OS;
 
       // Prevent keyboard scrolling while an accesskey modifier is in use.
-      if (modifierMask &&
-          (modifierMask == Prefs::ChromeAccessModifierMask() ||
-           modifierMask == Prefs::ContentAccessModifierMask())) {
-        AutoTArray<uint32_t, 10> accessCharCodes;
-        keyEvent->GetAccessKeyCandidates(accessCharCodes);
-
-        if (HandleAccessKey(aPresContext, accessCharCodes,
-                            keyEvent->IsTrusted(), modifierMask)) {
-          *aStatus = nsEventStatus_eConsumeNoDefault;
+      if (modifierMask) {
+        bool matchesContentAccessKey = (modifierMask == Prefs::ContentAccessModifierMask());
+        
+        if (modifierMask == Prefs::ChromeAccessModifierMask() ||
+            matchesContentAccessKey) {
+          AutoTArray<uint32_t, 10> accessCharCodes;
+          keyEvent->GetAccessKeyCandidates(accessCharCodes);
+
+          if (HandleAccessKey(keyEvent, aPresContext, accessCharCodes,
+                              modifierMask, matchesContentAccessKey)) {
+            *aStatus = nsEventStatus_eConsumeNoDefault;
+          }
         }
       }
     }
     // then fall through...
     MOZ_FALLTHROUGH;
   case eBeforeKeyDown:
   case eKeyDown:
   case eAfterKeyDown:
@@ -1031,63 +1034,69 @@ EventStateManager::GetAccessKeyLabelPref
     aPrefix.Append(modifierText + separator);
   }
   if (modifierMask & NS_MODIFIER_SHIFT) {
     nsContentUtils::GetShiftText(modifierText);
     aPrefix.Append(modifierText + separator);
   }
 }
 
-struct AccessKeyInfo
+struct MOZ_STACK_CLASS AccessKeyInfo
 {
+  WidgetKeyboardEvent* event;
   nsTArray<uint32_t>& charCodes;
-  bool isTrusted;
   int32_t modifierMask;
 
-  AccessKeyInfo(nsTArray<uint32_t>& aCharCodes, bool aIsTrusted, int32_t aModifierMask)
-    : charCodes(aCharCodes)
-    , isTrusted(aIsTrusted)
+  AccessKeyInfo(WidgetKeyboardEvent* aEvent, nsTArray<uint32_t>& aCharCodes, int32_t aModifierMask)
+    : event(aEvent)
+    , charCodes(aCharCodes)
     , modifierMask(aModifierMask)
   {
   }
 };
 
-static void
+static bool
 HandleAccessKeyInRemoteChild(TabParent* aTabParent, void* aArg)
 {
   AccessKeyInfo* accessKeyInfo = static_cast<AccessKeyInfo*>(aArg);
 
   // Only forward accesskeys for the active tab.
   bool active;
   aTabParent->GetDocShellIsActive(&active);
   if (active) {
-    aTabParent->HandleAccessKey(accessKeyInfo->charCodes, accessKeyInfo->isTrusted,
+    accessKeyInfo->event->mAccessKeyForwardedToChild = true;
+    aTabParent->HandleAccessKey(*accessKeyInfo->event,
+                                accessKeyInfo->charCodes,
                                 accessKeyInfo->modifierMask);
-  }
+    return true;
+  }
+
+  return false;
 }
 
 bool
-EventStateManager::HandleAccessKey(nsPresContext* aPresContext,
+EventStateManager::HandleAccessKey(WidgetKeyboardEvent* aEvent,
+                                   nsPresContext* aPresContext,
                                    nsTArray<uint32_t>& aAccessCharCodes,
-                                   bool aIsTrusted,
+                                   bool aMatchesContentAccessKey,
                                    nsIDocShellTreeItem* aBubbledFrom,
                                    ProcessingAccessKeyState aAccessKeyState,
                                    int32_t aModifierMask)
 {
   EnsureDocument(mPresContext);
   nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell();
   if (NS_WARN_IF(!docShell) || NS_WARN_IF(!mDocument)) {
     return false;
   }
 
   // Alt or other accesskey modifier is down, we may need to do an accesskey.
   if (mAccessKeys.Count() > 0 &&
       aModifierMask == GetAccessModifierMaskFor(docShell)) {
     // Someone registered an accesskey.  Find and activate it.
-    if (ExecuteAccessKey(aAccessCharCodes, aIsTrusted)) {
+    if (ExecuteAccessKey(aAccessCharCodes, aEvent->IsTrusted())) {
       return true;
     }
   }
 
   int32_t childCount;
   docShell->GetChildCount(&childCount);
   for (int32_t counter = 0; counter < childCount; counter++) {
     // Not processing the child which bubbles up the handling
@@ -1110,17 +1119,18 @@ EventStateManager::HandleAccessKey(nsPre
       }
 
       nsPresContext *subPC = subPS->GetPresContext();
 
       EventStateManager* esm =
         static_cast<EventStateManager*>(subPC->EventStateManager());
 
       if (esm &&
-          esm->HandleAccessKey(subPC, aAccessCharCodes, aIsTrusted, nullptr,
+          esm->HandleAccessKey(aEvent, subPC, aAccessCharCodes,
+                               aMatchesContentAccessKey, nullptr,
                                eAccessKeyProcessingDown, aModifierMask)) {
         return true;
       }
     }
   }// if end . checking all sub docshell ends here.
 
   // bubble up the process to the parent docshell if necessary
   if (eAccessKeyProcessingDown != aAccessKeyState) {
@@ -1132,32 +1142,37 @@ EventStateManager::HandleAccessKey(nsPre
       NS_ASSERTION(parentPS, "Our PresShell exists but the parent's does not?");
 
       nsPresContext *parentPC = parentPS->GetPresContext();
       NS_ASSERTION(parentPC, "PresShell without PresContext");
 
       EventStateManager* esm =
         static_cast<EventStateManager*>(parentPC->EventStateManager());
       if (esm &&
-          esm->HandleAccessKey(parentPC, aAccessCharCodes, aIsTrusted, docShell,
-                               eAccessKeyProcessingUp, aModifierMask)) {
+          esm->HandleAccessKey(aEvent, parentPC, aAccessCharCodes,
+                               aMatchesContentAccessKey, docShell,
+                               eAccessKeyProcessingDown, aModifierMask)) {
         return true;
       }
     }
   }// if end. bubble up process
 
-  // Now try remote children
-  if (mDocument && mDocument->GetWindow()) {
+  // If the content access key modifier is pressed, try remote children
+  if (aMatchesContentAccessKey && mDocument && mDocument->GetWindow()) {
     // If the focus is currently on a node with a TabParent, the key event will
     // get forwarded to the child process and HandleAccessKey called from there.
     // If focus is somewhere else, then we need to check the remote children.
     nsFocusManager* fm = nsFocusManager::GetFocusManager();
     nsIContent* focusedContent = fm ? fm->GetFocusedContent() : nullptr;
-    if (!TabParent::GetFrom(focusedContent)) {
-      AccessKeyInfo accessKeyInfo(aAccessCharCodes, aIsTrusted, aModifierMask);
+    if (TabParent::GetFrom(focusedContent)) {
+      // A remote child process is focused. The key event should get sent to
+      // the child process.
+      aEvent->mAccessKeyForwardedToChild = true;
+    } else {
+      AccessKeyInfo accessKeyInfo(aEvent, aAccessCharCodes, aModifierMask);
       nsContentUtils::CallOnAllRemoteChildren(mDocument->GetWindow(),
                                               HandleAccessKeyInRemoteChild, &accessKeyInfo);
     }
   }
 
   return false;
 }// end of HandleAccessKey
 
@@ -1525,17 +1540,17 @@ EventStateManager::FireContextClick()
         allowedToDispatch = false;
       }
     }
 
     if (allowedToDispatch) {
       // init the event while mCurrentTarget is still good
       WidgetMouseEvent event(true, eContextMenu, targetWidget,
                              WidgetMouseEvent::eReal);
-      event.clickCount = 1;
+      event.mClickCount = 1;
       FillInEventFromGestureDown(&event);
         
       // stop selection tracking, we're in control now
       if (mCurrentTarget)
       {
         RefPtr<nsFrameSelection> frameSel =
           mCurrentTarget->GetFrameSelection();
         
@@ -3938,17 +3953,17 @@ EventStateManager::DispatchMouseOrPointe
     // If we are entering/leaving remote content, dispatch a mouse enter/exit
     // event to the remote frame.
     if (IsRemoteTarget(aTargetContent)) {
       if (aMessage == eMouseOut) {
         // For remote content, send a "top-level" widget mouse exit event.
         nsAutoPtr<WidgetMouseEvent> remoteEvent;
         CreateMouseOrPointerWidgetEvent(aMouseEvent, eMouseExitFromWidget,
                                         aRelatedContent, remoteEvent);
-        remoteEvent->exit = WidgetMouseEvent::eTopLevel;
+        remoteEvent->mExitFrom = WidgetMouseEvent::eTopLevel;
 
         // mCurrentTarget is set to the new target, so we must reset it to the
         // old target and then dispatch a cross-process event. (mCurrentTarget
         // will be set back below.) HandleCrossProcessEvent will query for the
         // proper target via GetEventTarget which will return mCurrentTarget.
         mCurrentTarget = targetFrame;
         HandleCrossProcessEvent(remoteEvent, &status);
       } else if (aMessage == eMouseOver) {
@@ -4560,56 +4575,56 @@ EventStateManager::SetClickCount(WidgetM
   case WidgetMouseEvent::eLeftButton:
     if (aEvent->mMessage == eMouseDown) {
       mLastLeftMouseDownContent = mouseContent;
       mLastLeftMouseDownContentParent = mouseContentParent;
     } else if (aEvent->mMessage == eMouseUp) {
       if (mLastLeftMouseDownContent == mouseContent ||
           mLastLeftMouseDownContentParent == mouseContent ||
           mLastLeftMouseDownContent == mouseContentParent) {
-        aEvent->clickCount = mLClickCount;
+        aEvent->mClickCount = mLClickCount;
         mLClickCount = 0;
       } else {
-        aEvent->clickCount = 0;
+        aEvent->mClickCount = 0;
       }
       mLastLeftMouseDownContent = nullptr;
       mLastLeftMouseDownContentParent = nullptr;
     }
     break;
 
   case WidgetMouseEvent::eMiddleButton:
     if (aEvent->mMessage == eMouseDown) {
       mLastMiddleMouseDownContent = mouseContent;
       mLastMiddleMouseDownContentParent = mouseContentParent;
     } else if (aEvent->mMessage == eMouseUp) {
       if (mLastMiddleMouseDownContent == mouseContent ||
           mLastMiddleMouseDownContentParent == mouseContent ||
           mLastMiddleMouseDownContent == mouseContentParent) {
-        aEvent->clickCount = mMClickCount;
+        aEvent->mClickCount = mMClickCount;
         mMClickCount = 0;
       } else {
-        aEvent->clickCount = 0;
+        aEvent->mClickCount = 0;
       }
       mLastMiddleMouseDownContent = nullptr;
       mLastMiddleMouseDownContentParent = nullptr;
     }
     break;
 
   case WidgetMouseEvent::eRightButton:
     if (aEvent->mMessage == eMouseDown) {
       mLastRightMouseDownContent = mouseContent;
       mLastRightMouseDownContentParent = mouseContentParent;
     } else if (aEvent->mMessage == eMouseUp) {
       if (mLastRightMouseDownContent == mouseContent ||
           mLastRightMouseDownContentParent == mouseContent ||
           mLastRightMouseDownContent == mouseContentParent) {
-        aEvent->clickCount = mRClickCount;
+        aEvent->mClickCount = mRClickCount;
         mRClickCount = 0;
       } else {
-        aEvent->clickCount = 0;
+        aEvent->mClickCount = 0;
       }
       mLastRightMouseDownContent = nullptr;
       mLastRightMouseDownContentParent = nullptr;
     }
     break;
   }
 
   return NS_OK;
@@ -4618,31 +4633,31 @@ EventStateManager::SetClickCount(WidgetM
 nsresult
 EventStateManager::CheckForAndDispatchClick(WidgetMouseEvent* aEvent,
                                             nsEventStatus* aStatus)
 {
   nsresult ret = NS_OK;
 
   //If mouse is still over same element, clickcount will be > 1.
   //If it has moved it will be zero, so no click.
-  if (0 != aEvent->clickCount) {
+  if (aEvent->mClickCount) {
     //Check that the window isn't disabled before firing a click
     //(see bug 366544).
     if (aEvent->mWidget && !aEvent->mWidget->IsEnabled()) {
       return ret;
     }
     //fire click
     bool notDispatchToContents =
      (aEvent->button == WidgetMouseEvent::eMiddleButton ||
       aEvent->button == WidgetMouseEvent::eRightButton);
 
     WidgetMouseEvent event(aEvent->IsTrusted(), eMouseClick,
                            aEvent->mWidget, WidgetMouseEvent::eReal);
     event.mRefPoint = aEvent->mRefPoint;
-    event.clickCount = aEvent->clickCount;
+    event.mClickCount = aEvent->mClickCount;
     event.mModifiers = aEvent->mModifiers;
     event.buttons = aEvent->buttons;
     event.mTime = aEvent->mTime;
     event.mTimeStamp = aEvent->mTimeStamp;
     event.mFlags.mNoContentDispatch = notDispatchToContents;
     event.button = aEvent->button;
     event.inputSource = aEvent->inputSource;
 
@@ -4660,23 +4675,23 @@ EventStateManager::CheckForAndDispatchCl
       if (!mouseContent && !mCurrentTarget) {
         return NS_OK;
       }
 
       // HandleEvent clears out mCurrentTarget which we might need again
       nsWeakFrame currentTarget = mCurrentTarget;
       ret = presShell->HandleEventWithTarget(&event, currentTarget,
                                              mouseContent, aStatus);
-      if (NS_SUCCEEDED(ret) && aEvent->clickCount == 2 &&
+      if (NS_SUCCEEDED(ret) && aEvent->mClickCount == 2 &&
           mouseContent && mouseContent->IsInComposedDoc()) {
         //fire double click
         WidgetMouseEvent event2(aEvent->IsTrusted(), eMouseDoubleClick,
                                 aEvent->mWidget, WidgetMouseEvent::eReal);
         event2.mRefPoint = aEvent->mRefPoint;
-        event2.clickCount = aEvent->clickCount;
+        event2.mClickCount = aEvent->mClickCount;
         event2.mModifiers = aEvent->mModifiers;
         event2.buttons = aEvent->buttons;
         event2.mFlags.mNoContentDispatch = notDispatchToContents;
         event2.button = aEvent->button;
         event2.inputSource = aEvent->inputSource;
 
         ret = presShell->HandleEventWithTarget(&event2, currentTarget,
                                                mouseContent, aStatus);
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -178,23 +178,25 @@ public:
    *
    * @param  aContent  the given element (must not be null)
    * @return           registered accesskey
    */
   uint32_t GetRegisteredAccessKey(nsIContent* aContent);
 
   static void GetAccessKeyLabelPrefix(dom::Element* aElement, nsAString& aPrefix);
 
-  bool HandleAccessKey(nsPresContext* aPresContext,
+  bool HandleAccessKey(WidgetKeyboardEvent* aEvent,
+                       nsPresContext* aPresContext,
                        nsTArray<uint32_t>& aAccessCharCodes,
-                       bool aIsTrusted,
-                       int32_t aModifierMask)
+                       int32_t aModifierMask,
+                       bool aMatchesContentAccessKey)
   {
-    return HandleAccessKey(aPresContext, aAccessCharCodes, aIsTrusted,
-                           nullptr, eAccessKeyProcessingNormal, aModifierMask);
+    return HandleAccessKey(aEvent, aPresContext, aAccessCharCodes,
+                           aMatchesContentAccessKey, nullptr,
+                           eAccessKeyProcessingNormal, aModifierMask);
   }
 
   nsresult SetCursor(int32_t aCursor, imgIContainer* aContainer,
                      bool aHaveHotspot, float aHotspotX, float aHotspotY,
                      nsIWidget* aWidget, bool aLockCursor); 
 
   static void StartHandlingUserInput()
   {
@@ -430,37 +432,39 @@ protected:
 
   /**
    * Access key handling.  If there is registered content for the accesskey
    * given by the key event and modifier mask then call
    * content.PerformAccesskey(), otherwise call HandleAccessKey() recursively,
    * on descendant docshells first, then on the ancestor (with |aBubbledFrom|
    * set to the docshell associated with |this|), until something matches.
    *
+   * @param aEvent the keyboard event triggering the acccess key
    * @param aPresContext the presentation context
    * @param aAccessCharCodes list of charcode candidates
-   * @param aIsTrusted true if triggered by a trusted key event
+   * @param aMatchesContentAccessKey true if the content accesskey modifier is pressed
    * @param aBubbledFrom is used by an ancestor to avoid calling HandleAccessKey()
    *        on the child the call originally came from, i.e. this is the child
    *        that recursively called us in its Up phase. The initial caller
    *        passes |nullptr| here. This is to avoid an infinite loop.
    * @param aAccessKeyState Normal, Down or Up processing phase (see enums
    *        above). The initial event receiver uses 'normal', then 'down' when
    *        processing children and Up when recursively calling its ancestor.
    * @param aModifierMask modifier mask for the key event
    */
-  bool HandleAccessKey(nsPresContext* aPresContext,
-                     nsTArray<uint32_t>& aAccessCharCodes,
-                     bool aIsTrusted,
-                     nsIDocShellTreeItem* aBubbledFrom,
-                     ProcessingAccessKeyState aAccessKeyState,
-                     int32_t aModifierMask);
+  bool HandleAccessKey(WidgetKeyboardEvent* aEvent,
+                       nsPresContext* aPresContext,
+                       nsTArray<uint32_t>& aAccessCharCodes,
+                       bool aMatchesContentAccessKey,
+                       nsIDocShellTreeItem* aBubbledFrom,
+                       ProcessingAccessKeyState aAccessKeyState,
+                       int32_t aModifierMask);
 
   bool ExecuteAccessKey(nsTArray<uint32_t>& aAccessCharCodes,
-                          bool aIsTrustedEvent);
+                        bool aIsTrustedEvent);
 
   //---------------------------------------------
   // DocShell Focus Traversal Methods
   //---------------------------------------------
 
   nsIContent* GetFocusedContent();
   bool IsShellVisible(nsIDocShell* aShell);
 
--- a/dom/events/KeyboardEvent.cpp
+++ b/dom/events/KeyboardEvent.cpp
@@ -193,16 +193,17 @@ KeyboardEvent::CharCode()
   case eKeyDownOnPlugin:
   case eAfterKeyDown:
   case eBeforeKeyUp:
   case eKeyUp:
   case eKeyUpOnPlugin:
   case eAfterKeyUp:
     return 0;
   case eKeyPress:
+  case eAccessKeyNotFound:
     return mEvent->AsKeyboardEvent()->charCode;
   default:
     break;
   }
   return 0;
 }
 
 NS_IMETHODIMP
--- a/dom/events/MouseEvent.cpp
+++ b/dom/events/MouseEvent.cpp
@@ -32,19 +32,19 @@ MouseEvent::MouseEvent(EventTarget* aOwn
   else {
     mEventIsInternal = true;
     mEvent->mTime = PR_Now();
     mEvent->mRefPoint = LayoutDeviceIntPoint(0, 0);
     mouseEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
   }
 
   if (mouseEvent) {
-    MOZ_ASSERT(mouseEvent->reason != WidgetMouseEvent::eSynthesized,
+    MOZ_ASSERT(mouseEvent->mReason != WidgetMouseEvent::eSynthesized,
                "Don't dispatch DOM events from synthesized mouse events");
-    mDetail = mouseEvent->clickCount;
+    mDetail = mouseEvent->mClickCount;
   }
 }
 
 NS_IMPL_ADDREF_INHERITED(MouseEvent, UIEvent)
 NS_IMPL_RELEASE_INHERITED(MouseEvent, UIEvent)
 
 NS_INTERFACE_MAP_BEGIN(MouseEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOMMouseEvent)
@@ -82,17 +82,17 @@ MouseEvent::InitMouseEvent(const nsAStri
       mouseEventBase->InitBasicModifiers(aCtrlKey, aAltKey, aShiftKey, aMetaKey);
       mClientPoint.x = aClientX;
       mClientPoint.y = aClientY;
       mouseEventBase->mRefPoint.x = aScreenX;
       mouseEventBase->mRefPoint.y = aScreenY;
 
       WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent();
       if (mouseEvent) {
-        mouseEvent->clickCount = aDetail;
+        mouseEvent->mClickCount = aDetail;
       }
       break;
     }
     default:
        break;
   }
 }
 
--- a/dom/events/test/test_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -600,16 +600,20 @@ const kEventConstructors = {
   USSDReceivedEvent:                         { create: function (aName, aProps) {
                                                           return new USSDReceivedEvent(aName, aProps);
                                                        },
                                              },
   WheelEvent:                                { create: function (aName, aProps) {
                                                          return new WheelEvent(aName, aProps);
                                                        },
                                              },
+  WebGLContextEvent:                         { create: function (aName, aProps) {
+                                                         return new WebGLContextEvent(aName, aProps);
+                                                       },
+                                             },
 };
 
 for (var name of Object.keys(kEventConstructors)) {
   if (!kEventConstructors[name].chromeOnly) {
     continue;
   }
   if (window[name]) {
     ok(false, name + " should be chrome only.");
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -398,18 +398,19 @@ WorkerFetchResolver::OnResponseEnd()
     new WorkerFetchResponseEndRunnable(mPromiseProxy);
 
   if (!r->Dispatch()) {
     RefPtr<WorkerFetchResponseEndControlRunnable> cr =
       new WorkerFetchResponseEndControlRunnable(mPromiseProxy);
     // This can fail if the worker thread is canceled or killed causing
     // the PromiseWorkerProxy to give up its WorkerFeature immediately,
     // allowing the worker thread to become Dead.
-    NS_WARN_IF_FALSE(cr->Dispatch(),
-                     "Failed to dispatch WorkerFetchResponseEndControlRunnable");
+    if (!cr->Dispatch()) {
+      NS_WARNING("Failed to dispatch WorkerFetchResponseEndControlRunnable");
+    }
   }
 }
 
 namespace {
 nsresult
 ExtractFromArrayBuffer(const ArrayBuffer& aBuffer,
                        nsIInputStream** aStream)
 {
--- a/dom/html/HTMLImageElement.cpp
+++ b/dom/html/HTMLImageElement.cpp
@@ -41,20 +41,24 @@
 
 #include "nsILoadGroup.h"
 
 #include "nsRuleData.h"
 
 #include "nsIDOMHTMLMapElement.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventStates.h"
+#include "mozilla/net/ReferrerPolicy.h"
 
 #include "nsLayoutUtils.h"
 
 #include "mozilla/Preferences.h"
+
+using namespace mozilla::net;
+
 static const char *kPrefSrcsetEnabled = "dom.image.srcset.enabled";
 
 NS_IMPL_NS_NEW_HTML_ELEMENT(Image)
 
 #ifdef DEBUG
 // Is aSubject a previous sibling of aNode.
 static bool IsPreviousSibling(nsINode *aSubject, nsINode *aNode)
 {
@@ -507,17 +511,17 @@ HTMLImageElement::IsHTMLFocusable(bool a
   return false;
 }
 
 nsresult
 HTMLImageElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                           nsIAtom* aPrefix, const nsAString& aValue,
                           bool aNotify)
 {
-  bool forceReloadWithNewCORSMode = false;
+  bool forceReload = false;
   // We need to force our image to reload.  This must be done here, not in
   // AfterSetAttr or BeforeSetAttr, because we want to do it even if the attr is
   // being set to its existing value, which is normally optimized away as a
   // no-op.
   //
   // If we are in responsive mode, we drop the forced reload behavior,
   // but still trigger a image load task for img.src = img.src per
   // spec.
@@ -553,27 +557,39 @@ HTMLImageElement::SetAttr(int32_t aNameS
     }
   } else if (aNameSpaceID == kNameSpaceID_None &&
              aName == nsGkAtoms::crossorigin &&
              aNotify) {
     nsAttrValue attrValue;
     ParseCORSValue(aValue, attrValue);
     if (GetCORSMode() != AttrValueToCORSMode(&attrValue)) {
       // Force a new load of the image with the new cross origin policy.
-      forceReloadWithNewCORSMode = true;
+      forceReload = true;
+    }
+  } else if (aName == nsGkAtoms::referrerpolicy &&
+      aNameSpaceID == kNameSpaceID_None &&
+      aNotify) {
+    ReferrerPolicy referrerPolicy = ReferrerPolicyFromString(aValue);
+    if (!InResponsiveMode() && referrerPolicy != GetImageReferrerPolicy()) {
+      // XXX: Bug 1076583 - We still use the older synchronous algorithm
+      // Because referrerPolicy is not treated as relevant mutations, setting
+      // the attribute will neither trigger a reload nor update the referrer
+      // policy of the loading channel (whether it has previously completed or
+      // not). Force a new load of the image with the new referrerpolicy.
+      forceReload = true;
     }
   }
 
   nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix,
                                               aValue, aNotify);
 
   // Because we load image synchronously in non-responsive-mode, we need to do
   // reload after the attribute has been set if the reload is triggerred by
   // cross origin changing.
-  if (forceReloadWithNewCORSMode) {
+  if (forceReload) {
     if (InResponsiveMode()) {
       // per spec, full selection runs when this changes, even though
       // it doesn't directly affect the source selection
       QueueImageLoadTask(true);
     } else {
       // Bug 1076583 - We still use the older synchronous algorithm in
       // non-responsive mode. Force a new load of the image with the
       // new cross origin policy
--- a/dom/html/HTMLLabelElement.cpp
+++ b/dom/html/HTMLLabelElement.cpp
@@ -156,17 +156,17 @@ HTMLLabelElement::PostHandleEvent(EventC
           // Don't click the for-content if we did drag-select text or if we
           // have a kbd modifier (which adjusts a selection).
           if (dragSelect || mouseEvent->IsShift() || mouseEvent->IsControl() ||
               mouseEvent->IsAlt() || mouseEvent->IsMeta()) {
             break;
           }
           // Only set focus on the first click of multiple clicks to prevent
           // to prevent immediate de-focus.
-          if (mouseEvent->clickCount <= 1) {
+          if (mouseEvent->mClickCount <= 1) {
             nsIFocusManager* fm = nsFocusManager::GetFocusManager();
             if (fm) {
               // Use FLAG_BYMOVEFOCUS here so that the label is scrolled to.
               // Also, within HTMLInputElement::PostHandleEvent, inputs will
               // be selected only when focused via a key or when the navigation
               // flag is used and we want to select the text on label clicks as
               // well.
               // If the label has been clicked by the user, we also want to
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -2769,17 +2769,17 @@ nsresult HTMLMediaElement::BindToTree(ns
     // It's value may have changed, so update it.
     UpdatePreloadAction();
   }
   mElementInTreeState = ELEMENT_INTREE;
 
   if (mDecoder) {
     // When the MediaElement is binding to tree, the dormant status is
     // aligned to document's hidden status.
-    mDecoder->NotifyOwnerActivityChanged();
+    mDecoder->NotifyOwnerActivityChanged(!IsHidden());
   }
 
   return rv;
 }
 
 #ifdef MOZ_EME
 void
 HTMLMediaElement::ReportEMETelemetry()
@@ -2867,17 +2867,17 @@ void HTMLMediaElement::UnbindFromTree(bo
   }
 
   mElementInTreeState = ELEMENT_NOT_INTREE_HAD_INTREE;
 
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 
   if (mDecoder) {
     MOZ_ASSERT(IsHidden());
-    mDecoder->NotifyOwnerActivityChanged();
+    mDecoder->NotifyOwnerActivityChanged(false);
   }
 }
 
 /* static */
 CanPlayStatus
 HTMLMediaElement::GetCanPlay(const nsAString& aType,
                              DecoderDoctorDiagnostics* aDiagnostics)
 {
@@ -3647,16 +3647,18 @@ void HTMLMediaElement::DecodeError()
   ReportLoadError("MediaLoadDecodeError", params, ArrayLength(params));
 
   if (mDecoder) {
     ShutdownDecoder();
   }
   RemoveMediaElementFromURITable();
   mLoadingSrc = nullptr;
   mMediaSource = nullptr;
+  AudioTracks()->EmptyTracks();
+  VideoTracks()->EmptyTracks();
   if (mIsLoadingFromSourceChildren) {
     mError = nullptr;
     if (mSourceLoadCandidate) {
       DispatchAsyncSourceError(mSourceLoadCandidate);
       QueueLoadFromSourceTask();
     } else {
       NS_WARNING("Should know the source we were loading from!");
     }
@@ -4527,20 +4529,18 @@ void HTMLMediaElement::NotifyOwnerDocume
       NotifyAudioChannelAgent(false);
     }
   }
 }
 
 bool
 HTMLMediaElement::NotifyOwnerDocumentActivityChangedInternal()
 {
-  nsIDocument* ownerDoc = OwnerDoc();
   if (mDecoder && !IsBeingDestroyed()) {
-    mDecoder->SetElementVisibility(!ownerDoc->Hidden());
-    mDecoder->NotifyOwnerActivityChanged();
+    mDecoder->NotifyOwnerActivityChanged(!IsHidden());
   }
 
   bool pauseElement = !IsActive();
   SuspendOrResumeElement(pauseElement, !IsActive());
 
   if (!mPausedForInactiveDocumentOrChannel &&
       mPlayBlockedBecauseHidden &&
       !OwnerDoc()->Hidden()) {
--- a/dom/html/test/file_fullscreen-api.html
+++ b/dom/html/test/file_fullscreen-api.html
@@ -51,32 +51,34 @@ function sendMouseClick(element) {
 }
 
 function fullScreenElement() {
   return document.getElementById('full-screen-element');
 }
 
 function enter1(event) {
   is(event.target, document, "Event target should be full-screen document #1");
+  ok(document.fullscreen, "Document should be in fullscreen");
   is(document.fullscreenElement, fullScreenElement(),
      "Full-screen element should be div element.");
   ok(document.fullscreenElement.matches(":fullscreen"),
      "FSE should match :fullscreen");
   var fse = fullScreenElement();
   addFullscreenChangeContinuation("exit", exit1);
   fse.parentNode.removeChild(fse);
   is(document.fullscreenElement, null,
      "Full-screen element should be null after removing.");
   document.body.appendChild(fse);
   is(document.fullscreenElement, null,
      "Full-screen element should still be null after re-adding former FSE.");
 }
 
 function exit1(event) {
   is(event.target, document, "Event target should be full-screen document #2");
+  ok(!document.fullscreen, "Document should not be in fullscreen");
   is(document.fullscreenElement, null, "Full-screen element should be null.");
   iframe = document.createElement("iframe");
   iframe.allowFullscreen = true;
   addFullscreenChangeContinuation("enter", enter2);
   document.body.appendChild(iframe);
   iframe.src = iframeContents;
 }
 
new file mode 100644
--- /dev/null
+++ b/dom/html/test/file_fullscreen-lenient-setters.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="UTF-8">
+  <title>Test for Bug 1268798</title>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<script>
+"use strict";
+
+function ok(condition, msg) {
+  opener.ok(condition, "[lenient-setters] " + msg);
+}
+
+function is(a, b, msg) {
+  opener.is(a, b, "[lenient-setters] " + msg);
+}
+
+function info(msg) {
+  opener.info("[lenient-setters] " + msg);
+}
+
+let unattachedDiv = document.createElement("div");
+
+function begin() {
+  var originalValue = document.fullscreen;
+  try {
+    document.fullscreen = !document.fullscreen;
+    is(document.fullscreen, originalValue,
+       "fullscreen should not be changed");
+  } catch (e) {
+    ok(false, "Setting fullscreen should not throw");
+  }
+
+  var originalElem = document.fullscreenElement;
+  try {
+    document.fullscreenElement = unattachedDiv;
+    document.fullscreenElement = [];
+    is(document.fullscreenElement, originalElem,
+       "fullscreenElement should not be changed");
+  } catch (e) {
+    ok(false, "Setting fullscreenElement should not throw");
+  }
+
+  var originalEnabled = document.fullscreenEnabled;
+  try {
+    document.fullscreenEnabled = !originalEnabled;
+    is(document.fullscreenEnabled, originalEnabled,
+       "fullscreenEnabled should not be changed");
+  } catch (e) {
+    ok(false, "Setting fullscreenEnabled should not throw");
+  }
+
+  opener.nextTest();
+}
+
+</script>
+</body>
+</html>
--- a/dom/html/test/file_fullscreen-unprefix-disabled-inner.html
+++ b/dom/html/test/file_fullscreen-unprefix-disabled-inner.html
@@ -26,16 +26,17 @@ SimpleTest.requestFlakyTimeout(
   "need to wait for a while to confirm no unexpected event is dispatched");
 
 let div = document.getElementById("fullscreen");
 let unattachedDiv = document.createElement('div');
 
 function begin() {
   ok(!("requestFullscreen" in div), "No element.requestFullscreen");
   ok(!("exitFullscreen" in document), "No document.exitFullscreen");
+  ok(!("fullscreen" in document), "No document.fullscreen");
   ok(!("fullscreenElement" in document), "No document.fullscreenElement");
   ok(!("fullscreenEnabled" in document), "No document.fullscreenEnabled");
   ok(!("onfullscreenchange" in document), "No document.onfullscreenchange");
   ok(!("onfullscreenerror" in document), "No document.onfullscreenerror");
 
   for (var event of ["fullscreenchange", "fullscreenerror"]) {
     let customEvent = new Event(event, {bubbles: true});
     let gotCustomEventFromWindow = false;
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -48,16 +48,17 @@ support-files =
   file_formSubmission_text.txt
   file_fullscreen-api.html
   file_fullscreen-backdrop.html
   file_fullscreen-denied-inner.html
   file_fullscreen-denied.html
   file_fullscreen-esc-exit-inner.html
   file_fullscreen-esc-exit.html
   file_fullscreen-hidden.html
+  file_fullscreen-lenient-setters.html
   file_fullscreen-multiple-inner.html
   file_fullscreen-multiple.html
   file_fullscreen-navigation.html
   file_fullscreen-nested.html
   file_fullscreen-prefixed.html
   file_fullscreen-plugins.html
   file_fullscreen-rollback.html
   file_fullscreen-scrollbar.html
--- a/dom/html/test/test_fullscreen-api.html
+++ b/dom/html/test/test_fullscreen-api.html
@@ -39,16 +39,17 @@ var gTestWindows = [
   "file_fullscreen-navigation.html",
   "file_fullscreen-scrollbar.html",
   "file_fullscreen-selector.html",
   "file_fullscreen-top-layer.html",
   "file_fullscreen-backdrop.html",
   "file_fullscreen-nested.html",
   "file_fullscreen-prefixed.html",
   "file_fullscreen-unprefix-disabled.html",
+  "file_fullscreen-lenient-setters.html",
 ];
 
 var testWindow = null;
 var gTestIndex = 0;
 
 function finish() {
   SimpleTest.finish();
 }
--- a/dom/interfaces/base/nsITabChild.idl
+++ b/dom/interfaces/base/nsITabChild.idl
@@ -20,11 +20,14 @@ interface nsITabChild : nsISupports
   [notxpcom] void sendRequestFocus(in boolean canFocus);
 
   [notxpcom] void sendGetTabCount(out uint32_t tabCount);
 
   [noscript, notxpcom] void enableDisableCommands(in AString action,
                                                   in CommandsArrayRef enabledCommands,
                                                   in CommandsArrayRef disabledCommands);
 
+  [noscript] void remoteSizeShellTo(in int32_t width, in int32_t height,
+                                    in int32_t shellItemWidth, in int32_t shellItemHeight);
+
   readonly attribute uint64_t tabId;
 };
 
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -154,16 +154,31 @@ parent:
 
 parent:
     /**
      * When child sends this message, parent should move focus to
      * the next or previous focusable element or document.
      */
     async MoveFocus(bool forward, bool forDocumentNavigation);
 
+    /**
+     * SizeShellTo request propagation to parent.
+     *
+     * aFlag            Can indicate if one of the dimensions should be ignored.
+     *                  If only one dimension has changed it has to be indicated
+     *                  by the nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_* flags.
+     * aShellItemWidth,
+     * aShellItemHeight On parent side we won't be able to decide the dimensions
+     *                  of the shell item parameter in the original SizeShellTo
+     *                  call so we send over its dimensions that will be used
+     *                  for the actual resize.
+     **/
+    async SizeShellTo(uint32_t aFlag, int32_t aWidth, int32_t aHeight,
+                      int32_t aShellItemWidth, int32_t aShellItemHeight);
+
     async Event(RemoteDOMEvent aEvent);
 
     sync SyncMessage(nsString aMessage, ClonedMessageData aData,
                      CpowEntry[] aCpows, Principal aPrincipal)
       returns (StructuredCloneData[] retval);
 
     prio(high) sync RpcMessage(nsString aMessage, ClonedMessageData aData,
                                CpowEntry[] aCpows, Principal aPrincipal)
@@ -486,16 +501,18 @@ parent:
     /**
      * Returns the number of tabs in the window via the out parameter.
      * If the number of tabs can't be determined, returns 0.
      *
      * @param aValue where to store the tab count
      */
     sync GetTabCount() returns (uint32_t value);
 
+    async AccessKeyNotHandled(WidgetKeyboardEvent event);
+
 child:
     async NativeSynthesisResponse(uint64_t aObserverId, nsCString aResponse);
 
 
 parent:
 
     /**
      * Child informs the parent that the graphics objects are ready for
@@ -726,21 +743,22 @@ child:
      * with another.
      */
     async SwappedWithOtherRemoteLoader(IPCTabContext context);
 
     /**
      * A potential accesskey was just pressed. Look for accesskey targets
      * using the list of provided charCodes.
      *
-     * @param charCode array of potential character codes
+     * @param event keyboard event
      * @param isTrusted true if triggered by a trusted key event
      * @param modifierMask indicates which accesskey modifiers are pressed
      */
-    async HandleAccessKey(uint32_t[] charCodes, bool isTrusted, int32_t modifierMask);
+    async HandleAccessKey(WidgetKeyboardEvent event,
+                          uint32_t[] charCodes, int32_t modifierMask);
 
     /**
      * Propagate a refresh to the child process
      */
     async AudioChannelChangeNotification(uint32_t aAudioChannel,
                                          float aVolume,
                                          bool aMuted);
 
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -47,16 +47,17 @@
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/TouchEvents.h"
 #include "mozilla/unused.h"
 #include "mozIApplication.h"
 #include "nsContentUtils.h"
 #include "nsDocShell.h"
 #include "nsEmbedCID.h"
+#include "nsGlobalWindow.h"
 #include <algorithm>
 #ifdef MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"
 #endif
 #include "nsFilePickerProxy.h"
 #include "mozilla/dom/Element.h"
 #include "nsIBaseWindow.h"
 #include "nsIBrowserDOMWindow.h"
@@ -938,17 +939,40 @@ NS_IMETHODIMP
 TabChild::DestroyBrowserWindow()
 {
   NS_WARNING("TabChild::DestroyBrowserWindow not supported in TabChild");
 
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
-TabChild::SizeBrowserTo(int32_t aCX, int32_t aCY)
+TabChild::RemoteSizeShellTo(int32_t aWidth, int32_t aHeight,
+                            int32_t aShellItemWidth, int32_t aShellItemHeight)
+{
+  nsCOMPtr<nsIDocShell> ourDocShell = do_GetInterface(WebNavigation());
+  nsCOMPtr<nsIBaseWindow> docShellAsWin(do_QueryInterface(ourDocShell));
+  int32_t width, height;
+  docShellAsWin->GetSize(&width, &height);
+
+  uint32_t flags = 0;
+  if (width == aWidth) {
+    flags |= nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_CX;
+  }
+
+  if (height == aHeight) {
+    flags |= nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_CY;
+  }
+
+  bool sent = SendSizeShellTo(flags, aWidth, aHeight, aShellItemWidth, aShellItemHeight);
+
+  return sent ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+TabChild::SizeBrowserTo(int32_t aWidth, int32_t aHeight)
 {
   NS_WARNING("TabChild::SizeBrowserTo not supported in TabChild");
 
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 TabChild::ShowAsModal()
@@ -982,19 +1006,47 @@ TabChild::SetStatusWithContext(uint32_t 
   // mRemoteFrame is a good indicator.
   if (mRemoteFrame)
     SendSetStatus(aStatusType, nsString(aStatusText));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabChild::SetDimensions(uint32_t aFlags, int32_t aX, int32_t aY,
-                             int32_t aCx, int32_t aCy)
+                        int32_t aCx, int32_t aCy)
 {
-  Unused << PBrowserChild::SendSetDimensions(aFlags, aX, aY, aCx, aCy);
+  // The parent is in charge of the dimension changes. If JS code wants to
+  // change the dimensions (moveTo, screenX, etc.) we send a message to the
+  // parent about the new requested dimension, the parent does the resize/move
+  // then send a message to the child to update itself. For APIs like screenX
+  // this function is called with the current value for the non-changed values.
+  // In a series of calls like window.screenX = 10; window.screenY = 10; for
+  // the second call, since screenX is not yet updated we might accidentally
+  // reset back screenX to it's old value. To avoid this if a parameter did not
+  // change we want the parent to ignore its value.
+  int32_t x, y, cx, cy;
+  GetDimensions(aFlags, &x, &y, &cx, &cy);
+
+  if (x == aX) {
+    aFlags |= nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_X;
+  }
+
+  if (y == aY) {
+    aFlags |= nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_Y;
+  }
+
+  if (cx == aCx) {
+    aFlags |= nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_CX;
+  }
+
+  if (cy == aCy) {
+    aFlags |= nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_CY;
+  }
+
+  Unused << SendSetDimensions(aFlags, aX, aY, aCx, aCy);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabChild::GetDimensions(uint32_t aFlags, int32_t* aX,
                              int32_t* aY, int32_t* aCx, int32_t* aCy)
 {
@@ -2067,20 +2119,27 @@ TabChild::RecvRealKeyEvent(const WidgetK
   WidgetKeyboardEvent localEvent(event);
   localEvent.mWidget = mPuppetWidget;
   nsEventStatus status = APZCCallbackHelper::DispatchWidgetEvent(localEvent);
 
   if (event.mMessage == eKeyDown) {
     mIgnoreKeyPressEvent = status == nsEventStatus_eConsumeNoDefault;
   }
 
+  // If a response is desired from the content process, resend the key event.
+  // If mAccessKeyForwardedToChild is set, then don't resend the key event yet
+  // as RecvHandleAccessKey will do this.
   if (localEvent.mFlags.mWantReplyFromContentProcess) {
     SendReplyKeyEvent(localEvent);
   }
 
+  if (localEvent.mAccessKeyForwardedToChild) {
+    SendAccessKeyNotHandled(localEvent);
+  }
+
   if (PresShell::BeforeAfterKeyboardEventEnabled()) {
     SendDispatchAfterKeyboardEvent(localEvent);
   }
 
   return true;
 }
 
 bool
@@ -2333,26 +2392,35 @@ TabChild::RecvSwappedWithOtherRemoteLoad
   nsContentUtils::FirePageShowEvent(ourDocShell, ourEventTarget, true);
 
   docShell->SetInFrameSwap(false);
 
   return true;
 }
 
 bool
-TabChild::RecvHandleAccessKey(nsTArray<uint32_t>&& aCharCodes,
-                              const bool& aIsTrusted,
+TabChild::RecvHandleAccessKey(const WidgetKeyboardEvent& aEvent,
+                              nsTArray<uint32_t>&& aCharCodes,
                               const int32_t& aModifierMask)
 {
   nsCOMPtr<nsIDocument> document(GetDocument());
   nsCOMPtr<nsIPresShell> presShell = document->GetShell();
   if (presShell) {
     nsPresContext* pc = presShell->GetPresContext();
     if (pc) {
-      pc->EventStateManager()->HandleAccessKey(pc, aCharCodes, aIsTrusted, aModifierMask);
+      if (!pc->EventStateManager()->
+                 HandleAccessKey(&(const_cast<WidgetKeyboardEvent&>(aEvent)),
+                                 pc, aCharCodes,
+                                 aModifierMask, true)) {
+        // If no accesskey was found, inform the parent so that accesskeys on
+        // menus can be handled.
+        WidgetKeyboardEvent localEvent(aEvent);
+        localEvent.mWidget = mPuppetWidget;
+        SendAccessKeyNotHandled(localEvent);
+      }
     }
   }
 
   return true;
 }
 
 bool
 TabChild::RecvAudioChannelChangeNotification(const uint32_t& aAudioChannel,
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -561,18 +561,18 @@ public:
   }
 
   virtual bool RecvUIResolutionChanged(const float& aDpi,
                                        const double& aScale) override;
 
   virtual bool
   RecvThemeChanged(nsTArray<LookAndFeelInt>&& aLookAndFeelIntCache) override;
 
-  virtual bool RecvHandleAccessKey(nsTArray<uint32_t>&& aCharCodes,
-                                   const bool& aIsTrusted,
+  virtual bool RecvHandleAccessKey(const WidgetKeyboardEvent& aEvent,
+                                   nsTArray<uint32_t>&& aCharCodes,
                                    const int32_t& aModifierMask) override;
 
   virtual bool RecvAudioChannelChangeNotification(const uint32_t& aAudioChannel,
                                                   const float& aVolume,
                                                   const bool& aMuted) override;
 
   virtual bool RecvSetUseGlobalHistory(const bool& aUse) override;
 
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -19,16 +19,17 @@
 #include "mozilla/dom/ContentBridgeParent.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/DataTransfer.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/indexedDB/ActorsParent.h"
 #include "mozilla/plugins/PluginWidgetParent.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
 #include "mozilla/Hal.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/ipc/DocumentRendererParent.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "mozilla/layers/AsyncDragMetrics.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/InputAPZContext.h"
 #include "mozilla/layout/RenderFrameParent.h"
@@ -80,17 +81,16 @@
 #include "StructuredCloneData.h"
 #include "ColorPickerParent.h"
 #include "FilePickerParent.h"
 #include "TabChild.h"
 #include "LoadContext.h"
 #include "nsNetCID.h"
 #include "nsIAuthInformation.h"
 #include "nsIAuthPromptCallback.h"
-#include "SourceSurfaceRawData.h"
 #include "nsAuthInformationHolder.h"
 #include "nsICancelable.h"
 #include "gfxPrefs.h"
 #include "nsILoginManagerPrompter.h"
 #include "nsPIWindowRoot.h"
 #include "nsIAuthPrompt2.h"
 #include "gfxDrawable.h"
 #include "ImageOps.h"
@@ -615,16 +615,47 @@ TabParent::RecvMoveFocus(const bool& aFo
     nsCOMPtr<nsIDOMElement> frame = do_QueryInterface(mFrameElement);
     fm->MoveFocus(nullptr, frame, type, nsIFocusManager::FLAG_BYKEY,
                   getter_AddRefs(dummy));
   }
   return true;
 }
 
 bool
+TabParent::RecvSizeShellTo(const uint32_t& aFlags, const int32_t& aWidth, const int32_t& aHeight,
+                           const int32_t& aShellItemWidth, const int32_t& aShellItemHeight)
+{
+  NS_ENSURE_TRUE(mFrameElement, true);
+
+  nsCOMPtr<nsIDocShell> docShell = mFrameElement->OwnerDoc()->GetDocShell();
+  NS_ENSURE_TRUE(docShell, true);
+
+  nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+  nsresult rv = docShell->GetTreeOwner(getter_AddRefs(treeOwner));
+  NS_ENSURE_SUCCESS(rv, true);
+
+  int32_t width = aWidth;
+  int32_t height = aHeight;
+
+  if (aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_CX) {
+    width = mDimensions.width;
+  }
+
+  if (aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_CY) {
+    height = mDimensions.height;
+  }
+
+  nsCOMPtr<nsIXULWindow> xulWin(do_GetInterface(treeOwner));
+  NS_ENSURE_TRUE(xulWin, true);
+  xulWin->SizeShellToWithLimit(width, height, aShellItemWidth, aShellItemHeight);
+
+  return true;
+}
+
+bool
 TabParent::RecvEvent(const RemoteDOMEvent& aEvent)
 {
   nsCOMPtr<nsIDOMEvent> event = do_QueryInterface(aEvent.mEvent);
   NS_ENSURE_TRUE(event, true);
 
   nsCOMPtr<mozilla::dom::EventTarget> target = do_QueryInterface(mFrameElement);
   NS_ENSURE_TRUE(target, true);
 
@@ -809,29 +840,54 @@ TabParent::RecvSetDimensions(const uint3
   NS_ENSURE_TRUE(mFrameElement, true);
   nsCOMPtr<nsIDocShell> docShell = mFrameElement->OwnerDoc()->GetDocShell();
   NS_ENSURE_TRUE(docShell, true);
   nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
   docShell->GetTreeOwner(getter_AddRefs(treeOwner));
   nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = do_QueryInterface(treeOwner);
   NS_ENSURE_TRUE(treeOwnerAsWin, true);
 
+  // We only care about the parameters that actually changed, see more
+  // details in TabChild::SetDimensions.
+  int32_t unused;
+  int32_t x = aX;
+  if (aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_X) {
+    treeOwnerAsWin->GetPosition(&x, &unused);
+  }
+
+  int32_t y = aY;
+  if (aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_Y) {
+    treeOwnerAsWin->GetPosition(&unused, &y);
+  }
+
+  int32_t cx = aCx;
+  if (aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_CX) {
+    treeOwnerAsWin->GetSize(&cx, &unused);
+  }
+
+  int32_t cy = aCy;
+  if (aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_CY) {
+    treeOwnerAsWin->GetSize(&unused, &cy);
+  }
+
   if (aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_POSITION &&
       aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_SIZE_OUTER) {
-    treeOwnerAsWin->SetPositionAndSize(aX, aY, aCx, aCy, true);
+    treeOwnerAsWin->SetPositionAndSize(x, y, cx, cy, true);
     return true;
   }
 
   if (aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_POSITION) {
-    treeOwnerAsWin->SetPosition(aX, aY);
+    treeOwnerAsWin->SetPosition(x, y);
+    mUpdatedDimensions = false;
+    UpdatePosition();
     return true;
   }
 
   if (aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_SIZE_OUTER) {
-    treeOwnerAsWin->SetSize(aCx, aCy, true);
+    treeOwnerAsWin->SetSize(cx, cy, true);
     return true;
   }
 
   MOZ_ASSERT(false, "Unknown flags!");
   return false;
 }
 
 nsresult
@@ -927,22 +983,22 @@ TabParent::ThemeChanged()
     // LookAndFeel should have the up-to-date values, which we now
     // send down to the child. We do this for every remote tab for now,
     // but bug 1156934 has been filed to do it once per content process.
     Unused << SendThemeChanged(LookAndFeel::GetIntCache());
   }
 }
 
 void
-TabParent::HandleAccessKey(nsTArray<uint32_t>& aCharCodes,
-                           const bool& aIsTrusted,
+TabParent::HandleAccessKey(const WidgetKeyboardEvent& aEvent,
+                           nsTArray<uint32_t>& aCharCodes,
                            const int32_t& aModifierMask)
 {
   if (!mIsDestroyed) {
-    Unused << SendHandleAccessKey(aCharCodes, aIsTrusted, aModifierMask);
+    Unused << SendHandleAccessKey(aEvent, aCharCodes, aModifierMask);
   }
 }
 
 void
 TabParent::Activate()
 {
   if (!mIsDestroyed) {
     Unused << SendActivate();
@@ -1186,17 +1242,17 @@ bool TabParent::SendRealMouseEvent(Widge
     }
   }
 
   ScrollableLayerGuid guid;
   uint64_t blockId;
   ApzAwareEventRoutingToChild(&guid, &blockId, nullptr);
 
   if (eMouseMove == event.mMessage) {
-    if (event.reason == WidgetMouseEvent::eSynthesized) {
+    if (event.mReason == WidgetMouseEvent::eSynthesized) {
       return SendSynthMouseMoveEvent(event, guid, blockId);
     } else {
       return SendRealMouseMoveEvent(event, guid, blockId);
     }
   }
 
   return SendRealMouseButtonEvent(event, guid, blockId);
 }
@@ -1673,22 +1729,21 @@ TabParent::RecvSetCustomCursor(const nsC
   if (widget) {
     if (aForce) {
       widget->ClearCachedCursor();
     }
 
     if (mTabSetsCursor) {
       const gfx::IntSize size(aWidth, aHeight);
 
-      RefPtr<gfx::DataSourceSurface> customCursor = new mozilla::gfx::SourceSurfaceRawData();
-      mozilla::gfx::SourceSurfaceRawData* raw = static_cast<mozilla::gfx::SourceSurfaceRawData*>(customCursor.get());
-      raw->InitWrappingData(
-        reinterpret_cast<uint8_t*>(const_cast<nsCString&>(aCursorData).BeginWriting()),
-        size, aStride, static_cast<mozilla::gfx::SurfaceFormat>(aFormat), false);
-      raw->GuaranteePersistance();
+      RefPtr<gfx::DataSourceSurface> customCursor =
+          gfx::CreateDataSourceSurfaceFromData(size,
+                                               static_cast<gfx::SurfaceFormat>(aFormat),
+                                               reinterpret_cast<const uint8_t*>(aCursorData.BeginReading()),
+                                               aStride);
 
       RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(customCursor, size);
       nsCOMPtr<imgIContainer> cursorImage(image::ImageOps::CreateFromDrawable(drawable));
       widget->SetCursor(cursorImage, aHotspotX, aHotspotY);
       mCustomCursor = cursorImage;
       mCustomCursorHotspotX = aHotspotX;
       mCustomCursorHotspotY = aHotspotY;
     }
@@ -2070,16 +2125,41 @@ TabParent::RecvDispatchAfterKeyboardEven
     presShell->DispatchAfterKeyboardEvent(mFrameElement, localEvent,
                                           aEvent.DefaultPrevented());
   }
 
   return true;
 }
 
 bool
+TabParent::RecvAccessKeyNotHandled(const WidgetKeyboardEvent& aEvent)
+{
+  NS_ENSURE_TRUE(mFrameElement, true);
+
+  WidgetKeyboardEvent localEvent(aEvent);
+  localEvent.mMessage = eAccessKeyNotFound;
+  localEvent.mAccessKeyForwardedToChild = false;
+
+  // Here we convert the WidgetEvent that we received to an nsIDOMEvent
+  // to be able to dispatch it to the <browser> element as the target element.
+  nsIDocument* doc = mFrameElement->OwnerDoc();
+  nsIPresShell* presShell = doc->GetShell();
+  NS_ENSURE_TRUE(presShell, true);
+
+  if (presShell->CanDispatchEvent()) {
+    nsPresContext* presContext = presShell->GetPresContext();
+    NS_ENSURE_TRUE(presContext, true);
+
+    EventDispatcher::Dispatch(mFrameElement, presContext, &localEvent);
+  }
+
+  return true;
+}
+
+bool
 TabParent::HandleQueryContentEvent(WidgetQueryContentEvent& aEvent)
 {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return true;
   }
   if (NS_WARN_IF(!mContentCache.HandleQueryContentEvent(aEvent, widget)) ||
       NS_WARN_IF(!aEvent.mSucceeded)) {
@@ -3139,24 +3219,20 @@ TabParent::RecvInvokeDragSession(nsTArra
     }
   }
 
   if (aVisualDnDData.IsEmpty() ||
       (aVisualDnDData.Length() < aHeight * aStride)) {
     mDnDVisualization = nullptr;
   } else {
     mDnDVisualization =
-      new mozilla::gfx::SourceSurfaceRawData();
-    mozilla::gfx::SourceSurfaceRawData* raw =
-      static_cast<mozilla::gfx::SourceSurfaceRawData*>(mDnDVisualization.get());
-    raw->InitWrappingData(
-      reinterpret_cast<uint8_t*>(const_cast<nsCString&>(aVisualDnDData).BeginWriting()),
-      mozilla::gfx::IntSize(aWidth, aHeight), aStride,
-      static_cast<mozilla::gfx::SurfaceFormat>(aFormat), false);
-    raw->GuaranteePersistance();
+        gfx::CreateDataSourceSurfaceFromData(gfx::IntSize(aWidth, aHeight),
+                                             static_cast<gfx::SurfaceFormat>(aFormat),
+                                             reinterpret_cast<const uint8_t*>(aVisualDnDData.BeginReading()),
+                                             aStride);
   }
   mDragAreaX = aDragAreaX;
   mDragAreaY = aDragAreaY;
 
   esm->BeginTrackingRemoteDragGesture(mFrameElement);
 
   return true;
 }
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -156,23 +156,32 @@ public:
 
   void AddWindowListeners();
 
   void DidRefresh() override;
 
   virtual bool RecvMoveFocus(const bool& aForward,
                              const bool& aForDocumentNavigation) override;
 
+  virtual bool RecvSizeShellTo(const uint32_t& aFlags,
+                               const int32_t& aWidth,
+                               const int32_t& aHeight,
+                               const int32_t& aShellItemWidth,
+                               const int32_t& aShellItemHeight) override;
+
   virtual bool RecvEvent(const RemoteDOMEvent& aEvent) override;
 
   virtual bool RecvReplyKeyEvent(const WidgetKeyboardEvent& aEvent) override;
 
   virtual bool
   RecvDispatchAfterKeyboardEvent(const WidgetKeyboardEvent& aEvent) override;
 
+  virtual bool
+  RecvAccessKeyNotHandled(const WidgetKeyboardEvent& aEvent) override;
+
   virtual bool RecvBrowserFrameOpenWindow(PBrowserParent* aOpener,
                                           PRenderFrameParent* aRenderFrame,
                                           const nsString& aURL,
                                           const nsString& aName,
                                           const nsString& aFeatures,
                                           bool* aOutWindowOpened,
                                           TextureFactoryIdentifier* aTextureFactoryIdentifier,
                                           uint64_t* aLayersId) override;
@@ -353,18 +362,18 @@ public:
   void UpdateDimensions(const nsIntRect& aRect, const ScreenIntSize& aSize);
 
   void SizeModeChanged(const nsSizeMode& aSizeMode);
 
   void UIResolutionChanged();
 
   void ThemeChanged();
 
-  void HandleAccessKey(nsTArray<uint32_t>& aCharCodes,
-                       const bool& aIsTrusted,
+  void HandleAccessKey(const WidgetKeyboardEvent& aEvent,
+                       nsTArray<uint32_t>& aCharCodes,
                        const int32_t& aModifierMask);
 
   void Activate();
 
   void Deactivate();
 
   bool MapEventCoordinatesForChildProcess(mozilla::WidgetEvent* aEvent);
 
--- a/dom/media/AbstractMediaDecoder.h
+++ b/dom/media/AbstractMediaDecoder.h
@@ -57,17 +57,17 @@ public:
   // Can be called on any thread.
   virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded,
                                    uint32_t aDropped) = 0;
 
   virtual AbstractCanonical<media::NullableTimeUnit>* CanonicalDurationOrNull() { return nullptr; };
 
   // Return an event that will be notified when data arrives in MediaResource.
   // MediaDecoderReader will register with this event to receive notifications
-  // in order to udpate buffer ranges.
+  // in order to update buffer ranges.
   // Return null if this decoder doesn't support the event.
   virtual MediaEventSource<void>* DataArrivedEvent()
   {
     return nullptr;
   }
 
 protected:
   virtual void UpdateEstimatedMediaDuration(int64_t aDuration) {};
--- a/dom/media/AudioConverter.h
+++ b/dom/media/AudioConverter.h
@@ -150,36 +150,38 @@ public:
     // Perform the downmixing / reordering in temporary buffer.
     size_t frames = SamplesInToFrames(aBuffer.Length());
     AlignedBuffer<Value> temp1;
     if (!temp1.SetLength(FramesOutToSamples(frames))) {
       return AudioDataBuffer<Format, Value>(Move(temp1));
     }
     frames = ProcessInternal(temp1.Data(), aBuffer.Data(), frames);
     if (mIn.Rate() == mOut.Rate()) {
-      temp1.SetLength(FramesOutToSamples(frames));
+      MOZ_ALWAYS_TRUE(temp1.SetLength(FramesOutToSamples(frames)));
       return AudioDataBuffer<Format, Value>(Move(temp1));
     }
 
     // At this point, temp1 contains the buffer reordered and downmixed.
     // If we are downsampling we can re-use it.
     AlignedBuffer<Value>* outputBuffer = &temp1;
     AlignedBuffer<Value> temp2;
     if (!frames || mOut.Rate() > mIn.Rate()) {
       // We are upsampling or about to drain, we can't work in place.
       // Allocate another temporary buffer where the upsampling will occur.
-      temp2.SetLength(FramesOutToSamples(ResampleRecipientFrames(frames)));
+      if (!temp2.SetLength(FramesOutToSamples(ResampleRecipientFrames(frames)))) {
+        return AudioDataBuffer<Format, Value>(Move(temp2));
+      }
       outputBuffer = &temp2;
     }
     if (!frames) {
       frames = DrainResampler(outputBuffer->Data());
     } else {
       frames = ResampleAudio(outputBuffer->Data(), temp1.Data(), frames);
     }
-    outputBuffer->SetLength(FramesOutToSamples(frames));
+    MOZ_ALWAYS_TRUE(outputBuffer->SetLength(FramesOutToSamples(frames)));
     return AudioDataBuffer<Format, Value>(Move(*outputBuffer));
   }
 
   // Attempt to convert the AudioDataBuffer in place.
   // Will return 0 if the conversion wasn't possible.
   template <typename Value>
   size_t Process(Value* aBuffer, size_t aFrames)
   {
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -282,24 +282,26 @@ MediaDecoder::ResourceCallback::NotifyBy
     if (self->mDecoder) {
       self->mDecoder->NotifyBytesConsumed(aBytes, aOffset);
     }
   });
   AbstractThread::MainThread()->Dispatch(r.forget());
 }
 
 void
-MediaDecoder::NotifyOwnerActivityChanged()
+MediaDecoder::NotifyOwnerActivityChanged(bool aIsVisible)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mShuttingDown) {
     return;
   }
 
+  SetElementVisibility(aIsVisible);
+
   UpdateDormantState(false /* aDormantTimeout */, false /* aActivity */);
   // Start dormant timer if necessary
   StartDormantTimer();
 }
 
 bool
 MediaDecoder::IsHeuristicDormantSupported() const
 {
@@ -325,37 +327,37 @@ MediaDecoder::UpdateDormantState(bool aD
       !mOwner->GetVideoFrameContainer() ||
       (mOwner->GetMediaElement() && mOwner->GetMediaElement()->IsBeingDestroyed()) ||
       !mDormantSupported)
   {
     return;
   }
 
   DECODER_LOG("UpdateDormantState aTimeout=%d aActivity=%d mIsDormant=%d "
-              "ownerActive=%d ownerHidden=%d mIsHeuristicDormant=%d "
+              "ownerActive=%d mIsVisible=%d mIsHeuristicDormant=%d "
               "mPlayState=%s encrypted=%s",
               aDormantTimeout, aActivity, mIsDormant, mOwner->IsActive(),
-              mOwner->IsHidden(), mIsHeuristicDormant, PlayStateStr(),
+              mIsVisible.Ref(), mIsHeuristicDormant, PlayStateStr(),
               (!mInfo ? "Unknown" : (mInfo->IsEncrypted() ? "1" : "0")));
 
   bool prevDormant = mIsDormant;
   mIsDormant = false;
   if (!mOwner->IsActive()) {
     mIsDormant = true;
   }
 #ifdef MOZ_WIDGET_GONK
   if (mOwner->IsHidden()) {
     mIsDormant = true;
   }
 #endif
 
   // Try to enable dormant by idle heuristic, when the owner is hidden.
   bool prevHeuristicDormant = mIsHeuristicDormant;
   mIsHeuristicDormant = false;
-  if (IsHeuristicDormantSupported() && mOwner->IsHidden()) {
+  if (IsHeuristicDormantSupported() && !mIsVisible) {
     if (aDormantTimeout && !aActivity &&
         (mPlayState == PLAY_STATE_PAUSED || IsEnded())) {
       // Enable heuristic dormant
       mIsHeuristicDormant = true;
     } else if(prevHeuristicDormant && !aActivity) {
       // Continue heuristic dormant
       mIsHeuristicDormant = true;
     }
@@ -401,17 +403,17 @@ MediaDecoder::StartDormantTimer()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!IsHeuristicDormantSupported()) {
     return;
   }
 
   if (mIsHeuristicDormant ||
       mShuttingDown ||
-      !mOwner->IsHidden() ||
+      mIsVisible ||
       (mPlayState != PLAY_STATE_PAUSED &&
        !IsEnded()))
   {
     return;
   }
 
   if (!mDormantTimer) {
     mDormantTimer = do_CreateInstance("@mozilla.org/timer;1");
@@ -567,16 +569,18 @@ MediaDecoder::MediaDecoder(MediaDecoderO
   , mPlaybackRateReliable(AbstractThread::MainThread(), true,
                           "MediaDecoder::mPlaybackRateReliable (Canonical)")
   , mDecoderPosition(AbstractThread::MainThread(), 0,
                      "MediaDecoder::mDecoderPosition (Canonical)")
   , mMediaSeekable(AbstractThread::MainThread(), true,
                    "MediaDecoder::mMediaSeekable (Canonical)")
   , mMediaSeekableOnlyInBufferedRanges(AbstractThread::MainThread(), false,
                    "MediaDecoder::mMediaSeekableOnlyInBufferedRanges (Canonical)")
+  , mIsVisible(AbstractThread::MainThread(), !mOwner->IsHidden(),
+               "MediaDecoder::mIsVisible (Canonical)")
   , mTelemetryReported(false)
 {
   MOZ_COUNT_CTOR(MediaDecoder);
   MOZ_ASSERT(NS_IsMainThread());
   MediaMemoryTracker::AddMediaDecoder(this);
 
   mAudioChannel = AudioChannelService::GetDefaultAudioChannel();
   mResourceCallback->Connect(this);
@@ -1360,16 +1364,23 @@ MediaDecoder::DurationChanged()
   }
 
   if (CurrentPosition() > TimeUnit::FromSeconds(mDuration).ToMicroseconds()) {
     Seek(mDuration, SeekTarget::Accurate);
   }
 }
 
 void
+MediaDecoder::SetElementVisibility(bool aIsVisible)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mIsVisible = aIsVisible;
+}
+
+void
 MediaDecoder::UpdateEstimatedMediaDuration(int64_t aDuration)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mPlayState <= PLAY_STATE_LOADING) {
     return;
   }
 
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -175,17 +175,17 @@ public:
   // called.
   virtual nsresult Play();
 
   // Notify activity of the decoder owner is changed.
   // Based on the activity, dormant state is updated.
   // Dormant state is a state to free all scarce media resources
   //  (like hw video codec), did not decoding and stay dormant.
   // It is used to share scarece media resources in system.
-  virtual void NotifyOwnerActivityChanged();
+  virtual void NotifyOwnerActivityChanged(bool aIsVisible);
 
   void UpdateDormantState(bool aDormantTimeout, bool aActivity);
 
   // Pause video playback.
   virtual void Pause();
   // Adjust the speed of the playback, optionally with pitch correction,
   virtual void SetVolume(double aVolume);
 
@@ -245,19 +245,16 @@ protected:
   // instability is expected; if the incoming duration is not significantly
   // different from the existing duration, the change request is ignored.
   // If the incoming duration is significantly different, the duration is
   // changed, this causes a durationchanged event to fire to the media
   // element.
   void UpdateEstimatedMediaDuration(int64_t aDuration) override;
 
 public:
-  // Called from HTMLMediaElement when owner document activity changes
-  virtual void SetElementVisibility(bool aIsVisible) {}
-
   // Set a flag indicating whether random seeking is supported
   void SetMediaSeekable(bool aMediaSeekable);
   // Set a flag indicating whether seeking is supported only in buffered ranges
   void SetMediaSeekableOnlyInBufferedRanges(bool aMediaSeekableOnlyInBufferedRanges);
 
   // Returns true if this media supports random seeking. False for example with
   // chained ogg files.
   bool IsMediaSeekable();
@@ -363,16 +360,19 @@ private:
 
   // Returns true if we can play the entire media through without stopping
   // to buffer, given the current download and playback rates.
   virtual bool CanPlayThrough();
 
   void SetAudioChannel(dom::AudioChannel aChannel) { mAudioChannel = aChannel; }
   dom::AudioChannel GetAudioChannel() { return mAudioChannel; }
 
+  // Called from HTMLMediaElement when owner document activity changes
+  virtual void SetElementVisibility(bool aIsVisible);
+
   /******
    * The following methods must only be called on the main
    * thread.
    ******/
 
   // Change to a new play state. This updates the mState variable and
   // notifies any thread blocking on this object's monitor of the
   // change. Call on the main thread only.
@@ -739,17 +739,17 @@ protected:
   Mirror<media::NullableTimeUnit> mStateMachineDuration;
 
   // Current playback position in the stream. This is (approximately)
   // where we're up to playing back the stream. This is not adjusted
   // during decoder seek operations, but it's updated at the end when we
   // start playing back again.
   Mirror<int64_t> mPlaybackPosition;
 
-  // Used to distiguish whether the audio is producing sound.
+  // Used to distinguish whether the audio is producing sound.
   Mirror<bool> mIsAudioDataAudible;
 
   // Volume of playback.  0.0 = muted. 1.0 = full volume.
   Canonical<double> mVolume;
 
   // PlaybackRate and pitch preservation status we should start at.
   Canonical<double> mPlaybackRate;
 
@@ -801,16 +801,19 @@ protected:
   Canonical<int64_t> mDecoderPosition;
 
   // True if the media is seekable (i.e. supports random access).
   Canonical<bool> mMediaSeekable;
 
   // True if the media is only seekable within its buffered ranges.
   Canonical<bool> mMediaSeekableOnlyInBufferedRanges;
 
+  // True if the decoder is visible.
+  Canonical<bool> mIsVisible;
+
 public:
   AbstractCanonical<media::NullableTimeUnit>* CanonicalDurationOrNull() override;
   AbstractCanonical<double>* CanonicalVolume() {
     return &mVolume;
   }
   AbstractCanonical<double>* CanonicalPlaybackRate() {
     return &mPlaybackRate;
   }
@@ -848,16 +851,19 @@ public:
     return &mDecoderPosition;
   }
   AbstractCanonical<bool>* CanonicalMediaSeekable() {
     return &mMediaSeekable;
   }
   AbstractCanonical<bool>* CanonicalMediaSeekableOnlyInBufferedRanges() {
     return &mMediaSeekableOnlyInBufferedRanges;
   }
+  AbstractCanonical<bool>* CanonicalIsVisible() {
+    return &mIsVisible;
+  }
 
 private:
   // Notify owner when the audible state changed
   void NotifyAudibleStateChanged();
 
   /* Functions called by ResourceCallback */
 
   // A media stream is assumed to be infinite if the metadata doesn't
--- a/dom/media/MediaDecoderReader.cpp
+++ b/dom/media/MediaDecoderReader.cpp
@@ -127,26 +127,27 @@ size_t MediaDecoderReader::SizeOfVideoQu
   return mVideoQueue.GetSize();
 }
 
 size_t MediaDecoderReader::SizeOfAudioQueueInFrames()
 {
   return mAudioQueue.GetSize();
 }
 
-nsresult MediaDecoderReader::ResetDecode()
+nsresult MediaDecoderReader::ResetDecode(TargetQueues aQueues /*= AUDIO_VIDEO*/)
 {
   VideoQueue().Reset();
-  AudioQueue().Reset();
+  mVideoDiscontinuity = true;
+  mBaseVideoPromise.RejectIfExists(CANCELED, __func__);
 
-  mAudioDiscontinuity = true;
-  mVideoDiscontinuity = true;
-
-  mBaseAudioPromise.RejectIfExists(CANCELED, __func__);
-  mBaseVideoPromise.RejectIfExists(CANCELED, __func__);
+  if (aQueues == AUDIO_VIDEO) {
+    AudioQueue().Reset();
+    mAudioDiscontinuity = true;
+    mBaseAudioPromise.RejectIfExists(CANCELED, __func__);
+  }
 
   return NS_OK;
 }
 
 RefPtr<MediaDecoderReader::MediaDataPromise>
 MediaDecoderReader::DecodeToFirstVideoData()
 {
   MOZ_ASSERT(OnTaskQueue());
@@ -178,16 +179,20 @@ MediaDecoderReader::DecodeToFirstVideoDa
 void
 MediaDecoderReader::UpdateBuffered()
 {
   MOZ_ASSERT(OnTaskQueue());
   NS_ENSURE_TRUE_VOID(!mShutdown);
   mBuffered = GetBuffered();
 }
 
+void
+MediaDecoderReader::VisibilityChanged()
+{}
+
 media::TimeIntervals
 MediaDecoderReader::GetBuffered()
 {
   MOZ_ASSERT(OnTaskQueue());
   if (!HaveStartTime()) {
     return media::TimeIntervals();
   }
   AutoPinned<MediaResource> stream(mDecoder->GetResource());
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -68,16 +68,21 @@ class MediaDecoderReader {
 public:
   enum NotDecodedReason {
     END_OF_STREAM,
     DECODE_ERROR,
     WAITING_FOR_DATA,
     CANCELED
   };
 
+  enum TargetQueues {
+    VIDEO_ONLY,
+    AUDIO_VIDEO
+  };
+
   using MetadataPromise =
     MozPromise<RefPtr<MetadataHolder>, ReadMetadataFailureReason, IsExclusive>;
   using MediaDataPromise =
     MozPromise<RefPtr<MediaData>, NotDecodedReason, IsExclusive>;
   using SeekPromise = MozPromise<media::TimeUnit, nsresult, IsExclusive>;
 
   // Note that, conceptually, WaitForData makes sense in a non-exclusive sense.
   // But in the current architecture it's only ever used exclusively (by MDSM),
@@ -120,17 +125,17 @@ public:
   // Request*Data() calls after this is called. Calls to Request*Data()
   // made after this should be processed as usual.
   //
   // Normally this call preceedes a Seek() call, or shutdown.
   //
   // The first samples of every stream produced after a ResetDecode() call
   // *must* be marked as "discontinuities". If it's not, seeking work won't
   // properly!
-  virtual nsresult ResetDecode();
+  virtual nsresult ResetDecode(TargetQueues aQueues = AUDIO_VIDEO);
 
   // Requests one audio sample from the reader.
   //
   // The decode should be performed asynchronously, and the promise should
   // be resolved when it is complete. Don't hold the decoder
   // monitor while calling this, as the implementation may try to wait
   // on something that needs the monitor and deadlock.
   virtual RefPtr<MediaDataPromise> RequestAudioData();
@@ -390,16 +395,18 @@ private:
   virtual nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
   {
     MOZ_CRASH();
   }
 
   // Recomputes mBuffered.
   virtual void UpdateBuffered();
 
+  virtual void VisibilityChanged();
+
   virtual void NotifyDataArrivedInternal() {}
 
   // Overrides of this function should decodes an unspecified amount of
   // audio data, enqueuing the audio data in mAudioQueue. Returns true
   // when there's more audio to decode, false if the audio is finished,
   // end of file has been reached, or an un-recoverable read error has
   // occured. This function blocks until the decode is complete.
   virtual bool DecodeAudioData()
--- a/dom/media/MediaDecoderReaderWrapper.cpp
+++ b/dom/media/MediaDecoderReaderWrapper.cpp
@@ -325,25 +325,27 @@ MediaDecoderReaderWrapper::SetIdle()
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   nsCOMPtr<nsIRunnable> r =
     NewRunnableMethod(mReader, &MediaDecoderReader::SetIdle);
   mReader->OwnerThread()->Dispatch(r.forget());
 }
 
 void
-MediaDecoderReaderWrapper::ResetDecode()
+MediaDecoderReaderWrapper::ResetDecode(TargetQueues aQueues)
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
 
   mAudioDataRequest.DisconnectIfExists();
   mVideoDataRequest.DisconnectIfExists();
 
   nsCOMPtr<nsIRunnable> r =
-    NewRunnableMethod(mReader, &MediaDecoderReader::ResetDecode);
+    NewRunnableMethod<TargetQueues>(mReader,
+                                    &MediaDecoderReader::ResetDecode,
+                                    aQueues);
   mReader->OwnerThread()->Dispatch(r.forget());
 }
 
 RefPtr<ShutdownPromise>
 MediaDecoderReaderWrapper::Shutdown()
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   MOZ_ASSERT(!mRequestAudioDataCB);
--- a/dom/media/MediaDecoderReaderWrapper.h
+++ b/dom/media/MediaDecoderReaderWrapper.h
@@ -27,16 +27,17 @@ typedef MozPromise<bool, bool, /* isExcl
  * is passed to the underlying reader.
  */
 class MediaDecoderReaderWrapper {
   typedef MediaDecoderReader::MetadataPromise MetadataPromise;
   typedef MediaDecoderReader::MediaDataPromise MediaDataPromise;
   typedef MediaDecoderReader::SeekPromise SeekPromise;
   typedef MediaDecoderReader::WaitForDataPromise WaitForDataPromise;
   typedef MediaDecoderReader::BufferedUpdatePromise BufferedUpdatePromise;
+  typedef MediaDecoderReader::TargetQueues TargetQueues;
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderReaderWrapper);
 
   /*
    * Type 1: void(MediaData*)
    *         void(RefPtr<MediaData>)
    */
   template <typename T>
   class ArgType1CheckHelper {
@@ -250,17 +251,17 @@ public:
 
   RefPtr<SeekPromise> Seek(SeekTarget aTarget, media::TimeUnit aEndTime);
   RefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType);
   RefPtr<BufferedUpdatePromise> UpdateBufferedWithPromise();
   RefPtr<ShutdownPromise> Shutdown();
 
   void ReleaseMediaResources();
   void SetIdle();
-  void ResetDecode();
+  void ResetDecode(TargetQueues aQueues);
 
   nsresult Init() { return mReader->Init(); }
   bool IsWaitForDataSupported() const { return mReader->IsWaitForDataSupported(); }
   bool IsAsync() const { return mReader->IsAsync(); }
   bool UseBufferingHeuristics() const { return mReader->UseBufferingHeuristics(); }
   bool ForceZeroStartTime() const { return mReader->ForceZeroStartTime(); }
 
   bool VideoIsHardwareAccelerated() const {
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -196,16 +196,31 @@ static void InitVideoQueuePrefs() {
       "media.video-queue.default-size", MAX_VIDEO_QUEUE_SIZE);
     sVideoQueueHWAccelSize = Preferences::GetUint(
       "media.video-queue.hw-accel-size", HW_VIDEO_QUEUE_SIZE);
     sVideoQueueSendToCompositorSize = Preferences::GetUint(
       "media.video-queue.send-to-compositor-size", VIDEO_QUEUE_SEND_TO_COMPOSITOR_SIZE);
   }
 }
 
+static bool sSuspendBackgroundVideos = true;
+
+static void
+InitSuspendBackgroundPref()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
+
+  static bool sSetupPrefCache = false;
+  if (!sSetupPrefCache) {
+    sSetupPrefCache = true;
+    Preferences::AddBoolVarCache(&sSuspendBackgroundVideos,
+                                 "media.suspend-bkgnd-video.enabled", true);
+  }
+}
+
 MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
                                                    MediaDecoderReader* aReader,
                                                    bool aRealTime) :
   mDecoderID(aDecoder),
   mFrameStats(&aDecoder->GetFrameStatistics()),
   mVideoFrameContainer(aDecoder->GetVideoFrameContainer()),
   mAudioChannel(aDecoder->GetAudioChannel()),
   mTaskQueue(new TaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK),
@@ -266,16 +281,17 @@ MediaDecoderStateMachine::MediaDecoderSt
   mPlaybackRateReliable(mTaskQueue, true,
                         "MediaDecoderStateMachine::mPlaybackRateReliable (Mirror)"),
   mDecoderPosition(mTaskQueue, 0,
                    "MediaDecoderStateMachine::mDecoderPosition (Mirror)"),
   mMediaSeekable(mTaskQueue, true,
                  "MediaDecoderStateMachine::mMediaSeekable (Mirror)"),
   mMediaSeekableOnlyInBufferedRanges(mTaskQueue, false,
                  "MediaDecoderStateMachine::mMediaSeekableOnlyInBufferedRanges (Mirror)"),
+  mIsVisible(mTaskQueue, true, "MediaDecoderStateMachine::mIsVisible (Mirror)"),
   mDuration(mTaskQueue, NullableTimeUnit(),
             "MediaDecoderStateMachine::mDuration (Canonical"),
   mIsShutdown(mTaskQueue, false,
               "MediaDecoderStateMachine::mIsShutdown (Canonical)"),
   mNextFrameStatus(mTaskQueue, MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED,
                    "MediaDecoderStateMachine::mNextFrameStatus (Canonical)"),
   mCurrentPosition(mTaskQueue, 0,
                    "MediaDecoderStateMachine::mCurrentPosition (Canonical)"),
@@ -283,16 +299,17 @@ MediaDecoderStateMachine::MediaDecoderSt
                   "MediaDecoderStateMachine::mPlaybackOffset (Canonical)"),
   mIsAudioDataAudible(mTaskQueue, false,
                      "MediaDecoderStateMachine::mIsAudioDataAudible (Canonical)")
 {
   MOZ_COUNT_CTOR(MediaDecoderStateMachine);
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
   InitVideoQueuePrefs();
+  InitSuspendBackgroundPref();
 
   mBufferingWait = IsRealTime() ? 0 : 15;
   mLowDataThresholdUsecs = IsRealTime() ? 0 : detail::LOW_DATA_THRESHOLD_USECS;
 
 #ifdef XP_WIN
   // Ensure high precision timers are enabled on Windows, otherwise the state
   // machine isn't woken up at reliable intervals to set the next frame,
   // and we drop frames while painting. Note that multiple calls to this
@@ -328,29 +345,31 @@ MediaDecoderStateMachine::Initialization
   mPreservesPitch.Connect(aDecoder->CanonicalPreservesPitch());
   mSameOriginMedia.Connect(aDecoder->CanonicalSameOriginMedia());
   mMediaPrincipalHandle.Connect(aDecoder->CanonicalMediaPrincipalHandle());
   mPlaybackBytesPerSecond.Connect(aDecoder->CanonicalPlaybackBytesPerSecond());
   mPlaybackRateReliable.Connect(aDecoder->CanonicalPlaybackRateReliable());
   mDecoderPosition.Connect(aDecoder->CanonicalDecoderPosition());
   mMediaSeekable.Connect(aDecoder->CanonicalMediaSeekable());
   mMediaSeekableOnlyInBufferedRanges.Connect(aDecoder->CanonicalMediaSeekableOnlyInBufferedRanges());
+  mIsVisible.Connect(aDecoder->CanonicalIsVisible());
 
   // Initialize watchers.
   mWatchManager.Watch(mBuffered, &MediaDecoderStateMachine::BufferedRangeUpdated);
   mWatchManager.Watch(mState, &MediaDecoderStateMachine::UpdateNextFrameStatus);
   mWatchManager.Watch(mAudioCompleted, &MediaDecoderStateMachine::UpdateNextFrameStatus);
   mWatchManager.Watch(mVideoCompleted, &MediaDecoderStateMachine::UpdateNextFrameStatus);
   mWatchManager.Watch(mVolume, &MediaDecoderStateMachine::VolumeChanged);
   mWatchManager.Watch(mLogicalPlaybackRate, &MediaDecoderStateMachine::LogicalPlaybackRateChanged);
   mWatchManager.Watch(mPreservesPitch, &MediaDecoderStateMachine::PreservesPitchChanged);
   mWatchManager.Watch(mEstimatedDuration, &MediaDecoderStateMachine::RecomputeDuration);
   mWatchManager.Watch(mExplicitDuration, &MediaDecoderStateMachine::RecomputeDuration);
   mWatchManager.Watch(mObservedDuration, &MediaDecoderStateMachine::RecomputeDuration);
   mWatchManager.Watch(mPlayState, &MediaDecoderStateMachine::PlayStateChanged);
+  mWatchManager.Watch(mIsVisible, &MediaDecoderStateMachine::VisibilityChanged);
 
   // Configure MediaDecoderReaderWrapper.
   SetMediaDecoderReaderWrapperCallback();
 }
 
 media::MediaSink*
 MediaDecoderStateMachine::CreateAudioSink()
 {
@@ -452,16 +471,20 @@ bool MediaDecoderStateMachine::HaveEnoug
   // at which the DecodedStream is playing.
   return true;
 }
 
 bool MediaDecoderStateMachine::HaveEnoughDecodedVideo()
 {
   MOZ_ASSERT(OnTaskQueue());
 
+  if (IsVideoDecodeSuspended()) {
+    return true;
+  }
+
   if (VideoQueue().GetSize() == 0) {
     return false;
   }
 
   if (VideoQueue().GetSize() - 1 < GetAmpleVideoFrames() * mPlaybackRate) {
     return false;
   }
 
@@ -1307,16 +1330,95 @@ void MediaDecoderStateMachine::PlayState
   // when the state machine notices the decoder's state change to PLAYING.
   if (mState == DECODER_STATE_BUFFERING) {
     StartDecoding();
   }
 
   ScheduleStateMachine();
 }
 
+void MediaDecoderStateMachine::VisibilityChanged()
+{
+  MOZ_ASSERT(OnTaskQueue());
+  DECODER_LOG("VisibilityChanged: is visible = %d", mIsVisible.Ref());
+
+  // Not suspending background videos so there's nothing to do.
+  if (!sSuspendBackgroundVideos) {
+    return;
+  }
+
+  if (!HasVideo()) {
+    return;
+  }
+
+  // If not transitioning to visible and not playing then there's
+  // nothing to do.
+  if (!mIsVisible || mPlayState != MediaDecoder::PLAY_STATE_PLAYING) {
+    return;
+  }
+
+  // If an existing seek is in flight don't bother creating a new one to catch
+  // up.
+  if (mSeekTask || mQueuedSeek.Exists()) {
+    return;
+  }
+
+  // Start video-only seek to the current time...
+  InitiateVideoDecodeRecoverySeek();
+}
+
+// InitiateVideoDecodeRecoverySeek is responsible for setting up a video-only
+// seek using the seek task. When suspension of decoding for videos that are in
+// background tabs (ie. invisible) is enabled, the audio keeps playing and when
+// switching back to decoding video, it is highly desirable to not cause the
+// audio to pause as the video is seeked else there be a noticeable audio glitch
+// as the tab becomes visible.
+void MediaDecoderStateMachine::InitiateVideoDecodeRecoverySeek()
+{
+  MOZ_ASSERT(OnTaskQueue());
+
+  SeekJob seekJob;
+  seekJob.mTarget = SeekTarget(GetMediaTime(),
+                               SeekTarget::Type::AccurateVideoOnly,
+                               MediaDecoderEventVisibility::Suppressed);
+
+  SetState(DECODER_STATE_SEEKING);
+
+  // Discard the existing seek task.
+  DiscardSeekTaskIfExist();
+
+  mSeekTaskRequest.DisconnectIfExists();
+
+  // SeekTask will register its callbacks to MediaDecoderReaderWrapper.
+  CancelMediaDecoderReaderWrapperCallback();
+
+  // Create a new SeekTask instance for the incoming seek task.
+  mSeekTask = SeekTask::CreateSeekTask(mDecoderID, OwnerThread(),
+                                       mReader.get(), Move(seekJob),
+                                       mInfo, Duration(), GetMediaTime());
+
+  mOnSeekingStart.Notify(MediaDecoderEventVisibility::Suppressed);
+
+  // Reset our state machine and decoding pipeline before seeking.
+  if (mSeekTask->NeedToResetMDSM()) {
+    Reset(MediaDecoderReader::VIDEO_ONLY);
+  }
+
+  // Do the seek.
+  mSeekTaskRequest.Begin(
+    mSeekTask->Seek(Duration())->Then(OwnerThread(), __func__, this,
+                                      &MediaDecoderStateMachine::OnSeekTaskResolved,
+                                      &MediaDecoderStateMachine::OnSeekTaskRejected));
+  // Nobody is listening to this as OnSeekTaskResolved handles what is
+  // required but the promise needs to exist or SeekJob::Exists() will
+  // assert.
+  RefPtr<MediaDecoder::SeekPromise> unused =
+    mSeekTask->GetSeekJob().mPromise.Ensure(__func__);
+}
+
 void MediaDecoderStateMachine::BufferedRangeUpdated()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   // While playing an unseekable stream of unknown duration, mObservedDuration
   // is updated (in AdvanceFrame()) as we play. But if data is being downloaded
   // faster than played, mObserved won't reflect the end of playable data
   // since we haven't played the frame at the end of buffered data. So update
@@ -1654,16 +1756,23 @@ MediaDecoderStateMachine::EnsureVideoDec
   SAMPLE_LOG("EnsureVideoDecodeTaskQueued isDecoding=%d status=%s",
              IsVideoDecoding(), VideoRequestStatus());
 
   if (mState != DECODER_STATE_DECODING &&
       mState != DECODER_STATE_BUFFERING) {
     return NS_OK;
   }
 
+  if (IsVideoDecodeSuspended() && !IsDecodingFirstFrame()) {
+    // The element is invisible and background videos should be suspended.
+    // If the first frame has already been decoded, don't request anymore video
+    // frames.
+    return NS_OK;
+  }
+
   if (!IsVideoDecoding() || mReader->IsRequestingVidoeData() ||
       mVideoWaitRequest.Exists()) {
     return NS_OK;
   }
 
   RequestVideoData();
   return NS_OK;
 }
@@ -1986,17 +2095,17 @@ MediaDecoderStateMachine::SeekCompleted(
     }
   } else {
     newCurrentTime = video ? video->mTime : seekTime;
   }
 
   // Change state to DECODING or COMPLETED now.
   bool isLiveStream = mResource->IsLiveStream();
   State nextState;
-  if (GetMediaTime() == Duration().ToMicroseconds() && !isLiveStream) {
+  if (newCurrentTime == Duration().ToMicroseconds() && !isLiveStream) {
     // Seeked to end of media, move to COMPLETED state. Note we don't do
     // this when playing a live stream, since the end of media will advance
     // once we download more data!
     DECODER_LOG("Changed state from SEEKING (to %lld) to COMPLETED", seekTime);
     // Explicitly set our state so we don't decode further, and so
     // we report playback ended to the media element.
     nextState = DECODER_STATE_COMPLETED;
   } else {
@@ -2080,16 +2189,17 @@ MediaDecoderStateMachine::FinishShutdown
   mPreservesPitch.DisconnectIfConnected();
   mSameOriginMedia.DisconnectIfConnected();
   mMediaPrincipalHandle.DisconnectIfConnected();
   mPlaybackBytesPerSecond.DisconnectIfConnected();
   mPlaybackRateReliable.DisconnectIfConnected();
   mDecoderPosition.DisconnectIfConnected();
   mMediaSeekable.DisconnectIfConnected();
   mMediaSeekableOnlyInBufferedRanges.DisconnectIfConnected();
+  mIsVisible.DisconnectIfConnected();
 
   mDuration.DisconnectAll();
   mIsShutdown.DisconnectAll();
   mNextFrameStatus.DisconnectAll();
   mCurrentPosition.DisconnectAll();
   mPlaybackOffset.DisconnectAll();
   mIsAudioDataAudible.DisconnectAll();
 
@@ -2234,49 +2344,52 @@ nsresult MediaDecoderStateMachine::RunSt
       return NS_OK;
     }
   }
 
   return NS_OK;
 }
 
 void
-MediaDecoderStateMachine::Reset()
+MediaDecoderStateMachine::Reset(MediaDecoderReader::TargetQueues aQueues /*= AUDIO_VIDEO*/)
 {
   MOZ_ASSERT(OnTaskQueue());
   DECODER_LOG("MediaDecoderStateMachine::Reset");
 
   // We should be resetting because we're seeking, shutting down, or entering
   // dormant state. We could also be in the process of going dormant, and have
   // just switched to exiting dormant before we finished entering dormant,
   // hence the DECODING_NONE case below.
   MOZ_ASSERT(IsShutdown() ||
              mState == DECODER_STATE_SEEKING ||
              mState == DECODER_STATE_DORMANT);
 
-  // Stop the audio thread. Otherwise, MediaSink might be accessing AudioQueue
-  // outside of the decoder monitor while we are clearing the queue and causes
-  // crash for no samples to be popped.
-  StopMediaSink();
 
   mDecodedVideoEndTime = 0;
-  mDecodedAudioEndTime = 0;
-  mAudioCompleted = false;
   mVideoCompleted = false;
-  AudioQueue().Reset();
   VideoQueue().Reset();
+  mVideoWaitRequest.DisconnectIfExists();
+
+  if (aQueues == MediaDecoderReader::AUDIO_VIDEO) {
+    // Stop the audio thread. Otherwise, MediaSink might be accessing AudioQueue
+    // outside of the decoder monitor while we are clearing the queue and causes
+    // crash for no samples to be popped.
+    StopMediaSink();
+    mDecodedAudioEndTime = 0;
+    mAudioCompleted = false;
+    AudioQueue().Reset();
+    mAudioWaitRequest.DisconnectIfExists();
+  }
 
   mMetadataRequest.DisconnectIfExists();
-  mAudioWaitRequest.DisconnectIfExists();
-  mVideoWaitRequest.DisconnectIfExists();
   mSeekTaskRequest.DisconnectIfExists();
 
   mPlaybackOffset = 0;
 
-  mReader->ResetDecode();
+  mReader->ResetDecode(aQueues);
 }
 
 int64_t
 MediaDecoderStateMachine::GetClock(TimeStamp* aTimeStamp) const
 {
   MOZ_ASSERT(OnTaskQueue());
   int64_t clockTime = mMediaSink->GetPosition(aTimeStamp);
   NS_ASSERTION(GetMediaTime() <= clockTime, "Clock should go forwards.");
@@ -2466,16 +2579,22 @@ bool MediaDecoderStateMachine::OnTaskQue
 }
 
 bool MediaDecoderStateMachine::IsStateMachineScheduled() const
 {
   MOZ_ASSERT(OnTaskQueue());
   return mDispatchedStateMachine || mDelayedScheduler.IsScheduled();
 }
 
+bool MediaDecoderStateMachine::IsVideoDecodeSuspended() const
+{
+  MOZ_ASSERT(OnTaskQueue());
+  return sSuspendBackgroundVideos && !mIsVisible;
+}
+
 void
 MediaDecoderStateMachine::LogicalPlaybackRateChanged()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   if (mLogicalPlaybackRate == 0) {
     // This case is handled in MediaDecoder by pausing playback.
     return;
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -363,17 +363,17 @@ private:
   void OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
   {
     MOZ_ASSERT(OnTaskQueue());
     OnNotDecoded(MediaData::VIDEO_DATA, aReason);
   }
 
   // Resets all state related to decoding and playback, emptying all buffers
   // and aborting all pending operations on the decode task queue.
-  void Reset();
+  void Reset(MediaDecoderReader::TargetQueues aQueues = MediaDecoderReader::AUDIO_VIDEO);
 
 protected:
   virtual ~MediaDecoderStateMachine();
 
   void SetState(State aState);
 
   void BufferedRangeUpdated();
 
@@ -466,16 +466,19 @@ protected:
   // Create and start the media sink.
   // The decoder monitor must be held with exactly one lock count.
   // Called on the state machine thread.
   void StartMediaSink();
 
   // Notification method invoked when mPlayState changes.
   void PlayStateChanged();
 
+  // Notification method invoked when mIsVisible changes.
+  void VisibilityChanged();
+
   // Sets internal state which causes playback of media to pause.
   // The decoder monitor must be held.
   void StopPlayback();
 
   // If the conditions are right, sets internal state which causes playback
   // of media to begin or resume.
   // Must be called with the decode monitor held.
   void MaybeStartPlayback();
@@ -501,20 +504,25 @@ protected:
 
   // Dispatches a LoadedMetadataEvent.
   // This is threadsafe and can be called on any thread.
   // The decoder monitor must be held.
   void EnqueueLoadedMetadataEvent();
 
   void EnqueueFirstFrameLoadedEvent();
 
-  // Clears any previous seeking state and initiates a new see on the decoder.
+  // Clears any previous seeking state and initiates a new seek on the decoder.
   // The decoder monitor must be held.
   void InitiateSeek(SeekJob aSeekJob);
 
+  // Clears any previous seeking state and initiates a video-only seek on the
+  // decoder to catch up the video to the current audio position, when recovering
+  // from video decoding being suspended in background.
+  void InitiateVideoDecodeRecoverySeek();
+
   nsresult DispatchAudioDecodeTaskIfNeeded();
 
   // Ensures a task to decode audio has been dispatched to the decode task queue.
   // If a task to decode has already been dispatched, this does nothing,
   // otherwise this dispatches a task to do the decode.
   // This is called on the state machine or decode threads.
   // The decoder monitor must be held.
   nsresult EnsureAudioDecodeTaskQueued();
@@ -587,16 +595,20 @@ protected:
 
   bool IsStateMachineScheduled() const;
 
   // Returns true if we're not playing and the decode thread has filled its
   // decode buffers and is waiting. We can shut the decode thread down in this
   // case as it may not be needed again.
   bool IsPausedAndDecoderWaiting();
 
+  // Returns true if the video decoding is suspended because the element is not
+  // visible
+  bool IsVideoDecodeSuspended() const;
+
   // These return true if the respective stream's decode has not yet reached
   // the end of stream.
   bool IsAudioDecoding();
   bool IsVideoDecoding();
 
 private:
   // Resolved by the MediaSink to signal that all audio/video outstanding
   // work is complete and identify which part(a/v) of the sink is shutting down.
@@ -748,17 +760,17 @@ private:
 
   // If we're quick buffering, we'll remain in buffering mode while we have less than
   // QUICK_BUFFERING_LOW_DATA_USECS of decoded data available.
   int64_t mQuickBufferingLowDataThresholdUsecs;
 
   // At the start of decoding we want to "preroll" the decode until we've
   // got a few frames decoded before we consider whether decode is falling
   // behind. Otherwise our "we're falling behind" logic will trigger
-  // unneccessarily if we start playing as soon as the first sample is
+  // unnecessarily if we start playing as soon as the first sample is
   // decoded. These two fields store how many video frames and audio
   // samples we must consume before are considered to be finished prerolling.
   uint32_t AudioPrerollUsecs() const
   {
     MOZ_ASSERT(OnTaskQueue());
     return IsRealTime() ? 0 : mAmpleAudioThresholdUsecs / 2;
   }
 
@@ -773,17 +785,18 @@ private:
     MOZ_ASSERT(OnTaskQueue());
     return !IsAudioDecoding() ||
         GetDecodedAudioDuration() >= AudioPrerollUsecs() * mPlaybackRate;
   }
 
   bool DonePrerollingVideo()
   {
     MOZ_ASSERT(OnTaskQueue());
-    return !IsVideoDecoding() ||
+    return !mIsVisible ||
+        !IsVideoDecoding() ||
         static_cast<uint32_t>(VideoQueue().GetSize()) >=
             VideoPrerollFrames() * mPlaybackRate + 1;
   }
 
   void StopPrerollingAudio()
   {
     MOZ_ASSERT(OnTaskQueue());
     if (mIsAudioPrerolling) {
@@ -854,17 +867,17 @@ private:
   // Note that the odd semantics here are designed to replicate the current
   // behavior where we notify the decoder each time we come out of dormant, but
   // send suppressed event visibility for those cases. This code can probably be
   // simplified.
   bool mNotifyMetadataBeforeFirstFrame;
 
   // True if we've dispatched an event to the decode task queue to call
   // DecodeThreadRun(). We use this flag to prevent us from dispatching
-  // unneccessary runnables, since the decode thread runs in a loop.
+  // unnecessary runnables, since the decode thread runs in a loop.
   bool mDispatchedEventToDecode;
 
   // If this is true while we're in buffering mode, we can exit early,
   // as it's likely we may be able to playback. This happens when we enter
   // buffering mode soon after the decode starts, because the decode-ahead
   // ran fast enough to exhaust all data while the download is starting up.
   // Synchronised via decoder monitor.
   bool mQuickBuffering;
@@ -896,17 +909,17 @@ private:
   nsAutoPtr<MetadataTags> mMetadataTags;
 
   mozilla::MediaMetadataManager mMetadataManager;
 
   // Track our request to update the buffered ranges
   MozPromiseRequestHolder<MediaDecoderReader::BufferedUpdatePromise> mBufferedUpdateRequest;
 
   // True if we need to call FinishDecodeFirstFrame() upon frame decoding
-  // successeeding.
+  // succeeding.
   bool mDecodingFirstFrame;
 
   // True if we are back from DECODER_STATE_DORMANT state and
   // LoadedMetadataEvent was already sent.
   bool mSentLoadedMetadataEvent;
   // True if we are back from DECODER_STATE_DORMANT state and
   // FirstFrameLoadedEvent was already sent, then we can skip
   // SetStartTime because the mStartTime already set before. Also we don't need
@@ -992,16 +1005,19 @@ private:
   Mirror<int64_t> mDecoderPosition;
 
   // True if the media is seekable (i.e. supports random access).
   Mirror<bool> mMediaSeekable;
 
   // True if the media is seekable only in buffered ranges.
   Mirror<bool> mMediaSeekableOnlyInBufferedRanges;
 
+  // IsVisible, mirrored from the media decoder.
+  Mirror<bool> mIsVisible;
+
   // Duration of the media. This is guaranteed to be non-null after we finish
   // decoding the first frame.
   Canonical<media::NullableTimeUnit> mDuration;
 
   // Whether we're currently in or transitioning to shutdown state.
   Canonical<bool> mIsShutdown;
 
   // The status of our next frame. Mirrored on the main thread and used to
@@ -1011,17 +1027,17 @@ private:
   // The time of the current frame in microseconds, corresponding to the "current
   // playback position" in HTML5. This is referenced from 0, which is the initial
   // playback position.
   Canonical<int64_t> mCurrentPosition;
 
   // Current playback position in the stream in bytes.
   Canonical<int64_t> mPlaybackOffset;
 
-  // Used to distiguish whether the audio is producing sound.
+  // Used to distinguish whether the audio is producing sound.
   Canonical<bool> mIsAudioDataAudible;
 
 public:
   AbstractCanonical<media::TimeIntervals>* CanonicalBuffered() const;
 
   AbstractCanonical<media::NullableTimeUnit>* CanonicalDuration() {
     return &mDuration;
   }
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -1278,17 +1278,17 @@ MediaFormatReader::WaitForData(MediaData
     return WaitForDataPromise::CreateAndResolve(decoder.mType, __func__);
   }
   RefPtr<WaitForDataPromise> p = decoder.mWaitingPromise.Ensure(__func__);
   ScheduleUpdate(trackType);
   return p;
 }
 
 nsresult
-MediaFormatReader::ResetDecode()
+MediaFormatReader::ResetDecode(TargetQueues aQueues)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOGV("");
 
   mSeekPromise.RejectIfExists(NS_OK, __func__);
   mSkipRequest.DisconnectIfExists();
 
   // Do the same for any data wait promises.
@@ -1301,23 +1301,24 @@ MediaFormatReader::ResetDecode()
   ResetDemuxers();
 
   if (HasVideo()) {
     Reset(TrackInfo::kVideoTrack);
     if (mVideo.HasPromise()) {
       mVideo.RejectPromise(CANCELED, __func__);
     }
   }
-  if (HasAudio()) {
+
+  if (HasAudio() && aQueues == AUDIO_VIDEO) {
     Reset(TrackInfo::kAudioTrack);
     if (mAudio.HasPromise()) {
       mAudio.RejectPromise(CANCELED, __func__);
     }
   }
-  return MediaDecoderReader::ResetDecode();
+  return MediaDecoderReader::ResetDecode(aQueues);
 }
 
 void
 MediaFormatReader::ResetDemuxers()
 {
   if (HasVideo()) {
     mVideo.ResetDemuxer();
     mVideo.ResetState();
@@ -1603,19 +1604,20 @@ MediaFormatReader::DoVideoSeek()
 
 void
 MediaFormatReader::OnVideoSeekCompleted(media::TimeUnit aTime)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOGV("Video seeked to %lld", aTime.ToMicroseconds());
   mVideo.mSeekRequest.Complete();
 
-  if (HasAudio()) {
-    MOZ_ASSERT(mPendingSeekTime.isSome() && mOriginalSeekTarget.isSome());
-    if (mOriginalSeekTarget.ref().IsFast()) {
+  MOZ_ASSERT(mOriginalSeekTarget.isSome());
+  if (HasAudio() && !mOriginalSeekTarget->IsVideoOnly()) {
+    MOZ_ASSERT(mPendingSeekTime.isSome());
+    if (mOriginalSeekTarget->IsFast()) {
       // We are performing a fast seek. We need to seek audio to where the
       // video seeked to, to ensure proper A/V sync once playback resume.
       mPendingSeekTime = Some(aTime);
     }
     DoAudioSeek();
   } else {
     mPendingSeekTime.reset();
     mSeekPromise.Resolve(aTime, __func__);
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -57,17 +57,17 @@ public:
 
   RefPtr<BufferedUpdatePromise> UpdateBufferedWithPromise() override;
 
   bool ForceZeroStartTime() const override;
 
   // For Media Resource Management
   void ReleaseMediaResources() override;
 
-  nsresult ResetDecode() override;
+  nsresult ResetDecode(TargetQueues aQueues) override;
 
   RefPtr<ShutdownPromise> Shutdown() override;
 
   bool IsAsync() const override { return true; }
 
   bool VideoIsHardwareAccelerated() const override;
 
   bool IsWaitForDataSupported() const override { return true; }
@@ -317,17 +317,17 @@ private:
     uint64_t mNumSamplesInput;
     uint64_t mNumSamplesOutput;
     uint64_t mNumSamplesOutputTotal;
     uint64_t mNumSamplesSkippedTotal;
 
     uint64_t mNumSamplesOutputTotalSinceTelemetry;
     uint64_t mNumSamplesSkippedTotalSinceTelemetry;
 
-    // These get overriden in the templated concrete class.
+    // These get overridden in the templated concrete class.
     // Indicate if we have a pending promise for decoded frame.
     // Rejecting the promise will stop the reader from decoding ahead.
     virtual bool HasPromise() const = 0;
     virtual RefPtr<MediaDataPromise> EnsurePromise(const char* aMethodName) = 0;
     virtual void ResolvePromise(MediaData* aData, const char* aMethodName) = 0;
     virtual void RejectPromise(MediaDecoderReader::NotDecodedReason aReason,
                                const char* aMethodName) = 0;
 
--- a/dom/media/MediaInfo.h
+++ b/dom/media/MediaInfo.h
@@ -300,19 +300,22 @@ public:
     , mBitDepth(aOther.mBitDepth)
     , mProfile(aOther.mProfile)
     , mExtendedProfile(aOther.mExtendedProfile)
     , mCodecSpecificConfig(aOther.mCodecSpecificConfig)
     , mExtraData(aOther.mExtraData)
   {
   }
 
+  static const uint32_t MAX_RATE = 640000;
+
   bool IsValid() const override
   {
-    return mChannels > 0 && mChannels <= MAX_AUDIO_CHANNELS && mRate > 0;
+    return mChannels > 0 && mChannels <= MAX_AUDIO_CHANNELS
+           && mRate > 0 && mRate <= MAX_RATE;
   }
 
   AudioInfo* GetAsAudioInfo() override
   {
     return this;
   }
 
   const AudioInfo* GetAsAudioInfo() const override
--- a/dom/media/SeekJob.cpp
+++ b/dom/media/SeekJob.cpp
@@ -28,17 +28,17 @@ SeekJob& SeekJob::operator=(SeekJob&& aO
 {
   MOZ_DIAGNOSTIC_ASSERT(!Exists());
   mTarget = aOther.mTarget;
   aOther.mTarget.Reset();
   mPromise = Move(aOther.mPromise);
   return *this;
 }
 
-bool SeekJob::Exists()
+bool SeekJob::Exists() const
 {
   MOZ_ASSERT(mTarget.IsValid() == !mPromise.IsEmpty());
   return mTarget.IsValid();
 }
 
 void SeekJob::Resolve(bool aAtEnd, const char* aCallSite)
 {
   MediaDecoder::SeekResolveValue val(aAtEnd, mTarget.mEventVisibility);
--- a/dom/media/SeekJob.h
+++ b/dom/media/SeekJob.h
@@ -16,17 +16,17 @@ namespace mozilla {
 
 struct SeekJob {
   SeekJob();
 
   SeekJob(SeekJob&& aOther);
 
   SeekJob& operator=(SeekJob&& aOther);
 
-  bool Exists();
+  bool Exists() const;
 
   void Resolve(bool aAtEnd, const char* aCallSite);
 
   void RejectIfExists(const char* aCallSite);
 
   ~SeekJob();
 
   SeekTarget mTarget;
--- a/dom/media/SeekTarget.h
+++ b/dom/media/SeekTarget.h
@@ -12,22 +12,24 @@
 namespace mozilla {
 
 enum class MediaDecoderEventVisibility : int8_t {
   Observable,
   Suppressed
 };
 
 // Stores the seek target; the time to seek to, and whether an Accurate,
-// or "Fast" (nearest keyframe) seek was requested.
+// "Fast" (nearest keyframe), or "Video Only" (no audio seek) seek was
+// requested.
 struct SeekTarget {
   enum Type {
     Invalid,
     PrevSyncPoint,
-    Accurate
+    Accurate,
+    AccurateVideoOnly,
   };
   SeekTarget()
     : mEventVisibility(MediaDecoderEventVisibility::Observable)
     , mTime(media::TimeUnit::Invalid())
     , mType(SeekTarget::Invalid)
   {
   }
   SeekTarget(int64_t aTimeUsecs,
@@ -73,23 +75,26 @@ struct SeekTarget {
     mType = aType;
   }
   bool IsFast() const {
     return mType == SeekTarget::Type::PrevSyncPoint;
   }
   bool IsAccurate() const {
     return mType == SeekTarget::Type::Accurate;
   }
+  bool IsVideoOnly() const {
+    return mType == SeekTarget::Type::AccurateVideoOnly;
+  }
 
   MediaDecoderEventVisibility mEventVisibility;
 
 private:
   // Seek target time.
   media::TimeUnit mTime;
   // Whether we should seek "Fast", or "Accurate".
-  // "Fast" seeks to the seek point preceeding mTime, whereas
+  // "Fast" seeks to the seek point preceding mTime, whereas
   // "Accurate" seeks as close as possible to mTime.
   Type mType;
 };
 
 } // namespace mozilla
 
 #endif /* SEEK_TARGET_H */
--- a/dom/media/SeekTask.cpp
+++ b/dom/media/SeekTask.cpp
@@ -172,17 +172,17 @@ SeekTask::NeedToResetMDSM() const
 
 SeekJob&
 SeekTask::GetSeekJob()
 {
   return mSeekJob;
 }
 
 bool
-SeekTask::Exists()
+SeekTask::Exists() const
 {
   return mSeekJob.Exists();
 }
 
 RefPtr<SeekTask::SeekTaskPromise>
 SeekTask::Seek(const media::TimeUnit& aDuration)
 {
   AssertOwnerThread();
@@ -417,16 +417,17 @@ bool
 SeekTask::IsAudioSeekComplete()
 {
   AssertOwnerThread();
 
   SAMPLE_LOG("IsAudioSeekComplete() curTarVal=%d mAudDis=%d aqFin=%d aqSz=%d",
       mSeekJob.Exists(), mDropAudioUntilNextDiscontinuity, mIsAudioQueueFinished, !!mSeekedAudioData);
   return
     !HasAudio() ||
+    mSeekJob.mTarget.IsVideoOnly() ||
     (Exists() && !mDropAudioUntilNextDiscontinuity &&
      (mIsAudioQueueFinished || mSeekedAudioData));
 }
 
 bool
 SeekTask::IsVideoSeekComplete()
 {
   AssertOwnerThread();
@@ -473,18 +474,20 @@ SeekTask::CheckIfSeekComplete()
 
 void
 SeekTask::OnSeekResolved(media::TimeUnit)
 {
   AssertOwnerThread();
   mSeekRequest.Complete();
   // We must decode the first samples of active streams, so we can determine
   // the new stream time. So dispatch tasks to do that.
-  EnsureAudioDecodeTaskQueued();
   EnsureVideoDecodeTaskQueued();
+  if (!mSeekJob.mTarget.IsVideoOnly()) {
+    EnsureAudioDecodeTaskQueued();
+  }
 }
 
 void
 SeekTask::OnSeekRejected(nsresult aResult)
 {
   AssertOwnerThread();
   mSeekRequest.Complete();
   MOZ_ASSERT(NS_FAILED(aResult), "Cancels should also disconnect mSeekRequest");
--- a/dom/media/SeekTask.h
+++ b/dom/media/SeekTask.h
@@ -58,17 +58,17 @@ public:
   virtual void Discard();
 
   virtual RefPtr<SeekTaskPromise> Seek(const media::TimeUnit& aDuration);
 
   virtual bool NeedToResetMDSM() const;
 
   SeekJob& GetSeekJob();
 
-  bool Exists();
+  bool Exists() const;
 
 protected:
   SeekTask(const void* aDecoderID,
            AbstractThread* aThread,
            MediaDecoderReaderWrapper* aReader,
            SeekJob&& aSeekJob,
            const MediaInfo& aInfo,
            const media::TimeUnit& aDuration,
--- a/dom/media/android/AndroidMediaReader.cpp
+++ b/dom/media/android/AndroidMediaReader.cpp
@@ -89,34 +89,34 @@ nsresult AndroidMediaReader::ReadMetadat
  *aInfo = mInfo;
  *aTags = nullptr;
   return NS_OK;
 }
 
 RefPtr<ShutdownPromise>
 AndroidMediaReader::Shutdown()
 {
-  ResetDecode();
+  ResetDecode(AUDIO_VIDEO);
   if (mPlugin) {
     GetAndroidMediaPluginHost()->DestroyDecoder(mPlugin);
     mPlugin = nullptr;
   }
 
   return MediaDecoderReader::Shutdown();
 }
 
 // Resets all state related to decoding, emptying all buffers etc.
-nsresult AndroidMediaReader::ResetDecode()
+nsresult AndroidMediaReader::ResetDecode(TargetQueues aQueues)
 {
   if (mLastVideoFrame) {
     mLastVideoFrame = nullptr;
   }
   mSeekRequest.DisconnectIfExists();
   mSeekPromise.RejectIfExists(NS_OK, __func__);
-  return MediaDecoderReader::ResetDecode();
+  return MediaDecoderReader::ResetDecode(aQueues);
 }
 
 bool AndroidMediaReader::DecodeVideoFrame(bool &aKeyframeSkip,
                                           int64_t aTimeThreshold)
 {
   // Record number of frames decoded and parsed. Automatically update the
   // stats counters using the AutoNotifyDecoded stack-based class.
   AbstractMediaDecoder::AutoNotifyDecoded a(mDecoder);
--- a/dom/media/android/AndroidMediaReader.h
+++ b/dom/media/android/AndroidMediaReader.h
@@ -37,17 +37,17 @@ class AndroidMediaReader : public MediaD
   int64_t mAudioSeekTimeUs;
   RefPtr<VideoData> mLastVideoFrame;
   MozPromiseHolder<MediaDecoderReader::SeekPromise> mSeekPromise;
   MozPromiseRequestHolder<MediaDecoderReader::MediaDataPromise> mSeekRequest;
 public:
   AndroidMediaReader(AbstractMediaDecoder* aDecoder,
                      const nsACString& aContentType);
 
-  nsresult ResetDecode() override;
+  nsresult ResetDecode(TargetQueues aQueues) override;
 
   bool DecodeAudioData() override;
   bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold) override;
 
   nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) override;
   RefPtr<SeekPromise> Seek(SeekTarget aTarget, int64_t aEndTime) override;
 
   RefPtr<ShutdownPromise> Shutdown() override;
--- a/dom/media/ogg/OggReader.cpp
+++ b/dom/media/ogg/OggReader.cpp
@@ -164,27 +164,27 @@ OggReader::~OggReader()
 }
 
 nsresult OggReader::Init() {
   int ret = ogg_sync_init(&mOggState);
   NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE);
   return NS_OK;
 }
 
-nsresult OggReader::ResetDecode()
+nsresult OggReader::ResetDecode(TargetQueues aQueues)
 {
-  return ResetDecode(false);
+  return ResetDecode(false, aQueues);
 }
 
-nsresult OggReader::ResetDecode(bool start)
+nsresult OggReader::ResetDecode(bool start, TargetQueues aQueues)
 {
   MOZ_ASSERT(OnTaskQueue());
   nsresult res = NS_OK;
 
-  if (NS_FAILED(MediaDecoderReader::ResetDecode())) {
+  if (NS_FAILED(MediaDecoderReader::ResetDecode(aQueues))) {
     res = NS_ERROR_FAILURE;
   }
 
   // Discard any previously buffered packets/pages.
   ogg_sync_reset(&mOggState);
   if (mVorbisState && NS_FAILED(mVorbisState->Reset())) {
     res = NS_ERROR_FAILURE;
   }
--- a/dom/media/ogg/OggReader.h
+++ b/dom/media/ogg/OggReader.h
@@ -46,17 +46,17 @@ class OggReader final : public MediaDeco
 public:
   explicit OggReader(AbstractMediaDecoder* aDecoder);
 
 protected:
   ~OggReader();
 
 public:
   nsresult Init() override;
-  nsresult ResetDecode() override;
+  nsresult ResetDecode(TargetQueues aQueues = AUDIO_VIDEO) override;
   bool DecodeAudioData() override;
 
   // If the Theora granulepos has not been captured, it may read several packets
   // until one with a granulepos has been captured, to ensure that all packets
   // read have valid time info.
   bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold) override;
 
   nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) override;
@@ -81,25 +81,25 @@ private:
   RefPtr<AudioData> SyncDecodeToFirstAudioData();
   RefPtr<VideoData> SyncDecodeToFirstVideoData();
 
   // This monitor should be taken when reading or writing to mIsChained.
   ReentrantMonitor mMonitor;
 
   // Specialized Reset() method to signal if the seek is
   // to the start of the stream.
-  nsresult ResetDecode(bool start);
+  nsresult ResetDecode(bool start, TargetQueues aQueues = AUDIO_VIDEO);
 
   nsresult SeekInternal(int64_t aTime, int64_t aEndTime);
 
   bool HasSkeleton() {
     return mSkeletonState != 0 && mSkeletonState->mActive;
   }
 
-  // Seeks to the keyframe preceeding the target time using available
+  // Seeks to the keyframe preceding the target time using available
   // keyframe indexes.
   enum IndexedSeekResult {
     SEEK_OK,          // Success.
     SEEK_INDEX_FAIL,  // Failure due to no index, or invalid index.
     SEEK_FATAL_ERROR  // Error returned by a stream operation.
   };
   IndexedSeekResult SeekToKeyframeUsingIndex(int64_t aTarget);
 
--- a/dom/media/omx/MediaOmxCommonDecoder.cpp
+++ b/dom/media/omx/MediaOmxCommonDecoder.cpp
@@ -247,16 +247,17 @@ MediaOmxCommonDecoder::CurrentPosition()
   }
   return mAudioOffloadPlayer->GetMediaTimeUs();
 }
 
 void
 MediaOmxCommonDecoder::SetElementVisibility(bool aIsVisible)
 {
   MOZ_ASSERT(NS_IsMainThread());
+  MediaDecoder::SetElementVisibility(aIsVisible);
   if (mAudioOffloadPlayer) {
     mAudioOffloadPlayer->SetElementVisibility(aIsVisible);
   }
 }
 
 MediaDecoderOwner::NextFrameStatus
 MediaOmxCommonDecoder::NextFrameStatus()
 {
--- a/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
@@ -17,26 +17,27 @@
 #include "prsystem.h"
 
 namespace mozilla
 {
 
 StaticMutex FFmpegDataDecoder<LIBAV_VER>::sMonitor;
 
   FFmpegDataDecoder<LIBAV_VER>::FFmpegDataDecoder(FFmpegLibWrapper* aLib,
-                                                  FlushableTaskQueue* aTaskQueue,
+                                                  TaskQueue* aTaskQueue,
                                                   MediaDataDecoderCallback* aCallback,
                                                   AVCodecID aCodecID)
   : mLib(aLib)
   , mCallback(aCallback)
   , mCodecContext(nullptr)
   , mFrame(NULL)
   , mExtraData(nullptr)
   , mCodecID(aCodecID)
   , mTaskQueue(aTaskQueue)
+  , mIsFlushing(false)
 {
   MOZ_ASSERT(aLib);
   MOZ_COUNT_CTOR(FFmpegDataDecoder);
 }
 
 FFmpegDataDecoder<LIBAV_VER>::~FFmpegDataDecoder()
 {
   MOZ_COUNT_DTOR(FFmpegDataDecoder);
@@ -110,16 +111,19 @@ FFmpegDataDecoder<LIBAV_VER>::Shutdown()
   }
   return NS_OK;
 }
 
 void
 FFmpegDataDecoder<LIBAV_VER>::ProcessDecode(MediaRawData* aSample)
 {
   MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+  if (mIsFlushing) {
+    return;
+  }
   switch (DoDecode(aSample)) {
     case DecodeResult::DECODE_ERROR:
       mCallback->Error();
       break;
     default:
       if (mTaskQueue->IsEmpty()) {
         mCallback->InputExhausted();
       }
@@ -133,20 +137,21 @@ FFmpegDataDecoder<LIBAV_VER>::Input(Medi
     this, &FFmpegDataDecoder::ProcessDecode, aSample));
   return NS_OK;
 }
 
 nsresult
 FFmpegDataDecoder<LIBAV_VER>::Flush()
 {
   MOZ_ASSERT(mCallback->OnReaderTaskQueue());
-  mTaskQueue->Flush();
+  mIsFlushing = true;
   nsCOMPtr<nsIRunnable> runnable =
     NewRunnableMethod(this, &FFmpegDataDecoder<LIBAV_VER>::ProcessFlush);
   SyncRunnable::DispatchToThread(mTaskQueue, runnable);
+  mIsFlushing = false;
   return NS_OK;
 }
 
 nsresult
 FFmpegDataDecoder<LIBAV_VER>::Drain()
 {
   MOZ_ASSERT(mCallback->OnReaderTaskQueue());
   nsCOMPtr<nsIRunnable> runnable =
--- a/dom/media/platforms/ffmpeg/FFmpegDataDecoder.h
+++ b/dom/media/platforms/ffmpeg/FFmpegDataDecoder.h
@@ -19,17 +19,17 @@ template <int V>
 class FFmpegDataDecoder : public MediaDataDecoder
 {
 };
 
 template <>
 class FFmpegDataDecoder<LIBAV_VER> : public MediaDataDecoder
 {
 public:
-  FFmpegDataDecoder(FFmpegLibWrapper* aLib, FlushableTaskQueue* aTaskQueue,
+  FFmpegDataDecoder(FFmpegLibWrapper* aLib, TaskQueue* aTaskQueue,
                     MediaDataDecoderCallback* aCallback,
                     AVCodecID aCodecID);
   virtual ~FFmpegDataDecoder();
 
   static bool Link();
 
   RefPtr<InitPromise> Init() override = 0;
   nsresult Input(MediaRawData* aSample) override;
@@ -62,14 +62,17 @@ protected:
   AVCodecID mCodecID;
 
 private:
   void ProcessDecode(MediaRawData* aSample);
   virtual DecodeResult DoDecode(MediaRawData* aSample) = 0;
   virtual void ProcessDrain() = 0;
 
   static StaticMutex sMonitor;
-  const RefPtr<FlushableTaskQueue> mTaskQueue;
+  const RefPtr<TaskQueue> mTaskQueue;
+  // Set/cleared on reader thread calling Flush() to indicate that output is
+  // not required and so input samples on mTaskQueue need not be processed.
+  Atomic<bool> mIsFlushing;
 };
 
 } // namespace mozilla
 
 #endif // __FFmpegDataDecoder_h__
--- a/dom/media/raw/RawReader.cpp
+++ b/dom/media/raw/RawReader.cpp
@@ -22,20 +22,20 @@ RawReader::RawReader(AbstractMediaDecode
   MOZ_COUNT_CTOR(RawReader);
 }
 
 RawReader::~RawReader()
 {
   MOZ_COUNT_DTOR(RawReader);
 }
 
-nsresult RawReader::ResetDecode()
+nsresult RawReader::ResetDecode(TargetQueues aQueues)
 {
   mCurrentFrame = 0;
-  return MediaDecoderReader::ResetDecode();
+  return MediaDecoderReader::ResetDecode(aQueues);
 }
 
 nsresult RawReader::ReadMetadata(MediaInfo* aInfo,
                                  MetadataTags** aTags)
 {
   MOZ_ASSERT(OnTaskQueue());
 
   if (!ReadFromResource(reinterpret_cast<uint8_t*>(&mMetadata),
--- a/dom/media/raw/RawReader.h
+++ b/dom/media/raw/RawReader.h
@@ -15,17 +15,17 @@ class RawReader : public MediaDecoderRea
 {
 public:
   explicit RawReader(AbstractMediaDecoder* aDecoder);
 
 protected:
   ~RawReader();
 
 public:
-  nsresult ResetDecode() override;
+  nsresult ResetDecode(TargetQueues aQueues) override;
   bool DecodeAudioData() override;
 
   bool DecodeVideoFrame(bool &aKeyframeSkip,
                         int64_t aTimeThreshold) override;
 
   nsresult ReadMetadata(MediaInfo* aInfo,
                         MetadataTags** aTags) override;
   RefPtr<SeekPromise> Seek(SeekTarget aTarget, int64_t aEndTime) override;
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -2224,17 +2224,17 @@ TranslateToNPCocoaEvent(WidgetGUIEvent* 
             cocoaEvent.data.mouse.buttonNumber = 1;
             break;
           case WidgetMouseEvent::eMiddleButton:
             cocoaEvent.data.mouse.buttonNumber = 2;
             break;
           default:
             NS_WARNING("Mouse button we don't know about?");
         }
-        cocoaEvent.data.mouse.clickCount = mouseEvent->clickCount;
+        cocoaEvent.data.mouse.clickCount = mouseEvent->mClickCount;
       } else {
         NS_WARNING("eMouseUp/DOWN is not a WidgetMouseEvent?");
       }
       break;
     }
     case eLegacyMouseLineOrPageScroll: {
       WidgetWheelEvent* wheelEvent = anEvent->AsWheelEvent();
       if (wheelEvent) {
@@ -2406,17 +2406,17 @@ nsEventStatus nsPluginInstanceOwner::Pro
         break;
       }
       case eMouseDown: {
         static const int downMsgs[] =
           { WM_LBUTTONDOWN, WM_MBUTTONDOWN, WM_RBUTTONDOWN };
         static const int dblClickMsgs[] =
           { WM_LBUTTONDBLCLK, WM_MBUTTONDBLCLK, WM_RBUTTONDBLCLK };
         const WidgetMouseEvent* mouseEvent = anEvent.AsMouseEvent();
-        if (mouseEvent->clickCount == 2) {
+        if (mouseEvent->mClickCount == 2) {
           pluginEvent.event = dblClickMsgs[mouseEvent->button];
         } else {
           pluginEvent.event = downMsgs[mouseEvent->button];
         }
         break;
       }
       case eMouseUp: {
         static const int upMsgs[] =
--- a/dom/tests/mochitest/bugs/mochitest.ini
+++ b/dom/tests/mochitest/bugs/mochitest.ini
@@ -155,18 +155,18 @@ skip-if = (buildapp == 'b2g' && toolkit 
 [test_bug927901.html]
 [test_devicemotion_multiple_listeners.html]
 skip-if = toolkit == 'android' #bug 775227
 [test_domparser_after_blank.html]
 [test_errorReporting.html]
 [test_onerror_message.html]
 [test_protochains.html]
 [test_resize_move_windows.html]
-skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s #Windows can't change size and position on Android # b2g(Windows can't change size and position on B2G) b2g-debug(Windows can't change size and position on B2G) b2g-desktop(Windows can't change size and position on B2G)
+skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || os == 'linux' #Windows can't change size and position on Android # b2g(Windows can't change size and position on B2G) b2g-debug(Windows can't change size and position on B2G) b2g-desktop(Windows can't change size and position on B2G)
 [test_sizetocontent_clamp.html]
-skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s #Windows can't change size on Android # b2g(Windows can't change size on B2G) b2g-debug(Windows can't change size on B2G) b2g-desktop(Windows can't change size on B2G)
+skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' #Windows can't change size on Android # b2g(Windows can't change size on B2G) b2g-debug(Windows can't change size on B2G) b2g-desktop(Windows can't change size on B2G)
 [test_toJSON.html]
 [test_window_bar.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android'
 [test_bug1022869.html]
 [test_bug1112040.html]
 [test_bug1160342_marquee.html]
 [test_bug1171215.html]
--- a/dom/tests/mochitest/bugs/test_resize_move_windows.html
+++ b/dom/tests/mochitest/bugs/test_resize_move_windows.html
@@ -232,95 +232,86 @@ function checkChangeIsEnabled(aWindow, a
 
   function outerChangeCondition() {
     return aWindow.outerWidth != oWidth && aWindow.outerHeight != oHeight;
   }
 
   function outerChangeTest() {
     isnot(aWindow.outerWidth, oWidth, "Window outerWidth should have changed");
     isnot(aWindow.outerHeight, oHeight, "Window outerHeight should have changed");
-
-    aWindow.outerWidth = oWidth;
-    aWindow.outerHeight = oHeight;
   }
 
   /**
    * Size checks.
    */
   prevWidth = aWindow.innerWidth;
   prevHeight = aWindow.innerHeight;
-
   aWindow.innerWidth = getNewWidth(aWindow);
   aWindow.innerHeight = getNewHeight(aWindow);
 
   hitEventLoop(sizeChangeCondition, sizeChangeTest, hits)
   .then(function() {
     aWindow.resizeTo(getNewWidth(aWindow), getNewHeight(aWindow));
-  })
-  .then(function() {
     return hitEventLoop(sizeChangeCondition, sizeChangeTest, hits);
   })
   .then(function () {
     aWindow.resizeBy(getNewWidth(aWindow) - aWindow.innerWidth,
                      getNewHeight(aWindow) - aWindow.innerHeight);
-  })
-  .then(function() {
     return hitEventLoop(sizeChangeCondition, sizeChangeTest, hits);
   })
-  .then(function () {
-    prevWidth = aWindow.innerWidth = getNewWidth(aWindow);
-    prevHeight = aWindow.innerHeight = getNewHeight(aWindow);
+  .then(function() {
     aWindow.sizeToContent();
-  })
-  .then(function() {
-    hitEventLoop(sizeChangeCondition, sizeChangeTest, hits);
+    return hitEventLoop(sizeChangeCondition, sizeChangeTest, hits);
   })
   .then(function() {
     /**
      * Position checks.
      */
     prevX = aWindow.screenX;
     prevY = aWindow.screenY;
 
     aWindow.screenX = getNewX(aWindow);
     aWindow.screenY = getNewY(aWindow);
-  })
-  .then(function() {
     return hitEventLoop(posChangeCondition, posChangeTest, hits);
   })
   .then(function() {
     prevX = aWindow.screenX;
     prevY = aWindow.screenY;
 
     aWindow.moveTo(getNewX(aWindow), getNewY(aWindow));
-  })
-  .then(function() {
     return hitEventLoop(posChangeCondition, posChangeTest, hits);
   })
   .then(function() {
     prevX = aWindow.screenX;
     prevY = aWindow.screenY;
 
     aWindow.moveBy(getNewX(aWindow) - aWindow.screenX,
                    getNewY(aWindow) - aWindow.screenY);
-  })
-  .then(function() {
     return hitEventLoop(posChangeCondition, posChangeTest, hits);
   })
   .then(function() {
     /**
      * Outer width/height checks.
      */
     oWidth = aWindow.outerWidth;
     oHeight = aWindow.outerHeight;
 
     aWindow.outerWidth = oWidth * 2;
     aWindow.outerHeight = oHeight * 2;
+    return hitEventLoop(outerChangeCondition, outerChangeTest, hits);
   })
   .then(function() {
+    let origWidth = oWidth;
+    let origHeight = oHeight;
+
+    oWidth = aWindow.outerWidth;
+    oHeight = aWindow.outerHeight;
+
+    aWindow.outerWidth = origWidth;
+    aWindow.outerHeight = origHeight;
     return hitEventLoop(outerChangeCondition, outerChangeTest, hits);
   })
   .then(aNext);
 }
 
 SpecialPowers.pushPrefEnv({"set": [["dom.disable_window_move_resize", false]]}, function() {
 SimpleTest.waitForFocus(function() {
   if (screen.width <= 200 || screen.height <= 200) {
--- a/dom/tests/mochitest/bugs/test_sizetocontent_clamp.html
+++ b/dom/tests/mochitest/bugs/test_sizetocontent_clamp.html
@@ -30,35 +30,34 @@ var epsilon =  navigator.platform.indexO
 
 // Windows 8 has a minimum 122 pixel inner window width due to
 // outer window chrome.
 var isWin8 = (navigator.userAgent.indexOf("Windows NT 6.2") != -1);
 
 var innerWidthMin = (isWin8 ? 120 : 100);
 var innerWidthMax = (isWin8 ? 125 : 100);
 
+var isExecuted = false;
+
 function test() {
   var w = window.open('data:text/html,null', null, 'width=300,height=300');
-  var nbResize = 0;
 
   SimpleTest.waitForFocus(function() {
     w.onresize = function() {
-      nbResize++;
 
-      if (nbResize == 1) {
+      if (w.innerWidth > 300 - epsilon || isExecuted) {
         return;
       }
 
+      isExecuted = true;
+
       ok(w.innerWidth + epsilon >= innerWidthMin && w.innerWidth - epsilon <= innerWidthMax,
-         "innerWidth should be between " + innerWidthMin + " and " + innerWidthMax);
+         "innerWidth should be between " + innerWidthMin + " and " + innerWidthMax + " but it was: " + w.innerWidth);
       ok(w.innerHeight + epsilon >= 100 && w.innerHeight - epsilon <= 100,
-         "innerHeight should be around 100");
-
-      // It's not clear why 2 events are coming...
-      is(nbResize, 2, "We should get 2 events.");
+         "innerHeight should be around 100" + " but it was: " + w.innerHeight);
 
       w.close();
 
       SimpleTest.waitForFocus(function() {
         SimpleTest.finish();
       });
     };
     w.sizeToContent();
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -1400,16 +1400,18 @@ var interfaceNamesInGlobalScope =
     {name: "VTTRegion", disabled: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "WaveShaperNode",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "WebGLActiveInfo",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "WebGLBuffer",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    "WebGLContextEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "WebGLFramebuffer",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "WebGLProgram",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "WebGLQuery", nightly: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "WebGLRenderbuffer",
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -219,33 +219,33 @@ partial interface Document {
   readonly attribute unsigned long referrerPolicy;
 
 };
 
 // https://fullscreen.spec.whatwg.org/#api
 partial interface Document {
   // Note: Per spec the 'S' in these two is lowercase, but the "Moz"
   // versions hve it uppercase.
-  [Func="nsDocument::IsUnprefixedFullscreenEnabled"]
+  [LenientSetter, Func="nsDocument::IsUnprefixedFullscreenEnabled"]
+  readonly attribute boolean fullscreen;
+  [BinaryName="fullscreen", Deprecated="PrefixedFullscreenAPI"]
+  readonly attribute boolean mozFullScreen;
+  [LenientSetter, Func="nsDocument::IsUnprefixedFullscreenEnabled"]
   readonly attribute boolean fullscreenEnabled;
   [BinaryName="fullscreenEnabled", Deprecated="PrefixedFullscreenAPI"]
   readonly attribute boolean mozFullScreenEnabled;
-  [Func="nsDocument::IsUnprefixedFullscreenEnabled"]
+  [LenientSetter, Func="nsDocument::IsUnprefixedFullscreenEnabled"]
   readonly attribute Element? fullscreenElement;
   [BinaryName="fullscreenElement", Deprecated="PrefixedFullscreenAPI"]
   readonly attribute Element? mozFullScreenElement;
 
   [Func="nsDocument::IsUnprefixedFullscreenEnabled"]
   void exitFullscreen();
   [BinaryName="exitFullscreen", Deprecated="PrefixedFullscreenAPI"]
   void mozCancelFullScreen();
-
-  // Gecko-specific fullscreen bits
-  [Deprecated="PrefixedFullscreenAPI"]
-  readonly attribute boolean mozFullScreen;
 };
 
 // http://dvcs.w3.org/hg/pointerlock/raw-file/default/index.html#extensions-to-the-document-interface
 partial interface Document {
     readonly attribute Element? mozPointerLockElement;
     void mozExitPointerLock ();
 };
 
new file mode 100644
--- /dev/null
+++ b/dom/webidl/WebGLContextEvent.webidl
@@ -0,0 +1,20 @@
+/* -*- Mode: IDL; 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/.
+
+ * The origin of this IDL file is
+ * https://www.khronos.org/registry/webgl/specs/latest/1.0/#fire-a-webgl-context-event
+ */
+
+[Constructor(DOMString type, optional WebGLContextEventInit eventInit),
+ Exposed=(Window,Worker),
+ Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"]
+interface WebGLContextEvent : Event {
+  readonly attribute DOMString statusMessage;
+};
+
+// EventInit is defined in the DOM4 specification.
+dictionary WebGLContextEventInit : EventInit {
+  DOMString statusMessage = "";
+};
--- a/dom/webidl/WebGLRenderingContext.webidl
+++ b/dom/webidl/WebGLRenderingContext.webidl
@@ -788,31 +788,21 @@ interface WebGLRenderingContext {
 
     void viewport(GLint x, GLint y, GLsizei width, GLsizei height);
 };
 
 // For OffscreenCanvas
 // Reference: https://wiki.whatwg.org/wiki/OffscreenCanvas
 [Exposed=(Window,Worker)]
 partial interface WebGLRenderingContext {
-  [Func="mozilla::dom::OffscreenCanvas::PrefEnabled"]
-  void commit();
+    [Func="mozilla::dom::OffscreenCanvas::PrefEnabled"]
+    void commit();
 };
 
-/*[Constructor(DOMString type, optional WebGLContextEventInit eventInit)]
-interface WebGLContextEvent : Event {
-    readonly attribute DOMString statusMessage;
-    };*/
-
-// EventInit is defined in the DOM4 specification.
-/*dictionary WebGLContextEventInit : EventInit {
-    DOMString statusMessage;
-    };*/
-
-
+////////////////////////////////////////
 // specific extension interfaces
 
 [NoInterfaceObject]
 interface WEBGL_compressed_texture_s3tc
 {
     const GLenum COMPRESSED_RGB_S3TC_DXT1_EXT  = 0x83F0;
     const GLenum COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
     const GLenum COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -817,16 +817,17 @@ GENERATED_EVENTS_WEBIDL_FILES = [
     'TrackEvent.webidl',
     'TVCurrentChannelChangedEvent.webidl',
     'TVCurrentSourceChangedEvent.webidl',
     'TVEITBroadcastedEvent.webidl',
     'TVScanningStateChangedEvent.webidl',
     'UDPMessageEvent.webidl',
     'UserProximityEvent.webidl',
     'USSDReceivedEvent.webidl',
+    'WebGLContextEvent.webidl',
 ]
 
 if CONFIG['MOZ_WEBRTC']:
     GENERATED_EVENTS_WEBIDL_FILES += [
         'MediaStreamTrackEvent.webidl',
         'RTCDataChannelEvent.webidl',
         'RTCPeerConnectionIceEvent.webidl',
         'RTCTrackEvent.webidl',
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -4522,17 +4522,25 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
       MutexAutoLock lock(mMutex);
 
       while (mControlQueue.IsEmpty() &&
              !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) &&
              !(normalRunnablesPending = NS_HasPendingEvents(mThread))) {
         WaitForWorkerEvents();
       }
 
-      ProcessAllControlRunnablesLocked();
+      auto result = ProcessAllControlRunnablesLocked();
+      if (result != ProcessAllControlRunnablesResult::Nothing) {
+        // NB: There's no JS on the stack here, so Abort vs MayContinue is
+        // irrelevant
+
+        // The state of the world may have changed, recheck it.
+        normalRunnablesPending = NS_HasPendingEvents(mThread);
+        // The debugger queue doesn't get cleared, so we can ignore that.
+      }
 
       currentStatus = mStatus;
     }
 
     // If the close handler has finished and all features are done then we can
     // kill this thread.
     if (currentStatus != Running && !HasActiveFeatures()) {
       if (mCloseHandlerFinished && currentStatus != Killing) {
@@ -4648,17 +4656,19 @@ WorkerPrivate::OnProcessNextEvent()
   MOZ_ASSERT(recursionDepth);
 
   // Normally we process control runnables in DoRunLoop or RunCurrentSyncLoop.
   // However, it's possible that non-worker C++ could spin its own nested event
   // loop, and in that case we must ensure that we continue to process control
   // runnables here.
   if (recursionDepth > 1 &&
       mSyncLoopStack.Length() < recursionDepth - 1) {
-    ProcessAllControlRunnables();
+    Unused << ProcessAllControlRunnables();
+    // There's no running JS, and no state to revalidate, so we can ignore the
+    // return value.
   }
 }
 
 void
 WorkerPrivate::AfterProcessNextEvent()
 {
   AssertIsOnWorkerThread();
   MOZ_ASSERT(CycleCollectedJSRuntime::Get()->RecursionDepth());
@@ -4786,17 +4796,20 @@ WorkerPrivate::InterruptCallback(JSConte
 
   MOZ_ASSERT(!JS_IsExceptionPending(aCx));
 
   bool mayContinue = true;
   bool scheduledIdleGC = false;
 
   for (;;) {
     // Run all control events now.
-    mayContinue = ProcessAllControlRunnables();
+    auto result = ProcessAllControlRunnables();
+    if (result == ProcessAllControlRunnablesResult::Abort) {
+      mayContinue = false;
+    }
 
     bool mayFreeze = mFrozen;
     if (mayFreeze) {
       MutexAutoLock lock(mMutex);
       mayFreeze = mStatus <= Running;
     }
 
     if (!mayContinue || !mayFreeze) {
@@ -5022,23 +5035,23 @@ WorkerPrivate::WaitForWorkerEvents(PRInt
   }
 
   NS_ASSERTION(mBlockedForMemoryReporter, "Somehow we got unblocked!");
 
   // No need to notify here as the main thread isn't watching for this state.
   mBlockedForMemoryReporter = false;
 }
 
-bool
+WorkerPrivate::ProcessAllControlRunnablesResult
 WorkerPrivate::ProcessAllControlRunnablesLocked()
 {
   AssertIsOnWorkerThread();
   mMutex.AssertCurrentThreadOwns();
 
-  bool result = true;
+  auto result = ProcessAllControlRunnablesResult::Nothing;
 
   for (;;) {
     // Block here if the memory reporter is trying to run.
     if (mMemoryReporterRunning) {
       MOZ_ASSERT(!mBlockedForMemoryReporter);
 
       // Let the main thread know that we've received the block request and
       // that memory reporting may proceed.
@@ -5063,19 +5076,23 @@ WorkerPrivate::ProcessAllControlRunnable
     if (!mControlQueue.Pop(event)) {
       break;
     }
 
     MutexAutoUnlock unlock(mMutex);
 
     MOZ_ASSERT(event);
     if (NS_FAILED(static_cast<nsIRunnable*>(event)->Run())) {
-      result = false;
-    }
-
+      result = ProcessAllControlRunnablesResult::Abort;
+    }
+
+    if (result == ProcessAllControlRunnablesResult::Nothing) {
+      // We ran at least one thing.
+      result = ProcessAllControlRunnablesResult::MayContinue;
+    }
     event->Release();
   }
 
   return result;
 }
 
 void
 WorkerPrivate::ClearMainEventQueue(WorkerRanOrNot aRanOrNot)
@@ -5401,21 +5418,35 @@ WorkerPrivate::RunCurrentSyncLoop()
 
       for (;;) {
         while (mControlQueue.IsEmpty() &&
                !normalRunnablesPending &&
                !(normalRunnablesPending = NS_HasPendingEvents(mThread))) {
           WaitForWorkerEvents();
         }
 
-        ProcessAllControlRunnablesLocked();
-
-        // NB: If we processed a NotifyRunnable, we might have run non-control
-        // runnables, one of which may have shut down the sync loop.
-        if (normalRunnablesPending || loopInfo->mCompleted) {
+        auto result = ProcessAllControlRunnablesLocked();
+        if (result != ProcessAllControlRunnablesResult::Nothing) {
+          // XXXkhuey how should we handle Abort here? See Bug 1003730.
+
+          // The state of the world may have changed. Recheck it.
+          normalRunnablesPending = NS_HasPendingEvents(mThread);
+
+          // NB: If we processed a NotifyRunnable, we might have run
+          // non-control runnables, one of which may have shut down the
+          // sync loop.
+          if (loopInfo->mCompleted) {
+            break;
+          }
+        }
+
+        // If we *didn't* run any control runnables, this should be unchanged.
+        MOZ_ASSERT(!loopInfo->mCompleted);
+
+        if (normalRunnablesPending) {
           break;
         }
       }
     }
 
     if (normalRunnablesPending) {
       // Make sure the periodic timer is running before we continue.
       SetGCTimerMode(PeriodicTimer);
@@ -5634,16 +5665,18 @@ WorkerPrivate::EnterDebuggerEventLoop()
       MutexAutoLock lock(mMutex);
 
       while (mControlQueue.IsEmpty() &&
              !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty())) {
         WaitForWorkerEvents();
       }
 
       ProcessAllControlRunnablesLocked();
+
+      // XXXkhuey should we abort JS on the stack here if we got Abort above?
     }
 
     if (debuggerRunnablesPending) {
       // Start the periodic GC timer if it is not already running.
       SetGCTimerMode(PeriodicTimer);
 
       WorkerRunnable* runnable;
 
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -1377,24 +1377,35 @@ private:
   RemainingRunTimeMS() const;
 
   void
   CancelAllTimeouts();
 
   bool
   ScheduleKillCloseEventRunnable();
 
-  bool
+  enum class ProcessAllControlRunnablesResult
+  {
+    // We did not process anything.
+    Nothing,
+    // We did process something, states may have changed, but we can keep
+    // executing script.
+    MayContinue,
+    // We did process something, and should not continue executing script.
+    Abort
+  };
+
+  ProcessAllControlRunnablesResult
   ProcessAllControlRunnables()
   {
     MutexAutoLock lock(mMutex);
     return ProcessAllControlRunnablesLocked();
   }
 
-  bool
+  ProcessAllControlRunnablesResult
   ProcessAllControlRunnablesLocked();
 
   void
   EnableMemoryReporter();
 
   void
   DisableMemoryReporter();
 
--- a/dom/workers/WorkerRunnable.cpp
+++ b/dom/workers/WorkerRunnable.cpp
@@ -26,16 +26,58 @@
 USING_WORKERS_NAMESPACE
 
 namespace {
 
 const nsIID kWorkerRunnableIID = {
   0x320cc0b5, 0xef12, 0x4084, { 0x88, 0x6e, 0xca, 0x6a, 0x81, 0xe4, 0x1d, 0x68 }
 };
 
+// This runnable is used to stop a sync loop and it's meant to be used on the
+// main-thread only. As sync loops keep the busy count incremented as long as
+// they run this runnable does not modify the busy count
+// in any way.
+class MainThreadStopSyncLoopRunnable : public WorkerSyncRunnable
+{
+  bool mResult;
+
+public:
+  // Passing null for aSyncLoopTarget is not allowed.
+  MainThreadStopSyncLoopRunnable(
+                               WorkerPrivate* aWorkerPrivate,
+                               already_AddRefed<nsIEventTarget>&& aSyncLoopTarget,
+                               bool aResult);
+
+  // By default StopSyncLoopRunnables cannot be canceled since they could leave
+  // a sync loop spinning forever.
+  nsresult
+  Cancel() override;
+
+protected:
+  virtual ~MainThreadStopSyncLoopRunnable()
+  { }
+
+private:
+  virtual bool
+  PreDispatch(WorkerPrivate* aWorkerPrivate) override final
+  {
+    AssertIsOnMainThread();
+    return true;
+  }
+
+  virtual void
+  PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override;
+
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
+
+  virtual bool
+  DispatchInternal() override final;
+};
+
 } // namespace
 
 #ifdef DEBUG
 WorkerRunnable::WorkerRunnable(WorkerPrivate* aWorkerPrivate,
                                TargetAndBusyBehavior aBehavior)
 : mWorkerPrivate(aWorkerPrivate), mBehavior(aBehavior), mCanceled(0),
   mCallingCancelWithinRun(false)
 {
@@ -496,20 +538,16 @@ MainThreadStopSyncLoopRunnable::WorkerRu
                                           WorkerPrivate* aWorkerPrivate)
 {
   aWorkerPrivate->AssertIsOnWorkerThread();
   MOZ_ASSERT(mSyncLoopTarget);
 
   nsCOMPtr<nsIEventTarget> syncLoopTarget;
   mSyncLoopTarget.swap(syncLoopTarget);
 
-  if (!mResult) {
-    MaybeSetException();
-  }
-
   aWorkerPrivate->StopSyncLoop(syncLoopTarget, mResult);
   return true;
 }
 
 bool
 MainThreadStopSyncLoopRunnable::DispatchInternal()
 {
   MOZ_ASSERT(mSyncLoopTarget);
@@ -598,19 +636,21 @@ WorkerMainThreadRunnable::Dispatch(Error
     NS_DispatchToMainThread(runnable.forget(), NS_DISPATCH_NORMAL);
   MOZ_ASSERT(NS_SUCCEEDED(rv),
              "Should only fail after xpcom-shutdown-threads and we're gone by then");
 
   if (!syncLoop.Run()) {
     aRv.ThrowUncatchableException();
   }
 
-  Telemetry::Accumulate(Telemetry::SYNC_WORKER_OPERATION, mTelemetryKey,
-                        static_cast<uint32_t>((TimeStamp::NowLoRes() - startTime)
-                                                .ToMilliseconds()));
+  // Telemetry is apparently not threadsafe
+  // Telemetry::Accumulate(Telemetry::SYNC_WORKER_OPERATION, mTelemetryKey,
+  //                       static_cast<uint32_t>((TimeStamp::NowLoRes() - startTime)
+  //                                               .ToMilliseconds()));
+  Unused << startTime; // Shut the compiler up.
 }
 
 NS_IMETHODIMP
 WorkerMainThreadRunnable::Run()
 {
   AssertIsOnMainThread();
 
   bool runResult = MainThreadRun();
--- a/dom/workers/WorkerRunnable.h
+++ b/dom/workers/WorkerRunnable.h
@@ -259,68 +259,16 @@ private:
     AssertIsOnMainThread();
     return true;
   }
 
   virtual void
   PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override;
 };
 
-// This runnable is used to stop a sync loop and it's meant to be used on the
-// main-thread only. As sync loops keep the busy count incremented as long as
-// they run this runnable does not modify the busy count
-// in any way.
-class MainThreadStopSyncLoopRunnable : public WorkerSyncRunnable
-{
-  bool mResult;
-
-public:
-  // Passing null for aSyncLoopTarget is not allowed.
-  MainThreadStopSyncLoopRunnable(
-                               WorkerPrivate* aWorkerPrivate,
-                               already_AddRefed<nsIEventTarget>&& aSyncLoopTarget,
-                               bool aResult);
-
-  // By default StopSyncLoopRunnables cannot be canceled since they could leave
-  // a sync loop spinning forever.
-  nsresult
-  Cancel() override;
-
-protected:
-  virtual ~MainThreadStopSyncLoopRunnable()
-  { }
-
-  // Called on the worker thread, in WorkerRun, right before stopping the
-  // syncloop to set an exception (however subclasses want to handle that) if
-  // mResult is false.  Note that overrides of this method must NOT set an
-  // actual exception on the JSContext; they may only set some state that will
-  // get turned into an exception once the syncloop actually terminates and
-  // control is returned to whoever was spinning the syncloop.
-  virtual void
-  MaybeSetException()
-  { }
-
-private:
-  virtual bool
-  PreDispatch(WorkerPrivate* aWorkerPrivate) override final
-  {
-    AssertIsOnMainThread();
-    return true;
-  }
-
-  virtual void
-  PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override;
-
-  virtual bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
-
-  virtual bool
-  DispatchInternal() override final;
-};
-
 // This runnable is processed as soon as it is received by the worker,
 // potentially running before previously queued runnables and perhaps even with
 // other JS code executing on the stack. These runnables must not alter the
 // state of the JS runtime and should only twiddle state values. The busy count
 // is never modified.
 class WorkerControlRunnable : public WorkerRunnable
 {
   friend class WorkerPrivate;
--- a/dom/workers/XMLHttpRequest.cpp
+++ b/dom/workers/XMLHttpRequest.cpp
@@ -181,112 +181,73 @@ private:
   ~Proxy()
   {
     MOZ_ASSERT(!mXHR);
     MOZ_ASSERT(!mXHRUpload);
     MOZ_ASSERT(!mOutstandingSendCount);
   }
 };
 
-class WorkerThreadProxySyncRunnable : public Runnable
+class WorkerThreadProxySyncRunnable : public WorkerMainThreadRunnable
 {
 protected:
-  WorkerPrivate* mWorkerPrivate;
   RefPtr<Proxy> mProxy;
-  nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
 
 private:
-  // mRv is set on the worker thread by the constructor.  Must not be touched on
-  // the main thread, except for copying the reference to the ResponseRunnable.
-  ErrorResult& mRv;
-
-  class ResponseRunnable final: public MainThreadStopSyncLoopRunnable
-  {
-    RefPtr<Proxy> mProxy;
-    nsresult mErrorCode;
-    // mRv is set on the main thread by the constructor.  Must not be touched on
-    // the main thread otherwise.
-    ErrorResult& mRv;
-
-  public:
-    ResponseRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
-                     nsresult aErrorCode, ErrorResult& aRv)
-    : MainThreadStopSyncLoopRunnable(aWorkerPrivate, aProxy->GetEventTarget(),
-                                     NS_SUCCEEDED(aErrorCode)),
-      mProxy(aProxy), mErrorCode(aErrorCode), mRv(aRv)
-    {
-      AssertIsOnMainThread();
-      MOZ_ASSERT(aProxy);
-    }
-
-  private:
-    ~ResponseRunnable()
-    { }
-
-    virtual void
-    MaybeSetException() override
-    {
-      mWorkerPrivate->AssertIsOnWorkerThread();
-      MOZ_ASSERT(NS_FAILED(mErrorCode));
-
-      mRv.Throw(mErrorCode);
-    }
-  };
+  // mErrorCode is set on the main thread by MainThreadRun and it's used to at
+  // the end of the Dispatch() to return the error code.
+  nsresult mErrorCode;
 
 public:
-  WorkerThreadProxySyncRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
-                                ErrorResult& aRv)
-  : mWorkerPrivate(aWorkerPrivate), mProxy(aProxy), mRv(aRv)
+  WorkerThreadProxySyncRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
+  : WorkerMainThreadRunnable(aWorkerPrivate, NS_LITERAL_CSTRING("XHR"))
+  , mProxy(aProxy)
+  , mErrorCode(NS_OK)
   {
     MOZ_ASSERT(aWorkerPrivate);
     MOZ_ASSERT(aProxy);
     aWorkerPrivate->AssertIsOnWorkerThread();
   }
 
   void
-  Dispatch()
+  Dispatch(ErrorResult& aRv)
   {
-    mWorkerPrivate->AssertIsOnWorkerThread();
-
-    AutoSyncLoopHolder syncLoop(mWorkerPrivate);
-    mSyncLoopTarget = syncLoop.EventTarget();
-
-    if (NS_FAILED(NS_DispatchToMainThread(this))) {
-      MOZ_CRASH("How can this not work?  No good will come of this");
+    WorkerMainThreadRunnable::Dispatch(aRv);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return;
     }
 
-    DebugOnly<bool> ok = syncLoop.Run();
-    // If !ok, then our ResponseRunnable had a failing nsresult and should have
-    // stashed it in mRv.
-    MOZ_ASSERT_IF(!ok, mRv.Failed());
+    if (NS_FAILED(mErrorCode)) {
+      aRv.Throw(mErrorCode);
+    }
   }
 
 protected:
   virtual ~WorkerThreadProxySyncRunnable()
   { }
 
   virtual nsresult
-  MainThreadRun() = 0;
+  RunOnMainThread() = 0;
 
 private:
-  NS_DECL_NSIRUNNABLE
+  virtual bool MainThreadRun() override;
 };
 
 class SendRunnable final
   : public WorkerThreadProxySyncRunnable
   , public StructuredCloneHolder
 {
   nsString mStringBody;
   nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
   bool mHasUploadListeners;
 
 public:
   SendRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
-               const nsAString& aStringBody, ErrorResult& aRv)
-  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy, aRv)
+               const nsAString& aStringBody)
+  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
   , StructuredCloneHolder(CloningSupported, TransferringNotSupported,
                           SameProcessDifferentThread)
   , mStringBody(aStringBody)
   , mHasUploadListeners(false)
   {
   }
 
   void SetHaveUploadListeners(bool aHasUploadListeners)
@@ -299,17 +260,17 @@ public:
     mSyncLoopTarget = aSyncLoopTarget;
   }
 
 private:
   ~SendRunnable()
   { }
 
   virtual nsresult
-  MainThreadRun() override;
+  RunOnMainThread() override;
 };
 
 END_WORKERS_NAMESPACE
 
 namespace {
 
 inline void
 ConvertResponseTypeToString(XMLHttpRequestResponseType aType,
@@ -602,192 +563,192 @@ private:
 
   virtual bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
 };
 
 class SyncTeardownRunnable final : public WorkerThreadProxySyncRunnable
 {
 public:
-  SyncTeardownRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
-                       ErrorResult& aRv)
-  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy, aRv)
+  SyncTeardownRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
+  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
   { }
 
 private:
   ~SyncTeardownRunnable()
   { }
 
   virtual nsresult
-  MainThreadRun() override
+  RunOnMainThread() override
   {
     mProxy->Teardown(/* aSendUnpin */ true);
     MOZ_ASSERT(!mProxy->mSyncLoopTarget);
     return NS_OK;
   }
 };
 
 class SetBackgroundRequestRunnable final :
   public WorkerThreadProxySyncRunnable
 {
   bool mValue;
 
 public:
   SetBackgroundRequestRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
-                               bool aValue, ErrorResult& aRv)
-  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy, aRv), mValue(aValue)
+                               bool aValue)
+  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
+  , mValue(aValue)
   { }
 
 private:
   ~SetBackgroundRequestRunnable()
   { }
 
   virtual nsresult
-  MainThreadRun() override
+  RunOnMainThread() override
   {
     return mProxy->mXHR->SetMozBackgroundRequest(mValue);
   }
 };
 
 class SetWithCredentialsRunnable final :
   public WorkerThreadProxySyncRunnable
 {
   bool mValue;
 
 public:
   SetWithCredentialsRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
-                             bool aValue, ErrorResult& aRv)
-  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy, aRv), mValue(aValue)
+                             bool aValue)
+  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
+  , mValue(aValue)
   { }
 
 private:
   ~SetWithCredentialsRunnable()
   { }
 
   virtual nsresult
-  MainThreadRun() override
+  RunOnMainThread() override
   {
     return mProxy->mXHR->SetWithCredentials(mValue);
   }
 };
 
 class SetResponseTypeRunnable final : public WorkerThreadProxySyncRunnable
 {
   nsString mResponseType;
 
 public:
   SetResponseTypeRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
-                          const nsAString& aResponseType, ErrorResult& aRv)
-  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy, aRv),
+                          const nsAString& aResponseType)
+  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
     mResponseType(aResponseType)
   { }
 
   void
   GetResponseType(nsAString& aResponseType)
   {
     aResponseType.Assign(mResponseType);
   }
 
 private:
   ~SetResponseTypeRunnable()
   { }
 
   virtual nsresult
-  MainThreadRun() override
+  RunOnMainThread() override
   {
     nsresult rv = mProxy->mXHR->SetResponseType(mResponseType);
     mResponseType.Truncate();
     if (NS_SUCCEEDED(rv)) {
       rv = mProxy->mXHR->GetResponseType(mResponseType);
     }
     return rv;
   }
 };
 
 class SetTimeoutRunnable final : public WorkerThreadProxySyncRunnable
 {
   uint32_t mTimeout;
 
 public:
   SetTimeoutRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
-                     uint32_t aTimeout, ErrorResult& aRv)
-  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy, aRv),
+                     uint32_t aTimeout)
+  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
     mTimeout(aTimeout)
   { }
 
 private:
   ~SetTimeoutRunnable()
   { }
 
   virtual nsresult
-  MainThreadRun() override
+  RunOnMainThread() override
   {
     return mProxy->mXHR->SetTimeout(mTimeout);
   }
 };
 
 class AbortRunnable final : public WorkerThreadProxySyncRunnable
 {
 public:
-  AbortRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, ErrorResult& aRv)
-  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy, aRv)
+  AbortRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
+  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
   { }
 
 private:
   ~AbortRunnable()
   { }
 
   virtual nsresult
-  MainThreadRun() override;
+  RunOnMainThread() override;
 };
 
 class GetAllResponseHeadersRunnable final :
   public WorkerThreadProxySyncRunnable
 {
   nsCString& mResponseHeaders;
 
 public:
   GetAllResponseHeadersRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
-                                nsCString& aResponseHeaders, ErrorResult& aRv)
-  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy, aRv),
+                                nsCString& aResponseHeaders)
+  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
     mResponseHeaders(aResponseHeaders)
   { }
 
 private:
   ~GetAllResponseHeadersRunnable()
   { }
 
   virtual nsresult
-  MainThreadRun() override
+  RunOnMainThread() override
   {
     mProxy->mXHR->GetAllResponseHeaders(mResponseHeaders);
     return NS_OK;
   }
 };
 
 class GetResponseHeaderRunnable final : public WorkerThreadProxySyncRunnable
 {
   const nsCString mHeader;
   nsCString& mValue;
 
 public:
   GetResponseHeaderRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
-                            const nsACString& aHeader, nsCString& aValue,
-                            ErrorResult& aRv)
-  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy, aRv),
+                            const nsACString& aHeader, nsCString& aValue)
+  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
     mHeader(aHeader),
     mValue(aValue)
   { }
 
 private:
   ~GetResponseHeaderRunnable()
   { }
 
   virtual nsresult
-  MainThreadRun() override
+  RunOnMainThread() override
   {
     return mProxy->mXHR->GetResponseHeader(mHeader, mValue);
   }
 };
 
 class OpenRunnable final : public WorkerThreadProxySyncRunnable
 {
   nsCString mMethod;
@@ -801,18 +762,18 @@ class OpenRunnable final : public Worker
   uint32_t mTimeout;
 
 public:
   OpenRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                const nsACString& aMethod, const nsAString& aURL,
                const Optional<nsAString>& aUser,
                const Optional<nsAString>& aPassword,
                bool aBackgroundRequest, bool aWithCredentials,
-               uint32_t aTimeout, ErrorResult& aRv)
-  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy, aRv),
+               uint32_t aTimeout)
+  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
     mMethod(aMethod),
     mURL(aURL), mBackgroundRequest(aBackgroundRequest),
     mWithCredentials(aWithCredentials), mTimeout(aTimeout)
   {
     if (aUser.WasPassed()) {
       mUserStr = aUser.Value();
       mUser = &mUserStr;
     }
@@ -822,17 +783,17 @@ public:
     }
   }
 
 private:
   ~OpenRunnable()
   { }
 
   virtual nsresult
-  MainThreadRun() override
+  RunOnMainThread() override
   {
     WorkerPrivate* oldWorker = mProxy->mWorkerPrivate;
     mProxy->mWorkerPrivate = mWorkerPrivate;
 
     nsresult rv = MainThreadRunInternal();
 
     mProxy->mWorkerPrivate = oldWorker;
     return rv;
@@ -844,51 +805,50 @@ private:
 
 class SetRequestHeaderRunnable final : public WorkerThreadProxySyncRunnable
 {
   nsCString mHeader;
   nsCString mValue;
 
 public:
   SetRequestHeaderRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
-                           const nsACString& aHeader, const nsACString& aValue,
-                           ErrorResult& aRv)
-  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy, aRv),
+                           const nsACString& aHeader, const nsACString& aValue)
+  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
     mHeader(aHeader),
     mValue(aValue)
   { }
 
 private:
   ~SetRequestHeaderRunnable()
   { }
 
   virtual nsresult
-  MainThreadRun() override
+  RunOnMainThread() override
   {
     return mProxy->mXHR->SetRequestHeader(mHeader, mValue);
   }
 };
 
 class OverrideMimeTypeRunnable final : public WorkerThreadProxySyncRunnable
 {
   nsString mMimeType;
 
 public:
   OverrideMimeTypeRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
-                           const nsAString& aMimeType, ErrorResult& aRv)
-  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy, aRv),
+                           const nsAString& aMimeType)
+  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
     mMimeType(aMimeType)
   { }
 
 private:
   ~OverrideMimeTypeRunnable()
   { }
 
   virtual nsresult
-  MainThreadRun() override
+  RunOnMainThread() override
   {
     mProxy->mXHR->OverrideMimeType(mMimeType);
     return NS_OK;
   }
 };
 
 class AutoUnpinXHR
 {
@@ -1429,41 +1389,34 @@ EventRunnable::WorkerRun(JSContext* aCx,
   // types.
   if (StringBeginsWith(mResponseType, NS_LITERAL_STRING("moz-chunked-"))) {
     xhr->NullResponseText();
   }
 
   return true;
 }
 
-NS_IMETHODIMP
-WorkerThreadProxySyncRunnable::Run()
+bool
+WorkerThreadProxySyncRunnable::MainThreadRun()
 {
   AssertIsOnMainThread();
 
-  nsCOMPtr<nsIEventTarget> tempTarget;
-  mSyncLoopTarget.swap(tempTarget);
+  nsCOMPtr<nsIEventTarget> tempTarget = mSyncLoopTarget;
 
   mProxy->mSyncEventResponseTarget.swap(tempTarget);
 
-  nsresult rv = MainThreadRun();
-
-  RefPtr<ResponseRunnable> response =
-    new ResponseRunnable(mWorkerPrivate, mProxy, rv, mRv);
-  if (!response->Dispatch()) {
-    MOZ_ASSERT(false, "Failed to dispatch response!");
-  }
+  mErrorCode = RunOnMainThread();
 
   mProxy->mSyncEventResponseTarget.swap(tempTarget);
 
-  return NS_OK;
+  return true;
 }
 
 nsresult
-AbortRunnable::MainThreadRun()
+AbortRunnable::RunOnMainThread()
 {
   mProxy->mInnerEventStreamId++;
 
   WorkerPrivate* oldWorker = mProxy->mWorkerPrivate;
   mProxy->mWorkerPrivate = mWorkerPrivate;
 
   mProxy->mXHR->Abort();
 
@@ -1511,17 +1464,17 @@ OpenRunnable::MainThreadRunInternal()
     return rv2.StealNSResult();
   }
 
   return mProxy->mXHR->SetResponseType(NS_LITERAL_STRING("text"));
 }
 
 
 nsresult
-SendRunnable::MainThreadRun()
+SendRunnable::RunOnMainThread()
 {
   nsCOMPtr<nsIVariant> variant;
 
   if (HasData()) {
     AutoSafeJSContext cx;
     JSAutoRequest ar(cx);
 
     nsIXPConnect* xpc = nsContentUtils::XPConnect();
@@ -1694,23 +1647,23 @@ XMLHttpRequest::ReleaseProxy(ReleaseType
       // This isn't necessary if the worker is going away or the XHR is going
       // away.
       if (aType == Default) {
         // Don't let any more events run.
         mProxy->mOuterEventStreamId++;
       }
 
       // We need to make a sync call here.
-      ErrorResult forAsssertionsOnly;
       RefPtr<SyncTeardownRunnable> runnable =
-        new SyncTeardownRunnable(mWorkerPrivate, mProxy, forAsssertionsOnly);
+        new SyncTeardownRunnable(mWorkerPrivate, mProxy);
       mProxy = nullptr;
 
-      runnable->Dispatch();
-      if (forAsssertionsOnly.Failed()) {
+      ErrorResult forAssertionsOnly;
+      runnable->Dispatch(forAssertionsOnly);
+      if (forAssertionsOnly.Failed()) {
         NS_ERROR("Failed to dispatch teardown runnable!");
       }
     }
   }
 }
 
 void
 XMLHttpRequest::MaybePin(ErrorResult& aRv)
@@ -1876,17 +1829,17 @@ XMLHttpRequest::SendInternal(SendRunnabl
     syncLoopTarget = autoSyncLoop->EventTarget();
   }
 
   mProxy->mOuterChannelId++;
 
   aRunnable->SetSyncLoopTarget(syncLoopTarget);
   aRunnable->SetHaveUploadListeners(hasUploadListeners);
 
-  aRunnable->Dispatch();
+  aRunnable->Dispatch(aRv);
   if (aRv.Failed()) {
     // Dispatch() may have spun the event loop and we may have already unrooted.
     // If so we don't want autoUnpin to try again.
     if (!mRooted) {
       autoUnpin.Clear();
     }
     return;
   }
@@ -1944,20 +1897,20 @@ XMLHttpRequest::Open(const nsACString& a
     mProxy = new Proxy(this, mMozAnon, mMozSystem);
   }
 
   mProxy->mOuterEventStreamId++;
 
   RefPtr<OpenRunnable> runnable =
     new OpenRunnable(mWorkerPrivate, mProxy, aMethod, aUrl, aUser, aPassword,
                      mBackgroundRequest, mWithCredentials,
-                     mTimeout, aRv);
+                     mTimeout);
 
   ++mProxy->mOpenCount;
-  runnable->Dispatch();
+  runnable->Dispatch(aRv);
   if (aRv.Failed()) {
     if (!--mProxy->mOpenCount) {
       ReleaseProxy();
     }
 
     return;
   }
 
@@ -1983,18 +1936,18 @@ XMLHttpRequest::SetRequestHeader(const n
   }
 
   if (!mProxy) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   RefPtr<SetRequestHeaderRunnable> runnable =
-    new SetRequestHeaderRunnable(mWorkerPrivate, mProxy, aHeader, aValue, aRv);
-  runnable->Dispatch();
+    new SetRequestHeaderRunnable(mWorkerPrivate, mProxy, aHeader, aValue);
+  runnable->Dispatch(aRv);
 }
 
 void
 XMLHttpRequest::SetTimeout(uint32_t aTimeout, ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   if (mCanceled) {
@@ -2006,18 +1959,18 @@ XMLHttpRequest::SetTimeout(uint32_t aTim
 
   if (!mProxy) {
     // Open may not have been called yet, in which case we'll handle the
     // timeout in OpenRunnable.
     return;
   }
 
   RefPtr<SetTimeoutRunnable> runnable =
-    new SetTimeoutRunnable(mWorkerPrivate, mProxy, aTimeout, aRv);
-  runnable->Dispatch();
+    new SetTimeoutRunnable(mWorkerPrivate, mProxy, aTimeout);
+  runnable->Dispatch(aRv);
 }
 
 void
 XMLHttpRequest::SetWithCredentials(bool aWithCredentials, ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   if (mCanceled) {
@@ -2029,19 +1982,18 @@ XMLHttpRequest::SetWithCredentials(bool 
 
   if (!mProxy) {
     // Open may not have been called yet, in which case we'll handle the
     // credentials in OpenRunnable.
     return;
   }
 
   RefPtr<SetWithCredentialsRunnable> runnable =
-    new SetWithCredentialsRunnable(mWorkerPrivate, mProxy, aWithCredentials,
-                                   aRv);
-  runnable->Dispatch();
+    new SetWithCredentialsRunnable(mWorkerPrivate, mProxy, aWithCredentials);
+  runnable->Dispatch(aRv);
 }
 
 void
 XMLHttpRequest::SetMozBackgroundRequest(bool aBackgroundRequest,
                                         ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
@@ -2055,18 +2007,18 @@ XMLHttpRequest::SetMozBackgroundRequest(
   if (!mProxy) {
     // Open may not have been called yet, in which case we'll handle the
     // background request in OpenRunnable.
     return;
   }
 
   RefPtr<SetBackgroundRequestRunnable> runnable =
     new SetBackgroundRequestRunnable(mWorkerPrivate, mProxy,
-                                     aBackgroundRequest, aRv);
-  runnable->Dispatch();
+                                     aBackgroundRequest);
+  runnable->Dispatch(aRv);
 }
 
 XMLHttpRequestUpload*
 XMLHttpRequest::GetUpload(ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   if (mCanceled) {
@@ -2097,17 +2049,17 @@ XMLHttpRequest::Send(ErrorResult& aRv)
   }
 
   if (!mProxy) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   RefPtr<SendRunnable> sendRunnable =
-    new SendRunnable(mWorkerPrivate, mProxy, NullString(), aRv);
+    new SendRunnable(mWorkerPrivate, mProxy, NullString());
 
   // Nothing to clone.
   SendInternal(sendRunnable, aRv);
 }
 
 void
 XMLHttpRequest::Send(const nsAString& aBody, ErrorResult& aRv)
 {
@@ -2119,17 +2071,17 @@ XMLHttpRequest::Send(const nsAString& aB
   }
 
   if (!mProxy) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   RefPtr<SendRunnable> sendRunnable =
-    new SendRunnable(mWorkerPrivate, mProxy, aBody, aRv);
+    new SendRunnable(mWorkerPrivate, mProxy, aBody);
 
   // Nothing to clone.
   SendInternal(sendRunnable, aRv);
 }
 
 void
 XMLHttpRequest::Send(JS::Handle<JSObject*> aBody, ErrorResult& aRv)
 {
@@ -2159,17 +2111,17 @@ XMLHttpRequest::Send(JS::Handle<JSObject
     if (!bodyStr) {
       aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
       return;
     }
     valToClone.setString(bodyStr);
   }
 
   RefPtr<SendRunnable> sendRunnable =
-    new SendRunnable(mWorkerPrivate, mProxy, EmptyString(), aRv);
+    new SendRunnable(mWorkerPrivate, mProxy, EmptyString());
 
   sendRunnable->Write(cx, valToClone, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   SendInternal(sendRunnable, aRv);
 }
@@ -2200,17 +2152,17 @@ XMLHttpRequest::Send(Blob& aBody, ErrorR
   MOZ_ASSERT(blobImpl);
 
   aRv = blobImpl->SetMutable(false);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   RefPtr<SendRunnable> sendRunnable =
-    new SendRunnable(mWorkerPrivate, mProxy, EmptyString(), aRv);
+    new SendRunnable(mWorkerPrivate, mProxy, EmptyString());
 
   sendRunnable->Write(cx, value, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   SendInternal(sendRunnable, aRv);
 }
@@ -2233,17 +2185,17 @@ XMLHttpRequest::Send(FormData& aBody, Er
 
   JS::Rooted<JS::Value> value(cx);
   if (!GetOrCreateDOMReflector(cx, &aBody, &value)) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   RefPtr<SendRunnable> sendRunnable =
-    new SendRunnable(mWorkerPrivate, mProxy, EmptyString(), aRv);
+    new SendRunnable(mWorkerPrivate, mProxy, EmptyString());
 
   sendRunnable->Write(cx, value, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   SendInternal(sendRunnable, aRv);
 }
@@ -2290,19 +2242,18 @@ XMLHttpRequest::Abort(ErrorResult& aRv)
   if (mStateData.mReadyState == 4) {
     // No one did anything to us while we fired abort events, so reset our state
     // to "unsent"
     mStateData.mReadyState = 0;
   }
 
   mProxy->mOuterEventStreamId++;
 
-  RefPtr<AbortRunnable> runnable =
-    new AbortRunnable(mWorkerPrivate, mProxy, aRv);
-  runnable->Dispatch();
+  RefPtr<AbortRunnable> runnable = new AbortRunnable(mWorkerPrivate, mProxy);
+  runnable->Dispatch(aRv);
 }
 
 void
 XMLHttpRequest::GetResponseHeader(const nsACString& aHeader,
                                   nsACString& aResponseHeader, ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
@@ -2314,18 +2265,18 @@ XMLHttpRequest::GetResponseHeader(const 
   if (!mProxy) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   nsCString responseHeader;
   RefPtr<GetResponseHeaderRunnable> runnable =
     new GetResponseHeaderRunnable(mWorkerPrivate, mProxy, aHeader,
-                                  responseHeader, aRv);
-  runnable->Dispatch();
+                                  responseHeader);
+  runnable->Dispatch(aRv);
   if (aRv.Failed()) {
     return;
   }
   aResponseHeader = responseHeader;
 }
 
 void
 XMLHttpRequest::GetAllResponseHeaders(nsACString& aResponseHeaders,
@@ -2340,19 +2291,18 @@ XMLHttpRequest::GetAllResponseHeaders(ns
 
   if (!mProxy) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   nsCString responseHeaders;
   RefPtr<GetAllResponseHeadersRunnable> runnable =
-    new GetAllResponseHeadersRunnable(mWorkerPrivate, mProxy, responseHeaders,
-                                      aRv);
-  runnable->Dispatch();
+    new GetAllResponseHeadersRunnable(mWorkerPrivate, mProxy, responseHeaders);
+  runnable->Dispatch(aRv);
   if (aRv.Failed()) {
     return;
   }
 
   aResponseHeaders = responseHeaders;
 }
 
 void
@@ -2373,19 +2323,18 @@ XMLHttpRequest::OverrideMimeType(const n
   if (!mProxy || (SendInProgress() &&
                   (mProxy->mSeenLoadStart ||
                    mStateData.mReadyState > nsIXMLHttpRequest::OPENED))) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   RefPtr<OverrideMimeTypeRunnable> runnable =
-    new OverrideMimeTypeRunnable(mWorkerPrivate, mProxy, aMimeType,
-                                 aRv);
-  runnable->Dispatch();
+    new OverrideMimeTypeRunnable(mWorkerPrivate, mProxy, aMimeType);
+  runnable->Dispatch(aRv);
 }
 
 void
 XMLHttpRequest::SetResponseType(XMLHttpRequestResponseType aResponseType,
                                 ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
@@ -2406,18 +2355,18 @@ XMLHttpRequest::SetResponseType(XMLHttpR
   if (aResponseType == XMLHttpRequestResponseType::Document) {
     return;
   }
 
   nsString responseType;
   ConvertResponseTypeToString(aResponseType, responseType);
 
   RefPtr<SetResponseTypeRunnable> runnable =
-    new SetResponseTypeRunnable(mWorkerPrivate, mProxy, responseType, aRv);
-  runnable->Dispatch();
+    new SetResponseTypeRunnable(mWorkerPrivate, mProxy, responseType);
+  runnable->Dispatch(aRv);
   if (aRv.Failed()) {
     return;
   }
 
   nsString acceptedResponseTypeString;
   runnable->GetResponseType(acceptedResponseTypeString);
 
   mResponseType = ConvertStringToResponseType(acceptedResponseTypeString);
--- a/dom/workers/test/test_worker_interfaces.js
+++ b/dom/workers/test/test_worker_interfaces.js
@@ -192,16 +192,18 @@ var interfaceNamesInGlobalScope =
     "URL",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "URLSearchParams",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "WebGLActiveInfo", disabled: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "WebGLBuffer", disabled: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    { name: "WebGLContextEvent", disabled: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "WebGLFramebuffer", disabled: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "WebGLProgram", disabled: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "WebGLRenderbuffer", disabled: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "WebGLRenderingContext", disabled: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/embedding/browser/nsDocShellTreeOwner.cpp
+++ b/embedding/browser/nsDocShellTreeOwner.cpp
@@ -52,16 +52,17 @@
 #include "nsIStringBundle.h"
 #include "nsPIDOMWindow.h"
 #include "nsPIWindowRoot.h"
 #include "nsIDOMWindowCollection.h"
 #include "nsIWindowWatcher.h"
 #include "nsPIWindowWatcher.h"
 #include "nsIPrompt.h"
 #include "nsITabParent.h"
+#include "nsITabChild.h"
 #include "nsRect.h"
 #include "nsIWebBrowserChromeFocus.h"
 #include "nsIContent.h"
 #include "imgIContainer.h"
 #include "nsContextMenuInfo.h"
 #include "nsPresContext.h"
 #include "nsViewManager.h"
 #include "nsView.h"
@@ -450,16 +451,29 @@ nsDocShellTreeOwner::SizeShellTo(nsIDocS
 
   NS_ENSURE_STATE(mTreeOwner || webBrowserChrome);
 
   if (mTreeOwner) {
     return mTreeOwner->SizeShellTo(aShellItem, aCX, aCY);
   }
 
   if (aShellItem == mWebBrowser->mDocShell) {
+    nsCOMPtr<nsITabChild> tabChild = do_QueryInterface(webBrowserChrome);
+    if (tabChild) {
+      // The XUL window to resize is in the parent process, but there we
+      // won't be able to get aShellItem to do the hack in nsXULWindow::SizeShellTo,
+      // so let's send the width and height of aShellItem too.
+      nsCOMPtr<nsIBaseWindow> shellAsWin(do_QueryInterface(aShellItem));
+      NS_ENSURE_TRUE(shellAsWin, NS_ERROR_FAILURE);
+
+      int32_t width = 0;
+      int32_t height = 0;
+      shellAsWin->GetSize(&width, &height);
+      return tabChild->RemoteSizeShellTo(aCX, aCY, width, height);
+    }
     return webBrowserChrome->SizeBrowserTo(aCX, aCY);
   }
 
   nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(aShellItem));
   NS_ENSURE_TRUE(webNav, NS_ERROR_FAILURE);
 
   nsCOMPtr<nsIDOMDocument> domDocument;
   webNav->GetDocument(getter_AddRefs(domDocument));
@@ -573,17 +587,18 @@ nsDocShellTreeOwner::GetDevicePixelsPerD
   *aScale = 1.0;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShellTreeOwner::SetPositionDesktopPix(int32_t aX, int32_t aY)
 {
   if (mWebBrowser) {
-    return mWebBrowser->SetPositionDesktopPix(aX, aY);
+    nsresult rv = mWebBrowser->SetPositionDesktopPix(aX, aY);
+    NS_ENSURE_SUCCESS(rv, rv);
   }
 
   double scale = 1.0;
   GetDevicePixelsPerDesktopPixel(&scale);
   return SetPosition(NSToIntRound(aX * scale), NSToIntRound(aY * scale));
 }
 
 NS_IMETHODIMP
--- a/embedding/browser/nsIEmbeddingSiteWindow.idl
+++ b/embedding/browser/nsIEmbeddingSiteWindow.idl
@@ -42,23 +42,55 @@ interface nsIEmbeddingSiteWindow : nsISu
      *
      * @see setDimensions
      * @see getDimensions
      * @see DIM_FLAGS_SIZE_INNER
      */
     const unsigned long DIM_FLAGS_SIZE_OUTER = 4;
 
     /**
+     * Flag indicates that the x parameter should be ignored.
+     *
+     * @see setDimensions
+     */
+    const unsigned long DIM_FLAGS_IGNORE_X = 8;
+
+    /**
+     * Flag indicates that the y parameter should be ignored.
+     *
+     * @see setDimensions
+     */
+    const unsigned long DIM_FLAGS_IGNORE_Y = 16;
+
+    /**
+     * Flag indicates that the cx parameter should be ignored.
+     *
+     * @see setDimensions
+     */
+    const unsigned long DIM_FLAGS_IGNORE_CX = 32;
+
+    /**
+     * Flag indicates that the cy parameter should be ignored.
+     *
+     * @see setDimensions
+     */
+    const unsigned long DIM_FLAGS_IGNORE_CY = 64;
+
+
+    /**
      * Sets the dimensions for the window; the position & size. The
      * flags to indicate what the caller wants to set and whether the size
      * refers to the inner or outer area. The inner area refers to just
      * the embedded area, wheras the outer area can also include any 
      * surrounding chrome, window frame, title bar, and so on.
      *
      * @param flags  Combination of position, inner and outer size flags.
+     *               The ignore flags are telling the parent to use the
+     *               current values for those dimensions and ignore the
+     *               corresponding parameters the child sends.
      * @param x      Left hand corner of the outer area.
      * @param y      Top corner of the outer area.
      * @param cx     Width of the inner or outer area.
      * @param cy     Height of the inner or outer area.
      *
      * @return <code>NS_OK</code> if operation was performed correctly;
      *         <code>NS_ERROR_UNEXPECTED</code> if window could not be
      *           destroyed;
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -1018,19 +1018,19 @@ public:
 
   /**
    * Create a SourceSurface optimized for use with this DrawTarget from
    * existing bitmap data in memory.
    *
    * The SourceSurface does not take ownership of aData, and may be freed at any time.
    */
   virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData,
-                                                                  const IntSize &aSize,
-                                                                  int32_t aStride,
-                                                                  SurfaceFormat aFormat) const = 0;
+                                                                      const IntSize &aSize,
+                                                                      int32_t aStride,
+                                                                      SurfaceFormat aFormat) const = 0;
 
   /**
    * Create a SourceSurface optimized for use with this DrawTarget from an
    * arbitrary SourceSurface type supported by this backend. This may return
    * aSourceSurface or some other existing surface.
    */
   virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const = 0;
 
@@ -1305,25 +1305,38 @@ public:
    * specific stride, which must be large enough to fit all pixels.
    * It allocates new memory for the surface. This memory is freed when
    * the surface is destroyed.  The caller is responsible for handling the case
    * where nullptr is returned. The surface is not zeroed unless requested.
    */
   static already_AddRefed<DataSourceSurface>
     CreateDataSourceSurfaceWithStride(const IntSize &aSize, SurfaceFormat aFormat, int32_t aStride, bool aZero = false);
 
+  typedef void (*SourceSurfaceDeallocator)(void* aClosure);
+
   /**
    * This creates a simple data source surface for some existing data. It will
-   * wrap this data and the data for this source surface. The caller is
-   * responsible for deallocating the memory only after destruction of the
-   * surface.
+   * wrap this data and the data for this source surface.
+   *
+   * We can provide a custom destroying function for |aData|. This will be
+   * called in the surface dtor using |aDeallocator| and the |aClosure|. If
+   * there are errors during construction(return a nullptr surface), the caller
+   * is responsible for the deallocation.
+   *
+   * If there is no destroying function, the caller is responsible for
+   * deallocating the aData memory only after destruction of this
+   * DataSourceSurface.
    */
   static already_AddRefed<DataSourceSurface>
-    CreateWrappingDataSourceSurface(uint8_t *aData, int32_t aStride,
-                                    const IntSize &aSize, SurfaceFormat aFormat);
+    CreateWrappingDataSourceSurface(uint8_t *aData,
+                                    int32_t aStride,
+                                    const IntSize &aSize,
+                                    SurfaceFormat aFormat,
+                                    SourceSurfaceDeallocator aDeallocator = nullptr,
+                                    void* aClosure = nullptr);
 
   static void
     CopyDataSourceSurface(DataSourceSurface* aSource,
                           DataSourceSurface* aDest);
 
 
   static already_AddRefed<DrawEventRecorder>
     CreateEventRecorderForFile(const char *aFilename);
--- a/gfx/2d/DataSurfaceHelpers.cpp
+++ b/gfx/2d/DataSurfaceHelpers.cpp
@@ -10,16 +10,73 @@
 #include "Logging.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/PodOperations.h"
 #include "Tools.h"
 
 namespace mozilla {
 namespace gfx {
 
+already_AddRefed<DataSourceSurface>
+CreateDataSourceSurfaceFromData(const IntSize& aSize,
+                                SurfaceFormat aFormat,
+                                const uint8_t* aData,
+                                int32_t aDataStride)
+{
+  RefPtr<DataSourceSurface> srcSurface =
+      Factory::CreateWrappingDataSourceSurface(const_cast<uint8_t*>(aData),
+                                               aDataStride,
+                                               aSize,
+                                               aFormat);
+  RefPtr<DataSourceSurface> destSurface =
+      Factory::CreateDataSourceSurface(aSize, aFormat, false);
+
+  if (!srcSurface || !destSurface) {
+    return nullptr;
+  }
+
+  if (CopyRect(srcSurface,
+               destSurface,
+               IntRect(IntPoint(), srcSurface->GetSize()),
+               IntPoint())) {
+    return destSurface.forget();
+  }
+
+  return nullptr;
+}
+
+already_AddRefed<DataSourceSurface>
+CreateDataSourceSurfaceWithStrideFromData(const IntSize &aSize,
+                                          SurfaceFormat aFormat,
+                                          int32_t aStride,
+                                          const uint8_t* aData,
+                                          int32_t aDataStride)
+{
+  RefPtr<DataSourceSurface> srcSurface =
+      Factory::CreateWrappingDataSourceSurface(const_cast<uint8_t*>(aData),
+                                               aDataStride,
+                                               aSize,
+                                               aFormat);
+  RefPtr<DataSourceSurface> destSurface =
+      Factory::CreateDataSourceSurfaceWithStride(aSize, aFormat, aStride, false);
+
+  if (!srcSurface || !destSurface) {
+    return nullptr;
+  }
+
+  if (CopyRect(srcSurface,
+               destSurface,
+               IntRect(IntPoint(), srcSurface->GetSize()),
+               IntPoint())) {
+    return destSurface.forget();
+  }
+
+  return nullptr;
+}
+
 uint8_t*
 DataAtOffset(DataSourceSurface* aSurface,
              const DataSourceSurface::MappedSurface* aMap,
              IntPoint aPoint)
 {
   if (!SurfaceContainsPoint(aSurface, aPoint)) {
     MOZ_CRASH("GFX: sample position needs to be inside surface!");
   }
@@ -227,40 +284,40 @@ BufferSizeFromStrideAndHeight(int32_t aS
   }
   return requiredBytes.value();
 }
 
 /**
  * aSrcRect: Rect relative to the aSrc surface
  * aDestPoint: Point inside aDest surface
  */
-void
+bool
 CopyRect(DataSourceSurface* aSrc, DataSourceSurface* aDest,
          IntRect aSrcRect, IntPoint aDestPoint)
 {
   if (aSrcRect.Overflows() ||
       IntRect(aDestPoint, aSrcRect.Size()).Overflows()) {
     MOZ_CRASH("GFX: we should never be getting invalid rects at this point");
   }
 
   MOZ_RELEASE_ASSERT(aSrc->GetFormat() == aDest->GetFormat(),
                      "different surface formats");
   MOZ_RELEASE_ASSERT(IntRect(IntPoint(), aSrc->GetSize()).Contains(aSrcRect),
                      "source rect too big for source surface");
   MOZ_RELEASE_ASSERT(IntRect(IntPoint(), aDest->GetSize()).Contains(IntRect(aDestPoint, aSrcRect.Size())),
                      "dest surface too small");
 
   if (aSrcRect.IsEmpty()) {
-    return;
+    return false;
   }
 
   DataSourceSurface::ScopedMap srcMap(aSrc, DataSourceSurface::READ);
   DataSourceSurface::ScopedMap destMap(aDest, DataSourceSurface::WRITE);
   if (MOZ2D_WARN_IF(!srcMap.IsMapped() || !destMap.IsMapped())) {
-    return;
+    return false;
   }
 
   uint8_t* sourceData = DataAtOffset(aSrc, srcMap.GetMappedSurface(), aSrcRect.TopLeft());
   uint32_t sourceStride = srcMap.GetStride();
   uint8_t* destData = DataAtOffset(aDest, destMap.GetMappedSurface(), aDestPoint);
   uint32_t destStride = destMap.GetStride();
 
   if (BytesPerPixel(aSrc->GetFormat()) == 4) {
@@ -271,16 +328,18 @@ CopyRect(DataSourceSurface* aSrc, DataSo
     }
   } else if (BytesPerPixel(aSrc->GetFormat()) == 1) {
     for (int32_t y = 0; y < aSrcRect.height; y++) {
       PodCopy(destData, sourceData, aSrcRect.width);
       sourceData += sourceStride;
       destData += destStride;
     }
   }
+
+  return true;
 }
 
 already_AddRefed<DataSourceSurface>
 CreateDataSourceSurfaceByCloning(DataSourceSurface* aSource)
 {
   RefPtr<DataSourceSurface> copy =
     Factory::CreateDataSourceSurface(aSource->GetSize(), aSource->GetFormat(), true);
   if (copy) {
--- a/gfx/2d/DataSurfaceHelpers.h
+++ b/gfx/2d/DataSurfaceHelpers.h
@@ -8,16 +8,38 @@
 
 #include "2D.h"
 
 #include "mozilla/UniquePtr.h"
 
 namespace mozilla {
 namespace gfx {
 
+/**
+ * Create a DataSourceSurface and init the surface with the |aData|. The stride
+ * of this source surface might be different from the input data's |aDataStride|.
+ * System will try to use the optimal one.
+ */
+already_AddRefed<DataSourceSurface>
+CreateDataSourceSurfaceFromData(const IntSize& aSize,
+                                SurfaceFormat aFormat,
+                                const uint8_t* aData,
+                                int32_t aDataStride);
+
+/**
+ * Similar to CreateDataSourceSurfaceFromData(), but could setup the stride for
+ * this surface.
+ */
+already_AddRefed<DataSourceSurface>
+CreateDataSourceSurfaceWithStrideFromData(const IntSize &aSize,
+                                          SurfaceFormat aFormat,
+                                          int32_t aStride,
+                                          const uint8_t* aData,
+                                          int32_t aDataStride);
+
 void
 ConvertBGRXToBGRA(uint8_t* aData, const IntSize &aSize, const int32_t aStride);
 
 /**
  * Copy the pixel data from aSrc and pack it into aDst. aSrcSize, aSrcStride
  * and aBytesPerPixel give the size, stride and bytes per pixel for aSrc's
  * surface. Callers are responsible for making sure that aDst is big enough to
  * contain |aSrcSize.width * aSrcSize.height * aBytesPerPixel| bytes.
@@ -67,18 +89,19 @@ ClearDataSourceSurface(DataSourceSurface
  */
 size_t
 BufferSizeFromStrideAndHeight(int32_t aStride,
                               int32_t aHeight,
                               int32_t aExtraBytes = 0);
 
 /**
  * Copy aSrcRect from aSrc to aDest starting at aDestPoint.
+ * @returns false if the copy is not successful or the aSrc's size is empty.
  */
-void
+bool
 CopyRect(DataSourceSurface* aSrc, DataSourceSurface* aDest,
          IntRect aSrcRect, IntPoint aDestPoint);
 
 /**
  * Create a non aliasing copy of aSource. This creates a new DataSourceSurface
  * using the factory and copies the bits.
  *
  * @return a dss allocated by Factory that contains a copy a aSource.
--- a/gfx/2d/Factory.cpp
+++ b/gfx/2d/Factory.cpp
@@ -797,28 +797,35 @@ Factory::CreateDrawTargetForCairoCGConte
 already_AddRefed<GlyphRenderingOptions>
 Factory::CreateCGGlyphRenderingOptions(const Color &aFontSmoothingBackgroundColor)
 {
   return MakeAndAddRef<GlyphRenderingOptionsCG>(aFontSmoothingBackgroundColor);
 }
 #endif
 
 already_AddRefed<DataSourceSurface>
-Factory::CreateWrappingDataSourceSurface(uint8_t *aData, int32_t aStride,
+Factory::CreateWrappingDataSourceSurface(uint8_t *aData,
+                                         int32_t aStride,
                                          const IntSize &aSize,
-                                         SurfaceFormat aFormat)
+                                         SurfaceFormat aFormat,
+                                         SourceSurfaceDeallocator aDeallocator /* = nullptr */,
+                                         void* aClosure /* = nullptr */)
 {
   if (aSize.width <= 0 || aSize.height <= 0) {
     return nullptr;
   }
+  if (!aDeallocator && aClosure) {
+    return nullptr;
+  }
+
   MOZ_ASSERT(aData);
 
   RefPtr<SourceSurfaceRawData> newSurf = new SourceSurfaceRawData();
+  newSurf->InitWrappingData(aData, aSize, aStride, aFormat, aDeallocator, aClosure);
 
-  newSurf->InitWrappingData(aData, aSize, aStride, aFormat, false);
   return newSurf.forget();
 }
 
 already_AddRefed<DataSourceSurface>
 Factory::CreateDataSourceSurface(const IntSize &aSize,
                                  SurfaceFormat aFormat,
                                  bool aZero)
 {
--- a/gfx/2d/Logging.h
+++ b/gfx/2d/Logging.h
@@ -130,21 +130,22 @@ enum class LogReason : int {
   GlyphAllocFailedCG,
   InvalidRect,
   CannotDraw3D, // 20
   IncompatibleBasicTexturedEffect,
   InvalidFont,
   PAllocTextureBackendMismatch,
   GetFontFileDataFailed,
   MessageChannelCloseFailure,
+  MessageChannelInvalidHandle,
   TextureAliveAfterShutdown,
   InvalidContext,
   InvalidCommandList,
-  AsyncTransactionTimeout,
-  TextureCreation, // 30
+  AsyncTransactionTimeout, // 30
+  TextureCreation,
   // End
   MustBeLessThanThis = 101,
 };
 
 struct BasicLogger
 {
   // For efficiency, this method exists and copies the logic of the
   // OutputMessage below.  If making any changes here, also make it
--- a/gfx/2d/MacIOSurface.cpp
+++ b/gfx/2d/MacIOSurface.cpp
@@ -435,43 +435,55 @@ void MacIOSurface::DecrementUseCount() {
 void MacIOSurface::Lock() {
   MacIOSurfaceLib::IOSurfaceLock(mIOSurfacePtr, READ_ONLY, nullptr);
 }
 
 void MacIOSurface::Unlock() {
   MacIOSurfaceLib::IOSurfaceUnlock(mIOSurfacePtr, READ_ONLY, nullptr);
 }
 
-#include "SourceSurfaceRawData.h"
 using mozilla::gfx::SourceSurface;
-using mozilla::gfx::SourceSurfaceRawData;
 using mozilla::gfx::IntSize;
 using mozilla::gfx::SurfaceFormat;
 
+void
+MacIOSurfaceBufferDeallocator(void* aClosure)
+{
+  MOZ_ASSERT(aClosure);
+
+  delete [] static_cast<unsigned char*>(aClosure);
+}
+
 already_AddRefed<SourceSurface>
 MacIOSurface::GetAsSurface() {
   Lock();
   size_t bytesPerRow = GetBytesPerRow();
   size_t ioWidth = GetDevicePixelWidth();
   size_t ioHeight = GetDevicePixelHeight();
 
   unsigned char* ioData = (unsigned char*)GetBaseAddress();
-  unsigned char* dataCpy = (unsigned char*)malloc(bytesPerRow*ioHeight);
+  unsigned char* dataCpy =
+      new unsigned char[bytesPerRow * ioHeight / sizeof(unsigned char)];
   for (size_t i = 0; i < ioHeight; i++) {
     memcpy(dataCpy + i * bytesPerRow,
            ioData + i * bytesPerRow, ioWidth * 4);
   }
 
   Unlock();
 
   SurfaceFormat format = HasAlpha() ? mozilla::gfx::SurfaceFormat::B8G8R8A8 :
                                       mozilla::gfx::SurfaceFormat::B8G8R8X8;
 
-  RefPtr<SourceSurfaceRawData> surf = new SourceSurfaceRawData();
-  surf->InitWrappingData(dataCpy, IntSize(ioWidth, ioHeight), bytesPerRow, format, true);
+  RefPtr<mozilla::gfx::DataSourceSurface> surf =
+      mozilla::gfx::Factory::CreateWrappingDataSourceSurface(dataCpy,
+                                                             bytesPerRow,
+                                                             IntSize(ioWidth, ioHeight),
+                                                             format,
+                                                             &MacIOSurfaceBufferDeallocator,
+                                                             static_cast<void*>(dataCpy));
 
   return surf.forget();
 }
 
 SurfaceFormat
 MacIOSurface::GetFormat()
 {
   OSType pixelFormat = GetPixelFormat();
--- a/gfx/2d/SourceSurfaceRawData.cpp
+++ b/gfx/2d/SourceSurfaceRawData.cpp
@@ -12,23 +12,29 @@
 namespace mozilla {
 namespace gfx {
 
 void
 SourceSurfaceRawData::InitWrappingData(uint8_t *aData,
                                        const IntSize &aSize,
                                        int32_t aStride,
                                        SurfaceFormat aFormat,
-                                       bool aOwnData)
+                                       Factory::SourceSurfaceDeallocator aDeallocator,
+                                       void* aClosure)
 {
   mRawData = aData;
   mSize = aSize;
   mStride = aStride;
   mFormat = aFormat;
-  mOwnData = aOwnData;
+
+  if (aDeallocator) {
+    mOwnData = true;
+  }
+  mDeallocator = aDeallocator;
+  mClosure = aClosure;
 }
 
 void
 SourceSurfaceRawData::GuaranteePersistance()
 {
   if (mOwnData) {
     return;
   }
--- a/gfx/2d/SourceSurfaceRawData.h
+++ b/gfx/2d/SourceSurfaceRawData.h
@@ -12,38 +12,47 @@
 
 namespace mozilla {
 namespace gfx {
 
 class SourceSurfaceRawData : public DataSourceSurface
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceRawData, override)
+
   SourceSurfaceRawData()
-    : mMapCount(0)
-  {}
-  ~SourceSurfaceRawData()
+    : mRawData(0)
+    , mStride(0)
+    , mFormat(SurfaceFormat::UNKNOWN)
+    , mMapCount(0)
+    , mOwnData(false)
+    , mDeallocator(nullptr)
+    , mClosure(nullptr)
   {
-    if(mOwnData) delete [] mRawData;
+  }
+
+  virtual ~SourceSurfaceRawData()
+  {
+    if (mDeallocator) {
+      mDeallocator(mClosure);
+    } else if (mOwnData) {
+      // The buffer is created from GuaranteePersistance().
+      delete [] mRawData;
+    }
+
     MOZ_ASSERT(mMapCount == 0);
   }
 
   virtual uint8_t *GetData() override { return mRawData; }
   virtual int32_t Stride() override { return mStride; }
 
   virtual SurfaceType GetType() const override { return SurfaceType::DATA; }
   virtual IntSize GetSize() const override { return mSize; }
   virtual SurfaceFormat GetFormat() const override { return mFormat; }
 
-  void InitWrappingData(unsigned char *aData,
-                        const IntSize &aSize,
-                        int32_t aStride,
-                        SurfaceFormat aFormat,
-                        bool aOwnData);
-
   virtual void GuaranteePersistance() override;
 
   // Althought Map (and Moz2D in general) isn't normally threadsafe,
   // we want to allow it for SourceSurfaceRawData since it should
   // always be fine (for reading at least).
   //
   // This is the same as the base class implementation except using
   // mMapCount instead of mIsMapped since that breaks for multithread.
@@ -63,51 +72,59 @@ public:
 
   virtual void Unmap() override
   {
     mMapCount--;
     MOZ_ASSERT(mMapCount >= 0);
   }
 
 private:
+  friend class Factory;
+
+  // If we have a custom deallocator, the |aData| will be released using the
+  // custom deallocator and |aClosure| in dtor.
+  void InitWrappingData(unsigned char *aData,
+                        const IntSize &aSize,
+                        int32_t aStride,
+                        SurfaceFormat aFormat,
+                        Factory::SourceSurfaceDeallocator aDeallocator,
+                        void* aClosure);
+
   uint8_t *mRawData;
   int32_t mStride;
   SurfaceFormat mFormat;
   IntSize mSize;
   Atomic<int32_t> mMapCount;
+
   bool mOwnData;
+  Factory::SourceSurfaceDeallocator mDeallocator;
+  void* mClosure;
 };
 
 class SourceSurfaceAlignedRawData : public DataSourceSurface
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceAlignedRawData, override)
   SourceSurfaceAlignedRawData()
-    : mMapCount(0)
+    : mStride(0)
+    , mFormat(SurfaceFormat::UNKNOWN)
+    , mMapCount(0)
   {}
   ~SourceSurfaceAlignedRawData()
   {
     MOZ_ASSERT(mMapCount == 0);
   }
 
   virtual uint8_t *GetData() override { return mArray; }
   virtual int32_t Stride() override { return mStride; }
 
   virtual SurfaceType GetType() const override { return SurfaceType::DATA; }
   virtual IntSize GetSize() const override { return mSize; }
   virtual SurfaceFormat GetFormat() const override { return mFormat; }
 
-  bool Init(const IntSize &aSize,
-            SurfaceFormat aFormat,
-            bool aZero);
-  bool InitWithStride(const IntSize &aSize,
-                      SurfaceFormat aFormat,
-                      int32_t aStride,
-                      bool aZero);
-
   virtual bool Map(MapType, MappedSurface *aMappedSurface) override
   {
     aMappedSurface->mData = GetData();
     aMappedSurface->mStride = Stride();
     bool success = !!aMappedSurface->mData;
     if (success) {
       mMapCount++;
     }
@@ -116,16 +133,26 @@ public:
 
   virtual void Unmap() override
   {
     mMapCount--;
     MOZ_ASSERT(mMapCount >= 0);
   }
 
 private:
+  friend class Factory;
+
+  bool Init(const IntSize &aSize,
+            SurfaceFormat aFormat,
+            bool aZero);
+  bool InitWithStride(const IntSize &aSize,
+                      SurfaceFormat aFormat,
+                      int32_t aStride,
+                      bool aZero);
+
   AlignedArray<uint8_t> mArray;
   int32_t mStride;
   SurfaceFormat mFormat;
   IntSize mSize;
   Atomic<int32_t> mMapCount;
 };
 
 } // namespace gfx
--- a/gfx/gl/GLContextProviderEGL.cpp
+++ b/gfx/gl/GLContextProviderEGL.cpp
@@ -15,19 +15,22 @@
 
 #if defined(XP_UNIX)
     #ifdef MOZ_WIDGET_GONK
         #include "libdisplay/GonkDisplay.h"
         #include "nsWindow.h"
         #include "nsScreenManagerGonk.h"
     #endif
 
+    #ifdef MOZ_WIDGET_ANDROID
+        #include "AndroidBridge.h"
+    #endif
+
     #ifdef ANDROID
         #include <android/log.h>
-        #include "AndroidBridge.h"
         #define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args)
 
         #ifdef MOZ_WIDGET_GONK
             #include "cutils/properties.h"
             #include <ui/GraphicBuffer.h>
 
             using namespace android;
         #endif
--- a/gfx/ipc/GfxMessageUtils.h
+++ b/gfx/ipc/GfxMessageUtils.h
@@ -785,32 +785,49 @@ struct ParamTraits<mozilla::layers::Scro
             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::LayerClip>
+{
+  typedef mozilla::layers::LayerClip paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mClipRect);
+    WriteParam(aMsg, aParam.mMaskLayerIndex);
+  }
+
+  static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
+  {
+    return (ReadParam(aMsg, aIter, &aResult->mClipRect) &&
+            ReadParam(aMsg, aIter, &aResult->mMaskLayerIndex));
+  }
+};
+
+template <>
 struct ParamTraits<mozilla::layers::ScrollMetadata>
     : BitfieldHelper<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.mScrollParentId);
     WriteParam(aMsg, aParam.mBackgroundColor);
     WriteParam(aMsg, aParam.GetContentDescription());
     WriteParam(aMsg, aParam.mLineScrollAmount);
     WriteParam(aMsg, aParam.mPageScrollAmount);
-    WriteParam(aMsg, aParam.mMaskLayerIndex);
-    WriteParam(aMsg, aParam.mClipRect);
+    WriteParam(aMsg, aParam.mScrollClip);
     WriteParam(aMsg, aParam.mHasScrollgrab);
     WriteParam(aMsg, aParam.mAllowVerticalScrollWithWheel);
     WriteParam(aMsg, aParam.mIsLayersIdRoot);
     WriteParam(aMsg, aParam.mUsesContainerScrolling);
     WriteParam(aMsg, aParam.mForceDisableApz);
   }
 
   static bool ReadContentDescription(const Message* aMsg, void** aIter, paramType* aResult)
@@ -827,18 +844,17 @@ struct ParamTraits<mozilla::layers::Scro
   {
     return (ReadParam(aMsg, aIter, &aResult->mMetrics) &&
             ReadParam(aMsg, aIter, &aResult->mSnapInfo) &&
             ReadParam(aMsg, aIter, &aResult->mScrollParentId) &&
             ReadParam(aMsg, aIter, &aResult->mBackgroundColor) &&
             ReadContentDescription(aMsg, aIter, aResult) &&
             ReadParam(aMsg, aIter, &aResult->mLineScrollAmount) &&
             ReadParam(aMsg, aIter, &aResult->mPageScrollAmount) &&
-            ReadParam(aMsg, aIter, &aResult->mMaskLayerIndex) &&
-            ReadParam(aMsg, aIter, &aResult->mClipRect) &&
+            ReadParam(aMsg, aIter, &aResult->mScrollClip) &&
             ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetHasScrollgrab) &&
             ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetAllowVerticalScrollWithWheel) &&
             ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetIsLayersIdRoot) &&
             ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetUsesContainerScrolling) &&
             ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetForceDisableApz));
   }
 };
 
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -675,16 +675,64 @@ struct ScrollSnapInfo {
   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;
 };
 
 /**
+ * A clip that applies to a layer, that may be scrolled by some of the
+ * scroll frames associated with the layer.
+ */
+struct LayerClip {
+  friend struct IPC::ParamTraits<mozilla::layers::LayerClip>;
+
+public:
+  LayerClip()
+    : mClipRect()
+    , mMaskLayerIndex()
+  {}
+
+  explicit LayerClip(const ParentLayerIntRect& aClipRect)
+    : mClipRect(aClipRect)
+    , mMaskLayerIndex()
+  {}
+
+  bool operator==(const LayerClip& aOther) const
+  {
+    return mClipRect == aOther.mClipRect &&
+           mMaskLayerIndex == aOther.mMaskLayerIndex;
+  }
+
+  void SetClipRect(const ParentLayerIntRect& aClipRect) {
+    mClipRect = aClipRect;
+  }
+  const ParentLayerIntRect& GetClipRect() const {
+    return mClipRect;
+  }
+
+  void SetMaskLayerIndex(const Maybe<size_t>& aIndex) {
+    mMaskLayerIndex = aIndex;
+  }
+  const Maybe<size_t>& GetMaskLayerIndex() const {
+    return mMaskLayerIndex;
+  }
+
+private:
+  ParentLayerIntRect mClipRect;
+
+  // Optionally, specifies a mask layer that's part of the clip.
+  // This is an index into the MetricsMaskLayers array on the Layer.
+  Maybe<size_t> mMaskLayerIndex;
+};
+
+typedef Maybe<LayerClip> MaybeLayerClip;  // for passing over IPDL
+
+/**
  * 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>;
@@ -696,36 +744,34 @@ public:
   ScrollMetadata()
     : mMetrics()
     , mSnapInfo()
     , mScrollParentId(FrameMetrics::NULL_SCROLL_ID)
     , mBackgroundColor()
     , mContentDescription()
     , mLineScrollAmount(0, 0)
     , mPageScrollAmount(0, 0)
-    , mMaskLayerIndex()
-    , mClipRect()
+    , mScrollClip()
     , mHasScrollgrab(false)
     , mAllowVerticalScrollWithWheel(false)
     , mIsLayersIdRoot(false)
     , mUsesContainerScrolling(false)
     , mForceDisableApz(false)
   {}
 
   bool operator==(const ScrollMetadata& aOther) const
   {
     return mMetrics == aOther.mMetrics &&
            mSnapInfo == aOther.mSnapInfo &&
            mScrollParentId == aOther.mScrollParentId &&
            mBackgroundColor == aOther.mBackgroundColor &&
            // don't compare mContentDescription
            mLineScrollAmount == aOther.mLineScrollAmount &&
            mPageScrollAmount == aOther.mPageScrollAmount &&
-           mMaskLayerIndex == aOther.mMaskLayerIndex &&
-           mClipRect == aOther.mClipRect &&
+           mScrollClip == aOther.mScrollClip &&
            mHasScrollgrab == aOther.mHasScrollgrab &&
            mAllowVerticalScrollWithWheel == aOther.mAllowVerticalScrollWithWheel &&
            mIsLayersIdRoot == aOther.mIsLayersIdRoot &&
            mUsesContainerScrolling == aOther.mUsesContainerScrolling &&
            mForceDisableApz == aOther.mForceDisableApz;
   }
 
   bool operator!=(const ScrollMetadata& aOther) const
@@ -775,36 +821,38 @@ public:
     mLineScrollAmount = size;
   }
   const LayoutDeviceIntSize& GetPageScrollAmount() const {
     return mPageScrollAmount;
   }
   void SetPageScrollAmount(const LayoutDeviceIntSize& size) {
     mPageScrollAmount = size;
   }
-  void SetMaskLayerIndex(const Maybe<size_t>& aIndex) {
-    mMaskLayerIndex = aIndex;
+
+  void SetScrollClip(const Maybe<LayerClip>& aScrollClip) {
+    mScrollClip = aScrollClip;
+  }
+  const Maybe<LayerClip>& GetScrollClip() const {
+    return mScrollClip;
   }
-  const Maybe<size_t>& GetMaskLayerIndex() const {
-    return mMaskLayerIndex;
+  bool HasScrollClip() const {
+    return mScrollClip.isSome();
+  }
+  const LayerClip& ScrollClip() const {
+    return mScrollClip.ref();
+  }
+  LayerClip& ScrollClip() {
+    return mScrollClip.ref();
   }
 
-  void SetClipRect(const Maybe<ParentLayerIntRect>& aClipRect)
-  {
-    mClipRect = aClipRect;
+  bool HasMaskLayer() const {
+    return HasScrollClip() && ScrollClip().GetMaskLayerIndex();
   }
-  const Maybe<ParentLayerIntRect>& GetClipRect() const
-  {
-    return mClipRect;
-  }
-  bool HasClipRect() const {
-    return mClipRect.isSome();
-  }
-  const ParentLayerIntRect& ClipRect() const {
-    return mClipRect.ref();
+  Maybe<ParentLayerIntRect> GetClipRect() const {
+    return mScrollClip.isSome() ? Some(mScrollClip->GetClipRect()) : Nothing();
   }
 
   void SetHasScrollgrab(bool aHasScrollgrab) {
     mHasScrollgrab = aHasScrollgrab;
   }
   bool GetHasScrollgrab() const {
     return mHasScrollgrab;
   }
@@ -851,23 +899,23 @@ private:
   nsCString mContentDescription;
 
   // The value of GetLineScrollAmount(), for scroll frames.
   LayoutDeviceIntSize mLineScrollAmount;
 
   // The value of GetPageScrollAmount(), for scroll frames.
   LayoutDeviceIntSize mPageScrollAmount;
 
-  // 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;
+  // A clip to apply when compositing the layer bearing this ScrollMetadata,
+  // after applying any transform arising from scrolling this scroll frame.
+  // Note that, unlike most other fields of ScrollMetadata, this is allowed
+  // to differ between different layers scrolled by the same scroll frame.
+  // TODO: Group the fields of ScrollMetadata into sub-structures to separate
+  // fields with this property better.
+  Maybe<LayerClip> mScrollClip;
 
   // Whether or not this frame is for an element marked 'scrollgrab'.
   bool mHasScrollgrab:1;
 
   // Whether or not the frame can be vertically scrolled with a mouse wheel.
   bool mAllowVerticalScrollWithWheel:1;
 
   // Whether these framemetrics are for the root scroll frame (root element if
--- a/gfx/layers/LayerMetricsWrapper.h
+++ b/gfx/layers/LayerMetricsWrapper.h
@@ -367,20 +367,24 @@ public:
   }
 
   Maybe<ParentLayerIntRect> GetClipRect() const
   {
     MOZ_ASSERT(IsValid());
 
     Maybe<ParentLayerIntRect> result;
 
-    // The layer can have a clip rect, which is considered to apply
-    // only to the bottommost LayerMetrics.
+    // The layer can have a clip rect and a scrolled clip, which are considered
+    // to apply only to the bottommost LayerMetricsWrapper.
+    // TODO: These actually apply in a different coordinate space than the
+    // scroll clip of the bottommost metrics, so we shouldn't be intersecting
+    // them with the scroll clip; bug 1269537 tracks fixing this.
     if (AtBottomLayer()) {
       result = mLayer->GetClipRect();
+      result = IntersectMaybeRects(result, mLayer->GetScrolledClipRect());
     }
 
     // The scroll metadata can have a clip rect as well.
     result = IntersectMaybeRects(result, Metadata().GetClipRect());
 
     return result;
   }
 
@@ -429,16 +433,23 @@ public:
   }
 
   bool IsScrollbarContainer() const
   {
     MOZ_ASSERT(IsValid());
     return mLayer->IsScrollbarContainer();
   }
 
+  FrameMetrics::ViewID GetFixedPositionScrollContainerId() const
+  {
+    MOZ_ASSERT(IsValid());
+
+    return mLayer->GetFixedPositionScrollContainerId();
+  }
+
   // Expose an opaque pointer to the layer. Mostly used for printf
   // purposes. This is not intended to be a general-purpose accessor
   // for the underlying layer.
   const void* GetLayer() const
   {
     MOZ_ASSERT(IsValid());
 
     return (void*)mLayer;
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -846,16 +846,22 @@ Layer::CalculateScissorRect(const Render
   }
 
   if (container) {
     scissor.MoveBy(-container->GetIntermediateSurfaceRect().TopLeft());
   }
   return currentClip.Intersect(scissor);
 }
 
+Maybe<ParentLayerIntRect>
+Layer::GetScrolledClipRect() const
+{
+  return mScrolledClip ? Some(mScrolledClip->GetClipRect()) : Nothing();
+}
+
 const ScrollMetadata&
 Layer::GetScrollMetadata(uint32_t aIndex) const
 {
   MOZ_ASSERT(aIndex < GetScrollMetadataCount());
   return mScrollMetadata[aIndex];
 }
 
 const FrameMetrics&
@@ -1095,27 +1101,20 @@ Layer::GetVisibleRegionRelativeToRootLay
   return true;
 }
 
 Maybe<ParentLayerIntRect>
 Layer::GetCombinedClipRect() const
 {
   Maybe<ParentLayerIntRect> clip = GetClipRect();
 
-  for (size_t i = 0; i < mScrollMetadata.Length(); i++) {
-    if (!mScrollMetadata[i].HasClipRect()) {
-      continue;
-    }
+  clip = IntersectMaybeRects(clip, GetScrolledClipRect());
 
-    const ParentLayerIntRect& other = mScrollMetadata[i].ClipRect();
-    if (clip) {
-      clip = Some(clip.value().Intersect(other));
-    } else {
-      clip = Some(other);
-    }
+  for (size_t i = 0; i < mScrollMetadata.Length(); i++) {
+    clip = IntersectMaybeRects(clip, mScrollMetadata[i].GetClipRect());
   }
 
   return clip;
 }
 
 ContainerLayer::ContainerLayer(LayerManager* aManager, void* aImplData)
   : Layer(aManager, aImplData),
     mFirstChild(nullptr),
@@ -1906,16 +1905,19 @@ Layer::PrintInfo(std::stringstream& aStr
   aStream << aPrefix;
   aStream << nsPrintfCString("%s%s (0x%p)", mManager->Name(), Name(), this).get();
 
   layers::PrintInfo(aStream, AsLayerComposite());
 
   if (mClipRect) {
     AppendToString(aStream, *mClipRect, " [clip=", "]");
   }
+  if (mScrolledClip) {
+    AppendToString(aStream, mScrolledClip->GetClipRect(), " [scrolled-clip=", "]");
+  }
   if (1.0 != mPostXScale || 1.0 != mPostYScale) {
     aStream << nsPrintfCString(" [postScale=%g, %g]", mPostXScale, mPostYScale).get();
   }
   if (!mTransform.IsIdentity()) {
     AppendToString(aStream, mTransform, " [transform=", "]");
   }
   if (!GetEffectiveTransform().IsIdentity()) {
     AppendToString(aStream, GetEffectiveTransform(), " [effective-transform=", "]");
@@ -1961,21 +1963,20 @@ Layer::PrintInfo(std::stringstream& aStr
   if (GetScrollbarDirection() == VERTICAL) {
     aStream << nsPrintfCString(" [vscrollbar=%lld]", GetScrollbarTargetContainerId()).get();
   }
   if (GetScrollbarDirection() == HORIZONTAL) {
     aStream << nsPrintfCString(" [hscrollbar=%lld]", GetScrollbarTargetContainerId()).get();
   }
   if (GetIsFixedPosition()) {
     LayerPoint anchor = GetFixedPositionAnchor();
-    aStream << nsPrintfCString(" [isFixedPosition scrollId=%lld sides=0x%x anchor=%s%s]",
+    aStream << nsPrintfCString(" [isFixedPosition scrollId=%lld sides=0x%x anchor=%s]",
                      GetFixedPositionScrollContainerId(),
                      GetFixedPositionSides(),
-                     ToString(anchor).c_str(),
-                     IsClipFixed() ? "" : " scrollingClip").get();
+                     ToString(anchor).c_str()).get();
   }
   if (GetIsStickyPosition()) {
     aStream << nsPrintfCString(" [isStickyPosition scrollId=%d outer=%f,%f %fx%f "
                      "inner=%f,%f %fx%f]", mStickyPositionData->mScrollId,
                      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();
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -1032,16 +1032,36 @@ public:
         mClipRect = aRect;
         Mutated();
       }
     }
   }
 
   /**
    * CONSTRUCTION PHASE ONLY
+   * Set an optional scrolled clip on the layer.
+   * The scrolled clip, if present, consists of a clip rect and an optional mask.
+   * This scrolled clip is always scrolled by all scroll frames associated with
+   * this layer. (By contrast, the scroll clips stored in ScrollMetadata are
+   * only scrolled by scroll frames above that ScrollMetadata, and the layer's
+   * mClipRect is always fixed to the layer contents (which may or may not be
+   * scrolled by some of the scroll frames associated with the layer, depending
+   * on whether the layer is fixed).)
+   */
+  void SetScrolledClip(const Maybe<LayerClip>& aScrolledClip)
+  {
+    if (mScrolledClip != aScrolledClip) {
+      MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) ScrolledClip", this));
+      mScrolledClip = aScrolledClip;
+      Mutated();
+    }
+  }
+
+  /**
+   * CONSTRUCTION PHASE ONLY
    * Set a layer to mask this layer.
    *
    * The mask layer should be applied using its effective transform (after it
    * is calculated by ComputeEffectiveTransformForMaskLayer), this should use
    * this layer's parent's transform and the mask layer's transform, but not
    * this layer's. That is, the mask layer is specified relative to this layer's
    * position in it's parent layer's coord space.
    * Currently, only 2D translations are supported for the mask layer transform.
@@ -1063,28 +1083,37 @@ public:
       MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) MaskLayer", this));
       mMaskLayer = aMaskLayer;
       Mutated();
     }
   }
 
   /**
    * CONSTRUCTION PHASE ONLY
-   * Add a FrameMetrics-associated mask layer.
+   * Add mask layers associated with LayerClips.
    */
   void SetAncestorMaskLayers(const nsTArray<RefPtr<Layer>>& aLayers) {
     if (aLayers != mAncestorMaskLayers) {
       MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) AncestorMaskLayers", this));
       mAncestorMaskLayers = aLayers;
       Mutated();
     }
   }
 
   /**
    * CONSTRUCTION PHASE ONLY
+   * Add a mask layer associated with a LayerClip.
+   */
+  void AddAncestorMaskLayer(const RefPtr<Layer>& aLayer) {
+    mAncestorMaskLayers.AppendElement(aLayer);
+    Mutated();
+  }
+
+  /**
+   * CONSTRUCTION PHASE ONLY
    * Tell this layer what its transform should be. The transformation
    * is applied when compositing the layer into its parent container.
    */
   void SetBaseTransform(const gfx::Matrix4x4& aMatrix)
   {
     NS_ASSERTION(!aMatrix.IsSingular(),
                  "Shouldn't be trying to draw with a singular matrix!");
     mPendingTransform = nullptr;
@@ -1184,40 +1213,32 @@ public:
    *     point, that is, the point which remains in the same position when
    *     compositing the layer tree with a transformation (such as when
    *     asynchronously scrolling and zooming).
    *
    *   - |aSides| is the set of sides to which the element is fixed relative to.
    *     This is used if the viewport size is changed in the compositor and
    *     fixed position items need to shift accordingly. This value is made up
    *     combining appropriate values from mozilla::SideBits.
-   *
-   *   - |aIsClipFixed| is true if this layer's clip rect and mask layer
-   *     should also remain fixed during async scrolling/animations.
-   *     This is the case for fixed position layers, but not for
-   *     fixed background layers.
    */
   void SetFixedPositionData(FrameMetrics::ViewID aScrollId,
                             const LayerPoint& aAnchor,
-                            int32_t aSides,
-                            bool aIsClipFixed)
+                            int32_t aSides)
   {
     if (!mFixedPositionData ||
         mFixedPositionData->mScrollId != aScrollId ||
         mFixedPositionData->mAnchor != aAnchor ||
-        mFixedPositionData->mSides != aSides ||
-        mFixedPositionData->mIsClipFixed != aIsClipFixed) {
+        mFixedPositionData->mSides != aSides) {
       MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) FixedPositionData", this));
       if (!mFixedPositionData) {
         mFixedPositionData = MakeUnique<FixedPositionData>();
       }
       mFixedPositionData->mScrollId = aScrollId;
       mFixedPositionData->mAnchor = aAnchor;
       mFixedPositionData->mSides = aSides;
-      mFixedPositionData->mIsClipFixed = aIsClipFixed;
       Mutated();
     }
   }
 
   /**
    * CONSTRUCTION PHASE ONLY
    * If a layer is "sticky position", |aScrollId| holds the scroll identifier
    * of the scrollable content that contains it. The difference between the two
@@ -1276,16 +1297,18 @@ public:
       Mutated();
     }
   }
 
   // These getters can be used anytime.
   float GetOpacity() { return mOpacity; }
   gfx::CompositionOp GetMixBlendMode() const { return mMixBlendMode; }
   const Maybe<ParentLayerIntRect>& GetClipRect() const { return mClipRect; }
+  const Maybe<LayerClip>& GetScrolledClip() const { return mScrolledClip; }
+  Maybe<ParentLayerIntRect> GetScrolledClipRect() const;
   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 GetScrollMetadataCount() const { return mScrollMetadata.Length(); }
   const nsTArray<ScrollMetadata>& GetAllScrollMetadata() { return mScrollMetadata; }
   bool HasScrollableFrameMetrics() const;
@@ -1307,17 +1330,16 @@ public:
   virtual float GetPostXScale() const { return mPostXScale; }
   virtual float GetPostYScale() const { return mPostYScale; }
   bool GetIsFixedPosition() { return mIsFixedPosition; }
   bool GetTransformIsPerspective() const { return mTransformIsPerspective; }
   bool GetIsStickyPosition() { return mStickyPositionData; }
   FrameMetrics::ViewID GetFixedPositionScrollContainerId() { return mFixedPositionData ? mFixedPositionData->mScrollId : FrameMetrics::NULL_SCROLL_ID; }
   LayerPoint GetFixedPositionAnchor() { return mFixedPositionData ? mFixedPositionData->mAnchor : LayerPoint(); }
   int32_t GetFixedPositionSides() { return mFixedPositionData ? mFixedPositionData->mSides : eSideBitsNone; }
-  bool IsClipFixed() { return mFixedPositionData ? mFixedPositionData->mIsClipFixed : false; }
   FrameMetrics::ViewID GetStickyScrollContainerId() { return mStickyPositionData->mScrollId; }
   const LayerRect& GetStickyScrollRangeOuter() { return mStickyPositionData->mOuter; }
   const LayerRect& GetStickyScrollRangeInner() { return mStickyPositionData->mInner; }
   FrameMetrics::ViewID GetScrollbarTargetContainerId() { return mScrollbarTargetId; }
   ScrollDirection GetScrollbarDirection() { return mScrollbarDirection; }
   float GetScrollbarThumbRatio() { return mScrollbarThumbRatio; }
   bool IsScrollbarContainer() { return mIsScrollbarContainer; }
   Layer* GetMaskLayer() const { return mMaskLayer; }
@@ -1325,16 +1347,19 @@ public:
   // Ancestor mask layers are associated with FrameMetrics, but for simplicity
   // in maintaining the layer tree structure we attach them to the layer.
   size_t GetAncestorMaskLayerCount() const {
     return mAncestorMaskLayers.Length();
   }
   Layer* GetAncestorMaskLayerAt(size_t aIndex) const {
     return mAncestorMaskLayers.ElementAt(aIndex);
   }
+  const nsTArray<RefPtr<Layer>>& GetAllAncestorMaskLayers() const {
+    return mAncestorMaskLayers;
+  }
 
   bool HasMaskLayers() const {
     return GetMaskLayer() || mAncestorMaskLayers.Length() > 0;
   }
 
   /*
    * Get the combined clip rect of the Layer clip and all clips on FrameMetrics.
    * This is intended for use in Layout. The compositor needs to apply async
@@ -1845,28 +1870,28 @@ protected:
   AnimationArray mAnimations;
   // See mPendingTransform above.
   nsAutoPtr<AnimationArray> mPendingAnimations;
   InfallibleTArray<AnimData> mAnimationData;
   float mOpacity;
   gfx::CompositionOp mMixBlendMode;
   bool mForceIsolatedGroup;
   Maybe<ParentLayerIntRect> mClipRect;
+  Maybe<LayerClip> mScrolledClip;
   gfx::IntRect mTileSourceRect;
   gfx::TiledIntRegion mInvalidRegion;
   nsTArray<RefPtr<AsyncPanZoomController> > mApzcs;
   uint32_t mContentFlags;
   bool mUseTileSourceRect;
   bool mIsFixedPosition;
   bool mTransformIsPerspective;
   struct FixedPositionData {
     FrameMetrics::ViewID mScrollId;
     LayerPoint mAnchor;
     int32_t mSides;
-    bool mIsClipFixed;
   };
   UniquePtr<FixedPositionData> mFixedPositionData;
   struct StickyPositionData {
     FrameMetrics::ViewID mScrollId;
     LayerRect mOuter;
     LayerRect mInner;
   };
   nsAutoPtr<StickyPositionData> mStickyPositionData;
--- a/gfx/layers/LayersLogging.cpp
+++ b/gfx/layers/LayersLogging.cpp
@@ -149,18 +149,18 @@ AppendToString(std::stringstream& aStrea
                const char* pfx, const char* sfx)
 {
   aStream << pfx;
   AppendToString(aStream, m.GetMetrics(), "{ [metrics=");
   AppendToString(aStream, m.GetBackgroundColor(), "] [color=");
   if (m.GetScrollParentId() != FrameMetrics::NULL_SCROLL_ID) {
     AppendToString(aStream, m.GetScrollParentId(), "] [scrollParent=");
   }
-  if (m.HasClipRect()) {
-    AppendToString(aStream, m.ClipRect(), "] [clip=");
+  if (m.HasScrollClip()) {
+    AppendToString(aStream, m.ScrollClip().GetClipRect(), "] [clip=");
   }
   aStream << "] }" << sfx;
 }
 
 void
 AppendToString(std::stringstream& aStream, const FrameMetrics& m,
                const char* pfx, const char* sfx, bool detailed)
 {
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -364,16 +364,17 @@ APZCTreeManager::PrepareNodeForLayer(con
         GetEventRegions(aLayer),
         aLayer.GetTransformTyped(),
         aLayer.GetClipRect() ? Some(ParentLayerIntRegion(*aLayer.GetClipRect())) : Nothing(),
         GetEventRegionsOverride(aParent, aLayer));
     node->SetScrollbarData(aLayer.GetScrollbarTargetContainerId(),
                            aLayer.GetScrollbarDirection(),
                            aLayer.GetScrollbarSize(),
                            aLayer.IsScrollbarContainer());
+    node->SetFixedPosData(aLayer.GetFixedPositionScrollContainerId());
     return node;
   }
 
   AsyncPanZoomController* apzc = nullptr;
   // If we get here, aLayer is a scrollable layer and somebody
   // has registered a GeckoContentController for it, so we need to ensure
   // it has an APZC instance to manage its scrolling.
 
@@ -544,16 +545,17 @@ APZCTreeManager::PrepareNodeForLayer(con
         Some(clipRegion),
         GetEventRegionsOverride(aParent, aLayer));
   }
 
   node->SetScrollbarData(aLayer.GetScrollbarTargetContainerId(),
                          aLayer.GetScrollbarDirection(),
                          aLayer.GetScrollbarSize(),
                          aLayer.IsScrollbarContainer());
+  node->SetFixedPosData(aLayer.GetFixedPositionScrollContainerId());
   return node;
 }
 
 HitTestingTreeNode*
 APZCTreeManager::UpdateHitTestingTree(TreeBuildingState& aState,
                                       const LayerMetricsWrapper& aLayer,
                                       uint64_t aLayersId,
                                       const gfx::Matrix4x4& aAncestorTransform,
@@ -1768,20 +1770,35 @@ APZCTreeManager::GetAPZCAtPoint(HitTesti
       MOZ_ASSERT(resultNode);
       if (aOutHitScrollbar) {
         for (HitTestingTreeNode* n = resultNode; n; n = n->GetParent()) {
           if (n->IsScrollbarNode()) {
             *aOutHitScrollbar = true;
           }
         }
       }
-      AsyncPanZoomController* result = resultNode->GetNearestContainingApzcWithSameLayersId();
+
+      AsyncPanZoomController* result = nullptr;
+
+      FrameMetrics::ViewID fpTarget =
+          resultNode->GetNearestAncestorFixedPosTargetWithSameLayersId();
+      if (fpTarget != FrameMetrics::NULL_SCROLL_ID) {
+        ScrollableLayerGuid guid(resultNode->GetLayersId(), 0, fpTarget);
+        RefPtr<HitTestingTreeNode> hitNode = GetTargetNode(guid, &GuidComparatorIgnoringPresShell);
+        result = hitNode ? hitNode->GetApzc() : nullptr;
+        APZCTM_LOG("Found target %p using fixed-pos lookup on %" PRIu64 "\n", result, fpTarget);
+      }
+      if (!result) {
+        result = resultNode->GetNearestContainingApzcWithSameLayersId();
+        APZCTM_LOG("Found target %p using ancestor lookup\n", result);
+      }
       if (!result) {
         result = FindRootApzcForLayersId(resultNode->GetLayersId());
         MOZ_ASSERT(result);
+        APZCTM_LOG("Found target %p using root lookup\n", result);
       }
       APZCTM_LOG("Successfully matched APZC %p via node %p (hit result %d)\n",
           result, resultNode, *aOutHitResult);
       return result;
   }
 
   return nullptr;
 }
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -66,16 +66,20 @@
 #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"
+#if defined(MOZ_ANDROID_APZ)
+#include "GeneratedJNIWrappers.h"
+#include "FlingOverScrollerAnimation.h"
+#endif // defined(MOZ_ANDROID_APZ)
 
 #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, ...) \
     { std::stringstream ss; \
@@ -215,31 +219,34 @@ using mozilla::gfx::PointTyped;
  * Some example values for these prefs can be found at\n
  * http://mxr.mozilla.org/mozilla-central/source/layout/style/nsStyleStruct.cpp?rev=21282be9ad95#2462
  *
  * \li\b apz.fling_friction
  * Amount of friction applied during flings. This is used in the following
  * formula: v(t1) = v(t0) * (1 - f)^(t1 - t0), where v(t1) is the velocity
  * for a new sample, v(t0) is the velocity at the previous sample, f is the
  * value of this pref, and (t1 - t0) is the amount of time, in milliseconds,
- * that has elapsed between the two samples.
+ * that has elapsed between the two samples.\n
+ * NOTE: Not currently used in Android fling calculations.
  *
  * \li\b apz.fling_stop_on_tap_threshold
  * When flinging, if the velocity is above this number, then a tap on the
  * screen will stop the fling without dispatching a tap to content. If the
  * velocity is below this threshold a tap will also be dispatched.
  * Note: when modifying this pref be sure to run the APZC gtests as some of
  * them depend on the value of this pref.\n
  * Units: screen pixels per millisecond
  *
  * \li\b apz.fling_stopped_threshold
  * When flinging, if the velocity goes below this number, we just stop the
  * animation completely. This is to prevent asymptotically approaching 0
  * velocity and rerendering unnecessarily.\n
- * Units: screen pixels per millisecond
+ * Units: screen pixels per millisecond.\n
+ * NOTE: Should not be set to anything
+ * other than 0.0 for Android except for tests to disable flings.
  *
  * \li\b apz.max_velocity_inches_per_ms
  * Maximum velocity.  Velocity will be capped at this value if a faster fling
  * occurs.  Negative values indicate unlimited velocity.\n
  * Units: (real-world, i.e. screen) inches per millisecond
  *
  * \li\b apz.max_velocity_queue_size
  * Maximum size of velocity queue. The queue contains last N velocity records.
@@ -431,16 +438,17 @@ public:
     mApzc->DispatchStateChangeNotification(mInitialState, newState);
   }
 
 private:
   AsyncPanZoomController* mApzc;
   AsyncPanZoomController::PanZoomState mInitialState;
 };
 
+#if !defined(MOZ_ANDROID_APZ)
 class FlingAnimation: public AsyncPanZoomAnimation {
 public:
   FlingAnimation(AsyncPanZoomController& aApzc,
                  const RefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
                  bool aApplyAcceleration,
                  const RefPtr<const AsyncPanZoomController>& aScrolledApzc)
     : mApzc(aApzc)
     , mOverscrollHandoffChain(aOverscrollHandoffChain)
@@ -599,16 +607,17 @@ private:
     return (aBase * gfxPrefs::APZFlingAccelBaseMultiplier())
          + (aSupplemental * gfxPrefs::APZFlingAccelSupplementalMultiplier());
   }
 
   AsyncPanZoomController& mApzc;
   RefPtr<const OverscrollHandoffChain> mOverscrollHandoffChain;
   RefPtr<const AsyncPanZoomController> mScrolledApzc;
 };
+#endif
 
 class ZoomAnimation: public AsyncPanZoomAnimation {
 public:
   ZoomAnimation(CSSPoint aStartOffset, CSSToParentLayerScale2D aStartZoom,
                 CSSPoint aEndOffset, CSSToParentLayerScale2D aEndZoom)
     : mTotalDuration(TimeDuration::FromMilliseconds(gfxPrefs::APZZoomAnimationDuration()))
     , mStartOffset(aStartOffset)
     , mStartZoom(aStartZoom)
@@ -2605,20 +2614,35 @@ void AsyncPanZoomController::AcceptFling
   }
 
   // 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);
+#if defined(MOZ_ANDROID_APZ)
+    if (!mOverScroller) {
+      widget::sdk::OverScroller::LocalRef scroller;
+      if (widget::sdk::OverScroller::New(widget::GeckoAppShell::GetApplicationContext(), &scroller) != NS_OK) {
+        APZC_LOG("%p Failed to create Android OverScroller\n", this);
+        return;
+      }
+      mOverScroller = scroller;
+    }
+    FlingOverScrollerAnimation *fling = new FlingOverScrollerAnimation(*this,
+        mOverScroller,
+        aHandoffState.mChain,
+        aHandoffState.mScrolledApzc);
+#else
     FlingAnimation *fling = new FlingAnimation(*this,
         aHandoffState.mChain,
         !aHandoffState.mIsHandoff,  // only apply acceleration if this is an initial fling
         aHandoffState.mScrolledApzc);
+#endif
     StartAnimation(fling);
   }
 }
 
 bool AsyncPanZoomController::AttemptFling(FlingHandoffState& aHandoffState) {
   // If we are pannable, take over the fling ourselves.
   if (IsPannable()) {
     AcceptFling(aHandoffState);
@@ -3516,18 +3540,20 @@ void AsyncPanZoomController::NotifyLayer
     mFrameMetrics.SetCompositionBounds(aLayerMetrics.GetCompositionBounds());
     mFrameMetrics.SetRootCompositionSize(aLayerMetrics.GetRootCompositionSize());
     mFrameMetrics.SetPresShellResolution(aLayerMetrics.GetPresShellResolution());
     mFrameMetrics.SetCumulativeResolution(aLayerMetrics.GetCumulativeResolution());
     mScrollMetadata.SetHasScrollgrab(aScrollMetadata.GetHasScrollgrab());
     mScrollMetadata.SetLineScrollAmount(aScrollMetadata.GetLineScrollAmount());
     mScrollMetadata.SetPageScrollAmount(aScrollMetadata.GetPageScrollAmount());
     mScrollMetadata.SetSnapInfo(ScrollSnapInfo(aScrollMetadata.GetSnapInfo()));
-    mScrollMetadata.SetClipRect(aScrollMetadata.GetClipRect());
-    mScrollMetadata.SetMaskLayerIndex(aScrollMetadata.GetMaskLayerIndex());
+    // The scroll clip can differ between layers associated a given scroll frame,
+    // so APZC (which keeps a single copy of ScrollMetadata per scroll frame)
+    // has no business using it.
+    mScrollMetadata.SetScrollClip(Nothing());
     mScrollMetadata.SetIsLayersIdRoot(aScrollMetadata.IsLayersIdRoot());
     mScrollMetadata.SetUsesContainerScrolling(aScrollMetadata.UsesContainerScrolling());
     mFrameMetrics.SetIsScrollInfoLayer(aLayerMetrics.IsScrollInfoLayer());
     mScrollMetadata.SetForceDisableApz(aScrollMetadata.IsApzForceDisabled());
 
     if (scrollOffsetUpdated) {
       APZC_LOG("%p updating scroll offset from %s to %s\n", this,
         ToString(mFrameMetrics.GetScrollOffset()).c_str(),
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -24,16 +24,19 @@
 #include "APZUtils.h"
 #include "Layers.h"                     // for Layer::ScrollDirection
 #include "LayersTypes.h"
 #include "mozilla/gfx/Matrix.h"
 #include "nsIScrollableFrame.h"
 #include "nsRegion.h"
 #include "nsTArray.h"
 #include "PotentialCheckerboardDurationTracker.h"
+#if defined(MOZ_ANDROID_APZ)
+#include "OverScroller.h"
+#endif // defined(MOZ_ANDROID_APZ)
 
 #include "base/message_loop.h"
 
 namespace mozilla {
 
 namespace ipc {
 
 class SharedMemoryBasic;
@@ -44,17 +47,21 @@ namespace layers {
 
 class AsyncDragMetrics;
 struct ScrollableLayerGuid;
 class CompositorBridgeParent;
 class GestureEventListener;
 class PCompositorBridgeParent;
 struct AsyncTransform;
 class AsyncPanZoomAnimation;
+#if defined(MOZ_ANDROID_APZ)
+class FlingOverScrollerAnimation;
+#else
 class FlingAnimation;
+#endif
 class InputBlockState;
 class TouchBlockState;
 class PanGestureBlockState;
 class OverscrollHandoffChain;
 class StateChangeNotificationBlocker;
 class CheckerboardEvent;
 
 /**
@@ -860,17 +867,21 @@ public:
    * unused, residual velocity.
    * |aHandoffState.mIsHandoff| should be true iff. the fling was handed off
    * from a previous APZC, and determines whether acceleration is applied
    * to the fling.
    */
   bool AttemptFling(FlingHandoffState& aHandoffState);
 
 private:
+#if defined(MOZ_ANDROID_APZ)
+  friend class FlingOverScrollerAnimation;
+#else
   friend class FlingAnimation;
+#endif
   friend class OverscrollAnimation;
   friend class SmoothScrollAnimation;
   friend class WheelScrollAnimation;
 
   // The initial velocity of the most recent fling.
   ParentLayerPoint mLastFlingVelocity;
   // The time at which the most recent fling started.
   TimeStamp mLastFlingTime;
@@ -1168,14 +1179,17 @@ private:
 
   // Find a snap point near |aDestination| that we should snap to.
   // Returns the snap point if one was found, or an empty Maybe otherwise.
   // |aUnit| affects the snapping behaviour (see ScrollSnapUtils::
   // GetSnapPointForDestination). It should generally be determined by the
   // type of event that's triggering the scroll.
   Maybe<CSSPoint> FindSnapPointNear(const CSSPoint& aDestination,
                                     nsIScrollableFrame::ScrollUnit aUnit);
+#if defined(MOZ_ANDROID_APZ)
+  widget::sdk::OverScroller::GlobalRef mOverScroller;
+#endif // defined(MOZ_ANDROID_APZ)
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_layers_PanZoomController_h
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/FlingOverScrollerAnimation.cpp
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et 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 "AsyncPanZoomController.h"
+#include "FlingOverScrollerAnimation.h"
+#include "GeneratedJNIWrappers.h"
+#include "gfxPrefs.h"
+#include "OverscrollHandoffState.h"
+#include "OverScroller.h"
+
+namespace mozilla {
+namespace layers {
+
+// Value used in boundary detection. Due to round off error,
+// assume getting within 5 pixels of the boundary is close enough.
+static const float BOUNDS_EPSILON = 5.0f;
+// Maximum number of times the animator can calculate the same offset
+// before the animation is aborted. This is due to a bug in the Android
+// OverScroller class where under certain conditions the OverScroller
+// will overflow some internal value and begin scrolling beyond the bounds
+// of the page. Since we are clamping the results from the OverScroller,
+// if the offset does not change over the past 30 frames, we assume the
+// OverScroller has overflowed.
+static const int32_t MAX_OVERSCROLL_COUNT = 30;
+
+FlingOverScrollerAnimation::FlingOverScrollerAnimation(AsyncPanZoomController& aApzc,
+                                                       widget::sdk::OverScroller::GlobalRef aOverScroller,
+                                                       const RefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
+                                                       const RefPtr<const AsyncPanZoomController>& aScrolledApzc)
+  : mApzc(aApzc)
+  , mOverScroller(aOverScroller)
+  , mOverscrollHandoffChain(aOverscrollHandoffChain)
+  , mScrolledApzc(aScrolledApzc)
+  , mSentBounceX(false)
+  , mSentBounceY(false)
+  , mOverScrollCount(0)
+{
+  MOZ_ASSERT(mOverscrollHandoffChain);
+  MOZ_ASSERT(mOverScroller);
+
+  // Drop any velocity on axes where we don't have room to scroll anyways
+  // (in this APZC, or an APZC further in the handoff chain).
+  // This ensures that we don't take the 'overscroll' path in Sample()
+  // on account of one axis which can't scroll having a velocity.
+  if (!mOverscrollHandoffChain->CanScrollInDirection(&mApzc, Layer::HORIZONTAL)) {
+    ReentrantMonitorAutoEnter lock(mApzc.mMonitor);
+    mApzc.mX.SetVelocity(0);
+  }
+  if (!mOverscrollHandoffChain->CanScrollInDirection(&mApzc, Layer::VERTICAL)) {
+    ReentrantMonitorAutoEnter lock(mApzc.mMonitor);
+    mApzc.mY.SetVelocity(0);
+  }
+
+  ParentLayerPoint velocity = mApzc.GetVelocityVector();
+  float length = velocity.Length();
+  if (length > 0.0f) {
+    mFlingDirection = velocity / length;
+  }
+
+  mStartOffset.x = mPreviousOffset.x = mApzc.mX.GetOrigin().value;
+  mStartOffset.y = mPreviousOffset.y = mApzc.mY.GetOrigin().value;
+  mOverScroller->Fling((int32_t)mStartOffset.x, (int32_t)mStartOffset.y,
+                       // Android needs the velocity in pixels per second and it is in pixels per ms.
+                       (int32_t)(velocity.x * 1000.0f), (int32_t)(velocity.y * 1000.0f),
+                       (int32_t)mApzc.mX.GetPageStart().value, (int32_t)(mApzc.mX.GetPageEnd() - mApzc.mX.GetCompositionLength()).value,
+                       (int32_t)mApzc.mY.GetPageStart().value, (int32_t)(mApzc.mY.GetPageEnd() - mApzc.mY.GetCompositionLength()).value,
+                       0, 0);
+}
+
+/**
+ * Advances a fling by an interpolated amount based on the Android OverScroller.
+ * This should be called whenever sampling the content transform for this
+ * frame. Returns true if the fling animation should be advanced by one frame,
+ * or false if there is no fling or the fling has ended.
+ */
+bool
+FlingOverScrollerAnimation::DoSample(FrameMetrics& aFrameMetrics,
+                                     const TimeDuration& aDelta)
+{
+  bool shouldContinueFling = true;
+  mOverScroller->ComputeScrollOffset(&shouldContinueFling);
+
+  float speed = 0.0f;
+  mOverScroller->GetCurrVelocity(&speed);
+  speed = speed * 0.001f; // convert from pixels/sec to pixels/ms
+
+  // gfxPrefs::APZFlingStoppedThreshold is only used in tests.
+  if (!shouldContinueFling || (speed < gfxPrefs::APZFlingStoppedThreshold())) {
+    if (shouldContinueFling) {
+      // The OverScroller thinks it should continue but the speed is below
+      // the stopping threshold so abort the animation.
+      mOverScroller->AbortAnimation();
+    }
+    mApzc.mX.SetVelocity(0);
+    mApzc.mY.SetVelocity(0);
+    return false;
+  }
+
+  int32_t currentX = 0;
+  int32_t currentY = 0;
+  mOverScroller->GetCurrX(&currentX);
+  mOverScroller->GetCurrY(&currentY);
+  ParentLayerPoint offset((float)currentX, (float)currentY);
+  ParentLayerPoint velocity = mFlingDirection * speed;
+
+  bool hitBoundX = CheckBounds(mApzc.mX, offset.x, &(offset.x));
+  bool hitBoundY = CheckBounds(mApzc.mY, offset.y, &(offset.y));
+
+  if (IsZero(mPreviousOffset - offset)) {
+    mOverScrollCount++;
+  } else {
+    mOverScrollCount = 0;
+  }
+
+  // If the offset hasn't changed in over MAX_OVERSCROLL_COUNT we have overflowed
+  // the OverScroller and it needs to be aborted.
+  if (mOverScrollCount > MAX_OVERSCROLL_COUNT) {
+    velocity.x = velocity.y = 0.0f;
+    mOverScroller->AbortAnimation();
+  }
+
+  mPreviousOffset = offset;
+
+  mApzc.SetVelocityVector(velocity);
+  aFrameMetrics.SetScrollOffset(offset / aFrameMetrics.GetZoom());
+
+  if (hitBoundX || hitBoundY) {
+    ParentLayerPoint bounceVelocity = mFlingDirection * speed;
+
+    if (!mSentBounceX && hitBoundX && fabsf(offset.x - mStartOffset.x) > BOUNDS_EPSILON) {
+      mSentBounceX = true;
+    } else {
+      bounceVelocity.x = 0.0f;
+    }
+
+    if (!mSentBounceY && hitBoundY && fabsf(offset.y - mStartOffset.y) > BOUNDS_EPSILON) {
+      mSentBounceY = true;
+    } else {
+      bounceVelocity.y = 0.0f;
+    }
+    if (!IsZero(bounceVelocity)) {
+      mDeferredTasks.AppendElement(
+          NewRunnableMethod<ParentLayerPoint,
+                            RefPtr<const OverscrollHandoffChain>,
+                            RefPtr<const AsyncPanZoomController>>(&mApzc,
+                                                                  &AsyncPanZoomController::HandleFlingOverscroll,
+                                                                  bounceVelocity,
+                                                                  mOverscrollHandoffChain,
+                                                                  mScrolledApzc));
+    }
+  }
+
+  return true;
+}
+
+bool
+FlingOverScrollerAnimation::CheckBounds(Axis& aAxis, float aValue, float* aClamped)
+{
+  bool result = false;
+  if ((aValue - BOUNDS_EPSILON) <= aAxis.GetPageStart().value) {
+    result = true;
+    if (aClamped) {
+      *aClamped = aAxis.GetPageStart().value;
+    }
+  } else if ((aValue + BOUNDS_EPSILON) >= (aAxis.GetPageEnd() - aAxis.GetCompositionLength()).value) {
+    result = true;
+    if (aClamped) {
+      *aClamped = (aAxis.GetPageEnd() - aAxis.GetCompositionLength()).value;
+    }
+  }
+  return result;
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/FlingOverScrollerAnimation.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et 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_layers_FlingOverScrollerAnimation_h_
+#define mozilla_layers_FlingOverScrollerAnimation_h_
+
+#include "AsyncPanZoomAnimation.h"
+#include "OverScroller.h"
+
+namespace mozilla {
+namespace layers {
+
+class AsyncPanZoomController;
+
+class FlingOverScrollerAnimation: public AsyncPanZoomAnimation {
+public:
+  FlingOverScrollerAnimation(AsyncPanZoomController& aApzc,
+                 widget::sdk::OverScroller::GlobalRef aOverScroller,
+                 const RefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
+                 const RefPtr<const AsyncPanZoomController>& aScrolledApzc);
+  virtual bool DoSample(FrameMetrics& aFrameMetrics,
+                        const TimeDuration& aDelta) override;
+private:
+  // Returns true if value is on or outside of axis bounds.
+  bool CheckBounds(Axis& aAxis, float aValue, float* aClamped);
+
+  AsyncPanZoomController& mApzc;
+  widget::sdk::OverScroller::GlobalRef mOverScroller;
+  RefPtr<const OverscrollHandoffChain> mOverscrollHandoffChain;
+  RefPtr<const AsyncPanZoomController> mScrolledApzc;
+  bool mSentBounceX;
+  bool mSentBounceY;
+  ParentLayerPoint mStartOffset;
+  ParentLayerPoint mPreviousOffset;
+  // Unit vector in the direction of the fling.
+  ParentLayerPoint mFlingDirection;
+  int32_t mOverScrollCount;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_FlingOverScrollerAnimation_h_
--- a/gfx/layers/apz/src/HitTestingTreeNode.cpp
+++ b/gfx/layers/apz/src/HitTestingTreeNode.cpp
@@ -24,16 +24,17 @@ HitTestingTreeNode::HitTestingTreeNode(A
                                        uint64_t aLayersId)
   : mApzc(aApzc)
   , mIsPrimaryApzcHolder(aIsPrimaryHolder)
   , mLayersId(aLayersId)
   , mScrollViewId(FrameMetrics::NULL_SCROLL_ID)
   , mScrollDir(Layer::NONE)
   , mScrollSize(0)
   , mIsScrollbarContainer(false)
+  , mFixedPosTarget(FrameMetrics::NULL_SCROLL_ID)
   , mOverride(EventRegionsOverride::NoOverride)
 {
 if (mIsPrimaryApzcHolder) {
     MOZ_ASSERT(mApzc);
   }
   MOZ_ASSERT(!mApzc || mApzc->GetLayersId() == mLayersId);
 }
 
@@ -122,16 +123,35 @@ HitTestingTreeNode::GetScrollSize() cons
 
 bool
 HitTestingTreeNode::IsScrollbarNode() const
 {
   return mIsScrollbarContainer || (mScrollDir != Layer::NONE);
 }
 
 void
+HitTestingTreeNode::SetFixedPosData(FrameMetrics::ViewID aFixedPosTarget)
+{
+  mFixedPosTarget = aFixedPosTarget;
+}
+
+FrameMetrics::ViewID
+HitTestingTreeNode::GetNearestAncestorFixedPosTargetWithSameLayersId() const
+{
+  for (const HitTestingTreeNode* n = this;
+       n && n->mLayersId == mLayersId;
+       n = n->GetParent()) {
+    if (n->mFixedPosTarget != FrameMetrics::NULL_SCROLL_ID) {
+      return n->mFixedPosTarget;
+    }
+  }
+  return FrameMetrics::NULL_SCROLL_ID;
+}
+
+void
 HitTestingTreeNode::SetPrevSibling(HitTestingTreeNode* aSibling)
 {
   mPrevSibling = aSibling;
   if (aSibling) {
     aSibling->mParent = mParent;
 
     if (aSibling->GetApzc()) {
       AsyncPanZoomController* parent = mParent ? mParent->GetNearestContainingApzc() : nullptr;
@@ -288,21 +308,22 @@ HitTestingTreeNode::GetEventRegionsOverr
 }
 
 void
 HitTestingTreeNode::Dump(const char* aPrefix) const
 {
   if (mPrevSibling) {
     mPrevSibling->Dump(aPrefix);
   }
-  printf_stderr("%sHitTestingTreeNode (%p) APZC (%p) g=(%s) %s%sr=(%s) t=(%s) c=(%s)\n",
+  printf_stderr("%sHitTestingTreeNode (%p) APZC (%p) g=(%s) %s%s%sr=(%s) t=(%s) c=(%s)\n",
     aPrefix, this, mApzc.get(),
     mApzc ? Stringify(mApzc->GetGuid()).c_str() : nsPrintfCString("l=%" PRIu64, mLayersId).get(),
     (mOverride & EventRegionsOverride::ForceDispatchToContent) ? "fdtc " : "",
     (mOverride & EventRegionsOverride::ForceEmptyHitRegion) ? "fehr " : "",
+    (mFixedPosTarget != FrameMetrics::NULL_SCROLL_ID) ? nsPrintfCString("fixed=%" PRIu64 " ", mFixedPosTarget).get() : "",
     Stringify(mEventRegions).c_str(), Stringify(mTransform).c_str(),
     mClipRegion ? Stringify(mClipRegion.ref()).c_str() : "none");
   if (mLastChild) {
     mLastChild->Dump(nsPrintfCString("%s  ", aPrefix).get());
   }
 }
 
 void
--- a/gfx/layers/apz/src/HitTestingTreeNode.h
+++ b/gfx/layers/apz/src/HitTestingTreeNode.h
@@ -95,16 +95,21 @@ public:
   void SetScrollbarData(FrameMetrics::ViewID aScrollViewId,
                         Layer::ScrollDirection aDir,
                         int32_t aScrollSize,
                         bool aIsScrollContainer);
   bool MatchesScrollDragMetrics(const AsyncDragMetrics& aDragMetrics) const;
   int32_t GetScrollSize() const;
   bool IsScrollbarNode() const;
 
+  /* Fixed pos info */
+
+  void SetFixedPosData(FrameMetrics::ViewID aFixedPosTarget);
+  FrameMetrics::ViewID GetNearestAncestorFixedPosTargetWithSameLayersId() const;
+
   /* Convert aPoint into the LayerPixel space for the layer corresponding to
    * this node. */
   Maybe<LayerPoint> Untransform(const ParentLayerPoint& aPoint) const;
   /* Assuming aPoint is inside the clip region for this node, check which of the
    * event region spaces it falls inside. */
   HitTestResult HitTest(const ParentLayerPoint& aPoint) const;
   /* Returns the mOverride flag. */
   EventRegionsOverride GetEventRegionsOverride() const;
@@ -124,16 +129,18 @@ private:
 
   uint64_t mLayersId;
 
   FrameMetrics::ViewID mScrollViewId;
   Layer::ScrollDirection mScrollDir;
   int32_t mScrollSize;
   bool mIsScrollbarContainer;
 
+  FrameMetrics::ViewID mFixedPosTarget;
+
   /* Let {L,M} be the {layer, scrollable metrics} pair that this node
    * corresponds to in the layer tree. mEventRegions contains the event regions
    * from L, in the case where event-regions are enabled. If event-regions are
    * disabled, it will contain the visible region of L, which we use as an
    * approximation to the hit region for the purposes of obscuring other layers.
    * This value is in L's LayerPixels.
    */
   EventRegions mEventRegions;
--- a/gfx/layers/apz/test/gtest/TestHitTesting.cpp
+++ b/gfx/layers/apz/test/gtest/TestHitTesting.cpp
@@ -555,17 +555,17 @@ TEST_F(APZHitTestingTester, HitTestingRe
   // Importantly, give the layer a layer clip which leaks outside of the
   // subframe's composition bounds.
   ScrollMetadata rootMetadata = BuildScrollMetadata(
       FrameMetrics::START_SCROLL_ID, CSSRect(0,0,200,200),
       ParentLayerRect(0,0,200,200));
   ScrollMetadata subframeMetadata = BuildScrollMetadata(
       FrameMetrics::START_SCROLL_ID + 1, CSSRect(0,0,200,200),
       ParentLayerRect(0,0,200,100));
-  subframeMetadata.SetClipRect(Some(ParentLayerIntRect(0,0,200,100)));
+  subframeMetadata.SetScrollClip(Some(LayerClip(ParentLayerIntRect(0,0,200,100))));
   layers[2]->SetScrollMetadata({subframeMetadata, rootMetadata});
   layers[2]->SetClipRect(Some(ParentLayerIntRect(0,0,200,200)));
   SetEventRegionsBasedOnBottommostMetrics(layers[2]);
 
   // Build the hit testing tree.
   ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
   manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
 
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html
@@ -0,0 +1,76 @@
+<head>
+  <meta name="viewport" content="width=device-width; initial-scale=1.0">
+  <title>Wheel-scrolling over position:fixed and position:sticky elements, in the top-level document as well as iframes</title>
+  <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+  <script type="application/javascript" src="apz_test_utils.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+  <script type="application/javascript">
+
+var is = window.opener.is;
+var ok = window.opener.ok;
+
+// Scroll the mouse wheel at (dx, dy) relative to |element|.
+function scrollWheelOver(element, dx, dy) {
+  // Move the mouse to the desired wheel location.
+  // Not doing so can result in the wheel events from two consecutive
+  // scrollWheelOver() calls on different elements being incorrectly considered
+  // as part of the same wheel transaction.
+  // We also wait for the mouse move event to be processed before sending the
+  // wheel event, otherwise there is a chance they might get reordered, and
+  // we have the transaction problem again.
+  return synthesizeNativeMouseMoveAndWaitForMoveEvent(element, dx, dy, function() {
+    synthesizeNativeWheelAndWaitForScrollEvent(element, dx, dy, 0, -10, driveTest);
+  });
+}
+
+function* runTest() {
+  var iframeWin = document.getElementById('iframe').contentWindow;
+
+  // scroll over the middle of the iframe's position:sticky element, check
+  // that it scrolls the iframe
+  var scrollPos = iframeWin.scrollY;
+  yield scrollWheelOver(iframeWin.document.body, 50, 150);
+  ok(iframeWin.scrollY > scrollPos, "iframe scrolled after wheeling over the position:sticky element");
+
+  // same, but using the iframe's position:fixed element
+  scrollPos = iframeWin.scrollY;
+  yield scrollWheelOver(iframeWin.document.body, 250, 150);
+  ok(iframeWin.scrollY > scrollPos, "iframe scrolled after wheeling over the position:fixed element");
+
+  // same, but using the top-level window's position:sticky element
+  scrollPos = window.scrollY;
+  yield scrollWheelOver(document.body, 50, 150);
+  ok(window.scrollY > scrollPos, "top-level document scrolled after wheeling over the position:sticky element");
+
+  // same, but using the top-level window's position:fixed element
+  scrollPos = window.scrollY;
+  yield scrollWheelOver(document.body, 250, 150);
+  ok(window.scrollY > scrollPos, "top-level document scrolled after wheeling over the position:fixed element");
+}
+
+var gTestContinuation = null;
+function driveTest() {
+  if (!gTestContinuation) {
+    gTestContinuation = runTest();
+  }
+  var ret = gTestContinuation.next();
+  if (ret.done) {
+    window.opener.testDone();
+  } else {
+    is(ret.value, true, "Test continuation chunk was successful");
+  }
+}
+
+window.onload = function() {
+  waitForAllPaints(function() {
+    flushApzRepaints(driveTest);
+  });
+}
+  </script>
+</head>
+<body style="height:5000px; margin:0">
+  <div style="position:sticky; width: 100px; height: 300px; top: 0; background-color:red">sticky</div>
+  <div style="position:fixed; width: 100px; height: 300px; top: 0; left: 200px; background-color: green">fixed</div>
+  <iframe id='iframe' width="300" height="400" src="data:text/html,<body style='height:5000px; margin:0'><div style='position:sticky; width:100px; height:300px; top: 0; background-color:red'>sticky</div><div style='position:fixed; right:0; top: 0; width:100px; height:300px; background-color:green'>fixed</div>"></iframe>
+</body>
+</head>
--- a/gfx/layers/apz/test/mochitest/mochitest.ini
+++ b/gfx/layers/apz/test/mochitest/mochitest.ini
@@ -8,16 +8,17 @@ support-files =
   helper_iframe2.html
   helper_subframe_style.css
   helper_basic_pan.html
   helper_div_pan.html
   helper_iframe_pan.html
   helper_scrollto_tap.html
   helper_tap.html
   helper_long_tap.html
+  helper_scroll_on_position_fixed.html
 tags = apz
 [test_bug982141.html]
 [test_bug1151663.html]
 [test_wheel_scroll.html]
 skip-if = (os == 'android') || (os == 'b2g') || (buildapp == 'mulet') # wheel events not supported on mobile; see bug 1164274 for mulet
 [test_wheel_transactions.html]
 skip-if = (os == 'android') || (os == 'b2g') || (buildapp == 'mulet') # wheel events not supported on mobile; see bug 1164274 for mulet
 [test_bug1151667.html]
@@ -34,8 +35,10 @@ skip-if = (os == 'android') || (os == 'b
 skip-if = (os == 'android') || (os == 'b2g') || (buildapp == 'mulet') # wheel events not supported on mobile; see bug 1164274 for mulet
 [test_scroll_subframe_scrollbar.html]
 skip-if = (os == 'android') || (os == 'b2g') || (buildapp == 'mulet') # wheel events not supported on mobile; see bug 1164274 for mulet
 [test_frame_reconstruction.html]
 [test_tap.html]
 # Windows touch injection doesn't work in automation, but this test can be run locally on a windows touch device.
 # On OS X we don't support touch events at all.
 skip-if = (toolkit == 'windows') || (toolkit == 'cocoa')
+[test_scroll_window.html]
+skip-if = (toolkit == 'android') # wheel events not supported on mobile
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_scroll_window.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Various scrolling tests that spawn in a new window</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+// this page just serially loads each one of the following test helper pages in
+// a new window and waits for it to call testDone()
+var tests = [
+  {'file': 'helper_scroll_on_position_fixed.html', 'prefs': [
+        // turn off smooth scrolling so that we don't have to wait for
+        // APZ animations to finish before sampling the scroll offset
+        ['general.smoothScroll', false],
+        // ensure that any mouse movement will trigger a new wheel transaction,
+        // because in this test we move the mouse a bunch and want to recalculate
+        // the target APZC after each such movement.
+        ['mousewheel.transaction.ignoremovedelay', 0],
+        ['mousewheel.transaction.timeout', 0]]}
+];
+
+var testIndex = -1;
+var w = null;
+
+function testDone() {
+  var test = tests[testIndex];
+  if (w) {
+    if (!!test.prefs) {
+      // We pushed some prefs for this test, pop them, and re-invoke
+      // testDone() after that's been processed
+      SpecialPowers.popPrefEnv(function() {
+        w.close();
+        w = null;
+        testDone();
+      });
+      return;
+    }
+
+    w.close();
+  }
+
+  testIndex++;
+  if (testIndex >= tests.length) {
+    SimpleTest.finish();
+    return;
+  }
+
+  test = tests[testIndex];
+  if (!!test.prefs) {
+    // Got some prefs for this subtest, push them
+    SpecialPowers.pushPrefEnv({"set": test.prefs}, function() {
+      w = window.open(test.file, "_blank");
+    });
+  } else {
+    w = window.open(test.file, "_blank");
+  }
+}
+
+window.onload = function() {
+  if (!SpecialPowers.getDOMWindowUtils(window).asyncPanZoomEnabled) {
+    ok(true, "APZ is not enabled, this test is not relevant, sorry!\n");
+    SimpleTest.finish();
+    return;
+  }
+  testDone();
+};
+
+  </script>
+</head>
+<body>
+</body>
+</html>
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -482,19 +482,19 @@ APZCCallbackHelper::DispatchSynthesizedM
              aMsg == eMouseUp || aMsg == eMouseLongTap);
 
   WidgetMouseEvent event(true, aMsg, aWidget,
                          WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
   event.mRefPoint = LayoutDeviceIntPoint(aRefPoint.x, aRefPoint.y);
   event.mTime = aTime;
   event.button = WidgetMouseEvent::eLeftButton;
   event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
-  event.ignoreRootScrollFrame = true;
+  event.mIgnoreRootScrollFrame = true;
   if (aMsg != eMouseMove) {
-    event.clickCount = 1;
+    event.mClickCount = 1;
   }
   event.mModifiers = aModifiers;
 
   return DispatchWidgetEvent(event);
 }
 
 bool
 APZCCallbackHelper::DispatchMouseEvent(const nsCOMPtr<nsIPresShell>& aPresShell,
--- a/gfx/layers/client/SingleTiledContentClient.cpp
+++ b/gfx/layers/client/SingleTiledContentClient.cpp
@@ -187,17 +187,17 @@ ClientSingleTiledLayerBuffer::PaintThebe
   if (dtOnWhite) {
     dt = gfx::Factory::CreateDualDrawTarget(dt, dtOnWhite);
     dtOnWhite = nullptr;
   }
 
   {
     RefPtr<gfxContext> ctx = gfxContext::ForDrawTarget(dt);
     if (!ctx) {
-      gfxDevCrash(LogReason::InvalidContext) << "SingleTiledContextClient context problem " << gfx::hexa(dt);
+      gfxDevCrash(gfx::LogReason::InvalidContext) << "SingleTiledContextClient context problem " << gfx::hexa(dt);
       return;
     }
     ctx->SetMatrix(ctx->CurrentMatrix().Translate(-mTilingOrigin.x, -mTilingOrigin.y));
 
     aCallback(mPaintedLayer, ctx, paintRegion, paintRegion, DrawRegionClip::DRAW, nsIntRegion(), aCallbackData);
   }
 
   // Mark the area we just drew into the back buffer as invalid in the front buffer as they're
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -210,16 +210,30 @@ TransformClipRect(Layer* aLayer,
   MOZ_ASSERT(aTransform.Is2D());
   const Maybe<ParentLayerIntRect>& clipRect = aLayer->AsLayerComposite()->GetShadowClipRect();
   if (clipRect) {
     ParentLayerIntRect transformed = TransformBy(aTransform, *clipRect);
     aLayer->AsLayerComposite()->SetShadowClipRect(Some(transformed));
   }
 }
 
+// Similar to TransformFixedClip(), but only transforms the fixed part of the
+// clip.
+static void
+TransformFixedClip(Layer* aLayer,
+                   const ParentLayerToParentLayerMatrix4x4& aTransform,
+                   AsyncCompositionManager::ClipParts& aClipParts)
+{
+  MOZ_ASSERT(aTransform.Is2D());
+  if (aClipParts.mFixedClip) {
+    *aClipParts.mFixedClip = TransformBy(aTransform, *aClipParts.mFixedClip);
+    aLayer->AsLayerComposite()->SetShadowClipRect(aClipParts.Intersect());
+  }
+}
+
 /**
  * Set the given transform as the shadow transform on the layer, assuming
  * that the given transform already has the pre- and post-scales applied.
  * That is, this function cancels out the pre- and post-scales from aTransform
  * before setting it as the shadow transform on the layer, so that when
  * the layer's effective transform is computed, the pre- and post-scales will
  * only be applied once.
  */
@@ -235,40 +249,49 @@ SetShadowTransform(Layer* aLayer, LayerT
                        1.0f / aLayer->GetPostYScale(),
                        1);
   aLayer->AsLayerComposite()->SetShadowBaseTransform(aTransform.ToUnknownMatrix());
 }
 
 static void
 TranslateShadowLayer(Layer* aLayer,
                      const gfxPoint& aTranslation,
-                     bool aAdjustClipRect)
+                     bool aAdjustClipRect,
+                     AsyncCompositionManager::ClipPartsCache* aClipPartsCache)
 {
   // This layer might also be a scrollable layer and have an async transform.
   // To make sure we don't clobber that, we start with the shadow transform.
   // (i.e. GetLocalTransform() instead of GetTransform()).
   // Note that the shadow transform is reset on every frame of composition so
   // we don't have to worry about the adjustments compounding over successive
   // frames.
   LayerToParentLayerMatrix4x4 layerTransform = aLayer->GetLocalTransformTyped();
 
   // Apply the translation to the layer transform.
   layerTransform.PostTranslate(aTranslation.x, aTranslation.y, 0);
 
   SetShadowTransform(aLayer, layerTransform);
   aLayer->AsLayerComposite()->SetShadowTransformSetByAnimation(false);
 
   if (aAdjustClipRect) {
-    TransformClipRect(aLayer,
-        ParentLayerToParentLayerMatrix4x4::Translation(aTranslation.x, aTranslation.y, 0));
+    auto transform = ParentLayerToParentLayerMatrix4x4::Translation(aTranslation.x, aTranslation.y, 0);
+    // If we're passed a clip parts cache, only transform the fixed part of
+    // the clip.
+    if (aClipPartsCache) {
+      auto iter = aClipPartsCache->find(aLayer);
+      MOZ_ASSERT(iter != aClipPartsCache->end());
+      TransformFixedClip(aLayer, transform, iter->second);
+    } else {
+      TransformClipRect(aLayer, transform);
+    }
 
     // If a fixed- or sticky-position layer has a mask layer, that mask should
     // move along with the layer, so apply the translation to the mask layer too.
     if (Layer* maskLayer = aLayer->GetMaskLayer()) {
-      TranslateShadowLayer(maskLayer, aTranslation, false);
+      TranslateShadowLayer(maskLayer, aTranslation, false, aClipPartsCache);
     }
   }
 }
 
 static void
 AccumulateLayerTransforms(Layer* aLayer,
                           Layer* aAncestor,
                           Matrix4x4& aMatrix)
@@ -377,51 +400,57 @@ AsyncTransformShouldBeUnapplied(Layer* a
     // don't intend to match.
     if (current && current.AsRefLayer() != nullptr) {
       break;
     }
   }
   return false;
 }
 
+// If |aLayer| is fixed or sticky, returns the scroll id of the scroll frame
+// that it's fixed or sticky to. Otherwise, returns Nothing().
+static Maybe<FrameMetrics::ViewID>
+IsFixedOrSticky(Layer* aLayer)
+{
+  bool isRootOfFixedSubtree = aLayer->GetIsFixedPosition() &&
+    !aLayer->GetParent()->GetIsFixedPosition();
+  if (isRootOfFixedSubtree) {
+    return Some(aLayer->GetFixedPositionScrollContainerId());
+  }
+  if (aLayer->GetIsStickyPosition()) {
+    return Some(aLayer->GetStickyScrollContainerId());
+  }
+  return Nothing();
+}
+
 void
 AsyncCompositionManager::AlignFixedAndStickyLayers(Layer* aLayer,
                                                    Layer* aTransformedSubtreeRoot,
                                                    FrameMetrics::ViewID aTransformScrollId,
                                                    const LayerToParentLayerMatrix4x4& aPreviousTransformForRoot,
                                                    const LayerToParentLayerMatrix4x4& aCurrentTransformForRoot,
                                                    const ScreenMargin& aFixedLayerMargins,
-                                                   bool aTransformAffectsLayerClip)
+                                                   ClipPartsCache* aClipPartsCache)
 {
-  FrameMetrics::ViewID fixedTo;  // the scroll id of the scroll frame we are fixed/sticky to
-  bool isRootOfFixedSubtree = aLayer->GetIsFixedPosition() &&
-    !aLayer->GetParent()->GetIsFixedPosition();
-  if (isRootOfFixedSubtree) {
-    fixedTo = aLayer->GetFixedPositionScrollContainerId();
-  }
-  bool isSticky = aLayer->GetIsStickyPosition();
-  if (isSticky) {
-    fixedTo = aLayer->GetStickyScrollContainerId();
-  }
   bool needsAsyncTransformUnapplied = false;
-  if (isRootOfFixedSubtree || isSticky) {
+  if (Maybe<FrameMetrics::ViewID> fixedTo = IsFixedOrSticky(aLayer)) {
     needsAsyncTransformUnapplied = AsyncTransformShouldBeUnapplied(aLayer,
-        fixedTo, aTransformedSubtreeRoot, aTransformScrollId);
+        *fixedTo, aTransformedSubtreeRoot, aTransformScrollId);
   }
 
   // We want to process all the fixed and sticky descendants of
   // aTransformedSubtreeRoot. Once we do encounter such a descendant, we don't
   // need to recurse any deeper because the adjustment to the fixed or sticky
   // layer will apply to its subtree.
   if (!needsAsyncTransformUnapplied) {
     for (Layer* child = aLayer->GetFirstChild(); child; child = child->GetNextSibling()) {
       AlignFixedAndStickyLayers(child, aTransformedSubtreeRoot, aTransformScrollId,
                                 aPreviousTransformForRoot,
                                 aCurrentTransformForRoot, aFixedLayerMargins,
-                                true /* descendants' clip rects are always affected */);
+                                aClipPartsCache);
     }
     return;
   }
 
   // Insert a translation so that the position of the anchor point is the same
   // before and after the change to the transform of aTransformedSubtreeRoot.
 
   // Accumulate the transforms between this layer and the subtree root layer.
@@ -494,21 +523,18 @@ AsyncCompositionManager::AlignFixedAndSt
                     IntervalOverlap(translation.x, stickyInner.x, stickyInner.XMost());
   }
 
   // Finally, apply the translation to the layer transform. Note that in cases
   // where the async transform on |aTransformedSubtreeRoot| affects this layer's
   // clip rect, we need to apply the same translation to said clip rect, so
   // that the effective transform on the clip rect takes it back to where it was
   // originally, had there been no async scroll.
-  // Also, some layers want async scrolling to move their clip rect
-  // (IsClipFixed() = false), so we don't make a compensating adjustment for
-  // those.
-  bool adjustClipRect = aTransformAffectsLayerClip && aLayer->IsClipFixed();
-  TranslateShadowLayer(aLayer, ThebesPoint(translation.ToUnknownPoint()), adjustClipRect);
+  TranslateShadowLayer(aLayer, ThebesPoint(translation.ToUnknownPoint()),
+      true, aClipPartsCache);
 }
 
 static void
 SampleValue(float aPortion, Animation& aAnimation, StyleAnimationValue& aStart,
             StyleAnimationValue& aEnd, Animatable* aValue, Layer* aLayer)
 {
   StyleAnimationValue interpolatedValue;
   NS_ASSERTION(aStart.GetUnit() == aEnd.GetUnit() ||
@@ -763,76 +789,103 @@ MoveScrollbarForLayerMargin(Layer* aRoot
               aNode->GetScrollbarTargetContainerId() == aRootScrollId);
     });
   if (scrollbar) {
     // Shift the horizontal scrollbar down into the new space exposed by the
     // dynamic toolbar hiding. Technically we should also scale the vertical
     // scrollbar a bit to expand into the new space but it's not as noticeable
     // and it would add a lot more complexity, so we're going with the "it's not
     // worth it" justification.
-    TranslateShadowLayer(scrollbar, gfxPoint(0, -aFixedLayerMargins.bottom), true);
+    TranslateShadowLayer(scrollbar, gfxPoint(0, -aFixedLayerMargins.bottom), true, nullptr);
     if (scrollbar->GetParent()) {
       // The layer that has the HORIZONTAL direction sits inside another
       // ContainerLayer. This ContainerLayer also has a clip rect that causes
       // the scrollbar to get clipped. We need to expand that clip rect to
       // prevent that from happening. This is kind of ugly in that we're
       // assuming a particular layer tree structure but short of adding more
       // flags to the layer there doesn't appear to be a good way to do this.
       ExpandRootClipRect(scrollbar->GetParent(), aFixedLayerMargins);
     }
   }
 }
 #endif
 
 bool
 AsyncCompositionManager::ApplyAsyncContentTransformToTree(Layer *aLayer,
                                                           bool* aOutFoundRoot,
-                                                          Maybe<ParentLayerIntRect>& aClipDeferredToParent)
+                                                          Maybe<ParentLayerIntRect>& aClipDeferredToParent,
+                                                          ClipPartsCache& aClipPartsCache)
 {
   Maybe<ParentLayerIntRect> clipDeferredFromChildren;
   bool appliedTransform = false;
   for (Layer* child = aLayer->GetFirstChild();
       child; child = child->GetNextSibling()) {
     appliedTransform |=
       ApplyAsyncContentTransformToTree(child, aOutFoundRoot,
-          clipDeferredFromChildren);
+          clipDeferredFromChildren, aClipPartsCache);
   }
 
   LayerToParentLayerMatrix4x4 oldTransform = aLayer->GetTransformTyped() *
       AsyncTransformMatrix();
 
   AsyncTransformComponentMatrix combinedAsyncTransform;
   bool hasAsyncTransform = false;
   ScreenMargin fixedLayerMargins;
 
-  // Each layer has multiple clips. Its local clip, which must move with async
-  // transforms, and its scrollframe clips, which are the clips between each
-  // scrollframe and its ancestor scrollframe. Scrollframe clips include the
-  // composition bounds and any other clips induced by layout.
-  //
-  // The final clip for the layer is the intersection of these clips.
-  Maybe<ParentLayerIntRect> asyncClip = aLayer->GetClipRect();
+  // Each layer has multiple clips:
+  //  - Its local clip, which is fixed to the layer contents, i.e. it moves
+  //    with those async transforms which the layer contents move with.
+  //  - Its scrolled clip, which moves with all async transforms.
+  //  - For each ScrollMetadata on the layer, a scroll clip. This includes
+  //    the composition bounds and any other clips induced by layout. This
+  //    moves with async transforms from ScrollMetadatas above it.
+  // In this function, these clips are combined into two shadow clip parts:
+  //  - The fixed clip, which consists of the local clip only, initially
+  //    transformed by all async transforms.
+  //  - The scrolled clip, which consists of the other clips, transformed by
+  //    the appropriate transforms.
+  // These two parts are kept separate for now, because for fixed layers, we
+  // need to adjust the fixed clip (to cancel out some async transforms).
+  // The parts are kept in a cache which is cleared at the beginning of every
+  // composite.
+  // The final shadow clip for the layer is the intersection of the (possibly
+  // adjusted) fixed clip and the scrolled clip.
+  ClipParts& clipParts = aClipPartsCache[aLayer];
+  clipParts.mFixedClip = aLayer->GetClipRect();
+  clipParts.mScrolledClip = aLayer->GetScrolledClipRect();
 
   // If we are a perspective transform ContainerLayer, apply the clip deferred
   // from our child (if there is any) before we iterate over our frame metrics,
   // because this clip is subject to all async transforms of this layer.
-  asyncClip = IntersectMaybeRects(asyncClip, clipDeferredFromChildren);
+  // Since this clip came from the a scroll clip on the child, it becomes part
+  // of our scrolled clip.
+  clipParts.mScrolledClip = IntersectMaybeRects(
+      clipDeferredFromChildren, clipParts.mScrolledClip);
 
   // The transform of a mask layer is relative to the masked layer's parent
   // layer. So whenever we apply an async transform to a layer, we need to
   // apply that same transform to the layer's own mask layer.
   // A layer can also have "ancestor" mask layers for any rounded clips from
   // 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;
 
+  // The layer's scrolled clip can have an ancestor mask layer as well,
+  // which is moved by all async scrolls on this layer.
+  if (const Maybe<LayerClip>& scrolledClip = aLayer->GetScrolledClip()) {
+    if (scrolledClip->GetMaskLayerIndex()) {
+      ancestorMaskLayers.AppendElement(
+          aLayer->GetAncestorMaskLayerAt(*scrolledClip->GetMaskLayerIndex()));
+    }
+  }
+
   for (uint32_t i = 0; i < aLayer->GetScrollMetadataCount(); i++) {
     AsyncPanZoomController* controller = aLayer->GetAsyncPanZoomController(i);
     if (!controller) {
       continue;
     }
 
     hasAsyncTransform = true;
 
@@ -891,24 +944,31 @@ AsyncCompositionManager::ApplyAsyncConte
       }
     }
 #else
     // Non-Android platforms still care about this flag being cleared after
     // the first call to TransformShadowTree().
     mIsFirstPaint = false;
 #endif
 
-    // Transform the current local clip by this APZC's async transform. If we're
+    // Transform the current local clips by this APZC's async transform. If we're
     // using containerful scrolling, then the clip is not part of the scrolled
     // frame and should not be transformed.
-    if (asyncClip && !scrollMetadata.UsesContainerScrolling()) {
+    if (!scrollMetadata.UsesContainerScrolling()) {
       MOZ_ASSERT(asyncTransform.Is2D());
-      asyncClip = Some(TransformBy(asyncTransform, *asyncClip));
+      if (clipParts.mFixedClip) {
+        clipParts.mFixedClip = Some(TransformBy(asyncTransform, *clipParts.mFixedClip));
+      }
+      if (clipParts.mScrolledClip) {
+        clipParts.mScrolledClip = Some(TransformBy(asyncTransform, *clipParts.mScrolledClip));
+      }
     }
-    aLayer->AsLayerComposite()->SetShadowClipRect(asyncClip);
+    // Note: we don't set the layer's shadow clip rect property yet;
+    // AlignFixedAndStickyLayers will use the clip parts from the clip parts
+    // cache.
 
     combinedAsyncTransform *= asyncTransform;
 
     // For the purpose of aligning fixed and sticky layers, we disregard
     // the overscroll transform as well as any OMTA transform when computing the
     // 'aCurrentTransformForRoot' parameter. This ensures that the overscroll
     // and OMTA transforms are not unapplied, and therefore that the visual
     // effects apply to fixed and sticky layers. We do this by using
@@ -918,63 +978,69 @@ AsyncCompositionManager::ApplyAsyncConte
         aLayer->GetTransformTyped()
       * CompleteAsyncTransform(
           AdjustForClip(asyncTransformWithoutOverscroll, aLayer));
 
     // Since fixed/sticky layers are relative to their nearest scrolling ancestor,
     // we use the ViewID from the bottommost scrollable metrics here.
     AlignFixedAndStickyLayers(aLayer, aLayer, metrics.GetScrollId(), oldTransform,
                               transformWithoutOverscrollOrOmta, fixedLayerMargins,
-                              asyncClip.isSome());
-
-    // AlignFixedAndStickyLayers may have changed the clip rect, so we have to
-    // read it from the layer again.
-    asyncClip = aLayer->AsLayerComposite()->GetShadowClipRect();
+                              &aClipPartsCache);
 
-    // 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 (scrollMetadata.HasClipRect()) {
-      ParentLayerIntRect clip = scrollMetadata.ClipRect();
+    // Combine the scrolled portion of the local clip with the ancestor
+    // scroll clip. This is not included in the async transform above, since
+    // the ancestor clip should not move with this APZC.
+    if (scrollMetadata.HasScrollClip()) {
+      ParentLayerIntRect clip = scrollMetadata.ScrollClip().GetClipRect();
       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.
         // (A child with multiple FrameMetrics would mean that there's *another*
         // scrollable element between the one with the CSS perspective and the
         // transformed element. But you'd have to use preserve-3d on the inner
         // scrollable element in order to have the perspective apply to the
         // transformed child, and preserve-3d is not supported on scrollable
         // elements, so this case can't occur.)
         MOZ_ASSERT(!aClipDeferredToParent);
         aClipDeferredToParent = Some(clip);
       } else {
-        asyncClip = IntersectMaybeRects(Some(clip), asyncClip);
+        clipParts.mScrolledClip = IntersectMaybeRects(Some(clip), clipParts.mScrolledClip);
       }
     }
 
     // Do the same for the ancestor mask layers: ancestorMaskLayers contains
     // 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 (scrollMetadata.GetMaskLayerIndex()) {
-      size_t maskLayerIndex = scrollMetadata.GetMaskLayerIndex().value();
-      Layer* ancestorMaskLayer = aLayer->GetAncestorMaskLayerAt(maskLayerIndex);
-      ancestorMaskLayers.AppendElement(ancestorMaskLayer);
+    if (scrollMetadata.HasScrollClip()) {
+      const LayerClip& scrollClip = scrollMetadata.ScrollClip();
+      if (scrollClip.GetMaskLayerIndex()) {
+        size_t maskLayerIndex = scrollClip.GetMaskLayerIndex().value();
+        Layer* ancestorMaskLayer = aLayer->GetAncestorMaskLayerAt(maskLayerIndex);
+        ancestorMaskLayers.AppendElement(ancestorMaskLayer);
+      }
     }
   }
 
-  if (hasAsyncTransform || clipDeferredFromChildren) {
-    aLayer->AsLayerComposite()->SetShadowClipRect(asyncClip);
+  bool clipChanged = (hasAsyncTransform || clipDeferredFromChildren ||
+                      aLayer->GetScrolledClipRect());
+  if (clipChanged) {
+    // Intersect the two clip parts and apply them to the layer.
+    // During ApplyAsyncContentTransformTree on an ancestor layer,
+    // AlignFixedAndStickyLayers may overwrite this with a new clip it
+    // computes from the clip parts, but if that doesn't happen, this
+    // is the layer's final clip rect.
+    aLayer->AsLayerComposite()->SetShadowClipRect(clipParts.Intersect());
   }
 
   if (hasAsyncTransform) {
     // Apply the APZ transform on top of GetLocalTransform() here (rather than
     // GetTransform()) in case the OMTA code in SampleAnimations already set a
     // shadow transform; in that case we want to apply ours on top of that one
     // rather than clobber it.
     SetShadowTransform(aLayer,
@@ -1352,17 +1418,17 @@ AsyncCompositionManager::TransformScroll
       metrics.GetCompositionBounds().height;
   }
   oldTransform.PreScale(underZoomScale.width, underZoomScale.height, 1);
 
   // Make sure fixed position layers don't move away from their anchor points
   // when we're asynchronously panning or zooming
   AlignFixedAndStickyLayers(aLayer, aLayer, metrics.GetScrollId(), oldTransform,
                             aLayer->GetLocalTransformTyped(),
-                            fixedLayerMargins, false);
+                            fixedLayerMargins, nullptr);
 
   ExpandRootClipRect(aLayer, fixedLayerMargins);
 }
 
 void
 AsyncCompositionManager::GetFrameUniformity(FrameUniformityData* aOutData)
 {
   MOZ_ASSERT(CompositorBridgeParent::IsInCompositorThread());
@@ -1382,30 +1448,37 @@ AsyncCompositionManager::TransformShadow
   }
 
   // First, compute and set the shadow transforms from OMT animations.
   // NB: we must sample animations *before* sampling pan/zoom
   // transforms.
   bool wantNextFrame = SampleAnimations(root, aCurrentFrame);
 
   if (!(aSkip & TransformsToSkip::APZ)) {
+    // Maps layers to their ClipParts during ApplyAsyncContentTransformToTree.
+    // The parts are not stored individually on the layer, but during
+    // AlignFixedAndStickyLayers we need access to the individual parts for
+    // descendant layers.
+    ClipPartsCache clipPartsCache;
+
     // FIXME/bug 775437: unify this interface with the ~native-fennec
     // derived code
     //
     // Attempt to apply an async content transform to any layer that has
     // an async pan zoom controller (which means that it is rendered
     // async using Gecko). If this fails, fall back to transforming the
     // primary scrollable layer.  "Failing" here means that we don't
     // find a frame that is async scrollable.  Note that the fallback
     // code also includes Fennec which is rendered async.  Fennec uses
     // its own platform-specific async rendering that is done partially
     // in Gecko and partially in Java.
     bool foundRoot = false;
     Maybe<ParentLayerIntRect> clipDeferredFromChildren;
-    if (ApplyAsyncContentTransformToTree(root, &foundRoot, clipDeferredFromChildren)) {
+    if (ApplyAsyncContentTransformToTree(root, &foundRoot, clipDeferredFromChildren,
+                                         clipPartsCache)) {
 #if defined(MOZ_ANDROID_APZ)
       MOZ_ASSERT(foundRoot);
       if (foundRoot && mFixedLayerMargins != ScreenMargin()) {
         MoveScrollbarForLayerMargin(root, mRootScrollableId, mFixedLayerMargins);
       }
 #endif
     } else {
       AutoTArray<Layer*,1> scrollableLayers;
--- a/gfx/layers/composite/AsyncCompositionManager.h
+++ b/gfx/layers/composite/AsyncCompositionManager.h
@@ -114,27 +114,44 @@ public:
   // Returns true if the next composition will be the first for a
   // particular document.
   bool IsFirstPaint() { return mIsFirstPaint; }
 
   // GetFrameUniformity will return the frame uniformity for each layer attached to an APZ
   // from the recorded data in RecordShadowTransform
   void GetFrameUniformity(FrameUniformityData* aFrameUniformityData);
 
+  // Stores the clip rect of a layer in two parts: a fixed part and a scrolled
+  // part. When a layer is fixed, the clip needs to be adjusted to account for
+  // async transforms. Only the fixed part needs to be adjusted, so we need
+  // to store the two parts separately.
+  struct ClipParts {
+    Maybe<ParentLayerIntRect> mFixedClip;
+    Maybe<ParentLayerIntRect> mScrolledClip;
+
+    Maybe<ParentLayerIntRect> Intersect() const {
+      return IntersectMaybeRects(mFixedClip, mScrolledClip);
+    }
+  };
+
+  typedef std::map<Layer*, ClipParts> ClipPartsCache;
 private:
   void TransformScrollableLayer(Layer* aLayer);
   // Return true if an AsyncPanZoomController content transform was
   // applied for |aLayer|. |*aOutFoundRoot| is set to true on Android only, if
   // one of the metrics on one of the layers was determined to be the "root"
   // and its state was synced to the Java front-end. |aOutFoundRoot| must be
   // non-null. As the function recurses over the layer tree, a layer may
   // populate |aClipDeferredToParent| a clip rect it wants to set on its parent.
+  // |aClipPartsCache| is used to cache components of clips on descendant
+  // layers that may be needed while processing ancestor layers.
   bool ApplyAsyncContentTransformToTree(Layer* aLayer,
                                         bool* aOutFoundRoot,
-                                        Maybe<ParentLayerIntRect>& aClipDeferredToParent);
+                                        Maybe<ParentLayerIntRect>& aClipDeferredToParent,
+                                        ClipPartsCache& aClipPartsCache);
   /**
    * Update the shadow transform for aLayer assuming that is a scrollbar,
    * so that it stays in sync with the content that is being scrolled by APZ.
    */
   void ApplyAsyncTransformToScrollbar(Layer* aLayer);
 
   void SetFirstPaintViewport(const LayerIntPoint& aOffset,
                              const CSSToLayerScale& aZoom,
@@ -169,23 +186,25 @@ private:
    * the layer's sticky scroll ranges.
    * This function will also adjust layers so that the given content document
    * fixed position margins will be respected during asynchronous panning and
    * zooming.
    * aTransformAffectsLayerClip indicates whether the transform on
    * aTransformedSubtreeRoot affects aLayer's clip rects, so we know
    * whether we need to perform a corresponding unadjustment to keep
    * the clip rect fixed.
+   * aClipPartsCache optionally maps layers to separate fixed and scrolled
+   * clips, so we can only adjust the fixed portion.
    */
   void AlignFixedAndStickyLayers(Layer* aLayer, Layer* aTransformedSubtreeRoot,
                                  FrameMetrics::ViewID aTransformScrollId,
                                  const LayerToParentLayerMatrix4x4& aPreviousTransformForRoot,
                                  const LayerToParentLayerMatrix4x4& aCurrentTransformForRoot,
                                  const ScreenMargin& aFixedLayerMargins,
-                                 bool aTransformAffectsLayerClip);
+                                 ClipPartsCache* aClipPartsCache);
 
   /**
    * DRAWING PHASE ONLY
    *
    * For reach RefLayer in our layer tree, look up its referent and connect it
    * to the layer tree, if found.
    * aHasRemoteContent - indicates if the layer tre