Merge inbound to m-c a=merge
authorWes Kocher <wkocher@mozilla.com>
Thu, 02 Apr 2015 17:45:19 -0700
changeset 267286 a6b7227d8b72dc556fe4dc002f1f133b0a7bbf04
parent 267236 6177eed6d23d84d3dc8447fad42e315e495c6d65 (current diff)
parent 267285 ec5d1e03d2fe5d123cd87da25c0801a3eab68b1c (diff)
child 267287 f4bc044e4d2931a3372a5e0ce2e6f10ff6b98382
push id4830
push userjlund@mozilla.com
push dateMon, 29 Jun 2015 20:18:48 +0000
treeherdermozilla-beta@4c2175bb0420 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to m-c a=merge
browser/components/nsBrowserGlue.js
dom/media/gmp/GMPServiceParent.h
xpcom/threads/nsThread.cpp
xpcom/threads/nsTimerImpl.cpp
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -220,17 +220,16 @@ skip-if = e10s
 [browser_bug561636.js]
 [browser_bug562649.js]
 [browser_bug563588.js]
 [browser_bug565575.js]
 skip-if = e10s
 [browser_bug565667.js]
 skip-if = toolkit != "cocoa"
 [browser_bug567306.js]
-skip-if = e10s # Bug XXX - Needs some massaging to run in e10s
 [browser_bug575561.js]
 [browser_bug575830.js]
 skip-if = e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
 [browser_bug577121.js]
 [browser_bug578534.js]
 [browser_bug579872.js]
 [browser_bug580638.js]
 [browser_bug580956.js]
@@ -457,17 +456,16 @@ skip-if = os == "linux" || e10s # Bug 10
 [browser_urlbarRevert.js]
 skip-if = e10s # Bug 1093941 - ESC reverted the location bar value - Got foobar, expected example.com
 [browser_urlbarSearchSingleWordNotification.js]
 [browser_urlbarStop.js]
 [browser_urlbarTrimURLs.js]
 [browser_urlbar_search_healthreport.js]
 [browser_utilityOverlay.js]
 [browser_visibleFindSelection.js]
-skip-if = e10s # Bug 921935 - focusmanager issues with e10s (test calls waitForFocus)
 [browser_visibleLabel.js]
 [browser_visibleTabs.js]
 [browser_visibleTabs_bookmarkAllPages.js]
 skip-if = true # Bug 1005420 - fails intermittently. also with e10s enabled: bizarre problem with hidden tab having _mouseenter called, via _setPositionalAttributes, and tab not being found resulting in 'candidate is undefined'
 [browser_visibleTabs_bookmarkAllTabs.js]
 [browser_visibleTabs_contextMenu.js]
 [browser_visibleTabs_tabPreview.js]
 skip-if = (os == "win" && !debug) || e10s # Bug 1007418
--- a/browser/base/content/test/general/browser_bug567306.js
+++ b/browser/base/content/test/general/browser_bug567306.js
@@ -2,53 +2,49 @@
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 const {Ci: interfaces, Cc: classes} = Components;
 
 let Clipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
 let HasFindClipboard = Clipboard.supportsFindClipboard();
 
-function test() {
-  waitForExplicitFinish();
+add_task(function* () {
+  let newwindow = yield BrowserTestUtils.openNewBrowserWindow();
 
-  whenNewWindowLoaded(undefined, function (win) {
-    whenDelayedStartupFinished(win, function () {
-      let selectedBrowser = win.gBrowser.selectedBrowser;
-      selectedBrowser.addEventListener("pageshow", function pageshowListener() {
-        selectedBrowser.removeEventListener("pageshow", pageshowListener, true);
-        ok(true, "pageshow listener called: " + win.content.location);
-        waitForFocus(function () {
-          onFocus(win);
-        }, selectedBrowser.contentWindow);
-      }, true);
-      selectedBrowser.loadURI("data:text/html,<h1 id='h1'>Select Me</h1>");
-    });
+  let selectedBrowser = newwindow.gBrowser.selectedBrowser;
+  yield new Promise((resolve, reject) => {
+    selectedBrowser.addEventListener("pageshow", function pageshowListener() {
+      if (selectedBrowser.currentURI.spec == "about:blank")
+        return;
+
+      selectedBrowser.removeEventListener("pageshow", pageshowListener, true);
+      ok(true, "pageshow listener called: " + newwindow.content.location);
+      resolve();
+    }, true);
+    selectedBrowser.loadURI("data:text/html,<h1 id='h1'>Select Me</h1>");
   });
-}
 
-function selectText(win) {
-  let elt = win.document.getElementById("h1");
-  let selection = win.getSelection();
-  let range = win.document.createRange();
-  range.setStart(elt, 0);
-  range.setEnd(elt, 1);
-  selection.removeAllRanges();
-  selection.addRange(range);
-}
+  yield SimpleTest.promiseFocus(newwindow);
+
+  ok(!newwindow.gFindBarInitialized, "find bar is not yet initialized");
+  let findBar = newwindow.gFindBar;
 
-function onFocus(win) {
-  ok(!win.gFindBarInitialized, "find bar is not yet initialized");
-  let findBar = win.gFindBar;
-  selectText(win.content);
+  yield ContentTask.spawn(selectedBrowser, { }, function* () {
+    let elt = content.document.getElementById("h1");
+    let selection = content.getSelection();
+    let range = content.document.createRange();
+    range.setStart(elt, 0);
+    range.setEnd(elt, 1);
+    selection.removeAllRanges();
+    selection.addRange(range);
+  });
 
-  findBar.onFindCommand().then(onInitialized.bind(null, findBar, win));
-}
+  yield findBar.onFindCommand();
 
-function onInitialized(findBar, win) {
   // When the OS supports the Find Clipboard (OSX), the find field value is
   // persisted across Fx sessions, thus not useful to test.
   if (!HasFindClipboard)
     is(findBar._findField.value, "Select Me", "Findbar is initialized with selection");
   findBar.close();
-  win.close();
-  finish();
-}
+  yield promiseWindowClosed(newwindow);
+});
+
--- a/browser/base/content/test/general/browser_visibleFindSelection.js
+++ b/browser/base/content/test/general/browser_visibleFindSelection.js
@@ -1,39 +1,42 @@
-
-function test() {
-  waitForExplicitFinish();
+add_task(function*() {
+  const childContent = "<div style='position: absolute; left: 2200px; background: green; width: 200px; height: 200px;'>" +
+                       "div</div><div  style='position: absolute; left: 0px; background: red; width: 200px; height: 200px;'>" +
+                       "<span id='s'>div</span></div>";
 
-  let tab = gBrowser.addTab();
-  gBrowser.selectedTab = tab;
-  tab.linkedBrowser.addEventListener("load", function(aEvent) {
-    tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+  let tab = gBrowser.selectedTab = gBrowser.addTab();
 
-    ok(true, "Load listener called");
-    waitForFocus(onFocus, content);
-  }, true);
+  yield promiseTabLoadEvent(tab, "data:text/html," + escape(childContent));
+  yield SimpleTest.promiseFocus(gBrowser.selectedBrowser.contentWindowAsCPOW);
 
-  content.location = "data:text/html,<div style='position: absolute; left: 2200px; background: green; width: 200px; height: 200px;'>div</div><div style='position: absolute; left: 0px; background: red; width: 200px; height: 200px;'><span id='s'>div</span></div>";
-}
+  let findBarOpenPromise = promiseWaitForEvent(gBrowser, "findbaropen");
+  EventUtils.synthesizeKey("f", { accelKey: true });
+  yield findBarOpenPromise;
 
-function onFocus() {
-  EventUtils.synthesizeKey("f", { accelKey: true });
   ok(gFindBarInitialized, "find bar is now initialized");
 
+  // Finds the div in the green box.
+  let scrollPromise = promiseWaitForEvent(gBrowser, "scroll");
   EventUtils.synthesizeKey("d", {});
   EventUtils.synthesizeKey("i", {});
   EventUtils.synthesizeKey("v", {});
-  // finds the div in the green box
+  yield scrollPromise;
 
+  // Finds the div in the red box.
+  scrollPromise = promiseWaitForEvent(gBrowser, "scroll");
   EventUtils.synthesizeKey("g", { accelKey: true });
-  // finds the div in the red box
+  yield scrollPromise;
 
-  var rect = content.document.getElementById("s").getBoundingClientRect();
-  ok(rect.left >= 0, "scroll should include find result");
+  let scrollLeftPos = yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* (arg) {
+    return content.document.getElementById("s").getBoundingClientRect().left;
+  });
+
+  ok(scrollLeftPos >= 0, "scroll should include find result");
 
   // clear the find bar
   EventUtils.synthesizeKey("a", { accelKey: true });
   EventUtils.synthesizeKey("VK_DELETE", { });
 
   gFindBar.close();
   gBrowser.removeCurrentTab();
-  finish();
-}
+});
+
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -117,23 +117,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
                                   "resource:///modules/ContentSearch.jsm");
 
 #ifdef E10S_TESTING_ONLY
 XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
                                   "resource://gre/modules/UpdateChannel.jsm");
 #endif
 
-
-#if defined(MOZ_UPDATE_CHANNEL) && MOZ_UPDATE_CHANNEL != release
-#define MOZ_DEBUG_UA // Shorthand define for subsequent conditional sections.
-XPCOMUtils.defineLazyModuleGetter(this, "UserAgentOverrides",
-                                  "resource://gre/modules/UserAgentOverrides.jsm");
-#endif
-
 XPCOMUtils.defineLazyGetter(this, "ShellService", function() {
   try {
     return Cc["@mozilla.org/browser/shell-service;1"].
            getService(Ci.nsIShellService);
   }
   catch(ex) {
     return null;
   }
@@ -722,21 +715,16 @@ BrowserGlue.prototype = {
     ReaderParent.init();
 
     SelfSupportBackend.init();
 
 #ifdef NIGHTLY_BUILD
     Services.prefs.addObserver(POLARIS_ENABLED, this, false);
 #endif
 
-#ifdef MOZ_DEBUG_UA
-    UserAgentOverrides.init();
-    DebugUserAgent.init();
-#endif
-
 #ifndef RELEASE_BUILD
     let themeName = gBrowserBundle.GetStringFromName("deveditionTheme.name");
     let vendorShortName = gBrandBundle.GetStringFromName("vendorShortName");
 
     LightweightThemeManager.addBuiltInTheme({
       id: "firefox-devedition@mozilla.org",
       name: themeName,
       headerURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.header.png",
@@ -999,19 +987,16 @@ BrowserGlue.prototype = {
 
     CustomizationTabPreloader.uninit();
     WebappManager.uninit();
 #ifdef NIGHTLY_BUILD
     if (Services.prefs.getBoolPref("dom.identity.enabled")) {
       SignInToWebsiteUX.uninit();
     }
 #endif
-#ifdef MOZ_DEBUG_UA
-    UserAgentOverrides.uninit();
-#endif
     webrtcUI.uninit();
     FormValidationHandler.uninit();
     AddonWatcher.uninit();
   },
 
   _initServiceDiscovery: function () {
     if (!Services.prefs.getBoolPref("browser.casting.enabled")) {
       return;
@@ -2970,41 +2955,8 @@ this.NSGetFactory = XPCOMUtils.generateN
 
 // Listen for UITour messages.
 // Do it here instead of the UITour module itself so that the UITour module is lazy loaded
 // when the first message is received.
 let globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
 globalMM.addMessageListener("UITour:onPageEvent", function(aMessage) {
   UITour.onPageEvent(aMessage, aMessage.data);
 });
-
-#ifdef MOZ_DEBUG_UA
-// Modify the user agent string for specific domains
-// to route debug information through their logging.
-var DebugUserAgent = {
-  DEBUG_UA: null,
-  DOMAINS: [
-    'youtube.com',
-    'www.youtube.com',
-    'youtube-nocookie.com',
-    'www.youtube-nocookie.com',
-  ],
-
-  init: function() {
-    // Only run if the MediaSource Extension API is available.
-    if (!Services.prefs.getBoolPref("media.mediasource.enabled")) {
-      return;
-    }
-    // Install our override filter.
-    UserAgentOverrides.addComplexOverride(this.onRequest.bind(this));
-    let ua = Cc["@mozilla.org/network/protocol;1?name=http"]
-                .getService(Ci.nsIHttpProtocolHandler).userAgent;
-    this.DEBUG_UA = ua + " Build/" + Services.appinfo.appBuildID;
-  },
-
-  onRequest: function(channel, defaultUA) {
-    if (this.DOMAINS.indexOf(channel.URI.host) != -1) {
-      return this.DEBUG_UA;
-    }
-    return null;
-  },
-};
-#endif // MOZ_DEBUG_UA
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -1616,93 +1616,16 @@ struct GetParentObject<T, false>
 {
   static JSObject* Get(JSContext* cx, JS::Handle<JSObject*> obj)
   {
     MOZ_CRASH();
     return nullptr;
   }
 };
 
-MOZ_ALWAYS_INLINE
-JSObject* GetJSObjectFromCallback(CallbackObject* callback)
-{
-  return callback->Callback();
-}
-
-MOZ_ALWAYS_INLINE
-JSObject* GetJSObjectFromCallback(void* noncallback)
-{
-  return nullptr;
-}
-
-template<typename T>
-static inline bool
-WrapCallThisValue(JSContext* cx, const T& p, JS::MutableHandle<JS::Value> rval)
-{
-  // Callbacks are nsISupports, so WrapNativeParent will just happily wrap them
-  // up as an nsISupports XPCWrappedNative... which is not at all what we want.
-  // So we need to special-case them.
-  JS::Rooted<JSObject*> obj(cx, GetJSObjectFromCallback(p));
-  if (!obj) {
-    // WrapNativeParent is a bit of a Swiss army knife that will
-    // wrap anything for us.
-    obj = WrapNativeParent(cx, p);
-    if (!obj) {
-      return false;
-    }
-  }
-
-  // But all that won't necessarily put things in the compartment of cx.
-  if (!JS_WrapObject(cx, &obj)) {
-    return false;
-  }
-
-  rval.setObject(*obj);
-  return true;
-}
-
-/*
- * This specialized function simply wraps a JS::Rooted<> since
- * WrapNativeParent() is not applicable for JS objects.
- */
-template<>
-inline bool
-WrapCallThisValue<JS::Rooted<JSObject*>>(JSContext* cx,
-                                         const JS::Rooted<JSObject*>& p,
-                                         JS::MutableHandle<JS::Value> rval)
-{
-  JS::Rooted<JSObject*> obj(cx, p);
-
-  if (!JS_WrapObject(cx, &obj)) {
-    return false;
-  }
-
-  rval.setObject(*obj);
-  return true;
-}
-
-/*
- * This specialization is for wrapping any JS value.
- */
-template<>
-inline bool
-WrapCallThisValue<JS::Rooted<JS::Value>>(JSContext* cx,
-                                         const JS::Rooted<JS::Value>& v,
-                                         JS::MutableHandle<JS::Value> rval)
-{
-  JS::Rooted<JS::Value> val(cx, v);
-
-  if (!JS_WrapValue(cx, &val)) {
-    return false;
-  }
-
-  rval.set(val);
-  return true;
-}
-
 // Helper for calling GetOrCreateDOMReflector with smart pointers
 // (nsAutoPtr/nsRefPtr/nsCOMPtr) or references.
 template <class T, bool isSmartPtr=IsSmartPtr<T>::value>
 struct GetOrCreateDOMReflectorHelper
 {
   static inline bool GetOrCreate(JSContext* cx, const T& value,
                                  JS::MutableHandle<JS::Value> rval)
   {
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -1141,24 +1141,24 @@ class CGHeaders(CGWrapper):
             bindingHeaders.add(self.getDeclarationFilename(c))
 
         for c in callbackDescriptors:
             bindingHeaders.add(self.getDeclarationFilename(c.interface))
 
         if len(callbacks) != 0:
             # We need CallbackFunction to serve as our parent class
             declareIncludes.add("mozilla/dom/CallbackFunction.h")
-            # And we need BindingUtils.h so we can wrap "this" objects
-            declareIncludes.add("mozilla/dom/BindingUtils.h")
+            # And we need ToJSValue.h so we can wrap "this" objects
+            declareIncludes.add("mozilla/dom/ToJSValue.h")
 
         if len(callbackDescriptors) != 0 or len(jsImplementedDescriptors) != 0:
             # We need CallbackInterface to serve as our parent class
             declareIncludes.add("mozilla/dom/CallbackInterface.h")
-            # And we need BindingUtils.h so we can wrap "this" objects
-            declareIncludes.add("mozilla/dom/BindingUtils.h")
+            # And we need ToJSValue.h so we can wrap "this" objects
+            declareIncludes.add("mozilla/dom/ToJSValue.h")
 
         # Also need to include the headers for ancestors of
         # JS-implemented interfaces.
         for jsImplemented in jsImplementedDescriptors:
             jsParent = jsImplemented.interface.parent
             if jsParent:
                 parentDesc = jsImplemented.getDescriptor(jsParent.identifier.name)
                 declareIncludes.add(parentDesc.jsImplParentHeader)
@@ -13820,17 +13820,17 @@ class CGCallback(CGClass):
             }
             """,
             errorReturn=errorReturn)
 
         bodyWithThis = fill(
             """
             $*{setupCall}
             JS::Rooted<JS::Value> thisValJS(s.GetContext());
-            if (!WrapCallThisValue(s.GetContext(), thisVal, &thisValJS)) {
+            if (!ToJSValue(s.GetContext(), thisVal, &thisValJS)) {
               aRv.Throw(NS_ERROR_FAILURE);
               return${errorReturn};
             }
             return ${methodName}(${callArgs});
             """,
             setupCall=setupCall,
             errorReturn=errorReturn,
             methodName=method.name,
--- a/dom/bindings/ToJSValue.cpp
+++ b/dom/bindings/ToJSValue.cpp
@@ -33,29 +33,16 @@ ToJSValue(JSContext* aCx, const nsAStrin
   if (sharedBuffer) {
     NS_ADDREF(sharedBuffer);
   }
 
   return true;
 }
 
 
-namespace tojsvalue_detail {
-
-bool
-ISupportsToJSValue(JSContext* aCx,
-                   nsISupports* aArgument,
-                   JS::MutableHandle<JS::Value> aValue)
-{
-  nsresult rv = nsContentUtils::WrapNative(aCx, aArgument, aValue);
-  return NS_SUCCEEDED(rv);
-}
-
-} // namespace tojsvalue_detail
-
 bool
 ToJSValue(JSContext* aCx,
           nsresult aArgument,
           JS::MutableHandle<JS::Value> aValue)
 {
   nsRefPtr<Exception> exception = CreateException(aCx, aArgument);
   return ToJSValue(aCx, exception, aValue);
 }
--- a/dom/bindings/ToJSValue.h
+++ b/dom/bindings/ToJSValue.h
@@ -23,20 +23,26 @@ namespace dom {
 // JSContext.
 
 // Accept strings.
 MOZ_WARN_UNUSED_RESULT bool
 ToJSValue(JSContext* aCx,
           const nsAString& aArgument,
           JS::MutableHandle<JS::Value> aValue);
 
-// Accept booleans.
-MOZ_WARN_UNUSED_RESULT inline bool
+// Accept booleans.  But be careful here: if we just have a function that takes
+// a boolean argument, then any pointer that doesn't match one of our other
+// signatures/templates will get treated as a boolean, which is clearly not
+// desirable.  So make this a template that only gets used if the argument type
+// is actually boolean
+template<typename T>
+MOZ_WARN_UNUSED_RESULT
+typename EnableIf<IsSame<T, bool>::value, bool>::Type
 ToJSValue(JSContext* aCx,
-          bool aArgument,
+          T aArgument,
           JS::MutableHandle<JS::Value> aValue)
 {
   // Make sure we're called in a compartment
   MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx));
 
   aValue.setBoolean(aArgument);
   return true;
 }
@@ -169,40 +175,33 @@ ToJSValue(JSContext* aCx,
   JSObject* obj = aArgument.Create(aCx);
   if (!obj) {
     return false;
   }
   aValue.setObject(*obj);
   return true;
 }
 
-// We don't want to include nsContentUtils here, so use a helper
-// function for the nsISupports case.
-namespace tojsvalue_detail {
-bool
-ISupportsToJSValue(JSContext* aCx,
-                   nsISupports* aArgument,
-                   JS::MutableHandle<JS::Value> aValue);
-} // namespace tojsvalue_detail
-
 // Accept objects that inherit from nsISupports but not nsWrapperCache (e.g.
 // nsIDOMFile).
 template <class T>
 MOZ_WARN_UNUSED_RESULT
 typename EnableIf<!IsBaseOf<nsWrapperCache, T>::value &&
                   !IsBaseOf<CallbackObject, T>::value &&
                   IsBaseOf<nsISupports, T>::value, bool>::Type
 ToJSValue(JSContext* aCx,
           T& aArgument,
           JS::MutableHandle<JS::Value> aValue)
 {
   // Make sure we're called in a compartment
   MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx));
 
-  return tojsvalue_detail::ISupportsToJSValue(aCx, &aArgument, aValue);
+  qsObjectHelper helper(ToSupports(&aArgument), nullptr);
+  JS::Rooted<JSObject*> scope(aCx, JS::CurrentGlobalOrNull(aCx));
+  return XPCOMObjectToJsval(aCx, scope, helper, nullptr, true, aValue);
 }
 
 // Accept nsRefPtr/nsCOMPtr
 template <typename T>
 MOZ_WARN_UNUSED_RESULT bool
 ToJSValue(JSContext* aCx,
           const nsCOMPtr<T>& aArgument,
           JS::MutableHandle<JS::Value> aValue)
@@ -252,16 +251,26 @@ ToJSValue(JSContext* aCx, const JS::Heap
 MOZ_WARN_UNUSED_RESULT inline bool
 ToJSValue(JSContext* aCx, const JS::Rooted<JS::Value>& aArgument,
           JS::MutableHandle<JS::Value> aValue)
 {
   aValue.set(aArgument);
   return MaybeWrapValue(aCx, aValue);
 }
 
+// Accept existing rooted JS objects (which may not be same-compartment with
+// us).
+MOZ_WARN_UNUSED_RESULT inline bool
+ToJSValue(JSContext* aCx, const JS::Rooted<JSObject*>& aArgument,
+          JS::MutableHandle<JS::Value> aValue)
+{
+  aValue.setObjectOrNull(aArgument);
+  return MaybeWrapObjectOrNullValue(aCx, aValue);
+}
+
 // Accept nsresult, for use in rejections, and create an XPCOM
 // exception object representing that nsresult.
 MOZ_WARN_UNUSED_RESULT bool
 ToJSValue(JSContext* aCx,
           nsresult aArgument,
           JS::MutableHandle<JS::Value> aValue);
 
 // Accept ErrorResult, for use in rejections, and create an exception
--- a/dom/media/gmp/GMPServiceParent.h
+++ b/dom/media/gmp/GMPServiceParent.h
@@ -192,17 +192,17 @@ public:
                                 const nsString& aTopLevelOrigin,
                                 const bool& aInPrivateBrowsing,
                                 nsCString* aID) override;
   static bool RecvGetGMPPluginVersionForAPI(const nsCString& aAPI,
                                             nsTArray<nsCString>&& aTags,
                                             bool* aHasPlugin,
                                             nsCString* aVersion);
 
-  virtual void ActorDestroy(ActorDestroyReason aWhy);
+  virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
   static PGMPServiceParent* Create(Transport* aTransport, ProcessId aOtherPid);
 
 private:
   nsRefPtr<GeckoMediaPluginServiceParent> mService;
 };
 
 } // namespace gmp
--- a/dom/media/webrtc/MediaEngineGonkVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineGonkVideoSource.cpp
@@ -769,17 +769,17 @@ MediaEngineGonkVideoSource::RotateImage(
     // Align to 16 bytes boundary
     int32_t uvStride = ((yStride / 2) + 15) & ~0x0F;
 
     libyuv::ConvertToI420(srcPtr, size,
                           dstPtr, yStride,
                           dstPtr + (yStride * dstHeight + (uvStride * dstHeight / 2)), uvStride,
                           dstPtr + (yStride * dstHeight), uvStride,
                           0, 0,
-                          aWidth, aHeight,
+                          graphicBuffer->getStride(), aHeight,
                           aWidth, aHeight,
                           static_cast<libyuv::RotationMode>(mRotation),
                           libyuv::FOURCC_NV21);
     destBuffer->unlock();
 
     layers::GrallocImage::GrallocData data;
 
     data.mPicSize = gfx::IntSize(dstWidth, dstHeight);
@@ -791,17 +791,17 @@ MediaEngineGonkVideoSource::RotateImage(
     layers::PlanarYCbCrImage* videoImage = static_cast<layers::PlanarYCbCrImage*>(image.get());
     uint8_t* dstPtr = videoImage->AllocateAndGetNewBuffer(size);
 
     libyuv::ConvertToI420(srcPtr, size,
                           dstPtr, dstWidth,
                           dstPtr + (dstWidth * dstHeight), half_width,
                           dstPtr + (dstWidth * dstHeight * 5 / 4), half_width,
                           0, 0,
-                          aWidth, aHeight,
+                          graphicBuffer->getStride(), aHeight,
                           aWidth, aHeight,
                           static_cast<libyuv::RotationMode>(mRotation),
                           ConvertPixelFormatToFOURCC(graphicBuffer->getPixelFormat()));
 
     const uint8_t lumaBpp = 8;
     const uint8_t chromaBpp = 4;
 
     layers::PlanarYCbCrData data;
--- a/dom/media/webspeech/synth/nsSpeechTask.cpp
+++ b/dom/media/webspeech/synth/nsSpeechTask.cpp
@@ -113,45 +113,58 @@ nsSpeechTask::nsSpeechTask(float aVolume
   , mText(aText)
   , mCallback(nullptr)
   , mIndirectAudio(false)
 {
 }
 
 nsSpeechTask::~nsSpeechTask()
 {
+  LOG(PR_LOG_DEBUG, ("~nsSpeechTask"));
   if (mStream) {
     if (!mStream->IsDestroyed()) {
       mStream->Destroy();
     }
 
     mStream = nullptr;
   }
+
+  if (mPort) {
+    mPort->Destroy();
+    mPort = nullptr;
+  }
+}
+
+void
+nsSpeechTask::BindStream(ProcessedMediaStream* aStream)
+{
+  mStream = MediaStreamGraph::GetInstance()->CreateSourceStream(nullptr);
+  mPort = aStream->AllocateInputPort(mStream, 0);
 }
 
 NS_IMETHODIMP
 nsSpeechTask::Setup(nsISpeechTaskCallback* aCallback,
                     uint32_t aChannels, uint32_t aRate, uint8_t argc)
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
 
   LOG(PR_LOG_DEBUG, ("nsSpeechTask::Setup"));
 
   mCallback = aCallback;
 
-  if (argc < 2) {
+  if (mIndirectAudio) {
+    if (argc > 0) {
+      NS_WARNING("Audio info arguments in Setup() are ignored for indirect audio services.");
+    }
     return NS_OK;
   }
 
-  if (mIndirectAudio) {
-    NS_WARNING("Audio info arguments in Setup() are ignored for indirect audio services.");
-  }
+  // mStream is set up in BindStream() that should be called before this.
+  MOZ_ASSERT(mStream);
 
-  // XXX: Is there setup overhead here that hurtls latency?
-  mStream = MediaStreamGraph::GetInstance()->CreateSourceStream(nullptr);
   mStream->AddListener(new SynthStreamListener(this));
 
   // XXX: Support more than one channel
   NS_ENSURE_TRUE(aChannels == 1, NS_ERROR_FAILURE);
 
   mChannels = aChannels;
 
   AudioSegment* segment = new AudioSegment();
--- a/dom/media/webspeech/synth/nsSpeechTask.h
+++ b/dom/media/webspeech/synth/nsSpeechTask.h
@@ -40,16 +40,18 @@ public:
   float GetCurrentTime();
 
   uint32_t GetCurrentCharOffset();
 
   void SetSpeechSynthesis(SpeechSynthesis* aSpeechSynthesis);
 
   void SetIndirectAudio(bool aIndirectAudio) { mIndirectAudio = aIndirectAudio; }
 
+  void BindStream(ProcessedMediaStream* aStream);
+
 protected:
   virtual ~nsSpeechTask();
 
   virtual nsresult DispatchStartImpl();
 
   virtual nsresult DispatchEndImpl(float aElapsedTime, uint32_t aCharIndex);
 
   virtual nsresult DispatchPauseImpl(float aElapsedTime, uint32_t aCharIndex);
@@ -73,16 +75,18 @@ protected:
 
 private:
   void End();
 
   void SendAudioImpl(nsRefPtr<mozilla::SharedBuffer>& aSamples, uint32_t aDataLen);
 
   nsRefPtr<SourceMediaStream> mStream;
 
+  nsRefPtr<MediaInputPort> mPort;
+
   nsCOMPtr<nsISpeechTaskCallback> mCallback;
 
   uint32_t mChannels;
 
   nsRefPtr<SpeechSynthesis> mSpeechSynthesis;
 
   bool mIndirectAudio;
 };
--- a/dom/media/webspeech/synth/nsSynthVoiceRegistry.cpp
+++ b/dom/media/webspeech/synth/nsSynthVoiceRegistry.cpp
@@ -129,16 +129,24 @@ nsSynthVoiceRegistry::nsSynthVoiceRegist
 
 nsSynthVoiceRegistry::~nsSynthVoiceRegistry()
 {
   LOG(PR_LOG_DEBUG, ("~nsSynthVoiceRegistry"));
 
   // mSpeechSynthChild's lifecycle is managed by the Content protocol.
   mSpeechSynthChild = nullptr;
 
+  if (mStream) {
+    if (!mStream->IsDestroyed()) {
+     mStream->Destroy();
+   }
+
+   mStream = nullptr;
+  }
+
   mUriVoiceMap.Clear();
 }
 
 nsSynthVoiceRegistry*
 nsSynthVoiceRegistry::GetInstance()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -563,15 +571,20 @@ nsSynthVoiceRegistry::Speak(const nsAStr
 
   SpeechServiceType serviceType;
 
   DebugOnly<nsresult> rv = voice->mService->GetServiceType(&serviceType);
   NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to get speech service type");
 
   if (serviceType == nsISpeechService::SERVICETYPE_INDIRECT_AUDIO) {
     aTask->SetIndirectAudio(true);
+  } else {
+    if (!mStream) {
+      mStream = MediaStreamGraph::GetInstance()->CreateTrackUnionStream(nullptr);
+    }
+    aTask->BindStream(mStream);
   }
 
   voice->mService->Speak(aText, voice->mUri, aRate, aPitch, aTask);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/webspeech/synth/nsSynthVoiceRegistry.h
+++ b/dom/media/webspeech/synth/nsSynthVoiceRegistry.h
@@ -6,16 +6,17 @@
 
 #ifndef mozilla_dom_nsSynthVoiceRegistry_h
 #define mozilla_dom_nsSynthVoiceRegistry_h
 
 #include "nsAutoPtr.h"
 #include "nsISynthVoiceRegistry.h"
 #include "nsRefPtrHashtable.h"
 #include "nsTArray.h"
+#include "MediaStreamGraph.h"
 
 class nsISpeechService;
 
 namespace mozilla {
 namespace dom {
 
 class RemoteVoice;
 class SpeechSynthesisUtterance;
@@ -68,14 +69,16 @@ private:
 
   nsTArray<nsRefPtr<VoiceData> > mVoices;
 
   nsTArray<nsRefPtr<VoiceData> > mDefaultVoices;
 
   nsRefPtrHashtable<nsStringHashKey, VoiceData> mUriVoiceMap;
 
   SpeechSynthesisChild* mSpeechSynthChild;
+
+  nsRefPtr<ProcessedMediaStream> mStream;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif
--- a/dom/plugins/ipc/PluginAsyncSurrogate.cpp
+++ b/dom/plugins/ipc/PluginAsyncSurrogate.cpp
@@ -431,26 +431,30 @@ PluginAsyncSurrogate::SetStreamType(NPSt
     return false;
   }
   return streamListener->SetStreamType(aStreamType);
 }
 
 void
 PluginAsyncSurrogate::OnInstanceCreated(PluginInstanceParent* aInstance)
 {
-  for (uint32_t i = 0, len = mPendingNewStreamCalls.Length(); i < len; ++i) {
-    PendingNewStreamCall& curPendingCall = mPendingNewStreamCalls[i];
-    uint16_t streamType = NP_NORMAL;
-    NPError curError = aInstance->NPP_NewStream(
-                    const_cast<char*>(NullableStringGet(curPendingCall.mType)),
-                    curPendingCall.mStream, curPendingCall.mSeekable,
-                    &streamType);
-    if (curError != NPERR_NO_ERROR) {
-      // If we failed here then the send failed and we need to clean up
-      DestroyAsyncStream(curPendingCall.mStream);
+  if (!mDestroyPending) {
+    // If NPP_Destroy has already been called then these streams have already
+    // been cleaned up on the browser side and are no longer valid.
+    for (uint32_t i = 0, len = mPendingNewStreamCalls.Length(); i < len; ++i) {
+      PendingNewStreamCall& curPendingCall = mPendingNewStreamCalls[i];
+      uint16_t streamType = NP_NORMAL;
+      NPError curError = aInstance->NPP_NewStream(
+                      const_cast<char*>(NullableStringGet(curPendingCall.mType)),
+                      curPendingCall.mStream, curPendingCall.mSeekable,
+                      &streamType);
+      if (curError != NPERR_NO_ERROR) {
+        // If we failed here then the send failed and we need to clean up
+        DestroyAsyncStream(curPendingCall.mStream);
+      }
     }
   }
   mPendingNewStreamCalls.Clear();
   mInstantiated = true;
 }
 
 /**
  * During asynchronous initialization it might be necessary to wait for the
@@ -538,32 +542,32 @@ PluginAsyncSurrogate::AsyncCallArriving(
   if (--mAsyncCallsInFlight == 0) {
     mPluginDestructionGuard.reset(nullptr);
   }
 }
 
 void
 PluginAsyncSurrogate::NotifyAsyncInitFailed()
 {
-  // Clean up any pending NewStream requests
-  for (uint32_t i = 0, len = mPendingNewStreamCalls.Length(); i < len; ++i) {
-    PendingNewStreamCall& curPendingCall = mPendingNewStreamCalls[i];
-    DestroyAsyncStream(curPendingCall.mStream);
+  if (!mDestroyPending) {
+    // Clean up any pending NewStream requests
+    for (uint32_t i = 0, len = mPendingNewStreamCalls.Length(); i < len; ++i) {
+      PendingNewStreamCall& curPendingCall = mPendingNewStreamCalls[i];
+      DestroyAsyncStream(curPendingCall.mStream);
+    }
   }
   mPendingNewStreamCalls.Clear();
 
   nsNPAPIPluginInstance* inst =
     static_cast<nsNPAPIPluginInstance*>(mInstance->ndata);
   if (!inst) {
       return;
   }
   nsPluginInstanceOwner* owner = inst->GetOwner();
-  if (!owner) {
-      return;
-  }
+  MOZ_ASSERT(owner);
   owner->NotifyHostAsyncInitFailed();
 }
 
 // static
 NPObject*
 PluginAsyncSurrogate::ScriptableAllocate(NPP aInstance, NPClass* aClass)
 {
   PLUGIN_LOG_DEBUG_FUNCTION;
--- a/dom/plugins/ipc/PluginInstanceParent.cpp
+++ b/dom/plugins/ipc/PluginInstanceParent.cpp
@@ -1734,20 +1734,27 @@ PluginInstanceParent::RecvAsyncNPP_NewRe
     //     calling NPP_SetWindow!
     mUseSurrogate = false;
 
     mSurrogate->AsyncCallArriving();
     if (aResult == NPERR_NO_ERROR) {
         mSurrogate->SetAcceptingCalls(true);
     }
 
+    // It is possible for a plugin instance to outlive its owner (eg. When a
+    // PluginDestructionGuard was on the stack at the time the owner was being
+    // destroyed). We need to handle that case.
     nsPluginInstanceOwner* owner = GetOwner();
-    // It is possible for a plugin instance to outlive its owner when async
-    // plugin init is turned on, so we need to handle that case.
-    if (aResult != NPERR_NO_ERROR || !owner) {
+    if (!owner) {
+        // We can't do anything at this point, just return. Any pending browser
+        // streams will be cleaned up when the plugin instance is destroyed.
+        return true;
+    }
+
+    if (aResult != NPERR_NO_ERROR) {
         mSurrogate->NotifyAsyncInitFailed();
         return true;
     }
 
     // Now we need to do a bunch of exciting post-NPP_New housekeeping.
     owner->NotifyHostCreateWidget();
 
     MOZ_ASSERT(mSurrogate);
--- a/dom/xul/nsXULContentSink.cpp
+++ b/dom/xul/nsXULContentSink.cpp
@@ -864,18 +864,17 @@ XULContentSinkImpl::OpenScript(const cha
   nsresult rv;
 
   // Look for SRC attribute and look for a LANGUAGE attribute
   nsAutoString src;
   while (*aAttributes) {
       const nsDependentString key(aAttributes[0]);
       if (key.EqualsLiteral("src")) {
           src.Assign(aAttributes[1]);
-      }
-      else if (key.EqualsLiteral("type")) {
+      } else if (key.EqualsLiteral("type")) {
           nsDependentString str(aAttributes[1]);
           nsContentTypeParser parser(str);
           nsAutoString mimeType;
           rv = parser.GetType(mimeType);
           if (NS_FAILED(rv)) {
               if (rv == NS_ERROR_INVALID_ARG) {
                   // Might as well bail out now instead of setting langID to
                   // nsIProgrammingLanguage::UNKNOWN and bailing out later.
@@ -883,119 +882,104 @@ XULContentSinkImpl::OpenScript(const cha
               }
               // We do want the warning here
               NS_ENSURE_SUCCESS(rv, rv);
           }
 
           if (nsContentUtils::IsJavascriptMIMEType(mimeType)) {
               langID = nsIProgrammingLanguage::JAVASCRIPT;
               version = JSVERSION_LATEST;
-          } else {
-              langID = nsIProgrammingLanguage::UNKNOWN;
-          }
 
-          if (langID != nsIProgrammingLanguage::UNKNOWN) {
-              // Get the version string, and ensure the language supports it.
+              // Get the version string, and ensure that JavaScript supports it.
               nsAutoString versionName;
               rv = parser.GetParameter("version", versionName);
 
               if (NS_SUCCEEDED(rv)) {
                   version = nsContentUtils::ParseJavascriptVersion(versionName);
               } else if (rv != NS_ERROR_INVALID_ARG) {
                   return rv;
               }
+          } else {
+              langID = nsIProgrammingLanguage::UNKNOWN;
           }
-      }
-      else if (key.EqualsLiteral("language")) {
+      } else if (key.EqualsLiteral("language")) {
           // Language is deprecated, and the impl in nsScriptLoader ignores the
           // various version strings anyway.  So we make no attempt to support
           // languages other than JS for language=
           nsAutoString lang(aAttributes[1]);
           if (nsContentUtils::IsJavaScriptLanguage(lang)) {
               version = JSVERSION_DEFAULT;
               langID = nsIProgrammingLanguage::JAVASCRIPT;
           }
       }
       aAttributes += 2;
   }
 
-  // Not all script languages have a "sandbox" concept.  At time of
-  // writing, Python is the only other language, and it does not.
-  // For such languages, neither any inline script nor remote script are
-  // safe to execute from untrusted sources.
-  // So for such languages, we only allow script when the document
-  // itself is from chrome.  We then don't bother to check the
-  // "src=" tag - we trust chrome to do the right thing.
-  // (See also similar code in nsScriptLoader.cpp)
   nsCOMPtr<nsIDocument> doc(do_QueryReferent(mDocument));
-  if (langID != nsIProgrammingLanguage::UNKNOWN && 
-      langID != nsIProgrammingLanguage::JAVASCRIPT &&
-      doc && !nsContentUtils::IsChromeDoc(doc)) {
-      langID = nsIProgrammingLanguage::UNKNOWN;
-      NS_WARNING("Non JS language called from non chrome - ignored");
-  }
 
   // Don't process scripts that aren't known
-  if (langID != nsIProgrammingLanguage::UNKNOWN) {
-      nsCOMPtr<nsIScriptGlobalObject> globalObject;
-      if (doc)
-          globalObject = do_QueryInterface(doc->GetWindow());
-      nsRefPtr<nsXULPrototypeScript> script =
-          new nsXULPrototypeScript(aLineNumber, version);
-      if (! script)
-          return NS_ERROR_OUT_OF_MEMORY;
+  if (langID == nsIProgrammingLanguage::UNKNOWN) {
+      return NS_OK;
+  }
 
-      // If there is a SRC attribute...
-      if (! src.IsEmpty()) {
-          // Use the SRC attribute value to load the URL
-          rv = NS_NewURI(getter_AddRefs(script->mSrcURI), src, nullptr, mDocumentURL);
+  nsCOMPtr<nsIScriptGlobalObject> globalObject;
+  if (doc)
+      globalObject = do_QueryInterface(doc->GetWindow());
+  nsRefPtr<nsXULPrototypeScript> script =
+      new nsXULPrototypeScript(aLineNumber, version);
+  if (! script)
+      return NS_ERROR_OUT_OF_MEMORY;
 
-          // Check if this document is allowed to load a script from this source
-          // NOTE: if we ever allow scripts added via the DOM to run, we need to
-          // add a CheckLoadURI call for that as well.
+  // If there is a SRC attribute...
+  if (! src.IsEmpty()) {
+      // Use the SRC attribute value to load the URL
+      rv = NS_NewURI(getter_AddRefs(script->mSrcURI), src, nullptr, mDocumentURL);
+
+      // Check if this document is allowed to load a script from this source
+      // NOTE: if we ever allow scripts added via the DOM to run, we need to
+      // add a CheckLoadURI call for that as well.
+      if (NS_SUCCEEDED(rv)) {
+          if (!mSecMan)
+              mSecMan = do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
           if (NS_SUCCEEDED(rv)) {
-              if (!mSecMan)
-                  mSecMan = do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+              nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocument, &rv);
+
               if (NS_SUCCEEDED(rv)) {
-                  nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocument, &rv);
-
-                  if (NS_SUCCEEDED(rv)) {
-                      rv = mSecMan->
-                          CheckLoadURIWithPrincipal(doc->NodePrincipal(),
-                                                    script->mSrcURI,
-                                                    nsIScriptSecurityManager::ALLOW_CHROME);
-                  }
+                  rv = mSecMan->
+                      CheckLoadURIWithPrincipal(doc->NodePrincipal(),
+                                                script->mSrcURI,
+                                                nsIScriptSecurityManager::ALLOW_CHROME);
               }
           }
-
-          if (NS_FAILED(rv)) {
-              return rv;
-          }
-
-          // Attempt to deserialize an out-of-line script from the FastLoad
-          // file right away.  Otherwise we'll end up reloading the script and
-          // corrupting the FastLoad file trying to serialize it, in the case
-          // where it's already there.
-          script->DeserializeOutOfLine(nullptr, mPrototype);
       }
 
-      nsPrototypeArray* children = nullptr;
-      rv = mContextStack.GetTopChildren(&children);
       if (NS_FAILED(rv)) {
           return rv;
       }
 
-      children->AppendElement(script);
-
-      mConstrainSize = false;
+      // Attempt to deserialize an out-of-line script from the FastLoad
+      // file right away.  Otherwise we'll end up reloading the script and
+      // corrupting the FastLoad file trying to serialize it, in the case
+      // where it's already there.
+      script->DeserializeOutOfLine(nullptr, mPrototype);
+  }
 
-      mContextStack.Push(script, mState);
-      mState = eInScript;
+  nsPrototypeArray* children = nullptr;
+  rv = mContextStack.GetTopChildren(&children);
+  if (NS_FAILED(rv)) {
+      return rv;
   }
 
+  children->AppendElement(script);
+
+  mConstrainSize = false;
+
+  mContextStack.Push(script, mState);
+  mState = eInScript;
+
   return NS_OK;
 }
 
 nsresult
 XULContentSinkImpl::AddAttributes(const char16_t** aAttributes, 
                                   const uint32_t aAttrLen, 
                                   nsXULPrototypeElement* aElement)
 {
@@ -1066,18 +1050,17 @@ XULContentSinkImpl::AddText(const char16
         amount = aLength;
     }
     if (0 == amount) {
       if (mConstrainSize) {
         nsresult rv = FlushText();
         if (NS_OK != rv) {
             return rv;
         }
-      }
-      else {
+      } else {
         mTextSize += aLength;
         mText = (char16_t *) realloc(mText, sizeof(char16_t) * mTextSize);
         if (nullptr == mText) {
             return NS_ERROR_OUT_OF_MEMORY;
         }
       }
     }
     memcpy(&mText[mTextLength],aText + offset, sizeof(char16_t) * amount);
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -1978,20 +1978,21 @@ gfxWindowsPlatform::InitD3D11Devices()
                            // to prevent bug 1092260. IE 11 also uses this flag.
                            D3D11_CREATE_DEVICE_BGRA_SUPPORT,
                            featureLevels.Elements(), featureLevels.Length(),
                            D3D11_SDK_VERSION, byRef(mD3D11Device), nullptr, nullptr);
 
     if (FAILED(hr)) {
       // This should always succeed... in theory.
       gfxCriticalError() << "Failed to initialize WARP D3D11 device!" << hr;
-    } else {
-      mIsWARP = true;
-      reporterWARP.SetSuccessful();
+      return;
     }
+
+    mIsWARP = true;
+    reporterWARP.SetSuccessful();
   }
 
   mD3D11Device->SetExceptionMode(0);
 
   // We create our device for D2D content drawing here. Normally we don't use
   // D2D content drawing when using WARP. However when WARP is forced by
   // default we will let Direct2D use WARP as well.
   if (Factory::SupportsD2D1() && (!useWARP || gfxPrefs::LayersD3D11ForceWARP())) {
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -778,25 +778,25 @@ static const JSClass sUInt64Class = {
   nullptr, nullptr, nullptr, nullptr,
   nullptr, nullptr, nullptr, Int64Base::Finalize
 };
 
 static const JSFunctionSpec sInt64StaticFunctions[] = {
   JS_FN("compare", Int64::Compare, 2, CTYPESFN_FLAGS),
   JS_FN("lo", Int64::Lo, 1, CTYPESFN_FLAGS),
   JS_FN("hi", Int64::Hi, 1, CTYPESFN_FLAGS),
-  JS_FN("join", Int64::Join, 2, CTYPESFN_FLAGS),
+  // "join" is defined specially; see InitInt64Class.
   JS_FS_END
 };
 
 static const JSFunctionSpec sUInt64StaticFunctions[] = {
   JS_FN("compare", UInt64::Compare, 2, CTYPESFN_FLAGS),
   JS_FN("lo", UInt64::Lo, 1, CTYPESFN_FLAGS),
   JS_FN("hi", UInt64::Hi, 1, CTYPESFN_FLAGS),
-  JS_FN("join", UInt64::Join, 2, CTYPESFN_FLAGS),
+  // "join" is defined specially; see InitInt64Class.
   JS_FS_END
 };
 
 static const JSFunctionSpec sInt64Functions[] = {
   JS_FN("toString", Int64::ToString, 0, CTYPESFN_FLAGS),
   JS_FN("toSource", Int64::ToSource, 0, CTYPESFN_FLAGS),
   JS_FS_END
 };
@@ -1097,31 +1097,31 @@ InitInt64Class(JSContext* cx,
   RootedObject prototype(cx, JS_InitClass(cx, parent, js::NullPtr(), clasp, construct,
                                           0, nullptr, fs, nullptr, static_fs));
   if (!prototype)
     return nullptr;
 
   RootedObject ctor(cx, JS_GetConstructor(cx, prototype));
   if (!ctor)
     return nullptr;
-  if (!JS_FreezeObject(cx, ctor))
-    return nullptr;
-
-  // Redefine the 'join' function as an extended native and stash
+
+  // Define the 'join' function as an extended native and stash
   // ctypes.{Int64,UInt64}.prototype in a reserved slot of the new function.
   MOZ_ASSERT(clasp == &sInt64ProtoClass || clasp == &sUInt64ProtoClass);
   JSNative native = (clasp == &sInt64ProtoClass) ? Int64::Join : UInt64::Join;
   JSFunction* fun = js::DefineFunctionWithReserved(cx, ctor, "join", native,
                       2, CTYPESFN_FLAGS);
   if (!fun)
     return nullptr;
 
   js::SetFunctionNativeReserved(fun, SLOT_FN_INT64PROTO,
     OBJECT_TO_JSVAL(prototype));
 
+  if (!JS_FreezeObject(cx, ctor))
+    return nullptr;
   if (!JS_FreezeObject(cx, prototype))
     return nullptr;
 
   return prototype;
 }
 
 static void
 AttachProtos(JSObject* proto, const AutoObjectVector& protos)
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/xdr/debug-lazy.js
@@ -0,0 +1,19 @@
+load(libdir + 'bytecode-cache.js');
+
+// Ensure that if a function is encoded when non-lazy but relazifiable, then
+// decoded, the resulting LazyScript is marked as being non-lazy so that when
+// the debugger tries to delazify things it doesn't get all confused.  We just
+// use findScripts() to trigger debugger delazification; we don't really care
+// about the scripts themselves.
+function checkAfter(ctx) {
+    var dbg = new Debugger(ctx.global);
+    var allScripts = dbg.findScripts();
+    assertEq(allScripts.length == 0, false);
+}
+
+test = `
+  function f() { return true; };
+  f();
+  `
+evalWithCache(test, { assertEqBytecode: true, assertEqResult: true,
+		      checkAfter: checkAfter });
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -2590,17 +2590,17 @@ class PropertyDescriptorOperations
         MOZ_ASSERT(!hasAll(JSPROP_IGNORE_ENUMERATE | JSPROP_ENUMERATE));
         MOZ_ASSERT(!hasAll(JSPROP_IGNORE_PERMANENT | JSPROP_PERMANENT));
         if (isAccessorDescriptor()) {
             MOZ_ASSERT(has(JSPROP_SHARED));
             MOZ_ASSERT(!has(JSPROP_READONLY));
             MOZ_ASSERT(!has(JSPROP_IGNORE_READONLY));
             MOZ_ASSERT(!has(JSPROP_IGNORE_VALUE));
             MOZ_ASSERT(!has(SHADOWABLE));
-            MOZ_ASSERT(desc()->value.isUndefined());
+            MOZ_ASSERT(value().isUndefined());
             MOZ_ASSERT_IF(!has(JSPROP_GETTER), !getter());
             MOZ_ASSERT_IF(!has(JSPROP_SETTER), !setter());
         } else {
             MOZ_ASSERT(!hasAll(JSPROP_IGNORE_READONLY | JSPROP_READONLY));
             MOZ_ASSERT_IF(has(JSPROP_IGNORE_VALUE), value().isUndefined());
         }
         MOZ_ASSERT(getter() != JS_PropertyStub);
         MOZ_ASSERT(setter() != JS_StrictPropertyStub);
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -500,66 +500,77 @@ js::CanonicalizeArrayLengthValue(JSConte
 
     if (d == *newLen)
         return true;
 
     JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
     return false;
 }
 
-/* ES6 20130308 draft 8.4.2.4 ArraySetLength */
+/* ES6 draft rev 34 (2015 Feb 20) 9.4.2.4 ArraySetLength */
 bool
 js::ArraySetLength(JSContext* cx, Handle<ArrayObject*> arr, HandleId id,
                    unsigned attrs, HandleValue value, ObjectOpResult& result)
 {
     MOZ_ASSERT(id == NameToId(cx->names().length));
 
     if (!arr->maybeCopyElementsForWrite(cx))
         return false;
 
-    /* Steps 1-2 are irrelevant in our implementation. */
-
-    /* Steps 3-5. */
+    // Step 1.
     uint32_t newLen;
-    if (!CanonicalizeArrayLengthValue(cx, value, &newLen))
-        return false;
-
-    // Abort if we're being asked to change enumerability or configurability.
-    // (The length property of arrays is non-configurable, so such attempts
-    // must fail.)  This behavior is spread throughout the ArraySetLength spec
-    // algorithm, but we only need check it once as our array implementation
-    // is internally so different from the spec algorithm.  (ES5 and ES6 define
-    // behavior by delegating to the default define-own-property algorithm --
-    // OrdinaryDefineOwnProperty in ES6, the default [[DefineOwnProperty]] in
-    // ES5 -- but we reimplement all the conflict-detection bits ourselves here
-    // so that we can use a customized length representation.)
-    if (!(attrs & JSPROP_PERMANENT) || (attrs & JSPROP_ENUMERATE))
-        return result.fail(JSMSG_CANT_REDEFINE_PROP);
-
-    /* Steps 6-7. */
+    if (attrs & JSPROP_IGNORE_VALUE) {
+        // The spec has us calling OrdinaryDefineOwnProperty if
+        // Desc.[[Value]] is absent, but our implementation is so different that
+        // this is impossible. Instead, set newLen to the current length and
+        // proceed to step 9.
+        newLen = arr->length();
+    } else {
+        // Step 2 is irrelevant in our implementation.
+
+        // Steps 3-7.
+        MOZ_ASSERT_IF(attrs & JSPROP_IGNORE_VALUE, value.isUndefined());
+        if (!CanonicalizeArrayLengthValue(cx, value, &newLen))
+            return false;
+
+        // Step 8 is irrelevant in our implementation.
+    }
+
+    // Steps 9-11.
     bool lengthIsWritable = arr->lengthIsWritable();
 #ifdef DEBUG
     {
         RootedShape lengthShape(cx, arr->lookupPure(id));
         MOZ_ASSERT(lengthShape);
         MOZ_ASSERT(lengthShape->writable() == lengthIsWritable);
     }
 #endif
-
     uint32_t oldLen = arr->length();
 
-    /* Steps 8-9 for arrays with non-writable length. */
+    // Part of steps 1.a, 12.a, and 16: Fail if we're being asked to change
+    // enumerability or configurability, or otherwise break the object
+    // invariants. (ES6 checks these by calling OrdinaryDefineOwnProperty, but
+    // in SM, the array length property is hardly ordinary.)
+    if ((attrs & (JSPROP_PERMANENT | JSPROP_IGNORE_PERMANENT)) == 0 ||
+        (attrs & (JSPROP_ENUMERATE | JSPROP_IGNORE_ENUMERATE)) == JSPROP_ENUMERATE ||
+        (attrs & (JSPROP_GETTER | JSPROP_SETTER)) != 0 ||
+        (!lengthIsWritable && (attrs & (JSPROP_READONLY | JSPROP_IGNORE_READONLY)) == 0))
+    {
+        return result.fail(JSMSG_CANT_REDEFINE_PROP);
+    }
+
+    // Steps 12-13 for arrays with non-writable length.
     if (!lengthIsWritable) {
         if (newLen == oldLen)
             return result.succeed();
 
         return result.fail(JSMSG_CANT_REDEFINE_ARRAY_LENGTH);
     }
 
-    /* Step 8. */
+    // Step 19.
     bool succeeded = true;
     do {
         // The initialized length and capacity of an array only need updating
         // when non-hole elements are added or removed, which doesn't happen
         // when array length stays the same or increases.
         if (newLen >= oldLen)
             break;
 
@@ -611,20 +622,20 @@ js::ArraySetLength(JSContext* cx, Handle
         // |succeeded| to false.  Then we exit the loop, define the length
         // appropriately, and only then throw a TypeError, if necessary.
         uint32_t gap = oldLen - newLen;
         const uint32_t RemoveElementsFastLimit = 1 << 24;
         if (gap < RemoveElementsFastLimit) {
             // If we're removing a relatively small number of elements, just do
             // it exactly by the spec.
             while (newLen < oldLen) {
-                /* Step 15a. */
+                // Step 15a.
                 oldLen--;
 
-                /* Steps 15b-d. */
+                // Steps 15b-d.
                 ObjectOpResult deleteSucceeded;
                 if (!DeleteElement(cx, arr, oldLen, deleteSucceeded))
                     return false;
                 if (!deleteSucceeded) {
                     newLen = oldLen + 1;
                     succeeded = false;
                     break;
                 }
@@ -674,46 +685,48 @@ js::ArraySetLength(JSContext* cx, Handle
                                           ReverseIndexComparator()));
             }
 
             uint32_t index = UINT32_MAX;
             for (uint32_t i = 0; i < count; i++) {
                 MOZ_ASSERT(indexes[i] < index, "indexes should never repeat");
                 index = indexes[i];
 
-                /* Steps 15b-d. */
+                // Steps 15b-d.
                 ObjectOpResult deleteSucceeded;
                 if (!DeleteElement(cx, arr, index, deleteSucceeded))
                     return false;
                 if (!deleteSucceeded) {
                     newLen = index + 1;
                     succeeded = false;
                     break;
                 }
             }
         }
     } while (false);
 
-    /* Steps 12, 16. */
-
-    // Yes, we totally drop a non-stub getter/setter from a defineProperty
-    // API call on the floor here.  Given that getter/setter will go away in
-    // the long run, with accessors replacing them both internally and at the
-    // API level, just run with this.
-    RootedShape lengthShape(cx, arr->lookup(cx, id));
-    if (!NativeObject::changeProperty(cx, arr, lengthShape,
-                                      attrs | JSPROP_PERMANENT | JSPROP_SHARED |
-                                      (lengthShape->attributes() & JSPROP_READONLY),
-                                      array_length_getter, array_length_setter))
-    {
-        return false;
+    // Update array length. Technically we should have been doing this
+    // throughout the loop, in step 19.d.iii.
+    arr->setLength(cx, newLen);
+
+    // Step 20.
+    if (attrs & JSPROP_READONLY) {
+        // Yes, we totally drop a non-stub getter/setter from a defineProperty
+        // API call on the floor here.  Given that getter/setter will go away in
+        // the long run, with accessors replacing them both internally and at the
+        // API level, just run with this.
+        RootedShape lengthShape(cx, arr->lookup(cx, id));
+        if (!NativeObject::changeProperty(cx, arr, lengthShape,
+                                          lengthShape->attributes() | JSPROP_READONLY,
+                                          array_length_getter, array_length_setter))
+        {
+            return false;
+        }
     }
 
-    arr->setLength(cx, newLen);
-
     // All operations past here until the |!succeeded| code must be infallible,
     // so that all element fields remain properly synchronized.
 
     // Trim the initialized length, if needed, to preserve the <= length
     // invariant.  (Capacity was already reduced during element deletion, if
     // necessary.)
     ObjectElements* header = arr->getElementsHeader();
     header->initializedLength = Min(header->initializedLength, newLen);
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -1402,17 +1402,18 @@ JSFunction::createScriptForLazilyInterpr
 
         RootedScript script(cx, lazy->maybeScript());
 
         // Only functions without inner functions or direct eval are
         // re-lazified. Functions with either of those are on the static scope
         // chain of their inner functions, or in the case of eval, possibly
         // eval'd inner functions. This prohibits re-lazification as
         // StaticScopeIter queries isHeavyweight of those functions, which
-        // requires a non-lazy script.
+        // requires a non-lazy script.  Note that if this ever changes,
+        // XDRRelazificationInfo will have to be fixed.
         bool canRelazify = !lazy->numInnerFunctions() && !lazy->hasDirectEval();
 
         if (script) {
             fun->setUnlazifiedScript(script);
             // Remember the lazy script on the compiled script, so it can be
             // stored on the function again in case of re-lazification.
             if (canRelazify)
                 script->setLazyScript(lazy);
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -622,54 +622,18 @@ DefinePropertyOnArray(JSContext* cx, Han
 // ES6 draft rev31 9.4.5.3 [[DefineOwnProperty]]
 static bool
 DefinePropertyOnTypedArray(JSContext* cx, HandleObject obj, HandleId id,
                            Handle<PropertyDescriptor> desc, ObjectOpResult& result)
 {
     MOZ_ASSERT(IsAnyTypedArray(obj));
     // Steps 3.a-c.
     uint64_t index;
-    if (IsTypedArrayIndex(id, &index)) {
-        // These are all substeps of 3.c.
-        // Steps i-vi.
-        // We (wrongly) ignore out of range defines with a value.
-        if (index >= AnyTypedArrayLength(obj))
-            return result.succeed();
-
-        // Step vii.
-        if (desc.isAccessorDescriptor())
-            return result.fail(JSMSG_CANT_REDEFINE_PROP);
-
-        // Step viii.
-        if (desc.hasConfigurable() && desc.configurable())
-            return result.fail(JSMSG_CANT_REDEFINE_PROP);
-
-        // Step ix.
-        if (desc.hasEnumerable() && !desc.enumerable())
-            return result.fail(JSMSG_CANT_REDEFINE_PROP);
-
-        // Step x.
-        if (desc.hasWritable() && !desc.writable())
-            return result.fail(JSMSG_CANT_REDEFINE_PROP);
-
-        // Step xi.
-        if (desc.hasValue()) {
-            double d;
-            if (!ToNumber(cx, desc.value(), &d))
-                return false;
-
-            if (obj->is<TypedArrayObject>())
-                TypedArrayObject::setElement(obj->as<TypedArrayObject>(), index, d);
-            else
-                SharedTypedArrayObject::setElement(obj->as<SharedTypedArrayObject>(), index, d);
-        }
-
-        // Step xii.
-        return result.succeed();
-    }
+    if (IsTypedArrayIndex(id, &index))
+        return DefineTypedArrayElement(cx, obj, index, desc, result);
 
     // Step 4.
     return DefinePropertyOnObject(cx, obj.as<NativeObject>(), id, desc, result);
 }
 
 bool
 js::StandardDefineProperty(JSContext* cx, HandleObject obj, HandleId id,
                            Handle<PropertyDescriptor> desc, ObjectOpResult& result)
--- a/js/src/json.cpp
+++ b/js/src/json.cpp
@@ -705,26 +705,26 @@ Walk(JSContext* cx, HandleObject holder,
             for (uint32_t i = 0; i < length; i++) {
                 if (!IndexToId(cx, i, &id))
                     return false;
 
                 /* Step 2a(iii)(1). */
                 if (!Walk(cx, obj, id, reviver, &newElement))
                     return false;
 
+                ObjectOpResult ignored;
                 if (newElement.isUndefined()) {
-                    /* Step 2a(iii)(2). */
-                    ObjectOpResult ignored;
+                    /* Step 2a(iii)(2). The spec deliberately ignores strict failure. */
                     if (!DeleteProperty(cx, obj, id, ignored))
                         return false;
                 } else {
-                    /* Step 2a(iii)(3). */
-                    // XXX This definition should ignore success/failure, when
-                    //     our property-definition APIs indicate that.
-                    if (!DefineProperty(cx, obj, id, newElement))
+                    /* Step 2a(iii)(3). The spec deliberately ignores strict failure. */
+                    Rooted<PropertyDescriptor> desc(cx);
+                    desc.setDataDescriptor(newElement, JSPROP_ENUMERATE);
+                    if (!StandardDefineProperty(cx, obj, id, desc, ignored))
                         return false;
                 }
             }
         } else {
             /* Step 2b(i). */
             AutoIdVector keys(cx);
             if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY, &keys))
                 return false;
@@ -733,26 +733,26 @@ Walk(JSContext* cx, HandleObject holder,
             RootedId id(cx);
             RootedValue newElement(cx);
             for (size_t i = 0, len = keys.length(); i < len; i++) {
                 /* Step 2b(ii)(1). */
                 id = keys[i];
                 if (!Walk(cx, obj, id, reviver, &newElement))
                     return false;
 
+                ObjectOpResult ignored;
                 if (newElement.isUndefined()) {
-                    /* Step 2b(ii)(2). */
-                    ObjectOpResult ignored;
+                    /* Step 2b(ii)(2). The spec deliberately ignores strict failure. */
                     if (!DeleteProperty(cx, obj, id, ignored))
                         return false;
                 } else {
-                    /* Step 2b(ii)(3). */
-                    // XXX This definition should ignore success/failure, when
-                    //     our property-definition APIs indicate that.
-                    if (!DefineProperty(cx, obj, id, newElement))
+                    /* Step 2b(ii)(3). The spec deliberately ignores strict failure. */
+                    Rooted<PropertyDescriptor> desc(cx);
+                    desc.setDataDescriptor(newElement, JSPROP_ENUMERATE);
+                    if (!StandardDefineProperty(cx, obj, id, desc, ignored))
                         return false;
                 }
             }
         }
     }
 
     /* Step 3. */
     RootedString key(cx, IdToString(cx, name));
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -499,38 +499,43 @@ XDRRelazificationInfo(XDRState<mode>* xd
         uint32_t column = script->column();
 
         if (mode == XDR_ENCODE) {
             packedFields = lazy->packedFields();
             MOZ_ASSERT(begin == lazy->begin());
             MOZ_ASSERT(end == lazy->end());
             MOZ_ASSERT(lineno == lazy->lineno());
             MOZ_ASSERT(column == lazy->column());
+            // We can assert we have no inner functions because we don't
+            // relazify scripts with inner functions.  See
+            // JSFunction::createScriptForLazilyInterpretedFunction.
+            MOZ_ASSERT(lazy->numInnerFunctions() == 0);
         }
 
         if (!xdr->codeUint64(&packedFields))
             return false;
 
         if (mode == XDR_DECODE) {
-            lazy.set(LazyScript::Create(cx, fun, packedFields, begin, end, lineno, column));
+            lazy.set(LazyScript::Create(cx, fun, script, enclosingScope, script,
+                                        packedFields, begin, end, lineno, column));
 
             // As opposed to XDRLazyScript, we need to restore the runtime bits
             // of the script, as we are trying to match the fact this function
             // has already been parsed and that it would need to be re-lazified.
             lazy->initRuntimeFields(packedFields);
-
-            MOZ_ASSERT(!lazy->sourceObject());
-            lazy->setParent(enclosingScope, &script->scriptSourceUnwrap());
         }
     }
 
     // Code free variables.
     if (!XDRLazyFreeVariables(xdr, lazy))
         return false;
 
+    // No need to do anything with inner functions, since we asserted we don't
+    // have any.
+
     return true;
 }
 
 static inline uint32_t
 FindScopeObjectIndex(JSScript* script, NestedScopeObject& scope)
 {
     ObjectArray* objects = script->objects();
     HeapPtrObject* vector = objects->vector;
@@ -1138,17 +1143,18 @@ js::XDRLazyScript(XDRState<mode>* xdr, H
         if (!xdr->codeUint32(&begin) || !xdr->codeUint32(&end) ||
             !xdr->codeUint32(&lineno) || !xdr->codeUint32(&column) ||
             !xdr->codeUint64(&packedFields))
         {
             return false;
         }
 
         if (mode == XDR_DECODE)
-            lazy.set(LazyScript::Create(cx, fun, packedFields, begin, end, lineno, column));
+            lazy.set(LazyScript::Create(cx, fun, NullPtr(), enclosingScope, enclosingScript,
+                                        packedFields, begin, end, lineno, column));
     }
 
     // Code free variables.
     if (!XDRLazyFreeVariables(xdr, lazy))
         return false;
 
     // Code inner functions.
     {
@@ -1162,25 +1168,16 @@ js::XDRLazyScript(XDRState<mode>* xdr, H
             if (!XDRInterpretedFunction(xdr, fun, enclosingScript, &func))
                 return false;
 
             if (mode == XDR_DECODE)
                 innerFunctions[i] = func;
         }
     }
 
-    if (mode == XDR_DECODE) {
-        MOZ_ASSERT(!lazy->sourceObject());
-        ScriptSourceObject* sourceObject = &enclosingScript->scriptSourceUnwrap();
-
-        // Set the enclosing scope of the lazy function, this would later be
-        // used to define the environment when the function would be used.
-        lazy->setParent(enclosingScope, sourceObject);
-    }
-
     return true;
 }
 
 template bool
 js::XDRLazyScript(XDRState<XDR_ENCODE>*, HandleObject, HandleScript,
                   HandleFunction, MutableHandle<LazyScript*>);
 
 template bool
@@ -3821,16 +3818,18 @@ LazyScript::CreateRaw(ExclusiveContext* 
 
     LazyScript* res = LazyScript::CreateRaw(cx, fun, packedFields, begin, end, lineno, column);
     MOZ_ASSERT_IF(res, res->version() == version);
     return res;
 }
 
 /* static */ LazyScript*
 LazyScript::Create(ExclusiveContext* cx, HandleFunction fun,
+                   HandleScript script, HandleObject enclosingScope,
+                   HandleScript sourceObjectScript,
                    uint64_t packedFields, uint32_t begin, uint32_t end,
                    uint32_t lineno, uint32_t column)
 {
     // Dummy atom which is not a valid property name.
     RootedAtom dummyAtom(cx, cx->names().comma);
 
     // Dummy function which is not a valid function as this is the one which is
     // holding this lazy script.
@@ -3846,16 +3845,25 @@ LazyScript::Create(ExclusiveContext* cx,
     FreeVariable* variables = res->freeVariables();
     for (i = 0, num = res->numFreeVariables(); i < num; i++)
         variables[i] = FreeVariable(dummyAtom);
 
     HeapPtrFunction* functions = res->innerFunctions();
     for (i = 0, num = res->numInnerFunctions(); i < num; i++)
         functions[i].init(dummyFun);
 
+    // Set the enclosing scope of the lazy function, this would later be
+    // used to define the environment when the function would be used.
+    MOZ_ASSERT(!res->sourceObject());
+    res->setParent(enclosingScope, &sourceObjectScript->scriptSourceUnwrap());
+
+    MOZ_ASSERT(!res->maybeScript());
+    if (script)
+        res->initScript(script);
+
     return res;
 }
 
 void
 LazyScript::initRuntimeFields(uint64_t packedFields)
 {
     union {
         PackedView p;
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -1914,17 +1914,25 @@ class LazyScript : public gc::TenuredCel
     static LazyScript* CreateRaw(ExclusiveContext* cx, HandleFunction fun,
                                  uint32_t numFreeVariables, uint32_t numInnerFunctions,
                                  JSVersion version, uint32_t begin, uint32_t end,
                                  uint32_t lineno, uint32_t column);
 
     // Create a LazyScript and initialize the freeVariables and the
     // innerFunctions with dummy values to be replaced in a later initialization
     // phase.
+    //
+    // The "script" argument to this function can be null.  If it's non-null,
+    // then this LazyScript should be associated with the given JSScript.
+    //
+    // The sourceObjectScript argument must be non-null and is the script that
+    // should be used to get the sourceObject_ of this lazyScript.
     static LazyScript* Create(ExclusiveContext* cx, HandleFunction fun,
+                              HandleScript script, HandleObject enclosingScope,
+                              HandleScript sourceObjectScript,
                               uint64_t packedData, uint32_t begin, uint32_t end,
                               uint32_t lineno, uint32_t column);
 
     void initRuntimeFields(uint64_t packedFields);
 
     inline JSFunction* functionDelazifying(JSContext* cx) const;
     JSFunction* functionNonDelazifying() const {
         return function_;
--- a/js/src/tests/browser.js
+++ b/js/src/tests/browser.js
@@ -331,16 +331,20 @@ function jsTestDriverBrowserInit()
     else if (properties.test.match(/^ecma_6\/LexicalEnvironment/))
     {
       properties.version = '1.8';
     }
     else if (properties.test.match(/^ecma_6\/Class/))
     {
       properties.version = '1.8';
     }
+    else if (properties.test.match(/^ecma_6\/extensions/))
+    {
+      properties.version = '1.8';
+    }
   }
 
   // default to language=type;text/javascript. required for
   // reftest style manifests.
   if (!properties.language)
   {
     properties.language = 'type';
     properties.mimetype = 'text/javascript';
--- a/js/src/tests/ecma_6/Class/shell.js
+++ b/js/src/tests/ecma_6/Class/shell.js
@@ -1,9 +1,11 @@
-// Enable "let" in shell builds. So silly.
+// NOTE: This only turns on 1.8.5 in shell builds.  The browser requires the
+//       futzing in js/src/tests/browser.js (which only turns on 1.8, the most
+//       the browser supports).
 if (typeof version != 'undefined')
   version(185);
 
 function classesEnabled() {
     try {
         new Function("class Foo { constructor() { } }");
         return true;
     } catch (e if e instanceof SyntaxError) {
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/LexicalEnvironment/for-loop-with-bindings-added-at-runtime.js
@@ -0,0 +1,125 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+var gTestfile = "for-loop-with-bindings-added-at-runtime.js";
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 1149797;
+var summary =
+  "Don't assert when freshening the scope chain for a for-loop whose head " +
+  "contains a lexical declaration, where the loop body might add more " +
+  "bindings at runtime";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+for (let x = 0; x < 9; ++x)
+  eval("var y");
+
+{
+  for (let x = 0; x < 9; ++x)
+    eval("var y");
+}
+
+function f1()
+{
+  for (let x = 0; x < 9; ++x)
+    eval("var y");
+}
+f1();
+
+function f2()
+{
+  {
+    for (let x = 0; x < 9; ++x)
+      eval("var y");
+  }
+}
+f2();
+
+for (let x = 0; x < 9; ++x)
+{
+  // deliberately inside a block statement
+  eval("var y");
+}
+
+{
+  for (let x = 0; x < 9; ++x)
+  {
+    // deliberately inside a block statement
+    eval("var y");
+  }
+}
+
+function g1()
+{
+  for (let x = 0; x < 9; ++x)
+  {
+    // deliberately inside a block statement
+    eval("var y");
+  }
+}
+g1();
+
+function g2()
+{
+  {
+    for (let x = 0; x < 9; ++x)
+    {
+      // deliberately inside a block statement
+      eval("var y");
+    }
+  }
+}
+g2();
+
+for (let x = 0; x < 9; ++x) {
+  (function() {
+      eval("var y");
+  })();
+}
+
+{
+  for (let x = 0; x < 9; ++x)
+  {
+    // deliberately inside a block statement
+    (function() {
+        eval("var y");
+    })();
+  }
+}
+
+function h1()
+{
+  for (let x = 0; x < 9; ++x)
+  {
+    // deliberately inside a block statement
+    (function() {
+        eval("var y");
+    })();
+  }
+}
+h1();
+
+function h2()
+{
+  {
+    for (let x = 0; x < 9; ++x)
+    {
+      // deliberately inside a block statement
+      (function() { eval("var y"); })();
+    }
+  }
+}
+h2();
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("Tests complete");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/extensions/for-loop-with-lexical-declaration-and-nested-function-statement.js
@@ -0,0 +1,126 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+var gTestfile =
+  "for-loop-with-lexical-declaration-and-nested-function-statement.js";
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 1149797;
+var summary =
+  "Don't assert when freshening the scope chain for a for-loop whose head " +
+  "contains a lexical declaration, where the loop body might add more " +
+  "bindings at runtime";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+for (let x = 0; x < 9; ++x)
+  function q1() {}
+
+{
+  for (let x = 0; x < 9; ++x)
+    function q2() {}
+}
+
+function f1()
+{
+  for (let x = 0; x < 9; ++x)
+    function q3() {}
+}
+f1();
+
+function f2()
+{
+  {
+    for (let x = 0; x < 9; ++x)
+      function q4() {}
+  }
+}
+f2();
+
+for (let x = 0; x < 9; ++x)
+{
+  // deliberately inside a block statement
+  function q5() {}
+}
+
+{
+  for (let x = 0; x < 9; ++x)
+  {
+    // deliberately inside a block statement
+    function q6() {}
+  }
+}
+
+function g1()
+{
+  for (let x = 0; x < 9; ++x)
+  {
+    // deliberately inside a block statement
+    function q7() {}
+  }
+}
+g1();
+
+function g2()
+{
+  {
+    for (let x = 0; x < 9; ++x)
+    {
+      // deliberately inside a block statement
+      function q8() {}
+    }
+  }
+}
+g2();
+
+for (let x = 0; x < 9; ++x) {
+  (function() {
+    eval("function q9() {}");
+  })();
+}
+
+{
+  for (let x = 0; x < 9; ++x)
+  {
+    // deliberately inside a block statement
+    (function() {
+        eval("function q10() {}");
+    })();
+  }
+}
+
+function h1()
+{
+  for (let x = 0; x < 9; ++x)
+  {
+    // deliberately inside a block statement
+    (function() {
+        eval("function q11() {}");
+    })();
+  }
+}
+h1();
+
+function h2()
+{
+  {
+    for (let x = 0; x < 9; ++x)
+    {
+      // deliberately inside a block statement
+      (function() { eval("function q12() {}"); })();
+    }
+  }
+}
+h2();
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("Tests complete");
--- a/js/src/tests/ecma_6/extensions/shell.js
+++ b/js/src/tests/ecma_6/extensions/shell.js
@@ -0,0 +1,5 @@
+// NOTE: This only turns on 1.8.5 in shell builds.  The browser requires the
+//       futzing in js/src/tests/browser.js (which only turns on 1.8, the most
+//       the browser supports).
+if (typeof version != 'undefined')
+  version(185);
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -562,21 +562,24 @@ struct AutoStopwatch final
         MOZ_ASSERT(success);
         if (!success)
             return false;
 
         ULARGE_INTEGER kernelTimeInt;
         ULARGE_INTEGER userTimeInt;
         kernelTimeInt.LowPart = kernelFileTime.dwLowDateTime;
         kernelTimeInt.HighPart = kernelFileTime.dwHighDateTime;
-        *systemTime = kernelTimeInt.QuadPart / 10; // 100 ns to 1 us
+        // Convert 100 ns to 1 us, make sure that the result is monotonic
+        *systemTime = runtime_-> stopwatch.systemTimeFix.monotonize(kernelTimeInt.QuadPart / 10);
 
         userTimeInt.LowPart = userFileTime.dwLowDateTime;
         userTimeInt.HighPart = userFileTime.dwHighDateTime;
-        *userTime = userTimeInt.QuadPart / 10; // 100 ns to 1 us
+        // Convert 100 ns to 1 us, make sure that the result is monotonic
+        *userTime = runtime_-> stopwatch.userTimeFix.monotonize(userTimeInt.QuadPart / 10);
+
 #endif // defined(XP_UNIX) || defined(XP_WIN)
 
         return true;
     }
 
   private:
     // The compartment with which this object was initialized.
     // Non-null.
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -1106,119 +1106,16 @@ UpdateShapeTypeAndValue(ExclusiveContext
     }
     if (!shape->hasSlot() || !shape->hasDefaultGetter() || !shape->hasDefaultSetter())
         MarkTypePropertyNonData(cx, obj, id);
     if (!shape->writable())
         MarkTypePropertyNonWritable(cx, obj, id);
     return true;
 }
 
-static bool
-NativeSetExistingDataProperty(JSContext* cx, HandleNativeObject obj, HandleShape shape,
-                              HandleValue v, HandleValue receiver, ObjectOpResult& result);
-
-static inline bool
-DefinePropertyOrElement(ExclusiveContext* cx, HandleNativeObject obj, HandleId id,
-                        GetterOp getter, SetterOp setter, unsigned attrs, HandleValue value,
-                        bool callSetterAfterwards, ObjectOpResult& result)
-{
-    MOZ_ASSERT(getter != JS_PropertyStub);
-    MOZ_ASSERT(setter != JS_StrictPropertyStub);
-
-    /* Use dense storage for new indexed properties where possible. */
-    if (JSID_IS_INT(id) &&
-        !getter &&
-        !setter &&
-        attrs == JSPROP_ENUMERATE &&
-        (!obj->isIndexed() || !obj->containsPure(id)) &&
-        !IsAnyTypedArray(obj))
-    {
-        uint32_t index = JSID_TO_INT(id);
-        if (WouldDefinePastNonwritableLength(obj, index))
-            return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH);
-
-        NativeObject::EnsureDenseResult edResult = obj->ensureDenseElements(cx, index, 1);
-        if (edResult == NativeObject::ED_FAILED)
-            return false;
-        if (edResult == NativeObject::ED_OK) {
-            obj->setDenseElementWithType(cx, index, value);
-            if (!CallAddPropertyHookDense(cx, obj, index, value))
-                return false;
-            return result.succeed();
-        }
-    }
-
-    if (obj->is<ArrayObject>()) {
-        Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>());
-        if (id == NameToId(cx->names().length)) {
-            if (!cx->shouldBeJSContext())
-                return false;
-            return ArraySetLength(cx->asJSContext(), arr, id, attrs, value, result);
-        }
-
-        uint32_t index;
-        if (IdIsIndex(id, &index)) {
-            if (WouldDefinePastNonwritableLength(obj, index))
-                return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH);
-        }
-    }
-
-    // Don't define new indexed properties on typed arrays.
-    if (IsAnyTypedArray(obj)) {
-        uint64_t index;
-        if (IsTypedArrayIndex(id, &index))
-            return result.succeed();
-    }
-
-    AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter);
-    RootedShape shape(cx, NativeObject::putProperty(cx, obj, id, getter, setter,
-                                                    SHAPE_INVALID_SLOT, attrs, 0));
-    if (!shape)
-        return false;
-
-    if (!UpdateShapeTypeAndValue(cx, obj, shape, value))
-        return false;
-
-    /*
-     * Clear any existing dense index after adding a sparse indexed property,
-     * and investigate converting the object to dense indexes.
-     */
-    if (JSID_IS_INT(id)) {
-        if (!obj->maybeCopyElementsForWrite(cx))
-            return false;
-
-        uint32_t index = JSID_TO_INT(id);
-        NativeObject::removeDenseElementForSparseIndex(cx, obj, index);
-        NativeObject::EnsureDenseResult edResult = NativeObject::maybeDensifySparseElements(cx, obj);
-        if (edResult == NativeObject::ED_FAILED)
-            return false;
-        if (edResult == NativeObject::ED_OK) {
-            MOZ_ASSERT(!setter);
-            if (!CallAddPropertyHookDense(cx, obj, index, value))
-                return false;
-            return result.succeed();
-        }
-    }
-
-    if (!CallAddPropertyHook(cx, obj, shape, value))
-        return false;
-
-    if (callSetterAfterwards && setter) {
-        MOZ_ASSERT(!(attrs & JSPROP_GETTER));
-        MOZ_ASSERT(!(attrs & JSPROP_SETTER));
-        if (!cx->shouldBeJSContext())
-            return false;
-        RootedValue receiver(cx, ObjectValue(*obj));
-        return NativeSetExistingDataProperty(cx->asJSContext(), obj, shape, value, receiver,
-                                             result);
-    }
-
-    return result.succeed();
-}
-
 static unsigned
 ApplyOrDefaultAttributes(unsigned attrs, const Shape* shape = nullptr)
 {
     bool enumerable = shape ? shape->enumerable() : false;
     bool writable = shape ? shape->writable() : false;
     bool configurable = shape ? shape->configurable() : false;
     return ApplyAttributes(attrs, enumerable, writable, configurable);
 }
@@ -1292,105 +1189,204 @@ PurgeScopeChain(ExclusiveContext* cx, Ha
 }
 
 /*
  * Check whether we're redefining away a non-configurable getter, and
  * throw if so.
  */
 static inline bool
 CheckAccessorRedefinition(ExclusiveContext* cx, HandleObject obj, HandleShape shape,
-                          GetterOp getter, SetterOp setter, HandleId id, unsigned attrs)
+                          Handle<PropertyDescriptor> desc)
 {
     MOZ_ASSERT(shape->isAccessorDescriptor());
-    if (shape->configurable() || (getter == shape->getter() && setter == shape->setter()))
+    if (shape->configurable())
+        return true;
+    if (desc.getter() == shape->getter() && desc.setter() == shape->setter())
         return true;
 
     /*
      *  Only allow redefining if JSPROP_REDEFINE_NONCONFIGURABLE is set _and_
      *  the object is a non-DOM global.  The idea is that a DOM object can
      *  never have such a thing on its proto chain directly on the web, so we
      *  should be OK optimizing access to accessors found on such an object.
      */
-    if ((attrs & JSPROP_REDEFINE_NONCONFIGURABLE) &&
+    if ((desc.attributes() & JSPROP_REDEFINE_NONCONFIGURABLE) &&
         obj->is<GlobalObject>() &&
         !obj->getClass()->isDOMClass())
     {
         return true;
     }
 
     if (!cx->isJSContext())
         return false;
 
-    return Throw(cx->asJSContext(), id, JSMSG_CANT_REDEFINE_PROP);
+    return Throw(cx->asJSContext(), shape->propid(), JSMSG_CANT_REDEFINE_PROP);
+}
+
+static bool
+AddOrChangeProperty(ExclusiveContext *cx, HandleNativeObject obj, HandleId id,
+                    Handle<PropertyDescriptor> desc)
+{
+    desc.assertComplete();
+
+    if (!PurgeScopeChain(cx, obj, id))
+        return false;
+
+    // Use dense storage for new indexed properties where possible.
+    if (JSID_IS_INT(id) &&
+        !desc.getter() &&
+        !desc.setter() &&
+        desc.attributes() == JSPROP_ENUMERATE &&
+        (!obj->isIndexed() || !obj->containsPure(id)) &&
+        !IsAnyTypedArray(obj))
+    {
+        uint32_t index = JSID_TO_INT(id);
+        NativeObject::EnsureDenseResult edResult = obj->ensureDenseElements(cx, index, 1);
+        if (edResult == NativeObject::ED_FAILED)
+            return false;
+        if (edResult == NativeObject::ED_OK) {
+            obj->setDenseElementWithType(cx, index, desc.value());
+            if (!CallAddPropertyHookDense(cx, obj, index, desc.value()))
+                return false;
+            return true;
+        }
+    }
+
+    RootedShape shape(cx, NativeObject::putProperty(cx, obj, id, desc.getter(), desc.setter(),
+                                                    SHAPE_INVALID_SLOT, desc.attributes(), 0));
+    if (!shape)
+        return false;
+
+    if (!UpdateShapeTypeAndValue(cx, obj, shape, desc.value()))
+        return false;
+
+    // Clear any existing dense index after adding a sparse indexed property,
+    // and investigate converting the object to dense indexes.
+    if (JSID_IS_INT(id)) {
+        if (!obj->maybeCopyElementsForWrite(cx))
+            return false;
+
+        uint32_t index = JSID_TO_INT(id);
+        NativeObject::removeDenseElementForSparseIndex(cx, obj, index);
+        NativeObject::EnsureDenseResult edResult =
+            NativeObject::maybeDensifySparseElements(cx, obj);
+        if (edResult == NativeObject::ED_FAILED)
+            return false;
+        if (edResult == NativeObject::ED_OK) {
+            MOZ_ASSERT(!desc.setter());
+            return CallAddPropertyHookDense(cx, obj, index, desc.value());
+        }
+    }
+
+    return CallAddPropertyHook(cx, obj, shape, desc.value());
 }
 
 bool
 js::NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId id,
-                         Handle<PropertyDescriptor> desc,
+                         Handle<PropertyDescriptor> desc_,
                          ObjectOpResult& result)
 {
-    desc.assertValid();
+    desc_.assertValid();
+
+    // Section numbers and step numbers below refer to ES6 draft rev 36
+    // (17 March 2015).
+    //
+    // This function aims to implement 9.1.6 [[DefineOwnProperty]] as well as
+    // the [[DefineOwnProperty]] methods described in 9.4.2.1 (arrays), 9.4.4.2
+    // (arguments), and 9.4.5.3 (typed array views).
+
+    // Dispense with custom behavior of exotic native objects first.
+    if (obj->is<ArrayObject>()) {
+        // 9.4.2.1 step 2. Redefining an array's length is very special.
+        Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>());
+        if (id == NameToId(cx->names().length)) {
+            if (!cx->shouldBeJSContext())
+                return false;
+            return ArraySetLength(cx->asJSContext(), arr, id, desc_.attributes(), desc_.value(),
+                                  result);
+        }
 
-    GetterOp getter = desc.getter();
-    SetterOp setter = desc.setter();
-    unsigned attrs = desc.attributes();
+        // 9.4.2.1 step 3. Don't extend a fixed-length array.
+        uint32_t index;
+        if (IdIsIndex(id, &index)) {
+            if (WouldDefinePastNonwritableLength(obj, index))
+                return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH);
+        }
+    } else if (IsAnyTypedArray(obj)) {
+        // 9.4.5.3 step 3. Indexed properties of typed arrays are special.
+        uint64_t index;
+        if (IsTypedArrayIndex(id, &index)) {
+            if (!cx->shouldBeJSContext())
+                return false;
+            return DefineTypedArrayElement(cx->asJSContext(), obj, index, desc_, result);
+        }
+    } else if (obj->is<ArgumentsObject>()) {
+        if (id == NameToId(cx->names().length)) {
+            // Either we are resolving the .length property on this object, or
+            // redefining it. In the latter case only, we must set a bit. To
+            // distinguish the two cases, we note that when resolving, the
+            // property won't already exist; whereas the first time it is
+            // redefined, it will.
+            if (obj->containsPure(id))
+                obj->as<ArgumentsObject>().markLengthOverridden();
+        }
+    }
 
-    MOZ_ASSERT(!(attrs & JSPROP_PROPOP_ACCESSORS));
-
-    AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter);
+    Rooted<PropertyDescriptor> desc(cx, desc_);
 
     RootedShape shape(cx);
-    RootedValue updateValue(cx, desc.value());
-    bool shouldDefine = true;
 
-    /*
-     * If defining a getter or setter, we must check for its counterpart and
-     * update the attributes and property ops.  A getter or setter is really
-     * only half of a property.
-     */
+    // If defining a getter or setter, we must check for its counterpart and
+    // update the attributes and property ops.  A getter or setter is really
+    // only half of a property.
     if (desc.isAccessorDescriptor()) {
         if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &shape))
             return false;
         if (shape) {
-            /*
-             * If we are defining a getter whose setter was already defined, or
-             * vice versa, finish the job via obj->changeProperty.
-             */
+            // If we are defining a getter whose setter was already defined, or
+            // vice versa, finish the job via obj->changeProperty.
             if (IsImplicitDenseOrTypedArrayElement(shape)) {
                 if (IsAnyTypedArray(obj)) {
-                    /* Ignore getter/setter properties added to typed arrays. */
+                    // Ignore getter/setter properties added to typed arrays.
                     return result.succeed();
                 }
                 if (!NativeObject::sparsifyDenseElement(cx, obj, JSID_TO_INT(id)))
                     return false;
                 shape = obj->lookup(cx, id);
             }
             if (shape->isAccessorDescriptor()) {
-                if (!CheckAccessorRedefinition(cx, obj, shape, getter, setter, id, attrs))
+                if (!CheckAccessorRedefinition(cx, obj, shape, desc))
                     return false;
-                attrs = ApplyOrDefaultAttributes(attrs, shape);
-                shape = NativeObject::changeProperty(cx, obj, shape,
-                                                     attrs | JSPROP_GETTER | JSPROP_SETTER,
-                                                     (attrs & JSPROP_GETTER)
-                                                     ? getter
-                                                     : shape->getter(),
-                                                     (attrs & JSPROP_SETTER)
-                                                     ? setter
-                                                     : shape->setter());
+
+                desc.setAttributes(ApplyOrDefaultAttributes(desc.attributes(), shape));
+                if (!desc.hasGetterObject())
+                    desc.setGetter(shape->getter());
+                if (!desc.hasSetterObject())
+                    desc.setSetter(shape->setter());
+                desc.attributesRef() |= JSPROP_GETTER | JSPROP_SETTER;
+                desc.assertComplete();
+
+                shape = NativeObject::changeProperty(cx, obj, shape, desc.attributes(),
+                                                     desc.getter(), desc.setter());
                 if (!shape)
                     return false;
-                shouldDefine = false;
+                if (!PurgeScopeChain(cx, obj, id))
+                    return false;
+
+                JS_ALWAYS_TRUE(UpdateShapeTypeAndValue(cx, obj, shape, desc.value()));
+                if (!CallAddPropertyHook(cx, obj, shape, desc.value()))
+                    return false;
+                return result.succeed();
             }
         }
 
         // Either we are converting a data property to an accessor property, or
         // creating a new accessor property; either way [[Get]] and [[Set]]
         // must both be filled in.
-        if (shouldDefine)
-            attrs |= JSPROP_GETTER | JSPROP_SETTER;
+        desc.attributesRef() |= JSPROP_GETTER | JSPROP_SETTER;
     } else if (desc.hasValue()) {
         // If we did a normal lookup here, it would cause resolve hook recursion in
         // the following case. Suppose the first script we run in a lazy global is
         // |parseInt()|.
         //   - js::InitNumberClass is called to resolve parseInt.
         //   - js::InitNumberClass tries to define the Number constructor on the
         //     global.
         //   - We end up here.
@@ -1400,96 +1396,84 @@ js::NativeDefineProperty(ExclusiveContex
         //
         // Therefore we do a special lookup that does not call the resolve hook.
         NativeLookupOwnPropertyNoResolve(cx, obj, id, &shape);
 
         if (shape) {
             // If any other JSPROP_IGNORE_* attributes are present, copy the
             // corresponding JSPROP_* attributes from the existing property.
             if (IsImplicitDenseOrTypedArrayElement(shape)) {
-                attrs = ApplyAttributes(attrs, true, true, !IsAnyTypedArray(obj));
+                desc.setAttributes(ApplyAttributes(desc.attributes(), true, true,
+                                                   !IsAnyTypedArray(obj)));
             } else {
-                attrs = ApplyOrDefaultAttributes(attrs, shape);
+                desc.setAttributes(ApplyOrDefaultAttributes(desc.attributes(), shape));
 
                 // Do not redefine a nonconfigurable accessor property.
                 if (shape->isAccessorDescriptor()) {
-                    if (!CheckAccessorRedefinition(cx, obj, shape, getter, setter, id, attrs))
+                    if (!CheckAccessorRedefinition(cx, obj, shape, desc))
                         return false;
                 }
             }
         }
     } else {
         // We have been asked merely to update JSPROP_PERMANENT and/or JSPROP_ENUMERATE.
         if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &shape))
             return false;
 
         if (shape) {
             // Don't forget about arrays.
             if (IsImplicitDenseOrTypedArrayElement(shape)) {
                 if (IsAnyTypedArray(obj)) {
-                    /*
-                     * Silently ignore attempts to change individual index attributes.
-                     * FIXME: Uses the same broken behavior as for accessors. This should
-                     *        fail.
-                     */
+                    // Silently ignore attempts to change individual index attributes.
+                    // FIXME: Uses the same broken behavior as for accessors. This should
+                    //        fail.
                     return result.succeed();
                 }
                 if (!NativeObject::sparsifyDenseElement(cx, obj, JSID_TO_INT(id)))
                     return false;
                 shape = obj->lookup(cx, id);
             }
 
             if (shape->isAccessorDescriptor() &&
-                !CheckAccessorRedefinition(cx, obj, shape, getter, setter, id, attrs))
+                !CheckAccessorRedefinition(cx, obj, shape, desc))
             {
                 return false;
             }
 
-            attrs = ApplyOrDefaultAttributes(attrs, shape);
+            desc.setAttributes(ApplyOrDefaultAttributes(desc.attributes(), shape));
 
-            if (shape->isAccessorDescriptor() && !(attrs & JSPROP_IGNORE_READONLY)) {
+            if (shape->isAccessorDescriptor() && desc.hasWritable()) {
                 // ES6 draft 2014-10-14 9.1.6.3 step 7.c: Since [[Writable]]
                 // is present, change the existing accessor property to a data
                 // property.
-                updateValue = UndefinedValue();
+                desc.value().setUndefined();
             } else {
                 // We are at most changing some attributes, and cannot convert
                 // from data descriptor to accessor, or vice versa. Take
                 // everything from the shape that we aren't changing.
                 uint32_t propMask = JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT;
-                attrs = (shape->attributes() & ~propMask) | (attrs & propMask);
-                getter = shape->getter();
-                setter = shape->setter();
+                desc.setAttributes((shape->attributes() & ~propMask) |
+                                   (desc.attributes() & propMask));
+                desc.setGetter(shape->getter());
+                desc.setSetter(shape->setter());
                 if (shape->hasSlot())
-                    updateValue = obj->getSlot(shape->slot());
+                    desc.value().set(obj->getSlot(shape->slot()));
             }
         }
     }
 
-    /*
-     * Purge the property cache of any properties named by id that are about
-     * to be shadowed in obj's scope chain.
-     */
-    if (!PurgeScopeChain(cx, obj, id))
-        return false;
+    // Dispense with any remaining JSPROP_IGNORE_* attributes. Any bits that
+    // needed to be copied from an existing property have been copied by
+    // now. Since we can never get here with JSPROP_IGNORE_VALUE relevant, just
+    // clear it.
+    desc.setAttributes(ApplyOrDefaultAttributes(desc.attributes()) & ~JSPROP_IGNORE_VALUE);
 
-    if (shouldDefine) {
-        // Handle the default cases here. Anyone that wanted to set non-default attributes has
-        // cleared the IGNORE flags by now. Since we can never get here with JSPROP_IGNORE_VALUE
-        // relevant, just clear it.
-        attrs = ApplyOrDefaultAttributes(attrs) & ~JSPROP_IGNORE_VALUE;
-        return DefinePropertyOrElement(cx, obj, id, getter, setter,
-                                       attrs, updateValue, false, result);
-    }
-
-    MOZ_ASSERT(shape);
-
-    JS_ALWAYS_TRUE(UpdateShapeTypeAndValue(cx, obj, shape, updateValue));
-
-    if (!CallAddPropertyHook(cx, obj, shape, updateValue))
+    // At this point, no mutation has happened yet, but all ES6 error cases
+    // have been dealt with.
+    if (!AddOrChangeProperty(cx, obj, id, desc))
         return false;
     return result.succeed();
 }
 
 bool
 js::NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId id,
                          HandleValue value, GetterOp getter, SetterOp setter, unsigned attrs,
                          ObjectOpResult& result)
@@ -1958,16 +1942,65 @@ MaybeReportUndeclaredVarAssignment(JSCon
            JS_ReportErrorFlagsAndNumber(cx,
                                         (JSREPORT_WARNING | JSREPORT_STRICT
                                          | JSREPORT_STRICT_MODE_ERROR),
                                         GetErrorMessage, nullptr,
                                         JSMSG_UNDECLARED_VAR, bytes.ptr());
 }
 
 /*
+ * Finish assignment to a shapeful data property of a native object obj. This
+ * conforms to no standard and there is a lot of legacy baggage here.
+ */
+static bool
+NativeSetExistingDataProperty(JSContext* cx, HandleNativeObject obj, HandleShape shape,
+                              HandleValue v, HandleValue receiver, ObjectOpResult& result)
+{
+    MOZ_ASSERT(obj->isNative());
+    MOZ_ASSERT(shape->isDataDescriptor());
+
+    if (shape->hasDefaultSetter()) {
+        if (shape->hasSlot()) {
+            // The common path. Standard data property.
+
+            // Global properties declared with 'var' will be initially
+            // defined with an undefined value, so don't treat the initial
+            // assignments to such properties as overwrites.
+            bool overwriting = !obj->is<GlobalObject>() || !obj->getSlot(shape->slot()).isUndefined();
+            obj->setSlotWithType(cx, shape, v, overwriting);
+            return result.succeed();
+        }
+
+        // Bizarre: shared (slotless) property that's writable but has no
+        // JSSetterOp. JS code can't define such a property, but it can be done
+        // through the JSAPI. Treat it as non-writable.
+        return result.fail(JSMSG_GETTER_ONLY);
+    }
+
+    MOZ_ASSERT(!obj->is<DynamicWithObject>());  // See bug 1128681.
+
+    uint32_t sample = cx->runtime()->propertyRemovals;
+    RootedId id(cx, shape->propid());
+    RootedValue value(cx, v);
+    if (!CallJSSetterOp(cx, shape->setterOp(), obj, id, &value, result))
+        return false;
+
+    // Update any slot for the shape with the value produced by the setter,
+    // unless the setter deleted the shape.
+    if (shape->hasSlot() &&
+        (MOZ_LIKELY(cx->runtime()->propertyRemovals == sample) ||
+         obj->contains(cx, shape)))
+    {
+        obj->setSlot(shape->slot(), value);
+    }
+
+    return true;  // result is populated by CallJSSetterOp above.
+}
+
+/*
  * When a [[Set]] operation finds no existing property with the given id
  * or finds a writable data property on the prototype chain, we end up here.
  * Finish the [[Set]] by defining a new property on receiver.
  *
  * This implements ES6 draft rev 28, 9.1.9 [[Set]] steps 5.b-f, but it
  * is really old code and there are a few barnacles.
  */
 bool
@@ -2023,23 +2056,42 @@ js::SetPropertyByDefining(JSContext* cx,
     unsigned attrs =
         existing
         ? JSPROP_IGNORE_ENUMERATE | JSPROP_IGNORE_READONLY | JSPROP_IGNORE_PERMANENT
         : JSPROP_ENUMERATE;
     JSGetterOp getter = clasp->getProperty;
     JSSetterOp setter = clasp->setProperty;
     MOZ_ASSERT(getter != JS_PropertyStub);
     MOZ_ASSERT(setter != JS_StrictPropertyStub);
-    if (!receiver->is<NativeObject>())
-        return DefineProperty(cx, receiver, id, v, getter, setter, attrs, result);
+    if (!DefineProperty(cx, receiver, id, v, getter, setter, attrs, result))
+        return false;
 
     // If the receiver is native, there is one more legacy wrinkle: the class
     // JSSetterOp is called after defining the new property.
-    Rooted<NativeObject*> nativeReceiver(cx, &receiver->as<NativeObject>());
-    return DefinePropertyOrElement(cx, nativeReceiver, id, getter, setter, attrs, v, true, result);
+    if (setter && receiver->is<NativeObject>()) {
+        if (!result)
+            return true;
+
+        Rooted<NativeObject*> nativeReceiver(cx, &receiver->as<NativeObject>());
+        if (!cx->shouldBeJSContext())
+            return false;
+        RootedValue receiverValue(cx, ObjectValue(*receiver));
+
+        // This lookup is a bit unfortunate, but not nearly the most
+        // unfortunate thing about Class getters and setters. Since the above
+        // DefineProperty call succeeded, receiver is native, and the property
+        // has a setter (and thus can't be a dense element), this lookup is
+        // guaranteed to succeed.
+        RootedShape shape(cx, nativeReceiver->lookup(cx, id));
+        MOZ_ASSERT(shape);
+        return NativeSetExistingDataProperty(cx->asJSContext(), nativeReceiver, shape, v,
+                                             receiverValue, result);
+    }
+
+    return true;
 }
 
 // When setting |id| for |receiver| and |obj| has no property for id, continue
 // the search up the prototype chain.
 bool
 js::SetPropertyOnProto(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
                        HandleValue receiver, ObjectOpResult& result)
 {
@@ -2105,65 +2157,16 @@ SetDenseOrTypedArrayElement(JSContext* c
     if (!obj->maybeCopyElementsForWrite(cx))
         return false;
 
     obj->setDenseElementWithType(cx, index, v);
     return result.succeed();
 }
 
 /*
- * Finish assignment to a shapeful data property of a native object obj. This
- * conforms to no standard and there is a lot of legacy baggage here.
- */
-static bool
-NativeSetExistingDataProperty(JSContext* cx, HandleNativeObject obj, HandleShape shape,
-                              HandleValue v, HandleValue receiver, ObjectOpResult& result)
-{
-    MOZ_ASSERT(obj->isNative());
-    MOZ_ASSERT(shape->isDataDescriptor());
-
-    if (shape->hasDefaultSetter()) {
-        if (shape->hasSlot()) {
-            // The common path. Standard data property.
-
-            // Global properties declared with 'var' will be initially
-            // defined with an undefined value, so don't treat the initial
-            // assignments to such properties as overwrites.
-            bool overwriting = !obj->is<GlobalObject>() || !obj->getSlot(shape->slot()).isUndefined();
-            obj->setSlotWithType(cx, shape, v, overwriting);
-            return result.succeed();
-        }
-
-        // Bizarre: shared (slotless) property that's writable but has no
-        // JSSetterOp. JS code can't define such a property, but it can be done
-        // through the JSAPI. Treat it as non-writable.
-        return result.fail(JSMSG_GETTER_ONLY);
-    }
-
-    MOZ_ASSERT(!obj->is<DynamicWithObject>());  // See bug 1128681.
-
-    uint32_t sample = cx->runtime()->propertyRemovals;
-    RootedId id(cx, shape->propid());
-    RootedValue value(cx, v);
-    if (!CallJSSetterOp(cx, shape->setterOp(), obj, id, &value, result))
-        return false;
-
-    // Update any slot for the shape with the value produced by the setter,
-    // unless the setter deleted the shape.
-    if (shape->hasSlot() &&
-        (MOZ_LIKELY(cx->runtime()->propertyRemovals == sample) ||
-         obj->contains(cx, shape)))
-    {
-        obj->setSlot(shape->slot(), value);
-    }
-
-    return true;  // result is populated by CallJSSetterOp above.
-}
-
-/*
  * Finish the assignment `receiver[id] = v` when an existing property (shape)
  * has been found on a native object (pobj). This implements ES6 draft rev 32
  * (2015 Feb 2) 9.1.9 steps 5 and 6.
  *
  * It is necessary to pass both id and shape because shape could be an implicit
  * dense or typed array element (i.e. not actually a pointer to a Shape).
  */
 static bool
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -1527,16 +1527,38 @@ struct JSRuntime : public JS::shadow::Ru
 
         /**
          * `true` if the stopwatch is currently monitoring, `false` otherwise.
          */
         bool isActive() const {
             return isActive_;
         }
 
+        // Some systems have non-monotonic clocks. While we cannot
+        // improve the precision, we can make sure that our measures
+        // are monotonic nevertheless. We do this by storing the
+        // result of the latest call to the clock and making sure
+        // that the next timestamp is greater or equal.
+        struct MonotonicTimeStamp {
+            MonotonicTimeStamp()
+              : latestGood_(0)
+            {}
+            inline uint64_t monotonize(uint64_t stamp)
+            {
+                if (stamp <= latestGood_)
+                    return latestGood_;
+                latestGood_ = stamp;
+                return stamp;
+            }
+          private:
+            uint64_t latestGood_;
+        };
+        MonotonicTimeStamp systemTimeFix;
+        MonotonicTimeStamp userTimeFix;
+
     private:
         /**
          * A map used to collapse compartments belonging to the same
          * add-on (respectively to the same webpage, to the platform)
          * into a single group.
          *
          * Keys: for system compartments, a `JSAddonId*` (which may be
          * `nullptr`), and for webpages, a `JSPrincipals*` (which may
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -656,42 +656,29 @@ ClonedBlockObject::copyUnaliasedValues(A
         if (!block.isAliased(i)) {
             Value& val = frame.unaliasedLocal(block.blockIndexToLocalIndex(i));
             setVar(i, val, DONT_CHECK_ALIASING);
         }
     }
 }
 
 /* static */ ClonedBlockObject*
-ClonedBlockObject::clone(ExclusiveContext* cx, Handle<ClonedBlockObject*> block)
+ClonedBlockObject::clone(JSContext* cx, Handle<ClonedBlockObject*> clonedBlock)
 {
-    RootedObject enclosing(cx, &block->enclosingScope());
-
-    MOZ_ASSERT(block->getClass() == &BlockObject::class_);
-
-    RootedObjectGroup cloneGroup(cx, block->group());
-    RootedShape cloneShape(cx, block->lastProperty());
-
-    JSObject* obj = JSObject::create(cx, FINALIZE_KIND, gc::TenuredHeap, cloneShape, cloneGroup);
-    if (!obj)
+    Rooted<StaticBlockObject*> staticBlock(cx, &clonedBlock->staticBlock());
+    RootedObject enclosing(cx, &clonedBlock->enclosingScope());
+
+    Rooted<ClonedBlockObject*> copy(cx, create(cx, staticBlock, enclosing));
+    if (!copy)
         return nullptr;
 
-    ClonedBlockObject& copy = obj->as<ClonedBlockObject>();
-
-    MOZ_ASSERT(!copy.inDictionaryMode());
-    MOZ_ASSERT(copy.isDelegate());
-    MOZ_ASSERT(block->slotSpan() == copy.slotSpan());
-    MOZ_ASSERT(copy.slotSpan() >= copy.numVariables() + RESERVED_SLOTS);
-
-    copy.setReservedSlot(SCOPE_CHAIN_SLOT, block->getReservedSlot(SCOPE_CHAIN_SLOT));
-
-    for (uint32_t i = 0, count = copy.numVariables(); i < count; i++)
-        copy.setVar(i, block->var(i, DONT_CHECK_ALIASING), DONT_CHECK_ALIASING);
-
-    return &copy;
+    for (uint32_t i = 0, count = staticBlock->numVariables(); i < count; i++)
+        copy->setVar(i, clonedBlock->var(i, DONT_CHECK_ALIASING), DONT_CHECK_ALIASING);
+
+    return copy;
 }
 
 StaticBlockObject*
 StaticBlockObject::create(ExclusiveContext* cx)
 {
     RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &BlockObject::class_,
                                                              TaggedProto(nullptr)));
     if (!group)
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -669,17 +669,17 @@ class ClonedBlockObject : public BlockOb
 
     /* Copy in all the unaliased formals and locals. */
     void copyUnaliasedValues(AbstractFramePtr frame);
 
     /*
      * Create a new ClonedBlockObject with the same enclosing scope and
      * variable values as this.
      */
-    static ClonedBlockObject* clone(ExclusiveContext* cx, Handle<ClonedBlockObject*> block);
+    static ClonedBlockObject* clone(JSContext* cx, Handle<ClonedBlockObject*> block);
 };
 
 // Internal scope object used by JSOP_BINDNAME upon encountering an
 // uninitialized lexical slot.
 //
 // ES6 lexical bindings cannot be accessed in any way (throwing
 // ReferenceErrors) until initialized. Normally, NAME operations
 // unconditionally check for uninitialized lexical slots. When getting or
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -2100,16 +2100,61 @@ js::StringIsTypedArrayIndex(const CharT*
 }
 
 template bool
 js::StringIsTypedArrayIndex(const char16_t* s, size_t length, uint64_t* indexp);
 
 template bool
 js::StringIsTypedArrayIndex(const Latin1Char* s, size_t length, uint64_t* indexp);
 
+/* ES6 draft rev 34 (2015 Feb 20) 9.4.5.3 [[DefineOwnProperty]] step 3.c. */
+bool
+js::DefineTypedArrayElement(JSContext *cx, HandleObject obj, uint64_t index,
+                            Handle<PropertyDescriptor> desc, ObjectOpResult &result)
+{
+    MOZ_ASSERT(IsAnyTypedArray(obj));
+
+    // These are all substeps of 3.c.
+    // Steps i-vi.
+    // We (wrongly) ignore out of range defines with a value.
+    if (index >= AnyTypedArrayLength(obj))
+        return result.succeed();
+
+    // Step vii.
+    if (desc.isAccessorDescriptor())
+        return result.fail(JSMSG_CANT_REDEFINE_PROP);
+
+    // Step viii.
+    if (desc.hasConfigurable() && desc.configurable())
+        return result.fail(JSMSG_CANT_REDEFINE_PROP);
+
+    // Step ix.
+    if (desc.hasEnumerable() && !desc.enumerable())
+        return result.fail(JSMSG_CANT_REDEFINE_PROP);
+
+    // Step x.
+    if (desc.hasWritable() && !desc.writable())
+        return result.fail(JSMSG_CANT_REDEFINE_PROP);
+
+    // Step xi.
+    if (desc.hasValue()) {
+        double d;
+        if (!ToNumber(cx, desc.value(), &d))
+            return false;
+
+        if (obj->is<TypedArrayObject>())
+            TypedArrayObject::setElement(obj->as<TypedArrayObject>(), index, d);
+        else
+            SharedTypedArrayObject::setElement(obj->as<SharedTypedArrayObject>(), index, d);
+    }
+
+    // Step xii.
+    return result.succeed();
+}
+
 /* JS Friend API */
 
 JS_FRIEND_API(bool)
 JS_IsTypedArrayObject(JSObject* obj)
 {
     obj = CheckedUnwrap(obj);
     return obj ? obj->is<TypedArrayObject>() : false;
 }
--- a/js/src/vm/TypedArrayObject.h
+++ b/js/src/vm/TypedArrayObject.h
@@ -272,16 +272,24 @@ IsTypedArrayIndex(jsid id, uint64_t* ind
     }
 
     const char16_t* s = atom->twoByteChars(nogc);
     if (!JS7_ISDEC(*s) && *s != '-')
         return false;
     return StringIsTypedArrayIndex(s, length, indexp);
 }
 
+/*
+ * Implements [[DefineOwnProperty]] for TypedArrays and SharedTypedArrays
+ * when the property key is a TypedArray index.
+ */
+bool
+DefineTypedArrayElement(JSContext *cx, HandleObject arr, uint64_t index,
+                        Handle<PropertyDescriptor> desc, ObjectOpResult &result);
+
 static inline unsigned
 TypedArrayShift(Scalar::Type viewType)
 {
     switch (viewType) {
       case Scalar::Int8:
       case Scalar::Uint8:
       case Scalar::Uint8Clamped:
         return 0;
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -30,16 +30,17 @@
 
 #include "GeckoProfiler.h"
 #include "mozilla/gfx/Tools.h"
 #include "mozilla/gfx/2D.h"
 #include "gfxPrefs.h"
 #include "LayersLogging.h"
 #include "mozilla/unused.h"
 #include "mozilla/ReverseIterator.h"
+#include "mozilla/Move.h"
 
 #include <algorithm>
 
 using namespace mozilla::layers;
 using namespace mozilla::gfx;
 
 namespace mozilla {
 
@@ -245,66 +246,16 @@ static inline MaskLayerImageCache* GetMa
 {
   if (!gMaskLayerImageCache) {
     gMaskLayerImageCache = new MaskLayerImageCache();
   }
 
   return gMaskLayerImageCache;
 }
 
-/**
- * A wrapper for nsIntRegion that can express infinite regions.
- */
-struct PossiblyInfiniteRegion
-{
-  PossiblyInfiniteRegion() : mIsInfinite(false) {}
-  MOZ_IMPLICIT PossiblyInfiniteRegion(const nsIntRegion& aRegion)
-   : mRegion(aRegion)
-   , mIsInfinite(false)
-  {}
-  MOZ_IMPLICIT PossiblyInfiniteRegion(const nsIntRect& aRect)
-   : mRegion(aRect)
-   , mIsInfinite(false)
-  {}
-
-  // Create an infinite region.
-  static PossiblyInfiniteRegion InfiniteRegion()
-  {
-    PossiblyInfiniteRegion r;
-    r.mIsInfinite = true;
-    return r;
-  }
-
-  bool IsInfinite() const { return mIsInfinite; }
-  bool Intersects(const nsIntRegion& aRegion) const
-  {
-    if (IsInfinite()) {
-      return true;
-    }
-    return !mRegion.Intersect(aRegion).IsEmpty();
-  }
-
-  void AccumulateAndSimplifyOutward(const PossiblyInfiniteRegion& aRegion)
-  {
-    if (!IsInfinite()) {
-      if (aRegion.IsInfinite()) {
-        mIsInfinite = true;
-        mRegion.SetEmpty();
-      } else {
-        mRegion.OrWith(aRegion.mRegion);
-        mRegion.SimplifyOutward(8);
-      }
-    }
-  }
-
-protected:
-  nsIntRegion mRegion;
-  bool mIsInfinite;
-};
-
 struct AssignedDisplayItem
 {
   AssignedDisplayItem(nsDisplayItem* aItem,
                       const DisplayItemClip& aClip,
                       LayerState aLayerState)
     : mItem(aItem)
     , mClip(aClip)
     , mLayerState(aLayerState)
@@ -323,17 +274,16 @@ struct AssignedDisplayItem
  * PaintedLayer in z-order. This reduces the number of layers and
  * makes it more likely a display item will be rendered to an opaque
  * layer, giving us the best chance of getting subpixel AA.
  */
 class PaintedLayerData {
 public:
   PaintedLayerData() :
     mAnimatedGeometryRoot(nullptr),
-    mIsAsyncScrollable(false),
     mFixedPosFrameForLayerData(nullptr),
     mReferenceFrame(nullptr),
     mLayer(nullptr),
     mIsSolidColorInVisibleRegion(false),
     mFontSmoothingBackgroundColor(NS_RGBA(0,0,0,0)),
     mSingleItemFixedToViewport(false),
     mNeedComponentAlpha(false),
     mForceTransparentSurface(false),
@@ -392,31 +342,25 @@ public:
 
   /**
    * If this represents only a nsDisplayImage, and the image type
    * supports being optimized to an ImageLayer (TYPE_RASTER only) returns
    * an ImageContainer for the image.
    */
   already_AddRefed<ImageContainer> CanOptimizeImageLayer(nsDisplayListBuilder* aBuilder);
 
+  bool VisibleAboveRegionIntersects(const nsIntRect& aRect) const
+  { return mVisibleAboveRegion.Intersects(aRect); }
   bool VisibleAboveRegionIntersects(const nsIntRegion& aRegion) const
-  {
-    return mVisibleAboveRegion.Intersects(aRegion);
-  }
+  { return !mVisibleAboveRegion.Intersect(aRegion).IsEmpty(); }
 
   bool VisibleRegionIntersects(const nsIntRect& aRect) const
-  {
-    return IsSubjectToAsyncTransforms() || mVisibleRegion.Intersects(aRect);
-  }
-
-  bool IsSubjectToAsyncTransforms() const
-  {
-    return mFixedPosFrameForLayerData != nullptr
-        || mIsAsyncScrollable;
-  }
+  { return mVisibleRegion.Intersects(aRect); }
+  bool VisibleRegionIntersects(const nsIntRegion& aRegion) const
+  { return !mVisibleRegion.Intersect(aRegion).IsEmpty(); }
 
   /**
    * The region of visible content in the layer, relative to the
    * container layer (which is at the snapped top-left of the display
    * list reference frame).
    */
   nsIntRegion  mVisibleRegion;
   /**
@@ -463,22 +407,16 @@ public:
    * active scrolled root.
    */
   const nsIFrame* mAnimatedGeometryRoot;
   /**
    * The offset between mAnimatedGeometryRoot and the reference frame.
    */
   nsPoint mAnimatedGeometryRootOffset;
   /**
-   * Whether or not this layer is async scrollable. If it is, that means display
-   * items above this layer should not end up in a layer below this one, as they
-   * might be obscured when they shouldn't be.
-   */
-  bool mIsAsyncScrollable;
-  /**
    * If non-null, the frame from which we'll extract "fixed positioning"
    * metadata for this layer. This can be a position:fixed frame or a viewport
    * frame; the latter case is used for background-attachment:fixed content.
    */
   const nsIFrame* mFixedPosFrameForLayerData;
   const nsIFrame* mReferenceFrame;
   PaintedLayer* mLayer;
   /**
@@ -557,22 +495,20 @@ public:
    */
   void UpdateCommonClipCount(const DisplayItemClip& aCurrentClip);
   /**
    * The union of all the bounds of the display items in this layer.
    */
   nsIntRect mBounds;
   /**
    * The region of visible content above the layer and below the
-   * next PaintedLayerData currently in the stack, if any. Note that not
-   * all PaintedLayers for the container are in the PaintedLayerData stack.
-   * Same coordinate system as mVisibleRegion.
+   * next PaintedLayerData currently in the stack, if any.
    * This is a conservative approximation: it contains the true region.
    */
-  PossiblyInfiniteRegion mVisibleAboveRegion;
+  nsIntRegion mVisibleAboveRegion;
   /**
    * All the display items that have been assigned to this painted layer.
    * These items get added by Accumulate().
    */
   nsTArray<AssignedDisplayItem> mAssignedDisplayItems;
 
 };
 
@@ -613,16 +549,323 @@ struct NewLayerEntry {
   // mAnimatedGeometryRoot->GetParent().
   bool mOpaqueForAnimatedGeometryRootParent;
 
   // If true, then the content flags for this layer should contribute
   // to our decision to flatten component alpha layers, false otherwise.
   bool mPropagateComponentAlphaFlattening;
 };
 
+class PaintedLayerDataTree;
+
+/**
+ * This is tree node type for PaintedLayerDataTree.
+ * Each node corresponds to a different animated geometry root, and contains
+ * a stack of PaintedLayerDatas, in bottom-to-top order.
+ * There is at most one node per animated geometry root. The ancestor and
+ * descendant relations in PaintedLayerDataTree tree mirror those in the frame
+ * tree.
+ * Each node can have clip that describes the potential extents that items in
+ * this node can cover. If mHasClip is false, it means that the node's contents
+ * can move anywhere.
+ * Testing against the clip instead of the node's actual contents has the
+ * advantage that the node's contents can move or animate without affecting
+ * content in other nodes. So we don't need to re-layerize during animations
+ * (sync or async), and during async animations everything is guaranteed to
+ * look correct.
+ * The contents of a node's PaintedLayerData stack all share the node's
+ * animated geometry root. The child nodes are on top of the PaintedLayerData
+ * stack, in z-order, and the clip rects of the child nodes are allowed to
+ * intersect with the visible region or visible above region of their parent
+ * node's PaintedLayerDatas.
+ */
+class PaintedLayerDataNode {
+public:
+  PaintedLayerDataNode(PaintedLayerDataTree& aTree,
+                       PaintedLayerDataNode* aParent,
+                       const nsIFrame* aAnimatedGeometryRoot);
+  ~PaintedLayerDataNode();
+
+  const nsIFrame* AnimatedGeometryRoot() const { return mAnimatedGeometryRoot; }
+
+  /**
+   * Whether this node's contents can potentially intersect aRect.
+   * aRect is in our tree's ContainerState's coordinate space.
+   */
+  bool Intersects(const nsIntRect& aRect) const
+    { return !mHasClip || mClipRect.Intersects(aRect); }
+
+  /**
+   * Create a PaintedLayerDataNode for aAnimatedGeometryRoot, add it to our
+   * children, and return it.
+   */
+  PaintedLayerDataNode* AddChildNodeFor(const nsIFrame* aAnimatedGeometryRoot);
+
+  /**
+   * Find a PaintedLayerData in our mPaintedLayerDataStack that aItem can be
+   * added to. Creates a new PaintedLayerData by calling
+   * aNewPaintedLayerCallback if necessary.
+   */
+  template<typename NewPaintedLayerCallbackType>
+  PaintedLayerData* FindPaintedLayerFor(const nsIntRect& aVisibleRect,
+                                        NewPaintedLayerCallbackType aNewPaintedLayerCallback);
+
+  /**
+   * Find an opaque background color for aRegion. Pulls a color from the parent
+   * geometry root if appropriate, but only if that color is present underneath
+   * the whole clip of this node, so that this node's contents can animate or
+   * move (possibly async) without having to change the background color.
+   * @param aUnderIndex Searching will start in mPaintedLayerDataStack right
+   *                    below aUnderIndex.
+   */
+  enum { ABOVE_TOP = -1 };
+  nscolor FindOpaqueBackgroundColor(const nsIntRegion& aRegion,
+                                    int32_t aUnderIndex = ABOVE_TOP) const;
+  /**
+   * Same as FindOpaqueBackgroundColor, but only returns a color if absolutely
+   * nothing is in between, so that it can be used for a layer that can move
+   * anywhere inside our clip.
+   */
+  nscolor FindOpaqueBackgroundColorCoveringEverything() const;
+
+  /**
+   * Adds aRect to this node's top PaintedLayerData's mVisibleAboveRegion,
+   * or mVisibleAboveBackgroundRegion if mPaintedLayerDataStack is empty.
+   */
+  void AddToVisibleAboveRegion(const nsIntRect& aRect);
+  /**
+   * Call this if all of our existing content can potentially be covered, so
+   * nothing can merge with it and all new content needs to create new items
+   * on top. This will finish all of our children and pop our whole
+   * mPaintedLayerDataStack.
+   */
+  void SetAllDrawingAbove();
+
+  /**
+   * Finish this node: Finish all children, finish our PaintedLayer contents,
+   * and (if requested) adjust our parent's visible above region to include
+   * our clip.
+   */
+  void Finish(bool aParentNeedsAccurateVisibleAboveRegion);
+
+  /**
+   * Finish any children that intersect aRect.
+   */
+  void FinishChildrenIntersecting(const nsIntRect& aRect);
+
+  /**
+   * Finish all children.
+   */
+  void FinishAllChildren() { FinishAllChildren(true); }
+
+protected:
+  /**
+   * Finish the topmost item in mPaintedLayerDataStack and pop it from the
+   * stack.
+   */
+  void PopPaintedLayerData();
+  /**
+   * Finish all items in mPaintedLayerDataStack and clear the stack.
+   */
+  void PopAllPaintedLayerData();
+  /**
+   * Finish all of our child nodes, but don't touch mPaintedLayerDataStack.
+   */
+  void FinishAllChildren(bool aThisNodeNeedsAccurateVisibleAboveRegion);
+  /**
+   * Pass off opaque background color searching to our parent node, if we have
+   * one.
+   */
+  nscolor FindOpaqueBackgroundColorInParentNode() const;
+
+  PaintedLayerDataTree& mTree;
+  PaintedLayerDataNode* mParent;
+  const nsIFrame* mAnimatedGeometryRoot;
+
+  /**
+   * Our contents: a PaintedLayerData stack and our child nodes.
+   */
+  nsTArray<PaintedLayerData> mPaintedLayerDataStack;
+
+  /**
+   * UniquePtr is used here in the sense of "unique ownership", i.e. there is
+   * only one owner. Not in the sense of "this is the only pointer to the
+   * node": There are two other, non-owning, pointers to our child nodes: The
+   * node's respective children point to their parent node with their mParent
+   * pointer, and the tree keeps a map of animated geometry root to node in its
+   * mNodes member. These outside pointers are the reason that mChildren isn't
+   * just an nsTArray<PaintedLayerDataNode> (since the pointers would become
+   * invalid whenever the array expands its capacity).
+   */
+  nsTArray<UniquePtr<PaintedLayerDataNode>> mChildren;
+
+  /**
+   * The region that's covered between our "background" and the bottom of
+   * mPaintedLayerDataStack. This is used to indicate whether we can pull
+   * a background color from our parent node. If mVisibleAboveBackgroundRegion
+   * should be considered infinite, mAllDrawingAboveBackground will be true and
+   * the value of mVisibleAboveBackgroundRegion will be meaningless.
+   */
+  nsIntRegion mVisibleAboveBackgroundRegion;
+
+  /**
+   * Our clip, if we have any. If not, that means we can move anywhere, and
+   * mHasClip will be false and mClipRect will be meaningless.
+   */
+  nsIntRect mClipRect;
+  bool mHasClip;
+
+  /**
+   * Whether mVisibleAboveBackgroundRegion should be considered infinite.
+   */
+  bool mAllDrawingAboveBackground;
+};
+
+class ContainerState;
+
+/**
+ * A tree of PaintedLayerDataNodes. At any point in time, the tree only
+ * contains nodes for animated geometry roots that new items can potentially
+ * merge into. Any time content is added on top that overlaps existing things
+ * in such a way that we no longer want to merge new items with some existing
+ * content, that existing content gets "finished".
+ * The public-facing methods of this class are FindPaintedLayerFor,
+ * AddingOwnLayer, and Finish. The other public methods are for
+ * PaintedLayerDataNode.
+ * The tree calls out to its containing ContainerState for some things.
+ * All coordinates / rects in the tree or the tree nodes are in the
+ * ContainerState's coordinate space, i.e. relative to the reference frame and
+ * in layer pixels.
+ * The clip rects of sibling nodes never overlap. This is ensured by finishing
+ * existing nodes before adding new ones, if this property were to be violated.
+ * The root tree node doesn't get finished until the ContainerState is
+ * finished.
+ * The tree's root node is always the root reference frame of the builder. We
+ * don't stop at the container state's mContainerAnimatedGeometryRoot because
+ * some of our contents can have animated geometry roots that are not
+ * descendants of the container's animated geometry root. Every animated
+ * geometry root we encounter for our contents needs to have a defined place in
+ * the tree.
+ */
+class PaintedLayerDataTree {
+public:
+  PaintedLayerDataTree(ContainerState& aContainerState,
+                       nscolor& aBackgroundColor)
+    : mContainerState(aContainerState)
+    , mContainerUniformBackgroundColor(aBackgroundColor)
+  {}
+
+  ~PaintedLayerDataTree()
+  {
+    MOZ_ASSERT(!mRoot);
+    MOZ_ASSERT(mNodes.Count() == 0);
+  }
+
+  /**
+   * Notify our contents that some non-PaintedLayer content has been added.
+   * *aRect needs to be a rectangle that doesn't move with respect to
+   * aAnimatedGeometryRoot and that contains the added item.
+   * If aRect is null, the extents will be considered infinite.
+   * If aOutUniformBackgroundColor is non-null, it will be set to an opaque
+   * color that can be pulled into the background of the added content, or
+   * transparent if that is not possible.
+   */
+  void AddingOwnLayer(const nsIFrame* aAnimatedGeometryRoot,
+                      const nsIntRect* aRect,
+                      nscolor* aOutUniformBackgroundColor);
+
+  /**
+   * Find a PaintedLayerData for aItem. This can either be an existing
+   * PaintedLayerData from inside a node in our tree, or a new one that gets
+   * created by a call out to aNewPaintedLayerCallback.
+   */
+  template<typename NewPaintedLayerCallbackType>
+  PaintedLayerData* FindPaintedLayerFor(const nsIFrame* aAnimatedGeometryRoot,
+                                        const nsIntRect& aVisibleRect,
+                                        bool aShouldFixToViewport,
+                                        NewPaintedLayerCallbackType aNewPaintedLayerCallback);
+
+  /**
+   * Finish everything.
+   */
+  void Finish();
+
+  /**
+   * Get the parent animated geometry root of aAnimatedGeometryRoot.
+   * That's either aAnimatedGeometryRoot's animated geometry root, or, if
+   * that's aAnimatedGeometryRoot itself, then it's the animated geometry
+   * root for aAnimatedGeometryRoot's cross-doc parent frame.
+   */
+  const nsIFrame* GetParentAnimatedGeometryRoot(const nsIFrame* aAnimatedGeometryRoot);
+
+  /**
+   * Whether aAnimatedGeometryRoot has an intrinsic clip that doesn't move with
+   * respect to aAnimatedGeometryRoot's parent animated geometry root.
+   * If aAnimatedGeometryRoot is a scroll frame, this will be the scroll frame's
+   * scroll port, otherwise there is no clip.
+   * This method doesn't have much to do with PaintedLayerDataTree, but this is
+   * where we have easy access to a display list builder, which we use to get
+   * the clip rect result into the right coordinate space.
+   */
+  bool IsClippedWithRespectToParentAnimatedGeometryRoot(const nsIFrame* aAnimatedGeometryRoot,
+                                                        nsIntRect* aOutClip);
+
+  /**
+   * Called by PaintedLayerDataNode when it is finished, so that we can drop
+   * our pointers to it.
+   */
+  void NodeWasFinished(const nsIFrame* aAnimatedGeometryRoot);
+
+  nsDisplayListBuilder* Builder() const;
+  ContainerState& ContState() const { return mContainerState; }
+  nscolor UniformBackgroundColor() const { return mContainerUniformBackgroundColor; }
+
+protected:
+  /**
+   * Finish all nodes that potentially intersect *aRect, where *aRect is a rect
+   * that doesn't move with respect to aAnimatedGeometryRoot.
+   * If aRect is null, *aRect will be considered infinite.
+   */
+  void FinishPotentiallyIntersectingNodes(const nsIFrame* aAnimatedGeometryRoot,
+                                          const nsIntRect* aRect);
+
+  /**
+   * Make sure that there is a node for aAnimatedGeometryRoot and all of its
+   * ancestor geometry roots. Return the node for aAnimatedGeometryRoot.
+   */
+  PaintedLayerDataNode* EnsureNodeFor(const nsIFrame* aAnimatedGeometryRoot);
+
+  /**
+   * Find an existing node in the tree for an ancestor of aAnimatedGeometryRoot.
+   * *aOutAncestorChild will be set to the last ancestor that was encountered
+   * in the search up from aAnimatedGeometryRoot; it will be a child animated
+   * geometry root of the result, if neither are null.
+   */
+  PaintedLayerDataNode*
+    FindNodeForAncestorAnimatedGeometryRoot(const nsIFrame* aAnimatedGeometryRoot,
+                                            const nsIFrame** aOutAncestorChild);
+
+  ContainerState& mContainerState;
+  UniquePtr<PaintedLayerDataNode> mRoot;
+
+  /**
+   * The uniform opaque color from behind this container layer, or
+   * NS_RGBA(0,0,0,0) if the background behind this container layer is not
+   * uniform and opaque. This color can be pulled into PaintedLayers that are
+   * directly above the background.
+   */
+  nscolor mContainerUniformBackgroundColor;
+
+  /**
+   * A hash map for quick access the node belonging to a particular animated
+   * geometry root.
+   */
+  nsDataHashtable<nsPtrHashKey<const nsIFrame>, PaintedLayerDataNode*> mNodes;
+};
+
 /**
  * This is a helper object used to build up the layer children for
  * a ContainerLayer.
  */
 class ContainerState {
 public:
   ContainerState(nsDisplayListBuilder* aBuilder,
                  LayerManager* aManager,
@@ -635,27 +878,31 @@ public:
                  bool aFlattenToSingleLayer,
                  nscolor aBackgroundColor) :
     mBuilder(aBuilder), mManager(aManager),
     mLayerBuilder(aLayerBuilder),
     mContainerFrame(aContainerFrame),
     mContainerLayer(aContainerLayer),
     mContainerBounds(aContainerBounds),
     mParameters(aParameters),
-    mContainerUniformBackgroundColor(aBackgroundColor),
+    mPaintedLayerDataTree(*this, aBackgroundColor),
     mFlattenToSingleLayer(aFlattenToSingleLayer)
   {
     nsPresContext* presContext = aContainerFrame->PresContext();
     mAppUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
     mContainerReferenceFrame =
       const_cast<nsIFrame*>(aContainerItem ? aContainerItem->ReferenceFrameForChildren() :
                                              mBuilder->FindReferenceFrameFor(mContainerFrame));
-    mContainerAnimatedGeometryRoot = aContainerItem
-      ? nsLayoutUtils::GetAnimatedGeometryRootFor(aContainerItem, aBuilder, aManager)
-      : mContainerReferenceFrame;
+    bool isAtRoot = !aContainerItem || (aContainerItem->Frame() == mBuilder->RootReferenceFrame());
+    MOZ_ASSERT_IF(isAtRoot, mContainerReferenceFrame == mBuilder->RootReferenceFrame());
+    mContainerAnimatedGeometryRoot = isAtRoot
+      ? mContainerReferenceFrame
+      : nsLayoutUtils::GetAnimatedGeometryRootFor(aContainerItem, aBuilder, aManager);
+    MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(mBuilder->RootReferenceFrame(),
+                                                      mContainerAnimatedGeometryRoot));
     NS_ASSERTION(!aContainerItem || !aContainerItem->ShouldFixToViewport(aManager),
                  "Container items never return true for ShouldFixToViewport");
     mContainerFixedPosFrame =
         FindFixedPosFrameForLayerData(mContainerAnimatedGeometryRoot, false);
     // When AllowResidualTranslation is false, display items will be drawn
     // scaled with a translation by integer pixels, so we know how the snapping
     // will work.
     mSnappingEnabled = aManager->IsSnappingEffectiveTransforms() &&
@@ -682,62 +929,63 @@ public:
    * set *aTextContentFlags to CONTENT_COMPONENT_ALPHA
    */
   void Finish(uint32_t *aTextContentFlags, LayerManagerData* aData,
               const nsIntRect& aContainerPixelBounds,
               nsDisplayList* aChildItems, bool& aHasComponentAlphaChildren);
 
   nscoord GetAppUnitsPerDevPixel() { return mAppUnitsPerDevPixel; }
 
-  nsIntRect ScaleToNearestPixels(const nsRect& aRect)
+  nsIntRect ScaleToNearestPixels(const nsRect& aRect) const
   {
     return aRect.ScaleToNearestPixels(mParameters.mXScale, mParameters.mYScale,
                                       mAppUnitsPerDevPixel);
   }
-  nsIntRegion ScaleRegionToNearestPixels(const nsRegion& aRegion)
+  nsIntRegion ScaleRegionToNearestPixels(const nsRegion& aRegion) const
   {
     return aRegion.ScaleToNearestPixels(mParameters.mXScale, mParameters.mYScale,
                                         mAppUnitsPerDevPixel);
   }
-  nsIntRect ScaleToOutsidePixels(const nsRect& aRect, bool aSnap = false)
+  nsIntRect ScaleToOutsidePixels(const nsRect& aRect, bool aSnap = false) const
   {
     if (aSnap && mSnappingEnabled) {
       return ScaleToNearestPixels(aRect);
     }
     return aRect.ScaleToOutsidePixels(mParameters.mXScale, mParameters.mYScale,
                                       mAppUnitsPerDevPixel);
   }
-  nsIntRect ScaleToInsidePixels(const nsRect& aRect, bool aSnap = false)
+  nsIntRect ScaleToInsidePixels(const nsRect& aRect, bool aSnap = false) const
   {
     if (aSnap && mSnappingEnabled) {
       return ScaleToNearestPixels(aRect);
     }
     return aRect.ScaleToInsidePixels(mParameters.mXScale, mParameters.mYScale,
                                      mAppUnitsPerDevPixel);
   }
 
-  nsIntRegion ScaleRegionToInsidePixels(const nsRegion& aRegion, bool aSnap = false)
+  nsIntRegion ScaleRegionToInsidePixels(const nsRegion& aRegion, bool aSnap = false) const
   {
     if (aSnap && mSnappingEnabled) {
       return ScaleRegionToNearestPixels(aRegion);
     }
     return aRegion.ScaleToInsidePixels(mParameters.mXScale, mParameters.mYScale,
                                         mAppUnitsPerDevPixel);
   }
 
-  nsIntRegion ScaleRegionToOutsidePixels(const nsRegion& aRegion, bool aSnap = false)
+  nsIntRegion ScaleRegionToOutsidePixels(const nsRegion& aRegion, bool aSnap = false) const
   {
     if (aSnap && mSnappingEnabled) {
       return ScaleRegionToNearestPixels(aRegion);
     }
     return aRegion.ScaleToOutsidePixels(mParameters.mXScale, mParameters.mYScale,
                                         mAppUnitsPerDevPixel);
   }
 
   nsIFrame* GetContainerFrame() const { return mContainerFrame; }
+  nsDisplayListBuilder* Builder() const { return mBuilder; }
 
   /**
    * Sets aOuterVisibleRegion as aLayer's visible region. aOuterVisibleRegion
    * is in the coordinate space of the container reference frame.
    * aLayerContentsVisibleRect, if non-null, is in the layer's own
    * coordinate system.
    */
   void SetOuterVisibleRegionForLayer(Layer* aLayer,
@@ -749,16 +997,35 @@ public:
     mHoistedItems.AppendElement(aItem);
   }
 
   void AddHoistedItems(const nsTArray<nsDisplayScrollInfoLayer*>& aItems)
   {
     mHoistedItems.AppendElements(aItems);
   }
 
+  /**
+   * Try to determine whether the PaintedLayer aData has a single opaque color
+   * covering aRect. If successful, return that color, otherwise return
+   * NS_RGBA(0,0,0,0).
+   * If aRect turns out not to intersect any content in the layer,
+   * *aOutIntersectsLayer will be set to false.
+   */
+  nscolor FindOpaqueBackgroundColorInLayer(const PaintedLayerData* aData,
+                                           const nsIntRect& aRect,
+                                           bool* aOutIntersectsLayer) const;
+
+  /**
+   * Indicate that we are done adding items to the PaintedLayer represented by
+   * aData. Make sure that a real PaintedLayer exists for it, and set the final
+   * visible region and opaque-content.
+   */
+  template<typename FindOpaqueBackgroundColorCallbackType>
+  void FinishPaintedLayerData(PaintedLayerData& aData, FindOpaqueBackgroundColorCallbackType aFindOpaqueBackgroundColor);
+
 protected:
   friend class PaintedLayerData;
 
   LayerManager::PaintedLayerCreationHint
     GetLayerCreationHint(const nsIFrame* aAnimatedGeometryRoot);
 
   /**
    * Creates a new PaintedLayer and sets up the transform on the PaintedLayer
@@ -814,27 +1081,16 @@ protected:
   void CollectOldLayers();
   /**
    * If aItem used to belong to a PaintedLayer, invalidates the area of
    * aItem in that layer. If aNewLayer is a PaintedLayer, invalidates the area of
    * aItem in that layer.
    */
   void InvalidateForLayerChange(nsDisplayItem* aItem,
                                 PaintedLayer* aNewLayer);
-
-  /**
-   * Try to determine whether a layer with visible region aTargetVisibleRegion
-   * has a single opaque color behind it, over the entire bounds of its visible
-   * region. The target layer is assumed to be on top of all thebes layers in
-   * the thebes layer data stack that have a stack index < aUnderPaintedLayerIndex.
-   * If successful, return the color, otherwise return NS_RGBA(0,0,0,0).
-   * aTargetVisibleRegion is relative to the the container reference frame.
-   */
-  nscolor FindOpaqueBackgroundColorFor(const nsIntRegion& aTargetVisibleRegion,
-                                       int32_t aUnderPaintedLayerIndex);
   /**
    * Find the fixed-pos frame, if any, containing (or equal to)
    * aAnimatedGeometryRoot. Only return a fixed-pos frame if its viewport
    * has a displayport.
    * aDisplayItemFixedToViewport is true if the layer contains a single display
    * item which returned true for ShouldFixToViewport.
    * This can return the actual viewport frame for layers whose display items
    * are directly on the viewport (e.g. background-attachment:fixed backgrounds).
@@ -881,58 +1137,33 @@ protected:
                                 const nsIFrame* aAnimatedGeometryRoot,
                                 const nsIFrame* aFixedPosFrame,
                                 const DisplayItemClip& aClip,
                                 nsDisplayList* aList,
                                 bool* aHideAllLayersBelow,
                                 bool* aOpaqueForAnimatedGeometryRootParent);
 
   /**
-   * Indicate that we are done adding items to the PaintedLayer at the top of
-   * mPaintedLayerDataStack. Set the final visible region and opaque-content
-   * flag, and pop it off the stack.
-   */
-  void PopPaintedLayerData();
-  /**
-   * Check if any of the animated geometry roots from aAnimatedGeometryRoot up
-   * to and including mContainerAnimatedGeometryRoot are async scrollable. If
-   * so, return true. This is used to flag a particular PaintedLayer as being
-   * subject to async transforms.
+   * Return a PaintedLayerData object that is initialized for a layer that
+   * aItem will be assigned to.
+   * @param  aItem                 The item that is going to be added.
+   * @param  aVisibleRect          The visible rect of the item.
+   * @param  aAnimatedGeometryRoot The item's animated geometry root.
+   * @param  aTopLeft              The offset between aAnimatedGeometryRoot and
+   *                               the reference frame.
+   * @param aShouldFixToViewport   If true, aAnimatedGeometryRoot is the
+   *                               viewport and we will be adding fixed-pos
+   *                               metadata for this layer because the display
+   *                               item returned true from ShouldFixToViewport.
    */
-  bool HasAsyncScrollableGeometryInContainer(const nsIFrame* aAnimatedGeometryRoot);
-  /**
-   * Find the PaintedLayer to which we should assign the next display item.
-   * We scan the PaintedLayerData stack to find the topmost PaintedLayer
-   * that is compatible with the display item (i.e., has the same
-   * active scrolled root), and that has no content from other layers above
-   * it and intersecting the aVisibleRect.
-   * Returns the layer, and also updates the PaintedLayerData. Will
-   * push a new PaintedLayerData onto the stack if no suitable existing
-   * layer is found. If we choose a PaintedLayer that's already on the
-   * PaintedLayerData stack, later elements on the stack will be popped off.
-   * @param aVisibleRect the area of the next display item that's visible
-   * @param aAnimatedGeometryRoot the active scrolled root for the next
-   * display item
-   * @param aOpaqueRect if non-null, a region of the display item that is opaque
-   * @param aSolidColor if non-null, indicates that every pixel in aVisibleRect
-   * will be painted with aSolidColor by the item
-   * @param aShouldFixToViewport if true, aAnimatedGeometryRoot is the viewport
-   * and we will be adding fixed-pos metadata for this layer because the
-   * display item returned true from ShouldFixToViewport.
-   */
-  PaintedLayerData* FindPaintedLayerFor(nsDisplayItem* aItem,
-                                      const nsIntRect& aVisibleRect,
-                                      const nsIFrame* aAnimatedGeometryRoot,
-                                      const nsPoint& aTopLeft,
-                                      bool aShouldFixToViewport);
-  PaintedLayerData* GetTopPaintedLayerData()
-  {
-    return mPaintedLayerDataStack.IsEmpty() ? nullptr
-        : mPaintedLayerDataStack[mPaintedLayerDataStack.Length() - 1].get();
-  }
+  PaintedLayerData NewPaintedLayerData(nsDisplayItem* aItem,
+                                       const nsIntRect& aVisibleRect,
+                                       const nsIFrame* aAnimatedGeometryRoot,
+                                       const nsPoint& aTopLeft,
+                                       bool aShouldFixToViewport);
 
   /* Build a mask layer to represent the clipping region. Will return null if
    * there is no clipping specified or a mask layer cannot be built.
    * Builds an ImageLayer for the appropriate backend; the mask is relative to
    * aLayer's visible region.
    * aLayer is the layer to be clipped.
    * aLayerVisibleRegion is the region that will be set as aLayer's visible region,
    * relative to the container reference frame
@@ -942,85 +1173,48 @@ protected:
    */
   void SetupMaskLayer(Layer *aLayer, const DisplayItemClip& aClip,
                       const nsIntRegion& aLayerVisibleRegion,
                       uint32_t aRoundedRectClipCount = UINT32_MAX);
 
   bool ChooseAnimatedGeometryRoot(const nsDisplayList& aList,
                                   const nsIFrame **aAnimatedGeometryRoot);
 
-  /**
-   * When adding a new layer above the topmost PaintedLayerData layer in our
-   * PaintedLayerDataStack, update the visible above region of the topmost
-   * PaintedLayerData item.
-   * @param aVisibleRect   The visible rect of the newly-added display item
-   * @param aCanMoveFreely Whether the visible area of the item can change
-   *                       without new layer building.
-   * @param aClipRectIfAny A clip rect, if the layer is clipped, or nullptr.
-   */
-  void UpdateVisibleAboveRegionForNewItem(const nsIntRect& aVisibleRect,
-                                          bool aCanMoveFreely,
-                                          const nsIntRect* aClipRectIfAny);
-
-  /**
-   * When popping aData from the PaintedLayerDataStack, update the next
-   * PaintedLayerData item's visible above region to take the popped layer
-   * into account.
-   * @param aData                 The layer data that is getting popped from
-   *                              the stack.
-   * @param aNextPaintedLayerData The next lower item in the stack, or nullptr
-   *                              if there is none.
-   */
-  void UpdateVisibleAboveRegionOnPop(PaintedLayerData* aData,
-                                     PaintedLayerData* aNextPaintedLayerData);
-
   nsDisplayListBuilder*            mBuilder;
   LayerManager*                    mManager;
   FrameLayerBuilder*               mLayerBuilder;
   nsIFrame*                        mContainerFrame;
   nsIFrame*                        mContainerReferenceFrame;
   const nsIFrame*                  mContainerAnimatedGeometryRoot;
   const nsIFrame*                  mContainerFixedPosFrame;
   ContainerLayer*                  mContainerLayer;
   nsRect                           mContainerBounds;
   DebugOnly<nsRect>                mAccumulatedChildBounds;
   ContainerLayerParameters         mParameters;
   /**
    * The region of PaintedLayers that should be invalidated every time
    * we recycle one.
    */
   nsIntRegion                      mInvalidPaintedContent;
-  nsAutoTArray<nsAutoPtr<PaintedLayerData>,1>  mPaintedLayerDataStack;
+  PaintedLayerDataTree             mPaintedLayerDataTree;
   /**
    * We collect the list of children in here. During ProcessDisplayItems,
    * the layers in this array either have mContainerLayer as their parent,
    * or no parent.
    * PaintedLayers have two entries in this array: the second one is used only if
    * the PaintedLayer is optimized away to a ColorLayer or ImageLayer.
    * It's essential that this array is only appended to, since PaintedLayerData
    * records the index of its PaintedLayer in this array.
    */
   typedef nsAutoTArray<NewLayerEntry,1> AutoLayersArray;
   AutoLayersArray                  mNewChildLayers;
   nsTHashtable<nsRefPtrHashKey<PaintedLayer>> mPaintedLayersAvailableForRecycling;
   nsDataHashtable<nsPtrHashKey<Layer>, nsRefPtr<ImageLayer> >
     mRecycledMaskImageLayers;
-  /**
-   * The visible region of all visible content in this container layer under
-   * first PaintedLayerData layer in the PaintedLayerDataStack.
-   */
-  PossiblyInfiniteRegion           mVisibleAboveBackgroundRegion;
   nscoord                          mAppUnitsPerDevPixel;
-  /**
-   * The uniform opaque color from behind this container layer, or
-   * NS_RGBA(0,0,0,0) if the background behind this container layer is not
-   * uniform and opaque. This color can be pulled into PaintedLayers that are
-   * directly above the background.
-   */
-  nscolor                          mContainerUniformBackgroundColor;
   bool                             mSnappingEnabled;
   bool                             mFlattenToSingleLayer;
   /**
    * In some cases we need to hoist nsDisplayScrollInfoLayer items out from a
    * nested inactive container. This holds the items hoisted up from children.
    */
   nsTArray<nsDisplayScrollInfoLayer*> mHoistedItems;
 };
@@ -2020,85 +2214,138 @@ ContainerState::SetOuterVisibleRegionFor
                                               const nsIntRect* aLayerContentsVisibleRect) const
 {
   nsIntRegion visRegion = aOuterVisibleRegion;
   visRegion.MoveBy(mParameters.mOffset);
   SetOuterVisibleRegion(aLayer, &visRegion, aLayerContentsVisibleRect);
 }
 
 nscolor
-ContainerState::FindOpaqueBackgroundColorFor(const nsIntRegion& aTargetVisibleRegion,
-                                             int32_t aUnderPaintedLayerIndex)
+ContainerState::FindOpaqueBackgroundColorInLayer(const PaintedLayerData* aData,
+                                                 const nsIntRect& aRect,
+                                                 bool* aOutIntersectsLayer) const
 {
-  for (int32_t i = aUnderPaintedLayerIndex - 1; i >= 0; --i) {
-    PaintedLayerData* candidate = mPaintedLayerDataStack[i];
+  *aOutIntersectsLayer = true;
+
+  // Scan the candidate's display items.
+  nsIntRect deviceRect = aRect;
+  nsRect appUnitRect = deviceRect.ToAppUnits(mAppUnitsPerDevPixel);
+  appUnitRect.ScaleInverseRoundOut(mParameters.mXScale, mParameters.mYScale);
+
+  for (auto& assignedItem : Reversed(aData->mAssignedDisplayItems)) {
+    nsDisplayItem* item = assignedItem.mItem;
+    bool snap;
+    nsRect bounds = item->GetBounds(mBuilder, &snap);
+    if (snap && mSnappingEnabled) {
+      nsIntRect snappedBounds = ScaleToNearestPixels(bounds);
+      if (!snappedBounds.Intersects(deviceRect))
+        continue;
+
+      if (!snappedBounds.Contains(deviceRect))
+        return NS_RGBA(0,0,0,0);
+
+    } else {
+      // The layer's visible rect is already (close enough to) pixel
+      // aligned, so no need to round out and in here.
+      if (!bounds.Intersects(appUnitRect))
+        continue;
+
+      if (!bounds.Contains(appUnitRect))
+        return NS_RGBA(0,0,0,0);
+    }
+
+    if (item->IsInvisibleInRect(appUnitRect)) {
+      continue;
+    }
+
+    if (assignedItem.mClip.IsRectAffectedByClip(deviceRect,
+                                                mParameters.mXScale,
+                                                mParameters.mYScale,
+                                                mAppUnitsPerDevPixel)) {
+      return NS_RGBA(0,0,0,0);
+    }
+
+    nscolor color;
+    if (item->IsUniform(mBuilder, &color) && NS_GET_A(color) == 255)
+      return color;
+
+    return NS_RGBA(0,0,0,0);
+  }
+
+  *aOutIntersectsLayer = false;
+  return NS_RGBA(0,0,0,0);
+}
+
+nscolor
+PaintedLayerDataNode::FindOpaqueBackgroundColor(const nsIntRegion& aTargetVisibleRegion,
+                                                int32_t aUnderIndex) const
+{
+  if (aUnderIndex == ABOVE_TOP) {
+    aUnderIndex = mPaintedLayerDataStack.Length();
+  }
+  for (int32_t i = aUnderIndex - 1; i >= 0; --i) {
+    const PaintedLayerData* candidate = &mPaintedLayerDataStack[i];
     if (candidate->VisibleAboveRegionIntersects(aTargetVisibleRegion)) {
       // Some non-PaintedLayer content between target and candidate; this is
       // hopeless
       return NS_RGBA(0,0,0,0);
     }
 
-    nsIntRegion intersection;
-    intersection.And(candidate->mVisibleRegion, aTargetVisibleRegion);
-    if (intersection.IsEmpty()) {
+    if (!candidate->VisibleRegionIntersects(aTargetVisibleRegion)) {
       // The layer doesn't intersect our target, ignore it and move on
       continue;
     }
 
-    // The candidate intersects our target. If any layer has a solid-color
-    // area behind our target, this must be it. Scan its display items.
-    nsIntRect deviceRect = aTargetVisibleRegion.GetBounds();
-    nsRect appUnitRect = deviceRect.ToAppUnits(mAppUnitsPerDevPixel);
-    appUnitRect.ScaleInverseRoundOut(mParameters.mXScale, mParameters.mYScale);
-
-    for (auto& assignedItem : Reversed(candidate->mAssignedDisplayItems)) {
-      nsDisplayItem* item = assignedItem.mItem;
-      bool snap;
-      nsRect bounds = item->GetBounds(mBuilder, &snap);
-      if (snap && mSnappingEnabled) {
-        nsIntRect snappedBounds = ScaleToNearestPixels(bounds);
-        if (!snappedBounds.Intersects(deviceRect))
-          continue;
-
-        if (!snappedBounds.Contains(deviceRect))
-          return NS_RGBA(0,0,0,0);
-
-      } else {
-        // The layer's visible rect is already (close enough to) pixel
-        // aligned, so no need to round out and in here.
-        if (!bounds.Intersects(appUnitRect))
-          continue;
-
-        if (!bounds.Contains(appUnitRect))
-          return NS_RGBA(0,0,0,0);
-      }
-
-      if (item->IsInvisibleInRect(appUnitRect)) {
-        continue;
-      }
-
-      if (item->GetClip().IsRectAffectedByClip(deviceRect,
-                                               mParameters.mXScale,
-                                               mParameters.mYScale,
-                                               mAppUnitsPerDevPixel)) {
-        return NS_RGBA(0,0,0,0);
-      }
-
-      nscolor color;
-      if (item->IsUniform(mBuilder, &color) && NS_GET_A(color) == 255)
-        return color;
-
-      return NS_RGBA(0,0,0,0);
+    bool intersectsLayer = true;
+    nsIntRect rect = aTargetVisibleRegion.GetBounds();
+    nscolor color = mTree.ContState().FindOpaqueBackgroundColorInLayer(
+                                        candidate, rect, &intersectsLayer);
+    if (!intersectsLayer) {
+      continue;
     }
-  }
-  if (mVisibleAboveBackgroundRegion.Intersects(aTargetVisibleRegion)) {
-    // Some non-Thebes content is between container background and target.
+    return color;
+  }
+  if (mAllDrawingAboveBackground ||
+      !mVisibleAboveBackgroundRegion.Intersect(aTargetVisibleRegion).IsEmpty()) {
+    // Some non-PaintedLayer content is between this node's background and target.
+    return NS_RGBA(0,0,0,0);
+  }
+  return FindOpaqueBackgroundColorInParentNode();
+}
+
+nscolor
+PaintedLayerDataNode::FindOpaqueBackgroundColorCoveringEverything() const
+{
+  if (!mPaintedLayerDataStack.IsEmpty() ||
+      !mVisibleAboveBackgroundRegion.IsEmpty()) {
     return NS_RGBA(0,0,0,0);
   }
-  return mContainerUniformBackgroundColor;
+  return FindOpaqueBackgroundColorInParentNode();
+}
+
+nscolor
+PaintedLayerDataNode::FindOpaqueBackgroundColorInParentNode() const
+{
+  if (mParent) {
+    if (mHasClip) {
+      // Check whether our parent node has uniform content behind our whole
+      // clip.
+      // There's one tricky case here: If our parent node is also a scrollable,
+      // and is currently scrolled in such a way that this inner one is
+      // clipped by it, then it's not really clear how we should determine
+      // whether we have a uniform background in the parent: There might be
+      // non-uniform content in the parts that our scroll port covers in the
+      // parent and that are currently outside the parent's clip.
+      // For now, we'll fail to pull a background color in that case.
+      return mParent->FindOpaqueBackgroundColor(mClipRect);
+    }
+    return mParent->FindOpaqueBackgroundColorCoveringEverything();
+  }
+  // We are the root.
+  return mTree.UniformBackgroundColor();
 }
 
 void
 PaintedLayerData::UpdateCommonClipCount(
     const DisplayItemClip& aCurrentClip)
 {
   if (mCommonClipCount >= 0) {
     mCommonClipCount = mItemClip.GetCommonRoundedRectCount(aCurrentClip, mCommonClipCount);
@@ -2113,16 +2360,336 @@ PaintedLayerData::CanOptimizeImageLayer(
 {
   if (!mImage) {
     return nullptr;
   }
 
   return mImage->GetContainer(mLayer->Manager(), aBuilder);
 }
 
+PaintedLayerDataNode::PaintedLayerDataNode(PaintedLayerDataTree& aTree,
+                                           PaintedLayerDataNode* aParent,
+                                           const nsIFrame* aAnimatedGeometryRoot)
+  : mTree(aTree)
+  , mParent(aParent)
+  , mAnimatedGeometryRoot(aAnimatedGeometryRoot)
+  , mAllDrawingAboveBackground(false)
+{
+  MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(mTree.Builder()->RootReferenceFrame(), mAnimatedGeometryRoot));
+  mHasClip = mTree.IsClippedWithRespectToParentAnimatedGeometryRoot(mAnimatedGeometryRoot, &mClipRect);
+}
+
+PaintedLayerDataNode::~PaintedLayerDataNode()
+{
+  MOZ_ASSERT(mPaintedLayerDataStack.IsEmpty());
+  MOZ_ASSERT(mChildren.IsEmpty());
+}
+
+PaintedLayerDataNode*
+PaintedLayerDataNode::AddChildNodeFor(const nsIFrame* aAnimatedGeometryRoot)
+{
+  MOZ_ASSERT(mTree.GetParentAnimatedGeometryRoot(aAnimatedGeometryRoot) == mAnimatedGeometryRoot);
+  UniquePtr<PaintedLayerDataNode> child =
+    MakeUnique<PaintedLayerDataNode>(mTree, this, aAnimatedGeometryRoot);
+  mChildren.AppendElement(Move(child));
+  return mChildren.LastElement().get();
+}
+
+template<typename NewPaintedLayerCallbackType>
+PaintedLayerData*
+PaintedLayerDataNode::FindPaintedLayerFor(const nsIntRect& aVisibleRect,
+                                          NewPaintedLayerCallbackType aNewPaintedLayerCallback)
+{
+  if (!mPaintedLayerDataStack.IsEmpty()) {
+    if (mPaintedLayerDataStack[0].mSingleItemFixedToViewport) {
+      MOZ_ASSERT(mPaintedLayerDataStack.Length() == 1);
+      SetAllDrawingAbove();
+      MOZ_ASSERT(mPaintedLayerDataStack.IsEmpty());
+    } else {
+      PaintedLayerData* lowestUsableLayer = nullptr;
+      for (auto& data : Reversed(mPaintedLayerDataStack)) {
+        if (data.VisibleAboveRegionIntersects(aVisibleRect)) {
+          break;
+        }
+        MOZ_ASSERT(!data.mSingleItemFixedToViewport);
+        lowestUsableLayer = &data;
+        if (data.VisibleRegionIntersects(aVisibleRect)) {
+          break;
+        }
+      }
+      if (lowestUsableLayer) {
+        return lowestUsableLayer;
+      }
+    }
+  }
+  return mPaintedLayerDataStack.AppendElement(aNewPaintedLayerCallback());
+}
+
+void
+PaintedLayerDataNode::FinishChildrenIntersecting(const nsIntRect& aRect)
+{
+  for (int32_t i = mChildren.Length() - 1; i >= 0; i--) {
+    if (mChildren[i]->Intersects(aRect)) {
+      mChildren[i]->Finish(true);
+      mChildren.RemoveElementAt(i);
+    }
+  }
+}
+
+void
+PaintedLayerDataNode::FinishAllChildren(bool aThisNodeNeedsAccurateVisibleAboveRegion)
+{
+  for (int32_t i = mChildren.Length() - 1; i >= 0; i--) {
+    mChildren[i]->Finish(aThisNodeNeedsAccurateVisibleAboveRegion);
+  }
+  mChildren.Clear();
+}
+
+void
+PaintedLayerDataNode::Finish(bool aParentNeedsAccurateVisibleAboveRegion)
+{
+  // Skip "visible above region" maintenance, because this node is going away.
+  FinishAllChildren(false);
+
+  PopAllPaintedLayerData();
+
+  if (mParent && aParentNeedsAccurateVisibleAboveRegion) {
+    if (mHasClip) {
+      mParent->AddToVisibleAboveRegion(mClipRect);
+    } else {
+      mParent->SetAllDrawingAbove();
+    }
+  }
+  mTree.NodeWasFinished(mAnimatedGeometryRoot);
+}
+
+void
+PaintedLayerDataNode::AddToVisibleAboveRegion(const nsIntRect& aRect)
+{
+  nsIntRegion& visibleAboveRegion = mPaintedLayerDataStack.IsEmpty()
+    ? mVisibleAboveBackgroundRegion
+    : mPaintedLayerDataStack.LastElement().mVisibleAboveRegion;
+  visibleAboveRegion.Or(visibleAboveRegion, aRect);
+  visibleAboveRegion.SimplifyOutward(8);
+}
+
+void
+PaintedLayerDataNode::SetAllDrawingAbove()
+{
+  PopAllPaintedLayerData();
+  mAllDrawingAboveBackground = true;
+  mVisibleAboveBackgroundRegion.SetEmpty();
+}
+
+void
+PaintedLayerDataNode::PopPaintedLayerData()
+{
+  MOZ_ASSERT(!mPaintedLayerDataStack.IsEmpty());
+  size_t lastIndex = mPaintedLayerDataStack.Length() - 1;
+  PaintedLayerData& data = mPaintedLayerDataStack[lastIndex];
+  mTree.ContState().FinishPaintedLayerData(data, [this, &data, lastIndex]() {
+    return this->FindOpaqueBackgroundColor(data.mVisibleRegion, lastIndex);
+  });
+  mPaintedLayerDataStack.RemoveElementAt(lastIndex);
+}
+
+void
+PaintedLayerDataNode::PopAllPaintedLayerData()
+{
+  while (!mPaintedLayerDataStack.IsEmpty()) {
+    PopPaintedLayerData();
+  }
+}
+
+nsDisplayListBuilder*
+PaintedLayerDataTree::Builder() const
+{
+  return mContainerState.Builder();
+}
+
+const nsIFrame*
+PaintedLayerDataTree::GetParentAnimatedGeometryRoot(const nsIFrame* aAnimatedGeometryRoot)
+{
+  MOZ_ASSERT(aAnimatedGeometryRoot);
+  MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(Builder()->RootReferenceFrame(), aAnimatedGeometryRoot));
+
+  if (aAnimatedGeometryRoot == Builder()->RootReferenceFrame()) {
+    return nullptr;
+  }
+
+  nsIFrame* agr = Builder()->FindAnimatedGeometryRootFor(
+    const_cast<nsIFrame*>(aAnimatedGeometryRoot), Builder()->RootReferenceFrame());
+  MOZ_ASSERT_IF(agr, nsLayoutUtils::IsAncestorFrameCrossDoc(Builder()->RootReferenceFrame(), agr));
+  if (agr != aAnimatedGeometryRoot) {
+    return agr;
+  }
+  // aAnimatedGeometryRoot is its own animated geometry root.
+  // Find the animated geometry root for its cross-doc parent frame.
+  nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(aAnimatedGeometryRoot);
+  if (!parent) {
+    return nullptr;
+  }
+  return Builder()->FindAnimatedGeometryRootFor(parent, Builder()->RootReferenceFrame());
+}
+
+void
+PaintedLayerDataTree::Finish()
+{
+  if (mRoot) {
+    mRoot->Finish(false);
+  }
+  MOZ_ASSERT(mNodes.Count() == 0);
+  mRoot = nullptr;
+}
+
+void
+PaintedLayerDataTree::NodeWasFinished(const nsIFrame* aAnimatedGeometryRoot)
+{
+  mNodes.Remove(aAnimatedGeometryRoot);
+}
+
+void
+PaintedLayerDataTree::AddingOwnLayer(const nsIFrame* aAnimatedGeometryRoot,
+                                     const nsIntRect* aRect,
+                                     nscolor* aOutUniformBackgroundColor)
+{
+  FinishPotentiallyIntersectingNodes(aAnimatedGeometryRoot, aRect);
+  PaintedLayerDataNode* node = EnsureNodeFor(aAnimatedGeometryRoot);
+  if (aRect) {
+    if (aOutUniformBackgroundColor) {
+      *aOutUniformBackgroundColor = node->FindOpaqueBackgroundColor(*aRect);
+    }
+    node->AddToVisibleAboveRegion(*aRect);
+  } else {
+    if (aOutUniformBackgroundColor) {
+      *aOutUniformBackgroundColor = node->FindOpaqueBackgroundColorCoveringEverything();
+    }
+    node->SetAllDrawingAbove();
+  }
+}
+
+template<typename NewPaintedLayerCallbackType>
+PaintedLayerData*
+PaintedLayerDataTree::FindPaintedLayerFor(const nsIFrame* aAnimatedGeometryRoot,
+                                          const nsIntRect& aVisibleRect,
+                                          bool aShouldFixToViewport,
+                                          NewPaintedLayerCallbackType aNewPaintedLayerCallback)
+{
+  const nsIntRect* bounds = aShouldFixToViewport ? nullptr : &aVisibleRect;
+  FinishPotentiallyIntersectingNodes(aAnimatedGeometryRoot, bounds);
+  PaintedLayerDataNode* node = EnsureNodeFor(aAnimatedGeometryRoot);
+  if (aShouldFixToViewport) {
+    node->SetAllDrawingAbove();
+  }
+  return node->FindPaintedLayerFor(aVisibleRect, aNewPaintedLayerCallback);
+}
+
+void
+PaintedLayerDataTree::FinishPotentiallyIntersectingNodes(const nsIFrame* aAnimatedGeometryRoot,
+                                                         const nsIntRect* aRect)
+{
+  const nsIFrame* ancestorThatIsChildOfCommonAncestor = nullptr;
+  PaintedLayerDataNode* ancestorNode =
+    FindNodeForAncestorAnimatedGeometryRoot(aAnimatedGeometryRoot,
+                                            &ancestorThatIsChildOfCommonAncestor);
+  if (!ancestorNode) {
+    // None of our ancestors are in the tree. This should only happen if this
+    // is the very first item we're looking at.
+    MOZ_ASSERT(!mRoot);
+    return;
+  }
+
+  if (ancestorNode->AnimatedGeometryRoot() == aAnimatedGeometryRoot) {
+    // aAnimatedGeometryRoot already has a node in the tree.
+    // This is the common case.
+    MOZ_ASSERT(!ancestorThatIsChildOfCommonAncestor);
+    if (aRect) {
+      ancestorNode->FinishChildrenIntersecting(*aRect);
+    } else {
+      ancestorNode->FinishAllChildren();
+    }
+    return;
+  }
+
+  // We have found an existing ancestor, but it's a proper ancestor of our
+  // animated geometry root.
+  // ancestorThatIsChildOfCommonAncestor is the last animated geometry root
+  // encountered on the way up from aAnimatedGeometryRoot to ancestorNode.
+  MOZ_ASSERT(ancestorThatIsChildOfCommonAncestor);
+  MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(ancestorThatIsChildOfCommonAncestor, aAnimatedGeometryRoot));
+  MOZ_ASSERT(GetParentAnimatedGeometryRoot(ancestorThatIsChildOfCommonAncestor) == ancestorNode->AnimatedGeometryRoot());
+
+  // ancestorThatIsChildOfCommonAncestor is not in the tree yet!
+  MOZ_ASSERT(!mNodes.Get(ancestorThatIsChildOfCommonAncestor));
+
+  // We're about to add a node for ancestorThatIsChildOfCommonAncestor, so we
+  // finish all intersecting siblings.
+  nsIntRect clip;
+  if (IsClippedWithRespectToParentAnimatedGeometryRoot(ancestorThatIsChildOfCommonAncestor, &clip)) {
+    ancestorNode->FinishChildrenIntersecting(clip);
+  } else {
+    ancestorNode->FinishAllChildren();
+  }
+}
+
+PaintedLayerDataNode*
+PaintedLayerDataTree::EnsureNodeFor(const nsIFrame* aAnimatedGeometryRoot)
+{
+  MOZ_ASSERT(aAnimatedGeometryRoot);
+  PaintedLayerDataNode* node = mNodes.Get(aAnimatedGeometryRoot);
+  if (node) {
+    return node;
+  }
+
+  const nsIFrame* parentAnimatedGeometryRoot = GetParentAnimatedGeometryRoot(aAnimatedGeometryRoot);
+  if (!parentAnimatedGeometryRoot) {
+    MOZ_ASSERT(!mRoot);
+    MOZ_ASSERT(aAnimatedGeometryRoot == Builder()->RootReferenceFrame());
+    mRoot = MakeUnique<PaintedLayerDataNode>(*this, nullptr, aAnimatedGeometryRoot);
+    node = mRoot.get();
+  } else {
+    PaintedLayerDataNode* parentNode = EnsureNodeFor(parentAnimatedGeometryRoot);
+    MOZ_ASSERT(parentNode);
+    node = parentNode->AddChildNodeFor(aAnimatedGeometryRoot);
+  }
+  MOZ_ASSERT(node);
+  mNodes.Put(aAnimatedGeometryRoot, node);
+  return node;
+}
+
+bool
+PaintedLayerDataTree::IsClippedWithRespectToParentAnimatedGeometryRoot(const nsIFrame* aAnimatedGeometryRoot,
+                                                                       nsIntRect* aOutClip)
+{
+  nsIScrollableFrame* scrollableFrame = nsLayoutUtils::GetScrollableFrameFor(aAnimatedGeometryRoot);
+  if (!scrollableFrame) {
+    return false;
+  }
+  nsIFrame* scrollFrame = do_QueryFrame(scrollableFrame);
+  nsRect scrollPort = scrollableFrame->GetScrollPortRect() + Builder()->ToReferenceFrame(scrollFrame);
+  *aOutClip = mContainerState.ScaleToNearestPixels(scrollPort);
+  return true;
+}
+
+PaintedLayerDataNode*
+PaintedLayerDataTree::FindNodeForAncestorAnimatedGeometryRoot(const nsIFrame* aAnimatedGeometryRoot,
+                                                              const nsIFrame** aOutAncestorChild)
+{
+  if (!aAnimatedGeometryRoot) {
+    return nullptr;
+  }
+  PaintedLayerDataNode* node = mNodes.Get(aAnimatedGeometryRoot);
+  if (node) {
+    return node;
+  }
+  *aOutAncestorChild = aAnimatedGeometryRoot;
+  return FindNodeForAncestorAnimatedGeometryRoot(
+    GetParentAnimatedGeometryRoot(aAnimatedGeometryRoot), aOutAncestorChild);
+}
+
 const nsIFrame*
 ContainerState::FindFixedPosFrameForLayerData(const nsIFrame* aAnimatedGeometryRoot,
                                               bool aDisplayItemFixedToViewport)
 {
   if (!mManager->IsWidgetLayerManager()) {
     // Never attach any fixed-pos metadata to inactive layers, it's pointless!
     return nullptr;
   }
@@ -2217,23 +2784,21 @@ static int32_t FindIndexOfLayerIn(nsTArr
     if (aArray[i].mLayer == aLayer) {
       return i;
     }
   }
   return -1;
 }
 #endif
 
-void
-ContainerState::PopPaintedLayerData()
+
+template<typename FindOpaqueBackgroundColorCallbackType>
+void ContainerState::FinishPaintedLayerData(PaintedLayerData& aData, FindOpaqueBackgroundColorCallbackType aFindOpaqueBackgroundColor)
 {
-  NS_ASSERTION(!mPaintedLayerDataStack.IsEmpty(), "Can't pop");
-
-  int32_t lastIndex = mPaintedLayerDataStack.Length() - 1;
-  PaintedLayerData* data = mPaintedLayerDataStack[lastIndex];
+  PaintedLayerData* data = &aData;
 
   if (!data->mLayer) {
     // No layer was recycled, so we create a new one.
     nsRefPtr<PaintedLayer> paintedLayer = CreatePaintedLayer(data);
     data->mLayer = paintedLayer;
 
     NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, paintedLayer) < 0,
                  "Layer already in list???");
@@ -2353,17 +2918,17 @@ ContainerState::PopPaintedLayerData()
   transparentRegion.Sub(data->mVisibleRegion, data->mOpaqueRegion);
   bool isOpaque = transparentRegion.IsEmpty();
   // For translucent PaintedLayers, try to find an opaque background
   // color that covers the entire area beneath it so we can pull that
   // color into this layer to make it opaque.
   if (layer == data->mLayer) {
     nscolor backgroundColor = NS_RGBA(0,0,0,0);
     if (!isOpaque) {
-      backgroundColor = FindOpaqueBackgroundColorFor(data->mVisibleRegion, lastIndex);
+      backgroundColor = aFindOpaqueBackgroundColor();
       if (NS_GET_A(backgroundColor) == 255) {
         isOpaque = true;
       }
     }
 
     // Store the background color
     PaintedDisplayItemLayerUserData* userData =
       GetPaintedDisplayItemLayerUserData(data->mLayer);
@@ -2481,26 +3046,16 @@ ContainerState::PopPaintedLayerData()
     regions.mHitRegion.MoveBy(translation);
     regions.mDispatchToContentHitRegion.MoveBy(translation);
     regions.mNoActionRegion.MoveBy(translation);
     regions.mHorizontalPanRegion.MoveBy(translation);
     regions.mVerticalPanRegion.MoveBy(translation);
 
     layer->SetEventRegions(regions);
   }
-
-  // Since we're going to pop off the last PaintedLayerData, the
-  // mVisibleAboveRegion of the second-to-last item will need to include
-  // the regions of the last item. If we're emptying the PaintedLayerDataStack,
-  // we instead need to accumulate the regions into the container's
-  // mVisibleAboveBackgroundRegion.
-  UpdateVisibleAboveRegionOnPop(data,
-    lastIndex > 0 ? mPaintedLayerDataStack[lastIndex - 1].get() : nullptr);
-
-  mPaintedLayerDataStack.RemoveElementAt(lastIndex);
 }
 
 static bool
 IsItemAreaInWindowOpaqueRegion(nsDisplayListBuilder* aBuilder,
                                nsDisplayItem* aItem,
                                const nsRect& aComponentAlphaBounds)
 {
   if (!aItem->Frame()->PresContext()->IsChrome()) {
@@ -2653,121 +3208,42 @@ PaintedLayerData::Accumulate(ContainerSt
         } else {
           aItem->DisableComponentAlpha();
         }
       }
     }
   }
 }
 
-bool
-ContainerState::HasAsyncScrollableGeometryInContainer(const nsIFrame* aAnimatedGeometryRoot)
-{
-  const nsIFrame* f = aAnimatedGeometryRoot;
-  while (f) {
-    if (nsLayoutUtils::GetScrollableFrameFor(f) &&
-        nsLayoutUtils::GetDisplayPort(f->GetContent(), nullptr)) {
-      return true;
-    }
-    if (f == mContainerAnimatedGeometryRoot) {
-      break;
-    }
-    nsIFrame* fParent = nsLayoutUtils::GetCrossDocParentFrame(f);
-    if (!fParent) {
-      break;
-    }
-    f = nsLayoutUtils::GetAnimatedGeometryRootForFrame(
-          this->mBuilder, fParent, mContainerAnimatedGeometryRoot);
-  }
-  return false;
-}
-
-PaintedLayerData*
-ContainerState::FindPaintedLayerFor(nsDisplayItem* aItem,
-                                   const nsIntRect& aVisibleRect,
-                                   const nsIFrame* aAnimatedGeometryRoot,
-                                   const nsPoint& aTopLeft,
-                                   bool aShouldFixToViewport)
+PaintedLayerData
+ContainerState::NewPaintedLayerData(nsDisplayItem* aItem,
+                                    const nsIntRect& aVisibleRect,
+                                    const nsIFrame* aAnimatedGeometryRoot,
+                                    const nsPoint& aTopLeft,
+                                    bool aShouldFixToViewport)
 {
-  int32_t i;
-  int32_t lowestUsableLayerWithScrolledRoot = -1;
-  int32_t topmostLayerWithScrolledRoot = -1;
-  for (i = mPaintedLayerDataStack.Length() - 1; i >= 0; --i) {
-    // Don't let should-fix-to-viewport items share a layer with any other items.
-    if (aShouldFixToViewport) {
-      ++i;
-      break;
-    }
-    PaintedLayerData* data = mPaintedLayerDataStack[i];
-    // Give up if there is content visible above (in z-order) this layer that
-    // intersects aItem's visible region; aItem must be placed in a
-    // layer above this layer.
-    if (data->VisibleAboveRegionIntersects(aVisibleRect)) {
-      ++i;
-      break;
-    }
-    // If the animated scrolled roots are the same and we can share this layer
-    // with the item, note this as a usable layer.
-    if (data->mAnimatedGeometryRoot == aAnimatedGeometryRoot &&
-        !data->mSingleItemFixedToViewport) {
-      lowestUsableLayerWithScrolledRoot = i;
-      if (topmostLayerWithScrolledRoot < 0) {
-        topmostLayerWithScrolledRoot = i;
-      }
-    }
-    // If the layer's visible region intersects the item, stop now since no
-    // lower layer will be usable. Do the same if the layer is subject to
-    // async transforms, since we don't know where it will really be drawn.
-    if (data->VisibleRegionIntersects(aVisibleRect))
-      break;
-  }
-  if (topmostLayerWithScrolledRoot < 0) {
-    --i;
-    for (; i >= 0; --i) {
-      PaintedLayerData* data = mPaintedLayerDataStack[i];
-      if (data->mAnimatedGeometryRoot == aAnimatedGeometryRoot) {
-        topmostLayerWithScrolledRoot = i;
-        break;
-      }
-    }
-  }
-
-  if (topmostLayerWithScrolledRoot >= 0) {
-    while (uint32_t(topmostLayerWithScrolledRoot + 1) < mPaintedLayerDataStack.Length()) {
-      PopPaintedLayerData();
-    }
-  }
-
-  PaintedLayerData* paintedLayerData = nullptr;
-  if (lowestUsableLayerWithScrolledRoot < 0) {
-    paintedLayerData = new PaintedLayerData();
-    mPaintedLayerDataStack.AppendElement(paintedLayerData);
-    paintedLayerData->mAnimatedGeometryRoot = aAnimatedGeometryRoot;
-    paintedLayerData->mAnimatedGeometryRootOffset = aTopLeft;
-    paintedLayerData->mFixedPosFrameForLayerData =
-      FindFixedPosFrameForLayerData(aAnimatedGeometryRoot, aShouldFixToViewport);
-    paintedLayerData->mIsAsyncScrollable =
-      HasAsyncScrollableGeometryInContainer(aAnimatedGeometryRoot);
-    paintedLayerData->mReferenceFrame = aItem->ReferenceFrame();
-    paintedLayerData->mSingleItemFixedToViewport = aShouldFixToViewport;
-
-    paintedLayerData->mNewChildLayersIndex = mNewChildLayers.Length();
-    NewLayerEntry* newLayerEntry = mNewChildLayers.AppendElement();
-    newLayerEntry->mAnimatedGeometryRoot = aAnimatedGeometryRoot;
-    newLayerEntry->mFixedPosFrameForLayerData = paintedLayerData->mFixedPosFrameForLayerData;
-    // newLayerEntry->mOpaqueRegion is filled in later from
-    // paintedLayerData->mOpaqueRegion, if necessary.
-
-    // Allocate another entry for this layer's optimization to ColorLayer/ImageLayer
-    mNewChildLayers.AppendElement();
-  } else {
-    paintedLayerData = mPaintedLayerDataStack[lowestUsableLayerWithScrolledRoot];
-  }
-
-  return paintedLayerData;
+  PaintedLayerData data;
+  data.mAnimatedGeometryRoot = aAnimatedGeometryRoot;
+  data.mAnimatedGeometryRootOffset = aTopLeft;
+  data.mFixedPosFrameForLayerData =
+    FindFixedPosFrameForLayerData(aAnimatedGeometryRoot, aShouldFixToViewport);
+  data.mReferenceFrame = aItem->ReferenceFrame();
+  data.mSingleItemFixedToViewport = aShouldFixToViewport;
+
+  data.mNewChildLayersIndex = mNewChildLayers.Length();
+  NewLayerEntry* newLayerEntry = mNewChildLayers.AppendElement();
+  newLayerEntry->mAnimatedGeometryRoot = aAnimatedGeometryRoot;
+  newLayerEntry->mFixedPosFrameForLayerData = data.mFixedPosFrameForLayerData;
+  // newLayerEntry->mOpaqueRegion is filled in later from
+  // paintedLayerData->mOpaqueRegion, if necessary.
+
+  // Allocate another entry for this layer's optimization to ColorLayer/ImageLayer
+  mNewChildLayers.AppendElement();
+
+  return data;
 }
 
 #ifdef MOZ_DUMP_PAINTING
 static void
 DumpPaintedImage(nsDisplayItem* aItem, SourceSurface* aSurface)
 {
   nsCString string(aItem->Name());
   string.Append('-');
@@ -2937,61 +3413,16 @@ ContainerState::ComputeOpaqueRect(nsDisp
       if (opaque.Contains(displayport)) {
         *aOpaqueForAnimatedGeometryRootParent = true;
       }
     }
   }
   return opaquePixels;
 }
 
-void
-ContainerState::UpdateVisibleAboveRegionForNewItem(const nsIntRect& aVisibleRect,
-                                                   bool aCanMoveFreely,
-                                                   const nsIntRect* aClipRectIfAny)
-{
-  PaintedLayerData* data = GetTopPaintedLayerData();
-  PossiblyInfiniteRegion& visibleAboveRegion = data
-    ? data->mVisibleAboveRegion : mVisibleAboveBackgroundRegion;
-
-  if (aCanMoveFreely) {
-    // Prerendered transform items can be updated without layer building
-    // (async animations or an empty transaction), so we need to put items
-    // that the transform item can potentially move under into a layer above
-    // this item. We do this by making the visible above region infinite.
-    // If we have a clip, the transform can't escape from the clip rect, and
-    // the clip rect can't change without new layer building. In that case we
-    // can add just the clip rect to the visible above region.
-    visibleAboveRegion.AccumulateAndSimplifyOutward(
-      aClipRectIfAny ? *aClipRectIfAny : PossiblyInfiniteRegion::InfiniteRegion());
-  } else {
-    visibleAboveRegion.AccumulateAndSimplifyOutward(aVisibleRect);
-  }
-}
-
-void
-ContainerState::UpdateVisibleAboveRegionOnPop(PaintedLayerData* aData,
-                                              PaintedLayerData* aNextPaintedLayerData)
-{
-  PossiblyInfiniteRegion& visibleAboveRegion = aNextPaintedLayerData ?
-    aNextPaintedLayerData->mVisibleAboveRegion : mVisibleAboveBackgroundRegion;
-
-  // If aData has a draw region and is subject to async transforms then the
-  // layer can potentially be moved arbitrarily on the compositor. So we
-  // should avoid moving display items from on top of the layer to below the
-  // layer, which we do by making the visibleAboveRegion infinite. Note that
-  // if the visible region is empty (such as when aData has only event-regions
-  // items) then we don't need to do this.
-  if (aData->IsSubjectToAsyncTransforms() && !aData->mVisibleRegion.IsEmpty()) {
-    visibleAboveRegion.AccumulateAndSimplifyOutward(PossiblyInfiniteRegion::InfiniteRegion());
-  } else {
-    visibleAboveRegion.AccumulateAndSimplifyOutward(aData->mVisibleAboveRegion);
-    visibleAboveRegion.AccumulateAndSimplifyOutward(aData->mVisibleRegion);
-  }
-}
-
 /*
  * Iterate through the non-clip items in aList and its descendants.
  * For each item we compute the effective clip rect. Each item is assigned
  * to a layer. We invalidate the areas in PaintedLayers where an item
  * has moved from one PaintedLayer to another. Also,
  * aState->mInvalidPaintedContent is invalidated in every PaintedLayer.
  * We set the clip rect for items that generated their own layer, and
  * create a mask layer to do any rounded rect clipping.
@@ -3178,20 +3609,53 @@ ContainerState::ProcessDisplayItems(nsDi
         continue;
       }
 
       // 3D-transformed layers don't necessarily draw in the order in which
       // they're added to their parent container layer.
       bool mayDrawOutOfOrder = itemType == nsDisplayItem::TYPE_TRANSFORM &&
         (item->Frame()->Preserves3D() || item->Frame()->Preserves3DChildren());
 
-      // Pull up a uniform background color into the layer if possible.
-      mParameters.mBackgroundColor = (prerenderedTransform || mayDrawOutOfOrder)
-        ? NS_RGBA(0,0,0,0)
-        : FindOpaqueBackgroundColorFor(itemVisibleRect, mPaintedLayerDataStack.Length());
+      // Let mPaintedLayerDataTree know about this item, so that
+      // FindPaintedLayerFor and FindOpaqueBackgroundColor are aware of this
+      // item, even though it's not in any PaintedLayerDataStack.
+      // Ideally we'd only need the "else" case here and have
+      // mPaintedLayerDataTree figure out the right clip from the animated
+      // geometry root that we give it, but it can't easily figure about
+      // overflow:hidden clips on ancestors just by looking at the frame.
+      // So we'll do a little hand holding and pass the clip instead of the
+      // visible rect for the two important cases.
+      nscolor uniformColor = NS_RGBA(0,0,0,0);
+      nscolor* uniformColorPtr = !mayDrawOutOfOrder ? &uniformColor : nullptr;
+      nsIntRect* clipPtr = itemClip.HasClip() ? &clipRect : nullptr;
+      if (animatedGeometryRoot == item->Frame() &&
+          animatedGeometryRoot != mBuilder->RootReferenceFrame()) {
+        // This is the case for scrollbar thumbs, for example. In that case the
+        // clip we care about is the overflow:hidden clip on the scrollbar.
+        const nsIFrame* clipAnimatedGeometryRoot =
+          mPaintedLayerDataTree.GetParentAnimatedGeometryRoot(animatedGeometryRoot);
+        mPaintedLayerDataTree.AddingOwnLayer(clipAnimatedGeometryRoot,
+                                             clipPtr, uniformColorPtr);
+      } else if (prerenderedTransform) {
+        mPaintedLayerDataTree.AddingOwnLayer(animatedGeometryRoot,
+                                             clipPtr, uniformColorPtr);
+      } else {
+        // Using itemVisibleRect here isn't perfect. itemVisibleRect can be
+        // larger or smaller than the potential bounds of item's contents in
+        // animatedGeometryRoot: It's too large if there's a clipped display
+        // port somewhere among item's contents (see bug 1147673), and it can
+        // be too small if the contents can move, because it only looks at the
+        // contents' current bounds and doesn't anticipate any animations.
+        // Time will tell whether this is good enough, or whether we need to do
+        // something more sophisticated here.
+        mPaintedLayerDataTree.AddingOwnLayer(animatedGeometryRoot,
+                                             &itemVisibleRect, uniformColorPtr);
+      }
+
+      mParameters.mBackgroundColor = uniformColor;
 
       // Just use its layer.
       // Set layerContentsVisibleRect.width/height to -1 to indicate we
       // currently don't know. If BuildContainerLayerFor gets called by
       // item->BuildLayer, this will be set to a proper rect.
       nsIntRect layerContentsVisibleRect(0, 0, -1, -1);
       mParameters.mLayerContentsVisibleRect = &layerContentsVisibleRect;
       nsRefPtr<Layer> ownLayer = item->BuildLayer(mBuilder, mManager, mParameters);
@@ -3227,23 +3691,16 @@ ContainerState::ProcessDisplayItems(nsDi
                    "If we have rounded rects, we must have a clip rect");
       // It has its own layer. Update that layer's clip and visible rects.
       if (itemClip.HasClip()) {
         ownLayer->SetClipRect(&clipRect);
       } else {
         ownLayer->SetClipRect(nullptr);
       }
 
-      // Update the "visible above region" of the topmost PaintedLayerData item
-      // (or of the container's background) so that FindPaintedLayerFor and
-      // FindOpaqueBackgroundColorFor are aware of this item, even though it's
-      // not in the PaintedLayerDataStack.
-      UpdateVisibleAboveRegionForNewItem(itemVisibleRect, prerenderedTransform,
-                                         itemClip.HasClip() ? &clipRect : nullptr);
-
       // rounded rectangle clipping using mask layers
       // (must be done after visible rect is set on layer)
       if (itemClip.IsRectClippedByRoundedCorner(itemContent)) {
         SetupMaskLayer(ownLayer, itemClip, itemVisibleRect);
       }
 
       ContainerLayer* oldContainer = ownLayer->GetParent();
       if (oldContainer && oldContainer != mContainerLayer) {
@@ -3300,18 +3757,21 @@ ContainerState::ProcessDisplayItems(nsDi
        * No need to allocate geometry for items that aren't
        * part of a PaintedLayer.
        */
       mLayerBuilder->AddLayerDisplayItem(ownLayer, item,
                                          layerState,
                                          topLeft, nullptr);
     } else {
       PaintedLayerData* paintedLayerData =
-        FindPaintedLayerFor(item, itemVisibleRect, animatedGeometryRoot, topLeft,
-                           shouldFixToViewport);
+        mPaintedLayerDataTree.FindPaintedLayerFor(animatedGeometryRoot, itemVisibleRect,
+                                                  shouldFixToViewport, [&]() {
+          return NewPaintedLayerData(item, itemVisibleRect, animatedGeometryRoot,
+                                     topLeft, shouldFixToViewport);
+        });
 
       if (itemType == nsDisplayItem::TYPE_LAYER_EVENT_REGIONS) {
         nsDisplayLayerEventRegions* eventRegions =
             static_cast<nsDisplayLayerEventRegions*>(item);
         paintedLayerData->AccumulateEventRegions(eventRegions);
       } else {
         // check to see if the new item has rounded rect clips in common with
         // other items in the layer
@@ -3910,19 +4370,17 @@ ContainerState::PostprocessRetainedLayer
   }
 }
 
 void
 ContainerState::Finish(uint32_t* aTextContentFlags, LayerManagerData* aData,
                        const nsIntRect& aContainerPixelBounds,
                        nsDisplayList* aChildItems, bool& aHasComponentAlphaChildren)
 {
-  while (!mPaintedLayerDataStack.IsEmpty()) {
-    PopPaintedLayerData();
-  }
+  mPaintedLayerDataTree.Finish();
 
   NS_ASSERTION(mContainerBounds.IsEqualInterior(mAccumulatedChildBounds),
                "Bounds computation mismatch");
 
   if (mLayerBuilder->IsBuildingRetainedLayers()) {
     nsIntRegion containerOpaqueRegion;
     PostprocessRetainedLayers(&containerOpaqueRegion);
     if (containerOpaqueRegion.Contains(aContainerPixelBounds)) {
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -1932,16 +1932,17 @@ ScrollFrameHelper::ScrollFrameHelper(nsC
   , mHorizontalOverflow(false)
   , mVerticalOverflow(false)
   , mPostedReflowCallback(false)
   , mMayHaveDirtyFixedChildren(false)
   , mUpdateScrollbarAttributes(false)
   , mHasBeenScrolledRecently(false)
   , mCollapsedResizer(false)
   , mShouldBuildScrollableLayer(false)
+  , mIsScrollableLayerInRootContainer(false)
   , mHasBeenScrolled(false)
   , mIsResolutionSet(false)
   , mIgnoreMomentumScroll(false)
   , mScaleToResolution(false)
   , mTransformingByAPZ(false)
   , mVelocityQueue(aOuter->PresContext())
 {
   if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
@@ -2935,16 +2936,24 @@ ScrollFrameHelper::BuildDisplayList(nsDi
     // layers, so don't apply clipping again.
     mAddClipRectToLayer = false;
 
     // If we are a root scroll frame that has a display port we want to add
     // scrollbars, they will be children of the scrollable layer, but they get
     // adjusted by the APZC automatically.
     bool usingDisplayPort = aBuilder->IsPaintingToWindow() &&
         nsLayoutUtils::GetDisplayPort(mOuter->GetContent());
+
+    if (usingDisplayPort) {
+      // There is a display port for this frame, so we want to appear as having
+      // active scrolling, so that animated geometry roots are assigned correctly.
+      mShouldBuildScrollableLayer = true;
+      mIsScrollableLayerInRootContainer = true;
+    }
+
     bool addScrollBars = mIsRoot && usingDisplayPort && !aBuilder->IsForEventDelivery();
 
     if (addScrollBars) {
       // Add classic scrollbars.
       AppendScrollPartsTo(aBuilder, aDirtyRect, aLists, usingDisplayPort,
                           createLayersForScrollbars, false);
     }
 
@@ -3216,17 +3225,17 @@ ScrollFrameHelper::ComputeFrameMetrics(L
   // between them.
   bool omitClip = gfxPrefs::AsyncPanZoomEnabled() && aOutput->Length() > 0;
   if (!omitClip && (!gfxPrefs::LayoutUseContainersForRootFrames() || mAddClipRectToLayer)) {
     // When using containers, the container layer contains the clip. Otherwise
     // we always include the clip.
     *aClipRect = scrollport;
   }
 
-  if (!mShouldBuildScrollableLayer) {
+  if (!mShouldBuildScrollableLayer || mIsScrollableLayerInRootContainer) {
     return;
   }
 
   MOZ_ASSERT(mScrolledFrame->GetContent());
 
   bool isRoot = mIsRoot && mOuter->PresContext()->IsRootContentDocument();
 
   *aOutput->AppendElement() =
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -495,16 +495,22 @@ public:
   bool mHasBeenScrolledRecently:1;
   // If true, the resizer is collapsed and not displayed
   bool mCollapsedResizer:1;
 
   // If true, the layer should always be active because we always build a
   // scrollable layer. Used for asynchronous scrolling.
   bool mShouldBuildScrollableLayer:1;
 
+  // Whether we are the root scroll frame that is used for containerful
+  // scrolling with a display port. If true, the scrollable frame
+  // shouldn't attach frame metrics to its layers because the container
+  // will already have the necessary frame metrics.
+  bool mIsScrollableLayerInRootContainer:1;
+
   // If true, add clipping in ScrollFrameHelper::ComputeFrameMetrics.
   bool mAddClipRectToLayer:1;
 
   // True if this frame has been scrolled at least once
   bool mHasBeenScrolled:1;
 
   // True if the frame's resolution has been set via SetResolution or
   // SetResolutionAndScaleTo or restored via RestoreState.
new file mode 100644
--- /dev/null
+++ b/layout/reftests/invalidation/layer-splitting-1.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html lang="en" class="reftest-wait">
+<meta charset="utf-8">
+<title>Moving the transform under the absolutely-positioned layer should cause that to invalidate</title>
+
+<style>
+
+.content {
+  box-sizing: border-box;
+  width: 200px;
+  height: 200px;
+  border: 1px solid black;
+}
+
+.absolute {
+  position: absolute;
+  z-index: 2;
+  top: 20px;
+  left: 240px;
+}
+
+.reftest-no-paint {
+  border-color: lime;
+}
+
+.transform {
+  will-change: transform;
+}
+
+body {
+  margin: 0;
+  padding: 20px;
+  height: 3000px;
+}
+
+</style>
+
+<div class="transform content" id="aboutToMoveUnderAbsolute">
+  <!--
+    This transform is active + prerendered, and will move under the
+    absolutely-positioned item.
+  -->
+</div>
+
+<div class="absolute reftest-no-paint content">
+  <!--
+    This absolutely-positioned element should get its own PaintedLayer above the
+    transform.
+
+    It shouldn't attempt to pull up an opaque background color from the page,
+    because the transform can move under it.
+  -->
+</div>
+
+<script>
+
+function doTest() {
+  document.querySelector("#aboutToMoveUnderAbsolute").style.transform = "translateX(100px)";
+  document.documentElement.removeAttribute("class");
+}
+document.addEventListener("MozReftestInvalidate", doTest, false);
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/invalidation/layer-splitting-2.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html lang="en" class="reftest-wait">
+<meta charset="utf-8">
+<title>Scrolling shouldn't invalidate the fixed layer</title>
+
+<style>
+
+.content {
+  box-sizing: border-box;
+  width: 200px;
+  height: 200px;
+  border: 1px solid black;
+}
+
+.fixed {
+  position: fixed;
+  top: 20px;
+  left: 20px;
+}
+
+.reftest-no-paint {
+  border-color: lime;
+}
+
+.distanceFromTop {
+  margin-top: 240px;
+}
+
+body {
+  margin: 0;
+  padding: 20px;
+  height: 3000px;
+}
+
+
+</style>
+
+<div class="distanceFromTop content">
+  <!--
+    This is just a non-uniform item that will be scrolled so that it's between
+    the page background and the fixed layer.
+  -->
+</div>
+
+<div class="fixed reftest-no-paint content">
+  <!--
+    This fixed layer gets its own PaintedLayer above the page.
+
+    It shouldn't attempt to pull up an opaque background color from the page,
+    because the page can move under it.
+  -->
+</div>
+
+<script>
+
+function doTest() {
+  document.documentElement.scrollTop = 100;
+  document.documentElement.removeAttribute("class");
+}
+document.documentElement.scrollTop = 0;
+document.addEventListener("MozReftestInvalidate", doTest, false);
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/invalidation/layer-splitting-3.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<html lang="en" class="reftest-wait">
+<meta charset="utf-8">
+<title>Scrolling shouldn't invalidate the fixed items</title>
+
+<style>
+
+.content {
+  box-sizing: border-box;
+  width: 200px;
+  height: 200px;
+  border: 1px solid black;
+}
+
+.fixed {
+  position: fixed;
+  top: 20px;
+  left: 20px;
+}
+
+.reftest-no-paint {
+  border-color: lime;
+}
+
+.distanceFromTop {
+  margin-top: 240px;
+}
+
+.clip {
+  width: 200px;
+  height: 200px;
+  overflow: hidden;
+}
+
+.transform {
+  position: relative;
+  will-change: transform;
+}
+
+body {
+  margin: 0;
+  padding: 20px;
+  height: 3000px;
+}
+
+
+</style>
+
+<div class="fixed reftest-no-paint content">
+  <!--
+    This fixed layer gets its own PaintedLayer above the page.
+  -->
+</div>
+
+<div class="distanceFromTop clip">
+  <!--
+    This clip determines the potential pixels that can be affected by the
+    animated transform, *in relation to the scrolled page*. If the page
+    is scrolled, the clip moves relative to the fixed items, so the fixed
+    items need to anticipate the transform getting between them.
+  -->
+
+  <div class="transform content">
+    <!--
+      This is an animated transform item. It can move freely but will be
+      clipped by the .clip element.
+    -->
+  </div>
+
+</div>
+
+<div class="fixed reftest-no-paint content">
+  <!--
+    This fixed layer is above the animated transform, in z-order. The
+    transform is clipped in such a way that initially, the clip doesn't
+    intersect the fixed items, but once the page is scrolled, it does.
+    So this fixed item must not share a layer with the lower fixed item.
+  -->
+</div>
+
+<script>
+
+function doTest() {
+  document.documentElement.scrollTop = 100;
+  document.documentElement.removeAttribute("class");
+}
+document.documentElement.scrollTop = 0;
+document.addEventListener("MozReftestInvalidate", doTest, false);
+// setTimeout(doTest, 500);
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/invalidation/layer-splitting-4.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="utf-8">
+<title>The two items in the scroll box should share a layer, despite all the other stuff that's going on around them</title>
+
+<style>
+
+.content {
+  box-sizing: border-box;
+  width: 200px;
+  height: 200px;
+  border: 1px solid blue;
+  background: white;
+}
+
+body {
+  margin: 0;
+  padding: 20px;
+  height: 3000px;
+}
+
+.opacity {
+  opacity: 0.9;
+  width: 200px;
+  height: 200px;
+  background-color: yellow;
+  will-change: opacity;
+}
+
+.overlap {
+  margin-top: -100px;
+}
+
+.scrollable {
+  position: absolute;
+  top: 20px;
+  left: 240px;
+  width: 400px;
+  height: 400px;
+  border: 1px solid black;
+  overflow: auto;
+}
+
+.scrollarea {
+  height: 800px;
+  padding: 40px;
+}
+
+.low-z, .mid-z, .high-z {
+  position: relative;
+}
+
+.low-z  { z-index: 1; }
+.mid-z  { z-index: 2; }
+.high-z { z-index: 3; }
+
+</style>
+
+<div class="content" reftest-assigned-layer="page-background"></div>
+<div class="overlap opacity"></div>
+<div class="overlap mid-z content" reftest-assigned-layer="on-top-of-opacity">
+  <!--
+    This item cannot merge into the page background layer because there's an
+    active container layer for the opacity in between.
+  -->
+</div>
+
+<div class="scrollable">
+  <div class="scrollarea">
+    <div class="low-z content" reftest-assigned-layer="scrolled-content"></div>
+    <div class="high-z overlap content" reftest-assigned-layer="scrolled-content"></div>
+  </div>
+</div>
+<script>
+
+var scrollable = document.querySelector(".scrollable");
+
+// Make .scrollable start out with active scrolling.
+scrollable.scrollTop = 0;
+scrollable.scrollTop = 20;
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/invalidation/layer-splitting-5.html
@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<html lang="en" class="reftest-wait">
+<meta charset="utf-8">
+<title>Things overlapping active scrollboxes should be in a layer on top of the scrolled contents.</title>
+
+<style>
+
+div {
+  height: 50px;
+  border: 1px solid;
+  box-model: border-box;
+}
+
+.first, .second {
+  border-color: blue;
+  margin: 50px 0;
+}
+
+.overlap {
+  border-color: #088;
+  margin-left: 100px;
+  width: 80px;
+  margin-bottom: -30px;
+  position: relative;
+}
+
+.scrollable {
+  height: auto;
+  overflow: auto;
+}
+
+.scrollarea {
+  width: 5000px;
+  border: none;
+  padding: 10px 0 20px;
+  height: auto;
+}
+
+.scrolled {
+  margin-left: 220px;
+  width: 100px;
+  height: 100px;
+  border-color: red;
+}
+
+body {
+  margin: 0;
+  padding: 0 100px;
+  height: 3000px;
+}
+
+</style>
+
+<div class="first" reftest-assigned-layer="page-background">
+  <!--
+    This is just a regular box, it should end up in the page background layer.
+  -->
+</div>
+
+<div class="overlap reftest-no-paint">
+  <!--
+    This item intersects with the scrollable box and is positioned above
+    .scrolled, in z-order, so it should be split into its own layer as soon
+    as the scrollbox gets active scrolling. The splitting should not wait for
+    .scrolled to move under .overlap.
+  -->
+</div>
+
+<div class="scrollable">
+  <div class="scrollarea">
+    <div class="scrolled reftest-opaque-layer">
+      <!--
+        This will move under .overlap by .scrollable being scrolled. This
+        action should not invalidate .overlap.
+
+        Furthermore, since the background of .scrollable is uniform and opaque,
+        .scrolled should be able to pull up that background color and become
+        opaque itself.
+      -->
+    </div>
+  </div>
+</div>
+
+<div class="second" reftest-assigned-layer="page-background">
+  <!--
+    This should share a layer with .first and the page background.
+  -->
+</div>
+
+<script>
+
+var scrollable = document.querySelector(".scrollable");
+
+function doTest() {
+  scrollable.scrollLeft = 100;
+  document.documentElement.removeAttribute("class");
+}
+
+// Make .scrollable start out with active scrolling.
+scrollable.scrollLeft = 0;
+scrollable.scrollLeft = 20;
+document.addEventListener("MozReftestInvalidate", doTest, false);
+
+</script>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/invalidation/layer-splitting-6.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<html lang="en" class="reftest-wait">
+<meta charset="utf-8">
+<title>Things overlapping active scrollboxes should be in a layer on top of the scrolled contents, and that layer shouldn't pull up a background color through the scrollbox.</title>
+<!--
+  This test is the same as layer-splitting-5.html, but without the scrollbox
+  border. The lack of a border here makes it attractive for .overlap to pull
+  a background color from the page background (because there's no scrollbox
+  border in the way), but it shouldn't do that because .scrolled can move
+  under it.
+-->
+
+<style>
+
+div {
+  height: 50px;
+  border: 1px solid;
+  box-model: border-box;
+}
+
+.first, .second {
+  border-color: blue;
+  margin: 50px 0;
+}
+
+.overlap {
+  border-color: #088;
+  margin-left: 100px;
+  width: 80px;
+  margin-bottom: -30px;
+  position: relative;
+}
+
+.scrollable {
+  height: auto;
+  overflow: auto;
+  border: none;
+}
+
+.scrollarea {
+  width: 5000px;
+  border: none;
+  padding: 10px 0 20px;
+  height: auto;
+}
+
+.scrolled {
+  margin-left: 220px;
+  width: 100px;
+  height: 100px;
+  border-color: red;
+}
+
+body {
+  margin: 0;
+  padding: 0 100px;
+  height: 3000px;
+}
+
+</style>
+
+<div class="first" reftest-assigned-layer="page-background">
+  <!--
+    This is just a regular box, it should end up in the page background layer.
+  -->
+</div>
+
+<div class="overlap reftest-no-paint">
+  <!--
+    This item intersects with the scrollable box and is positioned above
+    .scrolled, in z-order, so it should be split into its own layer as soon
+    as the scrollbox gets active scrolling. The splitting should not wait for
+    .scrolled to move under .overlap.
+  -->
+</div>
+
+<div class="scrollable">
+  <div class="scrollarea">
+    <div class="scrolled reftest-opaque-layer">
+      <!--
+        This will move under .overlap by .scrollable being scrolled. This
+        action should not invalidate .overlap.
+
+        Furthermore, since the background of .scrollable is uniform and opaque,
+        .scrolled should be able to pull up that background color and become
+        opaque itself.
+      -->
+    </div>
+  </div>
+</div>
+
+<div class="second" reftest-assigned-layer="page-background">
+  <!--
+    This should share a layer with .first and the page background.
+  -->
+</div>
+
+<script>
+
+var scrollable = document.querySelector(".scrollable");
+
+function doTest() {
+  scrollable.scrollLeft = 100;
+  document.documentElement.removeAttribute("class");
+}
+
+// Make .scrollable start out with active scrolling.
+scrollable.scrollLeft = 0;
+scrollable.scrollLeft = 20;
+document.addEventListener("MozReftestInvalidate", doTest, false);
+
+</script>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/invalidation/layer-splitting-7.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html lang="en" class="reftest-wait">
+<meta charset="utf-8">
+<title>Scrolling shouldn't invalidate the relatively-positioned layer</title>
+
+<style>
+
+.content {
+  box-sizing: border-box;
+  width: 200px;
+  height: 200px;
+  border: 1px solid black;
+}
+
+.fixed {
+  position: fixed;
+  top: 20px;
+  left: 20px;
+}
+
+.reftest-no-paint {
+  border-color: lime;
+}
+
+.distanceFromTop {
+  margin-top: 240px;
+}
+
+.relative {
+  position: relative;
+}
+
+body {
+  margin: 0;
+  padding: 20px;
+  height: 3000px;
+}
+
+
+</style>
+
+<div class="fixed reftest-no-paint content">
+  <!--
+    This fixed layer gets its own PaintedLayer above the page.
+
+    It shouldn't attempt to pull up an opaque background color from the page,
+    because the page can move under it.
+  -->
+</div>
+
+<div class="distanceFromTop relative reftest-no-paint content">
+  <!--
+    This item is above .fixed in z-order, but it starts out not intersecting
+    .fixed. It should still get its own layer from the start, because it can
+    get scrolled on top of .fixed.
+  -->
+</div>
+
+<script>
+
+function doTest() {
+  document.documentElement.scrollTop = 100;
+  document.documentElement.removeAttribute("class");
+}
+document.documentElement.scrollTop = 0;
+document.addEventListener("MozReftestInvalidate", doTest, false);
+
+</script>
--- a/layout/reftests/invalidation/reftest.list
+++ b/layout/reftests/invalidation/reftest.list
@@ -52,8 +52,15 @@ pref(layout.animated-image-layers.enable
 != paintedlayer-recycling-2.html about:blank
 != paintedlayer-recycling-3.html about:blank
 != paintedlayer-recycling-4.html about:blank
 != paintedlayer-recycling-5.html about:blank
 != paintedlayer-recycling-6.html about:blank
 != paintedlayer-recycling-7.html about:blank
 != masklayer-1.html about:blank
 != masklayer-2.html about:blank
+!= layer-splitting-1.html about:blank
+!= layer-splitting-2.html about:blank
+!= layer-splitting-3.html about:blank
+!= layer-splitting-4.html about:blank
+!= layer-splitting-5.html about:blank
+!= layer-splitting-6.html about:blank
+!= layer-splitting-7.html about:blank
new file mode 100644
--- /dev/null
+++ b/layout/reftests/layers/pull-background-1.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="utf-8">
+<title>Scrollboxes with uniform backgrounds should pull that color into their contents</title>
+
+<style>
+
+div {
+  min-height: 50px;
+  box-model: border-box;
+}
+
+.first, .second {
+  border: 1px solid blue;
+  margin: 50px 0;
+}
+
+.border {
+  border: 1px solid black;
+}
+
+.scrollable {
+  height: auto;
+  overflow: auto;
+}
+
+.scrollarea {
+  width: 5000px;
+  border: none;
+  padding: 10px 0 20px;
+  height: auto;
+}
+
+.scrolled {
+  margin-left: 220px;
+  width: 100px;
+  height: 100px;
+  border-color: red;
+}
+
+body {
+  margin: 0;
+  padding: 0 100px;
+  height: 3000px;
+}
+
+</style>
+
+<div class="first" reftest-assigned-layer="page-background">
+  <!--
+    This is just a regular box, it should end up in the page background layer.
+  -->
+</div>
+
+<div class="scrollable border">
+  <div class="scrollarea">
+    <div class="scrolled border reftest-opaque-layer">
+      <!--
+        The background of .scrollable is uniform and opaque,
+        .scrolled should be able to pull up that background color and become
+        opaque itself.
+      -->
+    </div>
+  </div>
+</div>
+
+<div class="second" reftest-assigned-layer="page-background">
+  <!--
+    This should share a layer with .first and the page background.
+  -->
+</div>
+
+<script>
+
+var scrollable = document.querySelector(".scrollable");
+
+// Make .scrollable start out with active scrolling.
+scrollable.scrollLeft = 0;
+scrollable.scrollLeft = 20;
+
+</script>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/layers/pull-background-2.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="utf-8">
+<title>Scrollboxes with uniform backgrounds should pull that color into their contents, even through an opacity container layer</title>
+
+<style>
+
+div {
+  min-height: 50px;
+  box-model: border-box;
+}
+
+.first, .second {
+  border: 1px solid blue;
+  margin: 50px 0;
+}
+
+.border {
+  border: 1px solid black;
+}
+
+.scrollable {
+  height: 150px;
+  overflow: auto;
+}
+
+.scrollarea {
+  width: 5000px;
+  border: none;
+  padding: 10px 0 20px;
+  height: auto;
+}
+
+.scrolled {
+  margin-left: 220px;
+  width: 100px;
+  height: 100px;
+  border-color: red;
+}
+
+.opacity {
+  opacity: 0.9;
+}
+
+body {
+  margin: 0;
+  padding: 0 100px;
+  height: 3000px;
+}
+
+</style>
+
+<div class="first" reftest-assigned-layer="page-background">
+  <!--
+    This is just a regular box, it should end up in the page background layer.
+  -->
+</div>
+
+<div class="opacity border">
+  <div class="opacity scrollable">
+    <div class="scrollarea">
+      <div class="scrolled border reftest-opaque-layer">
+        <!--
+          The background of .scrollable is uniform and opaque,
+          .scrolled should be able to pull up that background color and become
+          opaque itself.
+        -->
+      </div>
+    </div>
+  </div>
+</div>
+
+<div class="second" reftest-assigned-layer="page-background">
+  <!--
+    This should share a layer with .first and the page background.
+  -->
+</div>
+
+<script>
+
+var scrollable = document.querySelector(".scrollable");
+
+// Make .scrollable start out with active scrolling.
+scrollable.scrollLeft = 0;
+scrollable.scrollLeft = 20;
+
+</script>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/layers/pull-background-3.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html lang="en" class="reftest-wait">
+<meta charset="utf-8">
+<title>Scrollboxes with non-uniform backgrounds should prevent their contents from pulling background colors, even if those contents start out above uniform backgrounds</title>
+
+<style>
+
+div {
+  min-height: 50px;
+  box-model: border-box;
+}
+
+.first, .second {
+  border: 1px solid blue;
+  margin: 50px 0;
+}
+
+.border {
+  border: 1px solid black;
+}
+
+.underlap {
+  border: 1px solid #088;
+  margin-left: 120px;
+  width: 80px;
+  margin-bottom: -30px;
+}
+
+.scrollable {
+  height: auto;
+  overflow: auto;
+}
+
+.scrollarea {
+  width: 5000px;
+  border: none;
+  padding: 10px 0 20px;
+  height: auto;
+}
+
+.scrolled {
+  margin-left: 220px;
+  width: 100px;
+  height: 100px;
+  border-color: red;
+}
+
+body {
+  margin: 0;
+  padding: 0 100px;
+  height: 3000px;
+}
+
+</style>
+
+<div class="first" reftest-assigned-layer="page-background">
+  <!--
+    This is just a regular box, it should end up in the page background layer.
+  -->
+</div>
+
+<div class="underlap">
+  <!--
+    This item intersects with the scrollable box and is positioned below
+    .scrollable, in z-order.
+  -->
+</div>
+
+<div class="scrollable border">
+  <div class="scrollarea">
+    <div class="scrolled border reftest-no-paint">
+      <!--
+        This box starts out above solid white background, but it will move so
+        that it intersects .underlap, so it shouldn't pull up a background
+        color to begin with so that it doesn't need to invalidate.
+      -->
+    </div>
+  </div>
+</div>
+
+<div class="second" reftest-assigned-layer="page-background">
+  <!--
+    This should share a layer with .first and the page background.
+  -->
+</div>
+
+<script>
+
+var scrollable = document.querySelector(".scrollable");
+
+function doTest() {
+  scrollable.scrollLeft = 100;
+  document.documentElement.removeAttribute("class");
+}
+
+// Make .scrollable start out with active scrolling.
+scrollable.scrollLeft = 0;
+scrollable.scrollLeft = 10;
+document.addEventListener("MozReftestInvalidate", doTest, false);
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/layers/pull-background-4.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<html lang="en" class="reftest-wait">
+<meta charset="utf-8">
+<title>Scrollboxes with non-uniform backgrounds should prevent their contents from pulling background colors, even if those contents start out above uniform backgrounds</title>
+<!--
+  This is the same test as pull-background-3.html, but with an additional
+  wrapping opacity container layer.
+-->
+
+<style>
+
+div {
+  min-height: 50px;
+  box-model: border-box;
+}
+
+.first, .second {
+  border: 1px solid blue;
+  margin: 50px 0;
+}
+
+.border {
+  border: 1px solid black;
+}
+
+.underlap {
+  border: 1px solid #088;
+  margin-left: 120px;
+  width: 80px;
+  margin-bottom: -30px;
+}
+
+.scrollable {
+  height: auto;
+  overflow: auto;
+}
+
+.scrollarea {
+  width: 5000px;
+  border: none;
+  padding: 10px 0 20px;
+  height: auto;
+}
+
+.scrolled {
+  margin-left: 220px;
+  width: 100px;
+  height: 100px;
+  border-color: red;
+}
+
+body {
+  margin: 0;
+  padding: 0 100px;
+  height: 3000px;
+}
+
+</style>
+
+<div class="first" reftest-assigned-layer="page-background">
+  <!--
+    This is just a regular box, it should end up in the page background layer.
+  -->
+</div>
+
+<div class="underlap">
+  <!--
+    This item intersects with the scrollable box and is positioned below
+    .scrollable, in z-order.
+  -->
+</div>
+
+<div class="opacity border">
+  <div class="opacity scrollable">
+    <div class="scrollarea">
+      <div class="scrolled border reftest-no-paint">
+        <!--
+          This box starts out above solid white background, but it will move so
+          that it intersects .underlap, so it shouldn't pull up a background
+          color to begin with so that it doesn't need to invalidate.
+        -->
+      </div>
+    </div>
+  </div>
+</div>
+
+<div class="second" reftest-assigned-layer="page-background">
+  <!--
+    This should share a layer with .first and the page background.
+  -->
+</div>
+
+<script>
+
+var scrollable = document.querySelector(".scrollable");
+
+function doTest() {
+  scrollable.scrollLeft = 100;
+  document.documentElement.removeAttribute("class");
+}
+
+// Make .scrollable start out with active scrolling.
+scrollable.scrollLeft = 0;
+scrollable.scrollLeft = 10;
+document.addEventListener("MozReftestInvalidate", doTest, false);
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/layers/pull-background-5.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="utf-8">
+<title>Scrollboxes with uniform backgrounds should pull that color into their contents, even if these contents are wider than the uniform area behind the scrollbox</title>
+<!--
+  Very similar to pull-background-2.html, but with a .scrolled element that is
+  wider than the scrollbox.
+-->
+
+<style>
+
+div {
+  min-height: 50px;
+  box-model: border-box;
+}
+
+.first, .second {
+  border: 1px solid blue;
+  margin: 50px 0;
+}
+
+.border {
+  border: 1px solid black;
+}
+
+.scrollable {
+  height: 150px;
+  overflow: auto;
+}
+
+.scrollarea {
+  width: 5000px;
+  border: none;
+  padding: 10px 0 20px;
+  height: auto;
+}
+
+.scrolled {
+  margin-left: 220px;
+  width: 2000px;
+  height: 100px;
+  border-color: red;
+}
+
+.opacity {
+  opacity: 0.9;
+}
+
+body {
+  margin: 0;
+  padding: 0 100px;
+  height: 3000px;
+}
+
+</style>
+
+<div class="first" reftest-assigned-layer="page-background">
+  <!--
+    This is just a regular box, it should end up in the page background layer.
+  -->
+</div>
+
+<div class="opacity border">
+  <div class="scrollable">
+    <div class="scrollarea">
+      <div class="scrolled border reftest-opaque-layer">
+        <!--
+          The background of .scrollable is uniform and opaque,
+          .scrolled should be able to pull up that background color and become
+          opaque itself.
+        -->
+      </div>
+    </div>
+  </div>
+</div>
+
+<div class="second" reftest-assigned-layer="page-background">
+  <!--
+    This should share a layer with .first and the page background.
+  -->
+</div>
+
+<script>
+
+var scrollable = document.querySelector(".scrollable");
+
+// Make .scrollable start out with active scrolling.
+scrollable.scrollLeft = 0;
+scrollable.scrollLeft = 20;
+
+</script>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/layers/pull-background-6.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="utf-8">
+<title>Scrollboxes with uniform backgrounds should pull that color into their contents, even if these contents are wider than the uniform area behind the scrollbox</title>
+<!--
+  Very similar to pull-background-1.html, but with a .scrolled element that is
+  wider than the scrollbox.
+-->
+
+<style>
+
+div {
+  min-height: 50px;
+  box-model: border-box;
+}
+
+.first, .second {
+  border: 1px solid blue;
+  margin: 50px 0;
+}
+
+.border {
+  border: 1px solid black;
+}
+
+.scrollable {
+  height: auto;
+  overflow: auto;
+}
+
+.scrollarea {
+  width: 5000px;
+  border: none;
+  padding: 10px 0 20px;
+  height: auto;
+}
+
+.scrolled {
+  margin-left: 220px;
+  width: 2000px;
+  height: 100px;
+  border-color: red;
+}
+
+body {
+  margin: 0;
+  padding: 0 100px;
+  height: 3000px;
+}
+
+</style>
+
+<div class="first" reftest-assigned-layer="page-background">
+  <!--
+    This is just a regular box, it should end up in the page background layer.
+  -->
+</div>
+
+<div class="scrollable border">
+  <div class="scrollarea">
+    <div class="scrolled border reftest-opaque-layer">
+      <!--
+        The background of .scrollable is uniform and opaque,
+        .scrolled should be able to pull up that background color and become
+        opaque itself.
+      -->
+    </div>
+  </div>
+</div>
+
+<div class="second" reftest-assigned-layer="page-background">
+  <!--
+    This should share a layer with .first and the page background.
+  -->
+</div>
+
+<script>
+
+var scrollable = document.querySelector(".scrollable");
+
+// Make .scrollable start out with active scrolling.
+scrollable.scrollLeft = 0;
+scrollable.scrollLeft = 20;
+
+</script>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/layers/pull-background-animated-position-1.html
@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<html lang="en" class="reftest-wait">
+<meta charset="utf-8">
+<title>Scrollboxes with uniform backgrounds should pull that color into their contents, even if those contents have an animated position</title>
+
+<style>
+
+div {
+  min-height: 50px;
+  box-model: border-box;
+}
+
+.first, .second {
+  border: 1px solid blue;
+  margin: 50px 0;
+  position: relative;
+}
+
+.border {
+  border: 1px solid black;
+}
+
+.scrollable {
+  height: auto;
+  overflow: auto;
+}
+
+.scrollarea {
+  width: 5000px;
+  border: none;
+  padding: 10px 0 20px;
+  height: auto;
+}
+
+.scrolled {
+  margin-left: 220px;
+  width: 100px;
+  height: 100px;
+  border-color: red;
+}
+
+.animated-position {
+  position: relative;
+  left: 20px;
+}
+
+body {
+  margin: 0;
+  padding: 0 100px;
+  height: 3000px;
+}
+
+</style>
+
+<div class="first" reftest-assigned-layer="page-background">
+  <!--
+    This is just a regular box, it should end up in the page background layer.
+  -->
+</div>
+
+<div class="scrollable border">
+  <div class="scrollarea">
+    <div class="scrolled border animated-position reftest-no-paint reftest-opaque-layer">
+      <!--
+        The background of .scrollable is uniform and opaque,
+        .scrolled should be able to pull up that background color and become
+        opaque itself.
+      -->
+    </div>
+  </div>
+</div>
+
+<div class="second" reftest-assigned-layer="page-background">
+  <!--
+    This should share a layer with .first and the page background.
+  -->
+</div>
+
+<script>
+
+var scrollable = document.querySelector(".scrollable");
+
+// Make .scrollable start out with active scrolling.
+scrollable.scrollLeft = 0;
+scrollable.scrollLeft = 20;
+
+
+var animatedLeft = document.querySelector(".animated-position");
+
+function doTest() {
+  animatedLeft.style.left = "100px";
+  document.documentElement.removeAttribute("class");
+}
+
+// Layerize #animatedLeft
+animatedLeft.offsetLeft;
+animatedLeft.style.left = "40px";
+animatedLeft.offsetLeft;
+animatedLeft.style.left = "60px";
+animatedLeft.offsetLeft;
+
+document.addEventListener("MozReftestInvalidate", doTest, false);
+
+</script>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/layers/pull-background-animated-position-2.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html lang="en" class="reftest-wait">
+<meta charset="utf-8">
+<title>Scrollboxes with non-uniform backgrounds should prevent their contents from pulling background colors, even if those contents start out above uniform backgrounds and have an animated position</title>
+
+<style>
+
+div {
+  min-height: 50px;
+  box-model: border-box;
+}
+
+.first, .second {
+  border: 1px solid blue;
+  margin: 50px 0;
+  position: relative;
+}
+
+.border {
+  border: 1px solid black;
+}
+
+.underlap {
+  border: 1px solid #088;
+  margin-left: 120px;
+  width: 80px;
+  margin-bottom: -30px;
+}
+
+.scrollable {
+  height: auto;
+  overflow: auto;
+}
+
+.scrollarea {
+  width: 5000px;
+  border: none;
+  padding: 10px 0 20px;
+  height: auto;
+}
+
+.scrolled {
+  margin-left: 220px;
+  width: 100px;
+  height: 100px;
+  border-color: red;
+}
+
+.animated-position {
+  position: relative;
+  left: 20px;
+}
+
+body {
+  margin: 0;
+  padding: 0 100px;
+  height: 3000px;
+}
+
+</style>
+
+<div class="first" reftest-assigned-layer="page-background">
+  <!--
+    This is just a regular box, it should end up in the page background layer.
+  -->
+</div>
+
+<div class="underlap">
+  <!--
+    This item intersects with the scrollable box and is positioned below
+    .scrollable, in z-order.
+  -->
+</div>
+
+<div class="scrollable border">
+  <div class="scrollarea">
+    <div class="scrolled border animated-position reftest-no-paint">
+      <!--
+        This box starts out above solid white background, but it will move so
+        that it intersects .underlap, so it shouldn't pull up a background
+        color to begin with so that it doesn't need to invalidate.
+      -->
+    </div>
+  </div>
+</div>
+
+<div class="second" reftest-assigned-layer="page-background">
+  <!--
+    This should share a layer with .first and the page background.
+  -->
+</div>
+
+<script>
+
+var scrollable = document.querySelector(".scrollable");
+
+// Make .scrollable start out with active scrolling.
+scrollable.scrollLeft = 0;
+scrollable.scrollLeft = 20;
+
+
+var animatedLeft = document.querySelector(".animated-position");
+
+function doTest() {
+  animatedLeft.style.left = "-40px";
+  document.documentElement.removeAttribute("class");
+}
+
+// Layerize #animatedLeft
+animatedLeft.offsetLeft;
+animatedLeft.style.left = "40px";
+animatedLeft.offsetLeft;
+animatedLeft.style.left = "60px";
+animatedLeft.offsetLeft;
+
+document.addEventListener("MozReftestInvalidate", doTest, false);
+setTimeout(doTest, 200);
+
+</script>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/layers/pull-background-animated-position-3.html
@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<html lang="en" class="reftest-wait">
+<meta charset="utf-8">
+<title>This test fails - Layerization should respect overflow:hidden clips around things with animated position</title>
+
+<style>
+
+div {
+  min-height: 50px;
+  box-model: border-box;
+}
+
+.first, .second {
+  border: 1px solid blue;
+  margin: 50px 0;
+  position: relative;
+}
+
+.border {
+  border: 1px solid black;
+}
+
+.underlap {
+  border: 1px solid #088;
+  margin-left: 120px;
+  width: 80px;
+  margin-bottom: -30px;
+}
+
+.clip {
+  height: auto;
+  overflow: hidden;
+  padding: 10px 0 20px;
+}
+
+.animated-position {
+  position: relative;
+  left: 20px;
+  margin-left: 220px;
+  width: 100px;
+  height: 100px;
+  border-color: red;
+}
+
+body {
+  margin: 0;
+  padding: 0 100px;
+  height: 3000px;
+}
+
+</style>
+
+<div class="first" reftest-assigned-layer="page-background">
+  <!--
+    This is just a regular box, it should end up in the page background layer.
+  -->
+</div>
+
+<div class="underlap">
+  <!--
+    This item intersects with the scrollable box and is positioned below
+    .scrollable, in z-order.
+  -->
+</div>
+
+<div class="clip border">
+  <div class="border animated-position reftest-no-paint">
+    <!--
+      This box starts out above solid white background, but it will move so
+      that it intersects .underlap, so it shouldn't pull up a background
+      color to begin with so that it doesn't need to invalidate.
+    -->
+  </div>
+</div>
+
+<div class="second" reftest-assigned-layer="page-background">
+  <!--
+    This should share a layer with .first and the page background.
+    However, we don't take .animated-position's clip into account when
+    layerizing, so .second will be pulled up into its own layer above
+    .animated-position. So this test will fail.
+  -->
+</div>
+
+<script>
+
+var animatedLeft = document.querySelector(".animated-position");
+
+function doTest() {
+  animatedLeft.style.left = "-40px";
+  document.documentElement.removeAttribute("class");
+}
+
+// Layerize #animatedLeft
+animatedLeft.offsetLeft;
+animatedLeft.style.left = "40px";
+animatedLeft.offsetLeft;
+animatedLeft.style.left = "60px";
+animatedLeft.offsetLeft;
+
+document.addEventListener("MozReftestInvalidate", doTest, false);
+setTimeout(doTest, 200);
+
+</script>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/layers/pull-background-animated-position-4.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<html lang="en" class="reftest-wait">
+<meta charset="utf-8">
+<title>This test fails - layerization should respect overflow:hidden clips around things with animated position</title>
+
+<style>
+
+div {
+  min-height: 50px;
+  box-model: border-box;
+}
+
+.first, .second {
+  border: 1px solid blue;
+  margin: 50px 0;
+  position: relative;
+}
+
+.border {
+  border: 1px solid black;
+}
+
+.clip {
+  height: auto;
+  overflow: hidden;
+  padding: 10px 0 20px;
+}
+
+.animated-position {
+  position: relative;
+  left: 20px;
+  margin-left: 220px;
+  width: 100px;
+  height: 100px;
+  border-color: red;
+}
+
+body {
+  margin: 0;
+  padding: 0 100px;
+  height: 3000px;
+}
+
+</style>
+
+<div class="first" reftest-assigned-layer="page-background">
+  <!--
+    This is just a regular box, it should end up in the page background layer.
+  -->
+</div>
+
+<div class="clip border">
+  <div class="border animated-position reftest-no-paint reftest-opaque-layer">
+    <!--
+      The background of .clip is uniform and opaque,
+      .animated-position should be able to pull up that background color and
+      become opaque itself.
+      However, since this clip is not created by an animated geometry root that
+      is a scrollable frame, we currently fail to recognize it, so this test
+      will fail.
+    -->
+  </div>
+</div>
+
+<div class="second" reftest-assigned-layer="page-background">
+  <!--
+    This should share a layer with .first and the page background.
+    However, since we don't recognize that .animated-position is contained in
+    a clip, .second gets its own layer above .animated-position, so this test
+    will fail.
+  -->
+</div>
+
+<script>
+
+var animatedLeft = document.querySelector(".animated-position");
+
+function doTest() {
+  animatedLeft.style.left = "-40px";
+  document.documentElement.removeAttribute("class");
+}
+
+// Layerize #animatedLeft
+animatedLeft.offsetLeft;
+animatedLeft.style.left = "40px";
+animatedLeft.offsetLeft;
+animatedLeft.style.left = "60px";
+animatedLeft.offsetLeft;
+
+document.addEventListener("MozReftestInvalidate", doTest, false);
+setTimeout(doTest, 200);
+
+</script>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/layers/pull-background-animated-position-5.html
@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<html lang="en" class="reftest-wait">
+<meta charset="utf-8">
+<title>This test fails - Opacity containers should anticipate animations of the contents when deciding whether to pull a background color</title>
+
+<style>
+
+div {
+  min-height: 50px;
+  box-model: border-box;
+}
+
+.first, .second {
+  border: 1px solid blue;
+  margin: 50px 0;
+  position: relative;
+}
+
+.border {
+  border: 1px solid black;
+}
+
+.underlap {
+  border: 1px solid #088;
+  margin-left: 120px;
+  width: 80px;
+  margin-bottom: -30px;
+}
+
+.opacity {
+  opacity: 0.9;
+  height: auto;
+  padding: 10px 0 20px;
+}
+
+.animated-position {
+  position: relative;
+  left: 20px;
+  margin-left: 220px;
+  width: 100px;
+  height: 100px;
+  border-color: red;
+}
+
+body {
+  margin: 0;
+  padding: 0 100px;
+  height: 3000px;
+}
+
+</style>
+
+<div class="first" reftest-assigned-layer="page-background">
+  <!--
+    This is just a regular box, it should end up in the page background layer.
+  -->
+</div>
+
+<div class="underlap">
+  <!--
+    This item intersects with the scrollable box and is positioned below
+    .scrollable, in z-order.
+  -->
+</div>
+
+<div class="border">
+  <div class="opacity">
+    <div class="border animated-position reftest-no-paint">
+      <!--
+        This item start out above solid white background but will move to
+        intersect .underlap, so it shouldn't pull up the background color.
+        However, the opacity item that wraps this item only looks at the
+        current bounds of its contents, so this test will fail.
+      -->
+    </div>
+  </div>
+</div>
+
+<div class="second" reftest-assigned-layer="page-background">
+  <!--
+    This should share a layer with .first and the page background.
+  -->
+</div>
+
+<script>
+
+var animatedLeft = document.querySelector(".animated-position");
+
+function doTest() {
+  animatedLeft.style.left = "-40px";
+  document.documentElement.removeAttribute("class");
+}
+
+// Layerize #animatedLeft
+animatedLeft.offsetLeft;
+animatedLeft.style.left = "40px";
+animatedLeft.offsetLeft;
+animatedLeft.style.left = "60px";
+animatedLeft.offsetLeft;
+
+document.addEventListener("MozReftestInvalidate", doTest, false);
+setTimeout(doTest, 200);
+
+</script>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/layers/pull-background-displayport-1.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html lang="en" reftest-async-scroll>
+<meta charset="utf-8">
+<title>Scrollboxes with uniform backgrounds should pull that color into their contents, even if their contents have a visible region that extends beyond the scrollbox clip</title>
+
+<style>
+
+div {
+  min-height: 50px;
+  box-model: border-box;
+}
+
+.first, .second {
+  border: 1px solid blue;
+  margin: 50px 0;
+}
+
+.border {
+  border: 1px solid black;
+}
+
+.scrollable {
+  height: auto;
+  overflow: auto;
+}
+
+.scrollarea {
+  width: 5000px;
+  border: none;
+  padding: 10px 0 20px;
+  height: auto;
+}
+
+.scrolled {
+  margin-left: 220px;
+  width: 100px;
+  height: 100px;
+  border-color: red;
+}
+
+body {
+  margin: 0;
+  padding: 0 100px;
+  height: 3000px;
+}
+
+</style>
+
+<div class="first" reftest-assigned-layer="page-background">
+  <!--
+    This is just a regular box, it should end up in the page background layer.
+  -->
+</div>
+
+<div class="scrollable border"
+     reftest-displayport-x="0" reftest-displayport-y="0"
+     reftest-displayport-w="2000" reftest-displayport-h="200">
+  <div class="scrollarea">
+    <div class="scrolled border reftest-opaque-layer">
+      <!--
+        The background of .scrollable is uniform and opaque,
+        .scrolled should be able to pull up that background color and become
+        opaque itself.
+      -->
+    </div>
+  </div>
+</div>
+
+<div class="second" reftest-assigned-layer="page-background">
+  <!--
+    This should share a layer with .first and the page background.
+  -->
+</div>
+
+<script>
+
+var scrollable = document.querySelector(".scrollable");
+
+// Make .scrollable start out with active scrolling.
+scrollable.scrollLeft = 0;
+scrollable.scrollLeft = 20;
+
+</script>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/layers/pull-background-displayport-2.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html lang="en" reftest-async-scroll>
+<meta charset="utf-8">
+<title>Scrollboxes with uniform backgrounds should pull that color into their contents, even if their contents have a visible region that extends beyond the scrollbox clip, even through an opacity container layer</title>
+
+<style>
+
+div {
+  min-height: 50px;
+  box-model: border-box;
+}
+
+.first, .second {
+  border: 1px solid blue;
+  margin: 50px 0;
+}
+
+.border {
+  border: 1px solid black;
+}
+
+.scrollable {
+  height: 150px;
+  overflow: auto;
+}
+
+.scrollarea {
+  width: 5000px;
+  border: none;
+  padding: 10px 0 20px;
+  height: auto;
+}
+
+.scrolled {
+  margin-left: 220px;
+  width: 100px;
+  height: 100px;
+  border-color: red;
+}
+
+.opacity {
+  opacity: 0.9;
+}
+
+body {
+  margin: 0;
+  padding: 0 100px;
+  height: 3000px;
+}
+
+</style>
+
+<div class="first" reftest-assigned-layer="page-background">
+  <!--
+    This is just a regular box, it should end up in the page background layer.
+  -->
+</div>
+
+<div class="opacity border">
+  <div class="opacity scrollable"
+       reftest-displayport-x="0" reftest-displayport-y="0"
+       reftest-displayport-w="2000" reftest-displayport-h="200">
+    <div class="scrollarea">
+      <div class="scrolled border reftest-opaque-layer">
+        <!--
+          The background of .scrollable is uniform and opaque,
+          .scrolled should be able to pull up that background color and become
+          opaque itself.
+        -->
+      </div>
+    </div>
+  </div>
+</div>
+
+<div class="second" reftest-assigned-layer="page-background">
+  <!--
+    This should share a layer with .first and the page background.
+  -->
+</div>
+
+<script>
+
+var scrollable = document.querySelector(".scrollable");
+
+// Make .scrollable start out with active scrolling.
+scrollable.scrollLeft = 0;
+scrollable.scrollLeft = 20;
+
+</script>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/layers/pull-background-displayport-3.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<html lang="en" class="reftest-wait" reftest-async-scroll>
+<meta charset="utf-8">
+<title>Scrollboxes with non-uniform backgrounds should prevent their contents from pulling background colors, even if those contents start out above uniform backgrounds</title>
+
+<style>
+
+div {
+  min-height: 50px;
+  box-model: border-box;
+}
+
+.first, .second {
+  border: 1px solid blue;
+  margin: 50px 0;
+}
+
+.border {
+  border: 1px solid black;
+}
+
+.underlap {
+  border: 1px solid #088;
+  margin-left: 120px;
+  width: 80px;
+  margin-bottom: -30px;
+}
+
+.scrollable {
+  height: auto;
+  overflow: auto;
+}
+
+.scrollarea {
+  width: 5000px;
+  border: none;
+  padding: 10px 0 20px;
+  height: auto;
+}
+
+.scrolled {
+  margin-left: 220px;
+  width: 100px;
+  height: 100px;
+  border-color: red;
+}
+
+body {
+  margin: 0;
+  padding: 0 100px;
+  height: 3000px;
+}
+
+</style>
+
+<div class="first" reftest-assigned-layer="page-background">
+  <!--
+    This is just a regular box, it should end up in the page background layer.
+  -->
+</div>
+
+<div class="underlap">
+  <!--
+    This item intersects with the scrollable box and is positioned below
+    .scrolled, in z-order.
+  -->
+</div>
+
+<div class="scrollable border"
+     reftest-displayport-x="0" reftest-displayport-y="0"
+     reftest-displayport-w="2000" reftest-displayport-h="200">
+  <div class="scrollarea">
+    <div class="scrolled border reftest-no-paint">
+      <!--
+        This box starts out above solid white background, but it will move so
+        that it intersects .underlap, so it shouldn't pull up a background
+        color to begin with so that it doesn't need to invalidate.
+      -->
+    </div>
+  </div>
+</div>
+
+<div class="second" reftest-assigned-layer="page-background">
+  <!--
+    This should share a layer with .first and the page background.
+  -->
+</div>
+
+<script>
+
+var scrollable = document.querySelector(".scrollable");
+
+function doTest() {
+  scrollable.scrollLeft = 100;
+  document.documentElement.removeAttribute("class");
+}
+
+// Make .scrollable start out with active scrolling.
+scrollable.scrollLeft = 0;
+scrollable.scrollLeft = 10;
+document.addEventListener("MozReftestInvalidate", doTest, false);
+// setTimeout(doTest, 500);
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/layers/pull-background-displayport-4.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<html lang="en" class="reftest-wait" reftest-async-scroll>
+<meta charset="utf-8">
+<title>Scrollboxes with non-uniform backgrounds should prevent their contents from pulling background colors, even if those contents start out above uniform backgrounds</title>
+<!--
+  This is the same test as pull-background-displayport-3.html, but with an additional
+  wrapping opacity container layer.
+-->
+
+<style>
+
+div {
+  min-height: 50px;
+  box-model: border-box;
+}
+
+.first, .second {
+  border: 1px solid blue;
+  margin: 50px 0;
+}
+
+.border {
+  border: 1px solid black;
+}
+
+.underlap {
+  border: 1px solid #088;
+  margin-left: 120px;
+  width: 80px;
+  margin-bottom: -30px;
+}
+
+.scrollable {
+  height: auto;
+  overflow: auto;
+}
+
+.scrollarea {
+  width: 5000px;
+  border: none;
+  padding: 10px 0 20px;
+  height: auto;
+}
+
+.scrolled {
+  margin-left: 220px;
+  width: 100px;
+  height: 100px;
+  border-color: red;
+}
+
+body {
+  margin: 0;
+  padding: 0 100px;
+  height: 3000px;
+}
+
+</style>
+
+<div class="first" reftest-assigned-layer="page-background">
+  <!--
+    This is just a regular box, it should end up in the page background layer.
+  -->
+</div>
+
+<div class="underlap">
+  <!--
+    This item intersects with the scrollable box and is positioned below
+    .scrolled, in z-order.
+  -->
+</div>
+
+<div class="opacity border">
+  <div class="opacity scrollable"
+       reftest-displayport-x="0" reftest-displayport-y="0"
+       reftest-displayport-w="2000" reftest-displayport-h="200">
+    <div class="scrollarea">
+      <div class="scrolled border reftest-no-paint">
+        <!--
+          This box starts out above solid white background, but it will move so
+          that it intersects .underlap, so it shouldn't pull up a background
+          color to begin with so that it doesn't need to invalidate.
+        -->
+      </div>
+    </div>
+  </div>
+</div>
+
+<div class="second" reftest-assigned-layer="page-background">
+  <!--
+    This should share a layer with .first and the page background.
+  -->
+</div>
+
+<script>
+
+var scrollable = document.querySelector(".scrollable");
+
+function doTest() {
+  scrollable.scrollLeft = 100;
+  document.documentElement.removeAttribute("class");
+}
+
+// Make .scrollable start out with active scrolling.
+scrollable.scrollLeft = 0;
+scrollable.scrollLeft = 10;
+document.addEventListener("MozReftestInvalidate", doTest, false);
+// setTimeout(doTest, 500);
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/layers/pull-background-displayport-5.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<html lang="en" reftest-async-scroll>
+<meta charset="utf-8">
+<title>Scrollboxes with uniform backgrounds should pull that color into their contents, even if these contents are wider than the uniform area behind the scrollbox</title>
+<!--
+  Very similar to pull-background-displayport-2.html, but with a .scrolled element that is
+  wider than the scrollbox.
+-->
+
+<style>
+
+div {
+  min-height: 50px;
+  box-model: border-box;
+}
+
+.first, .second {
+  border: 1px solid blue;
+  margin: 50px 0;
+}
+
+.border {
+  border: 1px solid black;
+}
+
+.scrollable {
+  height: 150px;
+  overflow: auto;
+}
+
+.scrollarea {
+  width: 5000px;
+  border: none;
+  padding: 10px 0 20px;
+  height: auto;
+}
+
+.scrolled {
+  margin-left: 220px;
+  width: 2000px;
+  height: 100px;
+  border-color: red;
+}
+
+.opacity {
+  opacity: 0.9;
+}
+
+body {
+  margin: 0;
+  padding: 0 100px;
+  height: 3000px;
+}
+
+</style>
+
+<div class="first" reftest-assigned-layer="page-background">
+  <!--
+    This is just a regular box, it should end up in the page background layer.
+  -->
+</div>
+
+<div class="opacity border">
+  <div class="scrollable"
+       reftest-displayport-x="0" reftest-displayport-y="0"
+       reftest-displayport-w="2000" reftest-displayport-h="200">
+    <div class="scrollarea">
+      <div class="scrolled border reftest-opaque-layer">
+        <!--
+          The background of .scrollable is uniform and opaque,
+          .scrolled should be able to pull up that background color and become
+          opaque itself.
+        -->
+      </div>
+    </div>
+  </div>
+</div>
+
+<div class="second" reftest-assigned-layer="page-background">
+  <!--
+    This should share a layer with .first and the page background.
+  -->
+</div>
+
+<script>
+
+var scrollable = document.querySelector(".scrollable");
+
+// Make .scrollable start out with active scrolling.
+scrollable.scrollLeft = 0;
+scrollable.scrollLeft = 20;
+
+</script>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/layers/pull-background-displayport-6.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html lang="en" reftest-async-scroll>
+<meta charset="utf-8">
+<title>Scrollboxes with uniform backgrounds should pull that color into their contents, even if these contents are wider than the uniform area behind the scrollbox</title>
+
+<style>
+
+div {
+  min-height: 50px;
+  box-model: border-box;
+}
+
+.first, .second {
+  border: 1px solid blue;
+  margin: 50px 0;
+}
+
+.border {
+  border: 1px solid black;
+}
+
+.scrollable {
+  height: auto;
+  overflow: auto;
+}
+
+.scrollarea {
+  width: 5000px;
+  border: none;
+  padding: 10px 0 20px;
+  height: auto;
+}
+
+.scrolled {
+  margin-left: 220px;
+  width: 2000px;
+  height: 100px;
+  border-color: red;
+}
+
+body {
+  margin: 0;
+  padding: 0 100px;
+  height: 3000px;
+}
+
+</style>
+
+<div class="first" reftest-assigned-layer="page-background">
+  <!--
+    This is just a regular box, it should end up in the page background layer.
+  -->
+</div>
+
+<div class="scrollable border"
+     reftest-displayport-x="0" reftest-displayport-y="0"
+     reftest-displayport-w="2000" reftest-displayport-h="200">
+  <div class="scrollarea">
+    <div class="scrolled border reftest-opaque-layer">
+      <!--
+        The background of .scrollable is uniform and opaque,
+        .scrolled should be able to pull up that background color and become
+        opaque itself.
+      -->
+    </div>
+  </div>
+</div>
+
+<div class="second" reftest-assigned-layer="page-background">
+  <!--
+    This should share a layer with .first and the page background.
+  -->
+</div>
+
+<script>
+
+var scrollable = document.querySelector(".scrollable");
+
+// Make .scrollable start out with active scrolling.
+scrollable.scrollLeft = 0;
+scrollable.scrollLeft = 20;
+
+</script>
+
--- a/layout/reftests/layers/reftest.list
+++ b/layout/reftests/layers/reftest.list
@@ -1,2 +1,19 @@
 == move-to-background-1.html move-to-background-1-ref.html
 fuzzy-if(cocoaWidget,2,6) random-if(Android&&!browserIsRemote) == component-alpha-exit-1.html component-alpha-exit-1-ref.html # bug 760275
+!= pull-background-1.html about:blank
+!= pull-background-2.html about:blank
+!= pull-background-3.html about:blank # fails with non-overlay scrollbars and event regions due to bug 1148515
+!= pull-background-4.html about:blank # fails with non-overlay scrollbars and event regions due to bug 1148515
+skip-if(asyncPanZoom) != pull-background-5.html about:blank # Fails with event regions
+!= pull-background-6.html about:blank
+skip-if(asyncPanZoom) != pull-background-animated-position-1.html about:blank # Fails with event regions
+!= pull-background-animated-position-2.html about:blank
+fails != pull-background-animated-position-3.html about:blank # Fails because PaintedLayer item assignment doesn't recognize overflow:hidden clips
+fails != pull-background-animated-position-4.html about:blank # Fails because PaintedLayer item assignment and background pulling don't recognize overflow:hidden clips
+fails-if(!asyncPanZoom&&!Android) != pull-background-animated-position-5.html about:blank # Fails because ownLayer bounds don't anticipate changes of animated contents, but doesn't fail with event regions
+skip-if(!asyncPanZoom) != pull-background-displayport-1.html about:blank
+skip-if(!asyncPanZoom) != pull-background-displayport-2.html about:blank
+skip-if(!asyncPanZoom) != pull-background-displayport-3.html about:blank # fails with non-overlay scrollbars and event regions due to bug 1148515
+skip-if(!asyncPanZoom) != pull-background-displayport-4.html about:blank # fails with non-overlay scrollbars and event regions due to bug 1148515
+fails skip-if(!asyncPanZoom) != pull-background-displayport-5.html about:blank # bug 1147673
+skip-if(!asyncPanZoom) != pull-background-displayport-6.html about:blank # fails with non-overlay scrollbars and event regions due to bug 1148515
--- a/layout/reftests/svg/reftest.list
+++ b/layout/reftests/svg/reftest.list
@@ -214,17 +214,17 @@ pref(svg.marker-improvements.enabled,tru
 == mask-transformed-child-01.svg mask-transformed-child-01-ref.svg
 pref(layout.css.masking.enabled,true) fuzzy-if(d2d,1,6400) == mask-type-01.svg mask-type-01-ref.svg
 pref(layout.css.masking.enabled,true) fuzzy-if(d2d,1,6400) == mask-type-02.svg mask-type-01-ref.svg
 pref(layout.css.masking.enabled,true) fuzzy-if(d2d,1,6400) == mask-type-03.svg mask-type-01-ref.svg
 pref(layout.css.masking.enabled,true) fuzzy-if(d2d,1,6400) == mask-type-04.svg mask-type-01-ref.svg
 == nested-viewBox-01.svg pass.svg
 == nesting-invalid-01.svg nesting-invalid-01-ref.svg
 fuzzy-if(d2d&&/^Windows\x20NT\x206\.1/.test(http.oscpu),1,168) fuzzy-if(azureQuartz,1,122) == non-scaling-stroke-01.svg non-scaling-stroke-01-ref.svg # bug 1074161 for Win7 and OSX 10.8
-fuzzy-if(Android||B2G,1,99) fuzzy-if(!contentSameGfxBackendAsCanvas,9,99) == non-scaling-stroke-02.svg non-scaling-stroke-02-ref.svg
+fuzzy-if(Android||B2G,1,586) fuzzy-if(!contentSameGfxBackendAsCanvas,9,99) == non-scaling-stroke-02.svg non-scaling-stroke-02-ref.svg
 == non-scaling-stroke-03.svg non-scaling-stroke-03-ref.svg
 == objectBoundingBox-and-clipPath.svg pass.svg
 # Bug 588684
 random-if(gtk2Widget) == objectBoundingBox-and-fePointLight-01.svg objectBoundingBox-and-fePointLight-01-ref.svg
 random-if(gtk2Widget) == objectBoundingBox-and-fePointLight-02.svg objectBoundingBox-and-fePointLight-02-ref.svg
 == objectBoundingBox-and-mask.svg pass.svg
 == objectBoundingBox-and-mask-02.svg pass.svg
 == objectBoundingBox-and-pattern-01a.svg objectBoundingBox-and-pattern-01-ref.svg
--- a/media/webrtc/trunk/webrtc/common_video/libyuv/webrtc_libyuv.cc
+++ b/media/webrtc/trunk/webrtc/common_video/libyuv/webrtc_libyuv.cc
@@ -240,17 +240,17 @@ int ConvertToI420(VideoType src_video_ty
   if (rotation == kRotate90 || rotation == kRotate270) {
     dst_width = dst_frame->height();
     dst_height =dst_frame->width();
   }
 #ifdef WEBRTC_GONK
   if (src_video_type == kYV12) {
     // In gralloc buffer, yv12 color format's cb and cr's strides are aligned
     // to 16 Bytes boundary. See /system/core/include/system/graphics.h
-    int stride_y = src_width;
+    int stride_y = (src_width + 15) & ~0x0F;
     int stride_uv = (((stride_y + 1) / 2) + 15) & ~0x0F;
     return libyuv::I420Rotate(src_frame,
                               stride_y,
                               src_frame + (stride_y * src_height) + (stride_uv * ((src_height + 1) / 2)),
                               stride_uv,
                               src_frame + (stride_y * src_height),
                               stride_uv,
                               dst_frame->buffer(kYPlane),
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1327,18 +1327,20 @@ pref("network.http.spdy.ping-threshold",
 pref("network.http.spdy.ping-timeout", 8);
 pref("network.http.spdy.send-buffer-size", 131072);
 pref("network.http.spdy.allow-push", true);
 pref("network.http.spdy.push-allowance", 131072);
 pref("network.http.spdy.default-concurrent", 100);
 
 // alt-svc allows separation of transport routing from
 // the origin host without using a proxy.
-pref("network.http.altsvc.enabled", true);
-pref("network.http.altsvc.oe", true);
+pref("network.http.atsvc.enabled", false);
+pref("network.http.atsvc.oe", false);
+pref("network.http.altsvc.enabled", false);
+pref("network.http.altsvc.oe", false);
 
 pref("network.http.diagnostics", false);
 
 pref("network.http.pacing.requests.enabled", true);
 pref("network.http.pacing.requests.min-parallelism", 6);
 pref("network.http.pacing.requests.hz", 100);
 pref("network.http.pacing.requests.burst", 32);
 
--- a/netwerk/base/LoadInfo.h
+++ b/netwerk/base/LoadInfo.h
@@ -20,18 +20,23 @@ namespace mozilla {
 namespace net {
 class HttpChannelParent;
 class FTPChannelParent;
 class WebSocketChannelParent;
 }
 
 /**
  * Class that provides an nsILoadInfo implementation.
+ *
+ * Note that there is no reason why this class should be MOZ_EXPORT, but
+ * Thunderbird relies on some insane hacks which require this, so we'll leave it
+ * as is for now, but hopefully we'll be able to remove the MOZ_EXPORT keyword
+ * from this class at some point.  See bug 1149127 for the discussion.
  */
-class LoadInfo final : public nsILoadInfo
+class MOZ_EXPORT LoadInfo final : public nsILoadInfo
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSILOADINFO
 
   // aLoadingPrincipal MUST NOT BE NULL.
   LoadInfo(nsIPrincipal* aLoadingPrincipal,
            nsIPrincipal* aTriggeringPrincipal,
--- a/netwerk/test/unit/test_http2.js
+++ b/netwerk/test/unit/test_http2.js
@@ -764,17 +764,17 @@ var tests = [ test_http2_post_big
             , test_http2_concurrent
             , test_http2_concurrent_post
             , test_http2_basic_unblocked_dep
             , test_http2_nospdy
             , test_http2_push1
             , test_http2_push2
             , test_http2_push3
             , test_http2_push4
-            , test_http2_altsvc
+            // , test_http2_altsvc
             , test_http2_doubleheader
             , test_http2_xhr
             , test_http2_header
             , test_http2_cookie_crumbling
             , test_http2_multiplex
             , test_http2_big
             , test_http2_huge_suspended
             , test_http2_post
--- a/testing/mozharness/mozharness.json
+++ b/testing/mozharness/mozharness.json
@@ -1,4 +1,4 @@
 {
     "repo": "https://hg.mozilla.org/build/mozharness",
-    "revision": "9c18f2f9e0c0"
+    "revision": "87da3e48572d"
 }
--- a/toolkit/components/aboutperformance/tests/browser/browser_compartments.js
+++ b/toolkit/components/aboutperformance/tests/browser/browser_compartments.js
@@ -88,33 +88,37 @@ function monotinicity_tester(source, tes
     sanityCheck(previous.processData, snapshot.processData);
     Assert.equal(snapshot.processData.isSystem, true);
     Assert.equal(snapshot.processData.name, "<process>");
     Assert.equal(snapshot.processData.addonId, "");
     previous.procesData = snapshot.processData;
 
     // Sanity check on components data.
     let set = new Set();
+    let keys = [];
     for (let item of snapshot.componentsData) {
-      let key = `{name: ${item.name}, addonId: ${item.addonId}}`;
+      let key = `{name: ${item.name}, addonId: ${item.addonId}, isSystem: ${item.isSystem}}`;
+      keys.push(key);
       set.add(key);
       sanityCheck(previous.componentsMap.get(key), item);
       previous.componentsMap.set(key, item);
 
       for (let k of ["totalUserTime", "totalSystemTime", "totalCPOWTime"]) {
         Assert_leq(item[k], snapshot.processData[k],
           `Sanity check (${name}): component has a lower ${k} than process`);
       }
-      for (let i = 0; i < item.durations.length; ++i) {
-        Assert_leq(item.durations[i], snapshot.processData.durations[i],
-          `Sanity check (${name}): component has a lower durations[${i}] than process.`);
-      }
     }
     // Check that we do not have duplicate components.
-    Assert.equal(set.size, snapshot.componentsData.length);
+    info(`Before deduplication, we had the following components: ${keys.sort().join(", ")}`);
+    info(`After deduplication, we have the following components: ${[...set.keys()].sort().join(", ")}`);
+
+    info(`Deactivating deduplication check (Bug 1150045)`);
+    if (false) {
+      Assert.equal(set.size, snapshot.componentsData.length);
+    }
   });
   let interval = window.setInterval(frameCheck, 300);
   registerCleanupFunction(() => {
     window.clearInterval(interval);
   });
 }
 
 add_task(function* test() {
@@ -128,21 +132,19 @@ add_task(function* test() {
   let browser = newTab.linkedBrowser;
   // Setup monitoring in the tab
   info("Setting up monitoring in the tab");
   yield ContentTask.spawn(newTab.linkedBrowser, null, frameScript);
 
   info("Opening URL");
   newTab.linkedBrowser.loadURI(URL);
 
-  info("Skipping monotonicity testing (1149897)");
-  if (false) {
-    monotinicity_tester(() => PerformanceStats.getSnapshot(), "parent process");
-    monotinicity_tester(() => promiseContentResponseOrNull(browser, "compartments-test:getStatistics", null), "content process" );
-  }
+  info("Setting up monotonicity testing");
+  monotinicity_tester(() => PerformanceStats.getSnapshot(), "parent process");
+  monotinicity_tester(() => promiseContentResponseOrNull(browser, "compartments-test:getStatistics", null), "content process" );
 
   while (true) {
     let stats = (yield promiseContentResponse(browser, "compartments-test:getStatistics", null));
     let found = stats.componentsData.find(stat => {
       return (stat.name.indexOf(URL) != -1)
       && (stat.totalUserTime > 1000)
     });
     if (found) {
--- a/toolkit/components/aboutperformance/tests/xpcshell/test_compartments.js
+++ b/toolkit/components/aboutperformance/tests/xpcshell/test_compartments.js
@@ -60,26 +60,26 @@ function ensureEquals(snap1, snap2, name
     "Same components data: " + name
   );
 }
 
 function hasLowPrecision() {
   let [sysName, sysVersion] = [Services.sysinfo.getPropertyAsAString("name"), Services.sysinfo.getPropertyAsDouble("version")];
   do_print(`Running ${sysName} version ${sysVersion}`);
 
-  if (sysName != "Windows_NT") {
-    do_print("Not running Windows, precision should be good.");
-    return false;
+  if (sysName == "Windows_NT" && sysVersion < 6) {
+    do_print("Running old Windows, need to deactivate tests due to bad precision.");
+    return true;
   }
-  if (sysVersion >= 6) {
-    do_print("Running a recent version of Windows, precision should be good.");
-    return false;
+  if (sysName == "Linux" && sysVersion <= 2.6) {
+    do_print("Running old Linux, need to deactivate tests due to bad precision.");
+    return true;
   }
-  do_print("Running old Windows, need to deactivate tests due to bad precision.");
-  return true;
+  do_print("This platform has good precision.")
+  return false;
 }
 
 add_task(function* test_measure() {
   let skipPrecisionTests = hasLowPrecision();
 
   do_print("Burn CPU without the stopwatch");
   yield promiseSetMonitoring(false);
   let stats0 = yield promiseStatistics("Initial state");
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -969,16 +969,24 @@ grippy {
 .tree-splitter {
   width: 0px;
   max-width: 0px;
   min-width: 0% ! important;
   min-height: 0% ! important;
   -moz-box-ordinal-group: 2147483646;
 }
 
+/******** scrollbar ********/
+
+slider {
+  /* This is a hint to layerization that the scrollbar thumb can never leave
+     the scrollbar track. */
+  overflow: hidden;
+}
+
 /******** scrollbox ********/
 
 scrollbox {
   -moz-binding: url("chrome://global/content/bindings/scrollbox.xml#scrollbox");
   /* This makes it scrollable! */
   overflow: hidden;
 }
 
--- a/toolkit/modules/AddonWatcher.jsm
+++ b/toolkit/modules/AddonWatcher.jsm
@@ -115,20 +115,20 @@ let AddonWatcher = {
    * slice.
    */
   _checkAddons: function() {
     try {
       let snapshot = PerformanceStats.getSnapshot();
 
       let limits = {
         // By default, warn if we have a total time of 1s of CPOW per 15 seconds
-        totalCPOWTime: Math.round(Preferences.get("browser.addon-watch.limits.totalCPOWTime", 1000) * this._interval / 15000),
+        totalCPOWTime: Math.round(Preferences.get("browser.addon-watch.limits.totalCPOWTime", 1000000) * this._interval / 15000),
         // By default, warn if we have skipped 4 consecutive frames
         // at least once during the latest slice.
-        longestDuration: Math.round(Math.log2(Preferences.get("browser.addon-watch.limits.longestDuration", 7))),
+        longestDuration: Math.round(Math.log2(Preferences.get("browser.addon-watch.limits.longestDuration", 128))),
       };
 
       for (let item of snapshot.componentsData) {
         let addonId = item.addonId;
         if (!item.isSystem || !addonId) {
           // We are only interested in add-ons.
           continue;
         }
--- a/tools/profiler/GeckoProfilerFunc.h
+++ b/tools/profiler/GeckoProfilerFunc.h
@@ -52,17 +52,21 @@ void mozilla_sampler_frame_number(int fr
 const double* mozilla_sampler_get_responsiveness();
 
 void mozilla_sampler_save();
 
 char* mozilla_sampler_get_profile();
 
 JSObject *mozilla_sampler_get_profile_data(JSContext *aCx);
 
-void mozilla_sampler_save_profile_to_file(const char* aFilename);
+// Make this function easily callable from a debugger in a build without
+// debugging information (work around http://llvm.org/bugs/show_bug.cgi?id=22211)
+extern "C" {
+  void mozilla_sampler_save_profile_to_file(const char* aFilename);
+}
 
 const char** mozilla_sampler_get_features();
 
 void mozilla_sampler_init(void* stackTop);
 
 void mozilla_sampler_shutdown();
 
 void mozilla_sampler_print_location1();
--- a/widget/windows/GfxInfo.cpp
+++ b/widget/windows/GfxInfo.cpp
@@ -1101,17 +1101,17 @@ GfxInfo::GetGfxDriverInfo()
       nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
       DRIVER_EQUAL, V(15,200,1006,0));
 
     /* Bug 1137716: XXX this should really check for the matching Intel piece as well.
      * Unfortunately, we don't have the infrastructure to do that */
     APPEND_TO_DRIVER_BLOCKLIST_RANGE_GPU2(DRIVER_OS_WINDOWS_7,
         (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(Bug1137716),
       GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
-      DRIVER_BETWEEN_INCLUSIVE, V(8,7,12,5730), V(8,17,12,6901), "Nvidia driver > 8.17.12.6901");
+      DRIVER_BETWEEN_INCLUSIVE, V(8,17,12,5730), V(8,17,12,6901), "Nvidia driver > 8.17.12.6901");
 
   }
   return *mDriverInfo;
 }
 
 nsresult
 GfxInfo::GetFeatureStatusImpl(int32_t aFeature,
                               int32_t *aStatus, 
--- a/xpcom/threads/nsEnvironment.cpp
+++ b/xpcom/threads/nsEnvironment.cpp
@@ -23,19 +23,16 @@ nsEnvironment::Create(nsISupports* aOute
   nsresult rv;
   *aResult = nullptr;
 
   if (aOuter) {
     return NS_ERROR_NO_AGGREGATION;
   }
 
   nsEnvironment* obj = new nsEnvironment();
-  if (!obj) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
 
   rv = obj->QueryInterface(aIID, aResult);
   if (NS_FAILED(rv)) {
     delete obj;
   }
   return rv;
 }
 
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -554,19 +554,16 @@ nsThread::DispatchInternal(nsIRunnable* 
     }
 
     // XXX we should be able to do something better here... we should
     //     be able to monitor the slot occupied by this event and use
     //     that to tell us when the event has been processed.
 
     nsRefPtr<nsThreadSyncDispatch> wrapper =
       new nsThreadSyncDispatch(thread, aEvent);
-    if (!wrapper) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
     nsresult rv = PutEvent(wrapper, aTarget);
     // Don't wait for the event to finish if we didn't dispatch it...
     if (NS_FAILED(rv)) {
       return rv;
     }
 
     // Allows waiting; ensure no locks are held that would deadlock us!
     while (wrapper->IsPending()) {
@@ -634,19 +631,16 @@ nsThread::Shutdown()
 
   nsThreadShutdownContext context;
   context.joiningThread = nsThreadManager::get()->GetCurrentThread();
   context.shutdownAck = false;
 
   // Set mShutdownContext and wake up the thread in case it is waiting for
   // events to process.
   nsCOMPtr<nsIRunnable> event = new nsThreadShutdownEvent(this, &context);
-  if (!event) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
   // XXXroc What if posting the event fails due to OOM?
   PutEvent(event, nullptr);
 
   // We could still end up with other events being added after the shutdown
   // task, but that's okay because we process pending events in ThreadFunc
   // after setting mShutdownContext just before exiting.
 
   // Process events on the current thread until we receive a shutdown ACK.
--- a/xpcom/threads/nsTimerImpl.cpp
+++ b/xpcom/threads/nsTimerImpl.cpp
@@ -299,19 +299,16 @@ nsTimerImpl::~nsTimerImpl()
 nsresult
 nsTimerImpl::Startup()
 {
   nsresult rv;
 
   nsTimerEvent::Init();
 
   gThread = new TimerThread();
-  if (!gThread) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
 
   NS_ADDREF(gThread);
   rv = gThread->InitLocks();
 
   if (NS_FAILED(rv)) {
     NS_RELEASE(gThread);
   }
 
@@ -822,19 +819,16 @@ nsTimerImpl::SizeOfIncludingThis(mozilla
 }
 
 // NOT FOR PUBLIC CONSUMPTION!
 nsresult
 NS_NewTimer(nsITimer** aResult, nsTimerCallbackFunc aCallback, void* aClosure,
             uint32_t aDelay, uint32_t aType)
 {
   nsTimerImpl* timer = new nsTimerImpl();
-  if (!timer) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
   NS_ADDREF(timer);
 
   nsresult rv = timer->InitWithFuncCallback(aCallback, aClosure,
                                             aDelay, aType);
   if (NS_FAILED(rv)) {
     NS_RELEASE(timer);
     return rv;
   }