Merge inbound to mozilla-central r=merge a=merge
authorAttila Craciun <acraciun@mozilla.com>
Wed, 25 Oct 2017 12:30:28 +0300
changeset 388082 e56ae7213756d93da1c1c72805c8f8b8ddb9dcdd
parent 388062 252a8528c5ab4affc2742e7ccfe4ef4aa5ee42f8 (current diff)
parent 388081 8990a530c1eaaec234bfb37b5860cde8a4c0bec7 (diff)
child 388103 6e5e01b3f15f8d995707875d8d2243f9dae52f08
child 388111 5b4e4ba89101c3f6c7a608581f49d3907e40befa
child 388140 060e68fcb4aa179838fa18ea5de750ebd595cc40
push id32740
push useracraciun@mozilla.com
push dateWed, 25 Oct 2017 09:30:59 +0000
treeherdermozilla-central@e56ae7213756 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone58.0a1
first release with
nightly linux32
e56ae7213756 / 58.0a1 / 20171025100449 / files
nightly linux64
e56ae7213756 / 58.0a1 / 20171025100449 / files
nightly mac
e56ae7213756 / 58.0a1 / 20171025100449 / files
nightly win32
e56ae7213756 / 58.0a1 / 20171025100449 / files
nightly win64
e56ae7213756 / 58.0a1 / 20171025100449 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central r=merge a=merge
gfx/webrender_bindings/WebRenderAPI.cpp
gfx/webrender_bindings/WebRenderAPI.h
gfx/webrender_bindings/src/bindings.rs
gfx/webrender_bindings/webrender_ffi_generated.h
layout/painting/nsDisplayList.cpp
testing/mach_commands.py
testing/mozbase/mozrunner/mozrunner/devices/android_device.py
testing/web-platform/tests/tools/wptrunner/wptrunner/testrunner.py
testing/web-platform/tests/tools/wptrunner/wptrunner/wptcommandline.py
testing/web-platform/tests/tools/wptrunner/wptrunner/wptrunner.py
--- a/gfx/layers/ipc/WebRenderMessages.ipdlh
+++ b/gfx/layers/ipc/WebRenderMessages.ipdlh
@@ -19,16 +19,17 @@ using mozilla::wr::ExternalImageId from 
 using mozilla::wr::MaybeFontInstanceOptions from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::MaybeFontInstancePlatformOptions from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::FontInstanceKey from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::FontKey from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::ImageKey from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::PipelineId from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::gfx::MaybeIntSize from "mozilla/gfx/Point.h";
 using mozilla::LayoutDeviceRect from "Units.h";
+using mozilla::ImageIntRect from "Units.h";
 using class mozilla::gfx::Matrix4x4 from "mozilla/gfx/Matrix.h";
 using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
 
 namespace mozilla {
 namespace layers {
 
 union OptionalTransform {
   Matrix4x4;
@@ -91,16 +92,17 @@ struct OpUpdateImage {
   OffsetRange bytes;
   ImageKey key;
 };
 
 struct OpUpdateBlobImage {
   ImageDescriptor descriptor;
   OffsetRange bytes;
   ImageKey key;
+  ImageIntRect dirtyRect;
 };
 
 struct OpDeleteImage {
   ImageKey key;
 };
 
 struct OpAddRawFont {
   OffsetRange bytes;
--- a/gfx/layers/wr/IpcResourceUpdateQueue.cpp
+++ b/gfx/layers/wr/IpcResourceUpdateQueue.cpp
@@ -274,23 +274,24 @@ IpcResourceUpdateQueue::UpdateImageBuffe
   }
   mUpdates.AppendElement(layers::OpUpdateImage(aDescriptor, bytes, aKey));
   return true;
 }
 
 bool
 IpcResourceUpdateQueue::UpdateBlobImage(ImageKey aKey,
                                         const ImageDescriptor& aDescriptor,
-                                        Range<uint8_t> aBytes)
+                                        Range<uint8_t> aBytes,
+                                        ImageIntRect aDirtyRect)
 {
   auto bytes = mWriter.Write(aBytes);
   if (!bytes.length()) {
     return false;
   }
-  mUpdates.AppendElement(layers::OpUpdateBlobImage(aDescriptor, bytes, aKey));
+  mUpdates.AppendElement(layers::OpUpdateBlobImage(aDescriptor, bytes, aKey, aDirtyRect));
   return true;
 }
 
 void
 IpcResourceUpdateQueue::DeleteImage(ImageKey aKey)
 {
   mUpdates.AppendElement(layers::OpDeleteImage(aKey));
 }
--- a/gfx/layers/wr/IpcResourceUpdateQueue.h
+++ b/gfx/layers/wr/IpcResourceUpdateQueue.h
@@ -78,17 +78,18 @@ public:
   void AddExternalImage(wr::ExternalImageId aExtId, wr::ImageKey aKey);
 
   bool UpdateImageBuffer(wr::ImageKey aKey,
                          const ImageDescriptor& aDescriptor,
                          Range<uint8_t> aBytes);
 
   bool UpdateBlobImage(wr::ImageKey aKey,
                        const ImageDescriptor& aDescriptor,
-                       Range<uint8_t> aBytes);
+                       Range<uint8_t> aBytes,
+                       ImageIntRect aDirtyRect);
 
   void UpdateExternalImage(ImageKey aKey,
                            const ImageDescriptor& aDescriptor,
                            ExternalImageId aExtID,
                            wr::WrExternalImageBufferType aBufferType,
                            uint8_t aChannelIndex = 0);
 
   void DeleteImage(wr::ImageKey aKey);
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -280,17 +280,17 @@ WebRenderBridgeParent::UpdateResources(c
         break;
       }
       case OpUpdateResource::TOpUpdateBlobImage: {
         const auto& op = cmd.get_OpUpdateBlobImage();
         wr::Vec_u8 bytes;
         if (!reader.Read(op.bytes(), bytes)) {
           return false;
         }
-        aUpdates.UpdateBlobImage(op.key(), op.descriptor(), bytes);
+        aUpdates.UpdateBlobImage(op.key(), op.descriptor(), bytes, wr::ToDeviceUintRect(op.dirtyRect()));
         break;
       }
       case OpUpdateResource::TOpAddExternalImage: {
         const auto& op = cmd.get_OpAddExternalImage();
         if (!AddExternalImage(op.externalImageId(), op.key(), aUpdates)) {
           return false;
         }
         break;
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -523,22 +523,24 @@ ResourceUpdateQueue::UpdateImageBuffer(I
                                    aKey,
                                    &aDescriptor,
                                    &aBytes.inner);
 }
 
 void
 ResourceUpdateQueue::UpdateBlobImage(ImageKey aKey,
                                      const ImageDescriptor& aDescriptor,
-                                     wr::Vec_u8& aBytes)
+                                     wr::Vec_u8& aBytes,
+                                     const wr::DeviceUintRect& aDirtyRect)
 {
   wr_resource_updates_update_blob_image(mUpdates,
                                         aKey,
                                         &aDescriptor,
-                                        &aBytes.inner);
+                                        &aBytes.inner,
+                                        aDirtyRect);
 }
 
 void
 ResourceUpdateQueue::UpdateExternalImage(ImageKey aKey,
                                          const ImageDescriptor& aDescriptor,
                                          ExternalImageId aExtID,
                                          wr::WrExternalImageBufferType aBufferType,
                                          uint8_t aChannelIndex)
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -81,17 +81,18 @@ public:
                         uint8_t aChannelIndex = 0);
 
   void UpdateImageBuffer(wr::ImageKey aKey,
                          const ImageDescriptor& aDescriptor,
                          wr::Vec_u8& aBytes);
 
   void UpdateBlobImage(wr::ImageKey aKey,
                        const ImageDescriptor& aDescriptor,
-                       wr::Vec_u8& aBytes);
+                       wr::Vec_u8& aBytes,
+                       const wr::DeviceUintRect& aDirtyRect);
 
   void UpdateExternalImage(ImageKey aKey,
                            const ImageDescriptor& aDescriptor,
                            ExternalImageId aExtID,
                            wr::WrExternalImageBufferType aBufferType,
                            uint8_t aChannelIndex = 0);
 
   void DeleteImage(wr::ImageKey aKey);
--- a/gfx/webrender_bindings/WebRenderTypes.h
+++ b/gfx/webrender_bindings/WebRenderTypes.h
@@ -299,16 +299,26 @@ static inline wr::LayoutRect ToLayoutRec
   wr::LayoutRect r;
   r.origin.x = rect.x;
   r.origin.y = rect.y;
   r.size.width = rect.Width();
   r.size.height = rect.Height();
   return r;
 }
 
+static inline wr::DeviceUintRect ToDeviceUintRect(const mozilla::ImageIntRect& rect)
+{
+  wr::DeviceUintRect r;
+  r.origin.x = rect.x;
+  r.origin.y = rect.y;
+  r.size.width = rect.width;
+  r.size.height = rect.height;
+  return r;
+}
+
 static inline wr::LayoutRect ToLayoutRect(const mozilla::LayoutDeviceIntRect& rect)
 {
   return ToLayoutRect(IntRectToRect(rect));
 }
 
 static inline wr::LayoutSize ToLayoutSize(const mozilla::LayoutDeviceSize& size)
 {
   wr::LayoutSize ls;
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -789,22 +789,23 @@ pub extern "C" fn wr_resource_updates_up
 }
 
 #[no_mangle]
 pub extern "C" fn wr_resource_updates_update_blob_image(
     resources: &mut ResourceUpdates,
     image_key: WrImageKey,
     descriptor: &WrImageDescriptor,
     bytes: &mut WrVecU8,
+    dirty_rect: DeviceUintRect,
 ) {
     resources.update_image(
         image_key,
         descriptor.into(),
         ImageData::new_blob_image(bytes.flush_into_vec()),
-        None
+        Some(dirty_rect)
     );
 }
 
 #[no_mangle]
 pub extern "C" fn wr_resource_updates_delete_image(
     resources: &mut ResourceUpdates,
     key: WrImageKey
 ) {
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -827,16 +827,50 @@ struct FontInstancePlatformOptions {
   bool operator==(const FontInstancePlatformOptions& aOther) const {
     return flags == aOther.flags &&
            lcd_filter == aOther.lcd_filter &&
            hinting == aOther.hinting;
   }
 };
 #endif
 
+// A 2d Point tagged with a unit.
+struct TypedPoint2D_u32__DevicePixel {
+  uint32_t x;
+  uint32_t y;
+
+  bool operator==(const TypedPoint2D_u32__DevicePixel& aOther) const {
+    return x == aOther.x &&
+           y == aOther.y;
+  }
+};
+
+struct TypedSize2D_u32__DevicePixel {
+  uint32_t width;
+  uint32_t height;
+
+  bool operator==(const TypedSize2D_u32__DevicePixel& aOther) const {
+    return width == aOther.width &&
+           height == aOther.height;
+  }
+};
+
+// A 2d Rectangle optionally tagged with a unit.
+struct TypedRect_u32__DevicePixel {
+  TypedPoint2D_u32__DevicePixel origin;
+  TypedSize2D_u32__DevicePixel size;
+
+  bool operator==(const TypedRect_u32__DevicePixel& aOther) const {
+    return origin == aOther.origin &&
+           size == aOther.size;
+  }
+};
+
+typedef TypedRect_u32__DevicePixel DeviceUintRect;
+
 /* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen.
  * To generate this file:
  *   1. Get the latest cbindgen using `cargo install --force cbindgen`
  *      a. Alternatively, you can clone `https://github.com/rlhunt/cbindgen` and use a tagged release
  *   2. Run `rustup run nightly cbindgen toolkit/library/rust/ --crate webrender_bindings -o gfx/webrender_bindings/webrender_ffi_generated.h`
  */
 
 extern void gfx_critical_note(const char *aMsg);
@@ -1369,17 +1403,18 @@ WR_INLINE
 void wr_resource_updates_serialize(ResourceUpdates *aResources,
                                    VecU8 *aInto)
 WR_FUNC;
 
 WR_INLINE
 void wr_resource_updates_update_blob_image(ResourceUpdates *aResources,
                                            WrImageKey aImageKey,
                                            const WrImageDescriptor *aDescriptor,
-                                           WrVecU8 *aBytes)
+                                           WrVecU8 *aBytes,
+                                           DeviceUintRect aDirtyRect)
 WR_FUNC;
 
 WR_INLINE
 void wr_resource_updates_update_external_image(ResourceUpdates *aResources,
                                                WrImageKey aKey,
                                                const WrImageDescriptor *aDescriptor,
                                                WrExternalImageId aExternalImageId,
                                                WrExternalImageBufferType aImageType,
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -2879,16 +2879,46 @@ js::str_replace_string_raw(JSContext* cx
         if (!repl)
             return nullptr;
     } else if (string->isRope()) {
         return BuildFlatRopeReplacement(cx, string, repl, match, patternLength);
     }
     return BuildFlatReplacement(cx, string, repl, match, patternLength);
 }
 
+static ArrayObject*
+NewFullyAllocatedStringArray(JSContext* cx, HandleObjectGroup group, uint32_t length)
+{
+    ArrayObject* array = NewFullyAllocatedArrayTryUseGroup(cx, group, length);
+    if (!array)
+        return nullptr;
+
+    // Only string values will be added to this array. Inform TI early about
+    // the element type, so we can directly initialize all elements using
+    // NativeObject::initDenseElement() instead of the slightly more expensive
+    // NativeObject::initDenseElementWithType() method.
+    // Since this function is never called to create a zero-length array, it's
+    // always necessary and correct to call AddTypePropertyId here.
+    MOZ_ASSERT(length > 0);
+    AddTypePropertyId(cx, array, JSID_VOID, TypeSet::StringType());
+
+    return array;
+}
+
+static ArrayObject*
+SingleElementStringArray(JSContext* cx, HandleObjectGroup group, HandleLinearString str)
+{
+    ArrayObject* array = NewFullyAllocatedStringArray(cx, group, 1);
+    if (!array)
+        return nullptr;
+    array->setDenseInitializedLength(1);
+    array->initDenseElement(0, StringValue(str));
+    return array;
+}
+
 // ES 2016 draft Mar 25, 2016 21.1.3.17 steps 4, 8, 12-18.
 static ArrayObject*
 SplitHelper(JSContext* cx, HandleLinearString str, uint32_t limit, HandleLinearString sep,
             HandleObjectGroup group)
 {
     size_t strLength = str->length();
     size_t sepLength = sep->length();
     MOZ_ASSERT(sepLength != 0);
@@ -2898,18 +2928,17 @@ SplitHelper(JSContext* cx, HandleLinearS
         // Step 12.a.
         int match = StringMatch(str, sep, 0);
 
         // Step 12.b.
         if (match != -1)
             return NewFullyAllocatedArrayTryUseGroup(cx, group, 0);
 
         // Steps 12.c-e.
-        RootedValue v(cx, StringValue(str));
-        return NewCopiedArrayTryUseGroup(cx, group, v.address(), 1);
+        return SingleElementStringArray(cx, group, str);
     }
 
     // Step 3 (reordered).
     AutoValueVector splits(cx);
 
     // Step 8 (reordered).
     size_t lastEndIndex = 0;
 
@@ -2986,72 +3015,77 @@ static ArrayObject*
 CharSplitHelper(JSContext* cx, HandleLinearString str, uint32_t limit, HandleObjectGroup group)
 {
     size_t strLength = str->length();
     if (strLength == 0)
         return NewFullyAllocatedArrayTryUseGroup(cx, group, 0);
 
     js::StaticStrings& staticStrings = cx->staticStrings();
     uint32_t resultlen = (limit < strLength ? limit : strLength);
-
-    AutoValueVector splits(cx);
-    if (!splits.reserve(resultlen))
+    MOZ_ASSERT(limit > 0 && resultlen > 0,
+               "Neither limit nor strLength is zero, so resultlen is greater than zero.");
+
+    RootedArrayObject splits(cx, NewFullyAllocatedStringArray(cx, group, resultlen));
+    if (!splits)
         return nullptr;
+    splits->ensureDenseInitializedLength(cx, 0, resultlen);
 
     for (size_t i = 0; i < resultlen; ++i) {
         JSString* sub = staticStrings.getUnitStringForElement(cx, str, i);
         if (!sub)
             return nullptr;
-        splits.infallibleAppend(StringValue(sub));
+        splits->initDenseElement(i, StringValue(sub));
     }
 
-    return NewCopiedArrayTryUseGroup(cx, group, splits.begin(), splits.length());
+    return splits;
 }
 
 template <typename TextChar>
 static MOZ_ALWAYS_INLINE ArrayObject*
 SplitSingleCharHelper(JSContext* cx, HandleLinearString str, const TextChar* text,
                       uint32_t textLen, char16_t patCh, HandleObjectGroup group)
 {
     // Count the number of occurrences of patCh within text.
     uint32_t count = 0;
     for (size_t index = 0; index < textLen; index++) {
         if (static_cast<char16_t>(text[index]) == patCh)
             count++;
     }
 
     // Handle zero-occurrence case - return input string in an array.
-    if (count == 0) {
-        RootedValue strValue(cx, StringValue(str.get()));
-        return NewCopiedArrayTryUseGroup(cx, group, &strValue.get(), 1);
-    }
-
-    // Reserve memory for substring values.
-    AutoValueVector splits(cx);
-    if (!splits.reserve(count + 1))
+    if (count == 0)
+        return SingleElementStringArray(cx, group, str);
+
+    // Create the result array for the substring values.
+    RootedArrayObject splits(cx, NewFullyAllocatedStringArray(cx, group, count + 1));
+    if (!splits)
         return nullptr;
+    splits->ensureDenseInitializedLength(cx, 0, count + 1);
 
     // Add substrings.
+    uint32_t splitsIndex = 0;
     size_t lastEndIndex = 0;
     for (size_t index = 0; index < textLen; index++) {
         if (static_cast<char16_t>(text[index]) == patCh) {
             size_t subLength = size_t(index - lastEndIndex);
             JSString* sub = NewDependentString(cx, str, lastEndIndex, subLength);
-            if (!sub || !splits.append(StringValue(sub)))
+            if (!sub)
                 return nullptr;
+            splits->initDenseElement(splitsIndex++, StringValue(sub));
             lastEndIndex = index + 1;
         }
     }
 
     // Add substring for tail of string (after last match).
     JSString* sub = NewDependentString(cx, str, lastEndIndex, textLen - lastEndIndex);
-    if (!sub || !splits.append(StringValue(sub)))
+    if (!sub)
         return nullptr;
-
-    return NewCopiedArrayTryUseGroup(cx, group, splits.begin(), splits.length());
+    splits->initDenseElement(splitsIndex++, StringValue(sub));
+
+    return splits;
 }
 
 // ES 2016 draft Mar 25, 2016 21.1.3.17 steps 4, 8, 12-18.
 static ArrayObject*
 SplitSingleCharHelper(JSContext* cx, HandleLinearString str, char16_t ch, HandleObjectGroup group)
 {
     // Step 12.
     size_t strLength = str->length();
@@ -3066,16 +3100,18 @@ SplitSingleCharHelper(JSContext* cx, Han
     return SplitSingleCharHelper(cx, str, linearChars.twoByteChars(), strLength, ch, group);
 }
 
 // ES 2016 draft Mar 25, 2016 21.1.3.17 steps 4, 8, 12-18.
 ArrayObject*
 js::str_split_string(JSContext* cx, HandleObjectGroup group, HandleString str, HandleString sep,
                      uint32_t limit)
 {
+    MOZ_ASSERT(limit > 0, "Only called for strictly positive limit.");
+
     RootedLinearString linearStr(cx, str->ensureLinear(cx));
     if (!linearStr)
         return nullptr;
 
     RootedLinearString linearSep(cx, sep->ensureLinear(cx));
     if (!linearSep)
         return nullptr;
 
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -4406,18 +4406,17 @@ js::Lambda(JSContext* cx, HandleFunction
     return clone;
 }
 
 JSObject*
 js::LambdaArrow(JSContext* cx, HandleFunction fun, HandleObject parent, HandleValue newTargetv)
 {
     MOZ_ASSERT(fun->isArrow());
 
-    JSFunction* clone = CloneFunctionObjectIfNotSingleton(cx, fun, parent, nullptr,
-                                                          TenuredObject);
+    JSFunction* clone = CloneFunctionObjectIfNotSingleton(cx, fun, parent);
     if (!clone)
         return nullptr;
 
     MOZ_ASSERT(clone->isArrow());
     clone->setExtendedSlot(0, newTargetv);
 
     MOZ_ASSERT(fun->global() == clone->global());
     return clone;
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -1716,16 +1716,17 @@ intrinsic_StringSplitStringLimit(JSConte
     MOZ_ASSERT(args.length() == 3);
 
     RootedString string(cx, args[0].toString());
     RootedString sep(cx, args[1].toString());
 
     // args[2] should be already in UInt32 range, but it could be double typed,
     // because of Ion optimization.
     uint32_t limit = uint32_t(args[2].toNumber());
+    MOZ_ASSERT(limit > 0, "Zero limit case is already handled in self-hosted code.");
 
     RootedObjectGroup group(cx, ObjectGroupCompartment::getStringSplitStringGroup(cx));
     if (!group)
         return false;
 
     JSObject* aobj = str_split_string(cx, group, string, sep, limit);
     if (!aobj)
         return false;
--- a/layout/base/FrameProperties.h
+++ b/layout/base/FrameProperties.h
@@ -283,22 +283,22 @@ public:
       }
     }
   }
 
   /**
    * Remove and destroy all property values for the frame.
    */
   void DeleteAll(const nsIFrame* aFrame) {
-    mozilla::DebugOnly<size_t> len = mProperties.Length();
-    for (auto& prop : mProperties) {
+    nsTArray<PropertyValue> toDelete;
+    toDelete.SwapElements(mProperties);
+    for (auto& prop : toDelete) {
       prop.DestroyValueFor(aFrame);
-      MOZ_ASSERT(mProperties.Length() == len);
     }
-    mProperties.Clear();
+    MOZ_ASSERT(mProperties.IsEmpty(), "a property dtor added new properties");
   }
 
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
     // We currently report only the shallow size of the mProperties array.
     // As for the PropertyValue entries: we don't need to measure the mProperty
     // field of because it always points to static memory, and we can't measure
     // mValue because the type is opaque.
     // XXX Can we do better, e.g. with a method on the descriptor?
--- a/layout/base/Units.h
+++ b/layout/base/Units.h
@@ -27,22 +27,24 @@ struct IsPixel : FalseType {};
 struct CSSPixel;
 struct LayoutDevicePixel;
 struct LayerPixel;
 struct CSSTransformedLayerPixel;
 struct RenderTargetPixel;
 struct ScreenPixel;
 struct ParentLayerPixel;
 struct DesktopPixel;
+struct ImagePixel;
 
 template<> struct IsPixel<CSSPixel>          : TrueType {};
 template<> struct IsPixel<LayoutDevicePixel> : TrueType {};
 template<> struct IsPixel<LayerPixel>        : TrueType {};
 template<> struct IsPixel<CSSTransformedLayerPixel> : TrueType {};
 template<> struct IsPixel<RenderTargetPixel> : TrueType {};
+template<> struct IsPixel<ImagePixel> : TrueType {};
 template<> struct IsPixel<ScreenPixel>       : TrueType {};
 template<> struct IsPixel<ParentLayerPixel>  : TrueType {};
 template<> struct IsPixel<DesktopPixel>      : TrueType {};
 
 typedef gfx::CoordTyped<CSSPixel> CSSCoord;
 typedef gfx::IntCoordTyped<CSSPixel> CSSIntCoord;
 typedef gfx::PointTyped<CSSPixel> CSSPoint;
 typedef gfx::IntPointTyped<CSSPixel> CSSIntPoint;
@@ -95,16 +97,18 @@ typedef gfx::IntPointTyped<RenderTargetP
 typedef gfx::SizeTyped<RenderTargetPixel> RenderTargetSize;
 typedef gfx::IntSizeTyped<RenderTargetPixel> RenderTargetIntSize;
 typedef gfx::RectTyped<RenderTargetPixel> RenderTargetRect;
 typedef gfx::IntRectTyped<RenderTargetPixel> RenderTargetIntRect;
 typedef gfx::MarginTyped<RenderTargetPixel> RenderTargetMargin;
 typedef gfx::IntMarginTyped<RenderTargetPixel> RenderTargetIntMargin;
 typedef gfx::IntRegionTyped<RenderTargetPixel> RenderTargetIntRegion;
 
+typedef gfx::IntRectTyped<ImagePixel> ImageIntRect;
+
 typedef gfx::CoordTyped<ScreenPixel> ScreenCoord;
 typedef gfx::IntCoordTyped<ScreenPixel> ScreenIntCoord;
 typedef gfx::PointTyped<ScreenPixel> ScreenPoint;
 typedef gfx::IntPointTyped<ScreenPixel> ScreenIntPoint;
 typedef gfx::SizeTyped<ScreenPixel> ScreenSize;
 typedef gfx::IntSizeTyped<ScreenPixel> ScreenIntSize;
 typedef gfx::RectTyped<ScreenPixel> ScreenRect;
 typedef gfx::IntRectTyped<ScreenPixel> ScreenIntRect;
@@ -404,16 +408,23 @@ struct CSSTransformedLayerPixel {
  * root render target RenderTargetPixel == ScreenPixel. Also
  * any ContainerLayer providing an intermediate surface will
  * have RenderTargetPixel == LayerPixel.
  */
 struct RenderTargetPixel {
 };
 
 /*
+ * This unit represents one pixel in an image. Image space
+ * is largely independent of any other space.
+ */
+struct ImagePixel {
+};
+
+/*
  * The pixels that are displayed on the screen.
  * On non-OMTC platforms this should be equivalent to LayerPixel units.
  * On OMTC platforms these may diverge from LayerPixel units temporarily,
  * while an asynchronous zoom is happening, but should eventually converge
  * back to LayerPixel units. Some variables (such as those representing
  * chrome UI element sizes) that are not subject to content zoom should
  * generally be represented in ScreenPixel units.
  */
--- a/layout/generic/ViewportFrame.cpp
+++ b/layout/generic/ViewportFrame.cpp
@@ -116,17 +116,17 @@ BuildDisplayListForTopLayerFrame(nsDispl
     // scroll frame is no longer on aBuilder. However, we need to make sure
     // that the display items we build in this function have finite clipped
     // bounds with respect to the root ASR, so we restore the *combined clip*
     // that we saved earlier. The combined clip will include the clip from the
     // root scroll frame.
     clipState.SetClipChainForContainingBlockDescendants(
       savedOutOfFlowData->mCombinedClipChain);
     clipState.ClipContainingBlockDescendantsExtra(
-      dirty + aBuilder->ToReferenceFrame(aFrame), nullptr);
+      visible + aBuilder->ToReferenceFrame(aFrame), nullptr);
     asrSetter.SetCurrentActiveScrolledRoot(
       savedOutOfFlowData->mContainingBlockActiveScrolledRoot);
   }
   nsDisplayListBuilder::AutoBuildingDisplayList
     buildingForChild(aBuilder, aFrame, visible, dirty,
                      aBuilder->IsAtRootOfPseudoStackingContext());
 
   nsDisplayList list(aBuilder);
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -952,18 +952,22 @@ nsIFrame::HasDisplayItem(nsDisplayItem* 
 void
 nsIFrame::RemoveDisplayItemDataForDeletion()
 {
   FrameLayerBuilder::RemoveFrameFromLayerManager(this, DisplayItemData());
   DisplayItemData().Clear();
 
   DisplayItemArray* items = RemoveProperty(DisplayItems());
   if (items) {
-    for (nsDisplayItem* item : *items) {
-      item->RemoveFrame(this);
+    for (nsDisplayItem* i : *items) {
+      if (i->GetDependentFrame() == this &&
+          !i->HasDeletedFrame()) {
+        i->Frame()->MarkNeedsDisplayItemRebuild();
+      }
+      i->RemoveFrame(this);
     }
     delete items;
   }
 }
 
 void
 nsIFrame::MarkNeedsDisplayItemRebuild()
 {
@@ -2513,17 +2517,18 @@ WrapSeparatorTransform(nsDisplayListBuil
                                         aBuilder->GetVisibleRect(), Matrix4x4(), aIndex);
     sepIdItem->SetNoExtendContext();
     aTarget->AppendToTop(sepIdItem);
   }
 }
 
 void
 nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder,
-                                             nsDisplayList*        aList) {
+                                             nsDisplayList*        aList,
+                                             bool*                 aCreatedContainerItem) {
   if (GetStateBits() & NS_FRAME_TOO_DEEP_IN_FRAME_TREE)
     return;
 
   // Replaced elements have their visibility handled here, because
   // they're visually atomic
   if (IsFrameOfType(eReplaced) && !IsVisibleForPainting(aBuilder))
     return;
 
@@ -2892,29 +2897,36 @@ nsIFrame::BuildDisplayListForStackingCon
 #endif
   resultList.AppendToTop(set.Outlines());
   // 8, 9: non-negative z-index children
   resultList.AppendToTop(set.PositionedDescendants());
 
   // Get the ASR to use for the container items that we create here.
   const ActiveScrolledRoot* containerItemASR = contASRTracker.GetContainerASR();
 
+  if (aCreatedContainerItem) {
+    *aCreatedContainerItem = false;
+  }
+
   /* If adding both a nsDisplayBlendContainer and a nsDisplayBlendMode to the
    * same list, the nsDisplayBlendContainer should be added first. This only
    * happens when the element creating this stacking context has mix-blend-mode
    * and also contains a child which has mix-blend-mode.
    * The nsDisplayBlendContainer must be added to the list first, so it does not
    * isolate the containing element blending as well.
    */
   if (aBuilder->ContainsBlendMode()) {
     DisplayListClipState::AutoSaveRestore blendContainerClipState(aBuilder);
     blendContainerClipState.ClearUpToASR(containerItemASR);
     resultList.AppendNewToTop(
       nsDisplayBlendContainer::CreateForMixBlendMode(aBuilder, this, &resultList,
                                                      containerItemASR));
+    if (aCreatedContainerItem) {
+      *aCreatedContainerItem = true;
+    }
   }
 
   /* If there are any SVG effects, wrap the list up in an SVG effects item
    * (which also handles CSS group opacity). Note that we create an SVG effects
    * item even if resultList is empty, since a filter can produce graphical
    * output even if the element being filtered wouldn't otherwise do so.
    */
   if (usingSVGEffects) {
@@ -2948,31 +2960,37 @@ nsIFrame::BuildDisplayListForStackingCon
           new (aBuilder) nsDisplayMask(aBuilder, this, &resultList,
                                        !useOpacity, containerItemASR));
     }
 
     // Also add the hoisted scroll info items. We need those for APZ scrolling
     // because nsDisplayMask items can't build active layers.
     aBuilder->ExitSVGEffectsContents();
     resultList.AppendToTop(&hoistedScrollInfoItemsStorage);
+    if (aCreatedContainerItem) {
+      *aCreatedContainerItem = false;
+    }
   }
 
   /* If the list is non-empty and there is CSS group opacity without SVG
    * effects, wrap it up in an opacity item.
    */
   if (useOpacity) {
     // Don't clip nsDisplayOpacity items. We clip their descendants instead.
     // The clip we would set on an element with opacity would clip
     // all descendant content, but some should not be clipped.
     DisplayListClipState::AutoSaveRestore opacityClipState(aBuilder);
     opacityClipState.ClearUpToASR(containerItemASR);
     resultList.AppendNewToTop(
         new (aBuilder) nsDisplayOpacity(aBuilder, this, &resultList,
                                         containerItemASR,
                                         opacityItemForEventsAndPluginsOnly));
+    if (aCreatedContainerItem) {
+      *aCreatedContainerItem = true;
+    }
   }
 
   /* If we're going to apply a transformation and don't have preserve-3d set, wrap
    * everything in an nsDisplayTransform. If there's nothing in the list, don't add
    * anything.
    *
    * For the preserve-3d case we want to individually wrap every child in the list with
    * a separate nsDisplayTransform instead. When the child is already an nsDisplayTransform,
@@ -3038,100 +3056,109 @@ nsIFrame::BuildDisplayListForStackingCon
         clipState.Restore();
       }
       resultList.AppendNewToTop(
         new (aBuilder) nsDisplayPerspective(
           aBuilder, this,
           GetContainingBlock(0, disp)->GetContent()->GetPrimaryFrame(),
           &resultList));
     }
+
+    if (aCreatedContainerItem) {
+      *aCreatedContainerItem = true;
+    }
   }
 
   if (clipCapturedBy == ContainerItemType::eOwnLayerForTransformWithRoundedClip) {
     clipState.Restore();
     resultList.AppendNewToTop(
       new (aBuilder) nsDisplayOwnLayer(aBuilder, this, &resultList,
                                        aBuilder->CurrentActiveScrolledRoot(), 0,
                                        mozilla::layers::FrameMetrics::NULL_SCROLL_ID,
                                        ScrollThumbData{}, /* aForceActive = */ false));
+    if (aCreatedContainerItem) {
+      *aCreatedContainerItem = true;
+    }
   }
 
   /* If we have sticky positioning, wrap it in a sticky position item.
    */
   if (useFixedPosition) {
     if (clipCapturedBy == ContainerItemType::eFixedPosition) {
       clipState.Restore();
     }
     // The ASR for the fixed item should be the ASR of our containing block,
     // which has been set as the builder's current ASR, unless this frame is
     // invisible and we hadn't saved display item data for it. In that case,
     // we need to take the containerItemASR since we might have fixed children.
     const ActiveScrolledRoot* fixedASR =
       ActiveScrolledRoot::PickAncestor(containerItemASR, aBuilder->CurrentActiveScrolledRoot());
     resultList.AppendNewToTop(
         new (aBuilder) nsDisplayFixedPosition(aBuilder, this, &resultList, fixedASR));
+    if (aCreatedContainerItem) {
+      *aCreatedContainerItem = true;
+    }
   } else if (useStickyPosition) {
     // For position:sticky, the clip needs to be applied both to the sticky
     // container item and to the contents. The container item needs the clip
     // because a scrolled clip needs to move independently from the sticky
     // contents, and the contents need the clip so that they have finite
     // clipped bounds with respect to the container item's ASR. The latter is
     // a little tricky in the case where the sticky item has both fixed and
     // non-fixed descendants, because that means that the sticky container
     // item's ASR is the ASR of the fixed descendant.
     const ActiveScrolledRoot* stickyASR =
       ActiveScrolledRoot::PickAncestor(containerItemASR, aBuilder->CurrentActiveScrolledRoot());
     resultList.AppendNewToTop(
         new (aBuilder) nsDisplayStickyPosition(aBuilder, this, &resultList, stickyASR));
+    if (aCreatedContainerItem) {
+      *aCreatedContainerItem = true;
+    }
   }
 
   /* If there's blending, wrap up the list in a blend-mode item. Note
    * that opacity can be applied before blending as the blend color is
    * not affected by foreground opacity (only background alpha).
    */
 
   if (useBlendMode) {
     DisplayListClipState::AutoSaveRestore blendModeClipState(aBuilder);
     blendModeClipState.ClearUpToASR(containerItemASR);
     resultList.AppendNewToTop(
         new (aBuilder) nsDisplayBlendMode(aBuilder, this, &resultList,
                                           effects->mMixBlendMode,
                                           containerItemASR));
-  }
-
-  CreateOwnLayerIfNeeded(aBuilder, &resultList);
+    if (aCreatedContainerItem) {
+      *aCreatedContainerItem = true;
+    }
+  }
+
+  CreateOwnLayerIfNeeded(aBuilder, &resultList, aCreatedContainerItem);
 
   aList->AppendToTop(&resultList);
 }
 
 static nsDisplayItem*
 WrapInWrapList(nsDisplayListBuilder* aBuilder,
                nsIFrame* aFrame, nsDisplayList* aList,
-               const ActiveScrolledRoot* aContainerASR)
+               const ActiveScrolledRoot* aContainerASR,
+               bool aCanSkipWrapList = false)
 {
   nsDisplayItem* item = aList->GetBottom();
   if (!item) {
     return nullptr;
   }
 
-  // For perspective items we want to treat the 'frame' as being the transform
-  // frame that created it. This stops the transform frame from wrapping another
-  // nsDisplayWrapList around it (with mismatching reference frames), but still
-  // makes the perspective frame create one (so we have an atomic entry for z-index
-  // sorting).
-  nsIFrame *itemFrame = item->Frame();
-  if (item->GetType() == DisplayItemType::TYPE_PERSPECTIVE) {
-    itemFrame = static_cast<nsDisplayPerspective*>(item)->TransformFrame();
-  }
-
-  if (item->GetAbove() || itemFrame != aFrame) {
-    return new (aBuilder) nsDisplayWrapList(aBuilder, aFrame, aList, aContainerASR);
-  }
-  aList->RemoveBottom();
-  return item;
+  if (aCanSkipWrapList) {
+    MOZ_ASSERT(!item->GetAbove());
+    aList->RemoveBottom();
+    return item;
+  }
+
+  return new (aBuilder) nsDisplayWrapList(aBuilder, aFrame, aList, aContainerASR);
 }
 
 /**
  * Check if a frame should be visited for building display list.
  */
 static bool
 DescendIntoChild(nsDisplayListBuilder* aBuilder, nsIFrame *aChild,
                  const nsRect& aVisible, const nsRect& aDirty)
@@ -3411,25 +3438,26 @@ nsIFrame::BuildDisplayListForChild(nsDis
     parent == this ? ourDisp : parent->StyleDisplay();
   if (ApplyOverflowClipping(aBuilder, parent, parentDisp, clipState)) {
     awayFromCommonPath = true;
   }
 
   nsDisplayList list(aBuilder);
   nsDisplayList extraPositionedDescendants(aBuilder);
   const ActiveScrolledRoot* wrapListASR = aBuilder->CurrentActiveScrolledRoot();
+  bool canSkipWrapList = false;
   if (isStackingContext) {
     if (effects->mMixBlendMode != NS_STYLE_BLEND_NORMAL) {
       aBuilder->SetContainsBlendMode(true);
     }
     // True stacking context.
     // For stacking contexts, BuildDisplayListForStackingContext handles
     // clipping and MarkAbsoluteFramesForDisplayList.
     nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder);
-    child->BuildDisplayListForStackingContext(aBuilder, &list);
+    child->BuildDisplayListForStackingContext(aBuilder, &list, &canSkipWrapList);
     wrapListASR = contASRTracker.GetContainerASR();
     aBuilder->DisplayCaret(child, &list);
   } else {
     Maybe<nsRect> clipPropClip =
       child->GetClipPropClipRect(disp, effects, child->GetSize());
     if (clipPropClip) {
       aBuilder->IntersectVisibleRect(*clipPropClip);
       aBuilder->IntersectDirtyRect(*clipPropClip);
@@ -3512,17 +3540,17 @@ nsIFrame::BuildDisplayListForChild(nsDis
   // clipping all their contents, they themselves don't need to be clipped.
   clipState.ClearUpToASR(wrapListASR);
 
   if (isPositioned || isVisuallyAtomic ||
       (aFlags & DISPLAY_CHILD_FORCE_STACKING_CONTEXT)) {
     // Genuine stacking contexts, and positioned pseudo-stacking-contexts,
     // go in this level.
     if (!list.IsEmpty()) {
-      nsDisplayItem* item = WrapInWrapList(aBuilder, child, &list, wrapListASR);
+      nsDisplayItem* item = WrapInWrapList(aBuilder, child, &list, wrapListASR, canSkipWrapList);
       if (isSVG) {
         aLists.Content()->AppendNewToTop(item);
       } else {
         aLists.PositionedDescendants()->AppendNewToTop(item);
       }
     }
   } else if (!isSVG && disp->IsFloating(child)) {
     if (!list.IsEmpty()) {
@@ -10638,23 +10666,27 @@ nsIFrame::SetParent(nsContainerFrame* aP
     InvalidateFrame();
   } else {
     SchedulePaint();
   }
 }
 
 void
 nsIFrame::CreateOwnLayerIfNeeded(nsDisplayListBuilder* aBuilder,
-                                 nsDisplayList* aList)
+                                 nsDisplayList* aList,
+                                 bool* aCreatedContainerItem)
 {
   if (GetContent() &&
       GetContent()->IsXULElement() &&
       GetContent()->HasAttr(kNameSpaceID_None, nsGkAtoms::layer)) {
     aList->AppendNewToTop(new (aBuilder)
         nsDisplayOwnLayer(aBuilder, this, aList, aBuilder->CurrentActiveScrolledRoot()));
+    if (aCreatedContainerItem) {
+      *aCreatedContainerItem = true;
+    }
   }
 }
 
 bool
 nsIFrame::IsSelected() const
 {
   return (GetContent() && GetContent()->IsSelectionDescendant()) ?
     IsFrameSelected() : false;
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -1696,19 +1696,23 @@ public:
         theme->GetWidgetTransparency(mutable_this, aDisp->mAppearance);
     }
     return true;
   }
 
   /**
    * Builds a display list for the content represented by this frame,
    * treating this frame as the root of a stacking context.
+   * Optionally sets aCreatedContainerItem to true if we created a
+   * single container display item for the stacking context, and no
+   * other wrapping items are needed.
    */
   void BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder,
-                                          nsDisplayList*        aList);
+                                          nsDisplayList*        aList,
+                                          bool*                 aCreatedContainerItem = nullptr);
 
   enum {
     DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT = 0x01,
     DISPLAY_CHILD_FORCE_STACKING_CONTEXT = 0x02,
     DISPLAY_CHILD_INLINE = 0x04
   };
   /**
    * Adjusts aDirtyRect for the child's offset, checks that the dirty rect
@@ -3933,17 +3937,18 @@ public:
    * of the enumerated values.  If this is an SVG text frame, it returns a value
    * that corresponds to the value of dominant-baseline.  If the
    * vertical-align property has length or percentage value, this returns
    * eInvalidVerticalAlign.
    */
   uint8_t VerticalAlignEnum() const;
   enum { eInvalidVerticalAlign = 0xFF };
 
-  void CreateOwnLayerIfNeeded(nsDisplayListBuilder* aBuilder, nsDisplayList* aList);
+  void CreateOwnLayerIfNeeded(nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
+                              bool* aCreatedContainerItem = nullptr);
 
   /**
    * Adds the NS_FRAME_IN_POPUP state bit to aFrame, and
    * all descendant frames (including cross-doc ones).
    */
   static void AddInPopupStateBitToDescendants(nsIFrame* aFrame);
   /**
    * Removes the NS_FRAME_IN_POPUP state bit from aFrame and
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -1124,25 +1124,24 @@ void nsDisplayListBuilder::MarkOutOfFlow
     NS_ASSERTION(aDirtyFrame == aFrame->GetParent(), "Dirty frame should be viewport frame");
     // position: fixed items are reflowed into and only drawn inside the
     // viewport, or the scroll position clamping scrollport size, if one is
     // set.
     nsIPresShell* ps = aFrame->PresContext()->PresShell();
     if (ps->IsScrollPositionClampingScrollPortSizeSet()) {
       dirtyRectRelativeToDirtyFrame =
         nsRect(nsPoint(0, 0), ps->GetScrollPositionClampingScrollPortSize());
+      visible = dirtyRectRelativeToDirtyFrame;
 #ifdef MOZ_WIDGET_ANDROID
     } else {
       dirtyRectRelativeToDirtyFrame =
         nsRect(nsPoint(0, 0), aDirtyFrame->GetSize());
+      visible = dirtyRectRelativeToDirtyFrame;
 #endif
     }
-    // TODO: We probably don't want visible and dirty to be the same here, figure
-    // out what to do.
-    visible = dirtyRectRelativeToDirtyFrame;
   }
   nsPoint offset = aFrame->GetOffsetTo(aDirtyFrame);
   visible -= offset;
   nsRect dirty = dirtyRectRelativeToDirtyFrame - offset;
   nsRect overflowRect = aFrame->GetVisualOverflowRect();
 
   if (aFrame->IsTransformed() &&
       EffectCompositor::HasAnimationsForCompositor(aFrame,
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -191,27 +191,29 @@ struct AnimatedGeometryRoot
   RefPtr<AnimatedGeometryRoot> mParentAGR;
   bool mIsAsync;
   bool mIsRetained;
 
 protected:
   static void DetachAGR(AnimatedGeometryRoot* aAGR) {
     aAGR->mFrame = nullptr;
     aAGR->mParentAGR = nullptr;
+    NS_RELEASE(aAGR);
   }
   NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(AnimatedGeometryRootCache, AnimatedGeometryRoot, DetachAGR)
 
   AnimatedGeometryRoot(nsIFrame* aFrame, AnimatedGeometryRoot* aParent, bool aIsAsync, bool aIsRetained)
     : mFrame(aFrame)
     , mParentAGR(aParent)
     , mIsAsync(aIsAsync)
     , mIsRetained(aIsRetained)
   {
     MOZ_ASSERT(mParentAGR || mIsAsync, "The root AGR should always be treated as an async AGR.");
     if (mIsRetained) {
+      NS_ADDREF(this);
       aFrame->SetProperty(AnimatedGeometryRootCache(), this);
     }
   }
 
   ~AnimatedGeometryRoot()
   {
     if (mFrame && mIsRetained) {
       mFrame->DeleteProperty(AnimatedGeometryRootCache());
@@ -249,17 +251,18 @@ struct ActiveScrolledRoot {
     if (aIsRetained) {
       asr = f->GetProperty(ActiveScrolledRootCache());
     }
 
     if (!asr) {
       asr = new ActiveScrolledRoot();
 
       if (aIsRetained) {
-        f->SetProperty(ActiveScrolledRootCache(), asr);
+        RefPtr<ActiveScrolledRoot> ref = asr;
+        f->SetProperty(ActiveScrolledRootCache(), ref.forget().take());
       }
     }
     asr->mParent = aParent;
     asr->mScrollableFrame = aScrollableFrame;
     asr->mDepth = aParent ? aParent->mDepth + 1 : 1;
     asr->mRetained = aIsRetained;
 
     return asr.forget();
@@ -303,16 +306,17 @@ private:
       nsIFrame* f = do_QueryFrame(mScrollableFrame);
       f->DeleteProperty(ActiveScrolledRootCache());
     }
   }
 
   static void DetachASR(ActiveScrolledRoot* aASR) {
     aASR->mParent = nullptr;
     aASR->mScrollableFrame = nullptr;
+    NS_RELEASE(aASR);
   }
   NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(ActiveScrolledRootCache, ActiveScrolledRoot, DetachASR)
 
   static uint32_t Depth(const ActiveScrolledRoot* aActiveScrolledRoot) {
     return aActiveScrolledRoot ? aActiveScrolledRoot->mDepth : 0;
   }
 
   uint32_t mDepth;
--- a/layout/reftests/display-list/reftest.list
+++ b/layout/reftests/display-list/reftest.list
@@ -2,8 +2,9 @@ skip-if(!retainedDisplayList) == retaine
 skip-if(!retainedDisplayList) == retained-dl-frame-deleted-1.html retained-dl-style-change-1-ref.html
 skip-if(!retainedDisplayList) == retained-dl-frame-created-1.html retained-dl-style-change-1-ref.html
 skip-if(!retainedDisplayList) == retained-dl-style-change-stacking-context-1.html retained-dl-style-change-stacking-context-1-ref.html
 skip-if(!retainedDisplayList||!asyncPan) == retained-dl-async-scrolled-1.html retained-dl-async-scrolled-1-ref.html
 skip-if(!retainedDisplayList) == retained-dl-remove-for-ancestor-change-1.html retained-dl-remove-for-ancestor-change-1-ref.html
 skip-if(!retainedDisplayList) == retained-dl-scroll-out-of-view-1.html retained-dl-scroll-out-of-view-1-ref.html
 skip-if(!retainedDisplayList) == retained-dl-displayport-1.html retained-dl-displayport-1-ref.html
 skip-if(!retainedDisplayList) == retained-dl-prerender-transform-1.html retained-dl-prerender-transform-1-ref.html
+== retained-dl-wrap-list.html retained-dl-wrap-list-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/display-list/retained-dl-wrap-list-ref.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<style>
+#hover {
+  background-color:blue;
+}
+</style>
+</head>
+<body>
+  <div style="float:left; width:200px; height:200px; background-color: red">
+    <div style="width:100px; height:100px; " id="hover"></div>
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/display-list/retained-dl-wrap-list.html
@@ -0,0 +1,15 @@
+<html class="reftest-wait">
+<body>
+  <div style="float:left; width:200px; height:200px; background-color: red">
+    <div style="width:100px; height:100px;" id="hover"></div>
+  </div>
+</body>
+<script>
+function doTest() {
+  document.getElementById("hover").style.backgroundColor = "blue";
+  document.documentElement.removeAttribute("class");
+}
+
+window.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</html>
--- a/layout/tools/reftest/mach_commands.py
+++ b/layout/tools/reftest/mach_commands.py
@@ -131,19 +131,22 @@ class ReftestRunner(MozbuildObject):
             args.xrePath = os.environ.get("MOZ_HOST_BIN")
         if not args.app:
             args.app = self.substs["ANDROID_PACKAGE_NAME"]
         if not args.utilityPath:
             args.utilityPath = args.xrePath
         args.ignoreWindowSize = True
         args.printDeviceInfo = False
 
-        from mozrunner.devices.android_device import grant_runtime_permissions
+        from mozrunner.devices.android_device import grant_runtime_permissions, get_adb_path
         grant_runtime_permissions(self)
 
+        if not args.adb_path:
+            args.adb_path = get_adb_path(self)
+
         # A symlink and some path manipulations are required so that test
         # manifests can be found both locally and remotely (via a url)
         # using the same relative path.
         if args.suite == "jstestbrowser":
             staged_js_dir = os.path.join(self.topobjdir, "dist", "test-stage", "jsreftest")
             tests = os.path.join(self.reftest_dir, 'jsreftest')
             if not os.path.isdir(tests):
                 os.symlink(staged_js_dir, tests)
--- a/layout/tools/reftest/reftestcommandline.py
+++ b/layout/tools/reftest/reftestcommandline.py
@@ -413,17 +413,17 @@ class RemoteArgumentsParser(ReftestArgum
                           help="Path to remote executable relative to device root using only "
                                "forward slashes.  Either this or app must be specified, "
                                "but not both.")
 
         self.add_argument("--adbpath",
                           action="store",
                           type=str,
                           dest="adb_path",
-                          default="adb",
+                          default=None,
                           help="path to adb")
 
         self.add_argument("--deviceIP",
                           action="store",
                           type=str,
                           dest="deviceIP",
                           help="ip address of remote device to test")
 
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -93,28 +93,30 @@ using namespace mozilla;
 
 #ifdef DEBUG
 
 #define ENSURE_MAIN_PROCESS(func, pref)                                        \
   do {                                                                         \
     if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {                                \
       nsPrintfCString msg(                                                     \
         "ENSURE_MAIN_PROCESS: called %s on %s in a non-main process",          \
-        func, pref);                                                           \
+        func,                                                                  \
+        pref);                                                                 \
       NS_ERROR(msg.get());                                                     \
       return NS_ERROR_NOT_AVAILABLE;                                           \
     }                                                                          \
   } while (0)
 
 #define ENSURE_MAIN_PROCESS_WITH_WARNING(func, pref)                           \
   do {                                                                         \
     if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {                                \
       nsPrintfCString msg(                                                     \
         "ENSURE_MAIN_PROCESS: called %s on %s in a non-main process",          \
-        func, pref);                                                           \
+        func,                                                                  \
+        pref);                                                                 \
       NS_WARNING(msg.get());                                                   \
       return NS_ERROR_NOT_AVAILABLE;                                           \
     }                                                                          \
   } while (0)
 
 #else // DEBUG
 
 #define ENSURE_MAIN_PROCESS(func, pref)                                        \
@@ -238,21 +240,18 @@ private:
   }
 
   // We pack both the value of type (PrefType) and flags into the same int. The
   // flag enum starts at 4 so that the PrefType can occupy the bottom two bits.
   enum
   {
     PREF_FLAG_LOCKED = 4,
     PREF_FLAG_USERSET = 8,
-    PREF_FLAG_CONFIG = 16,
-    PREF_FLAG_REMOTE = 32,
-    PREF_FLAG_LILOCAL = 64,
-    PREF_FLAG_HAS_DEFAULT = 128,
-    PREF_FLAG_STICKY_DEFAULT = 256,
+    PREF_FLAG_HAS_DEFAULT = 16,
+    PREF_FLAG_STICKY_DEFAULT = 32,
   };
   uint16_t mValue;
 };
 
 struct PrefHashEntry : PLDHashEntryHdr
 {
   PrefTypeFlags mPrefFlags; // this field first to minimize 64-bit struct size
   const char* mKey;
@@ -2171,18 +2170,16 @@ public:
   NS_DECL_NSIPREFBRANCH
   NS_DECL_NSIOBSERVER
 
   nsPrefBranch(const char* aPrefRoot, bool aDefaultBranch);
   nsPrefBranch() = delete;
 
   int32_t GetRootLength() const { return mPrefRoot.Length(); }
 
-  nsresult RemoveObserverFromMap(const char* aDomain, nsISupports* aObserver);
-
   static void NotifyObserver(const char* aNewpref, void* aData);
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 
   static void ReportToConsole(const nsAString& aMessage);
 
 protected:
   // Helper class for either returning a raw cstring or nsCString.
@@ -2256,18 +2253,17 @@ protected:
 private:
   const nsCString mPrefRoot;
   bool mIsDefault;
 
   bool mFreeingObserverList;
   nsClassHashtable<PrefCallback, PrefCallback> mObservers;
 };
 
-class nsPrefLocalizedString final
-  : public nsIPrefLocalizedString
+class nsPrefLocalizedString final : public nsIPrefLocalizedString
 {
 public:
   nsPrefLocalizedString();
 
   NS_DECL_ISUPPORTS
   NS_FORWARD_NSISUPPORTSPRIMITIVE(mUnicodeString->)
   NS_FORWARD_NSISUPPORTSSTRING(mUnicodeString->)
 
--- a/moz.build
+++ b/moz.build
@@ -45,26 +45,26 @@ with Files('**/l10n.toml'):
 with Files('README.txt'):
     BUG_COMPONENT = ('Core', 'General')
 
 with Files('**/Makefile.in'):
     BUG_COMPONENT = ('Core', 'Build Config')
     FINAL = True
 
 with Files("**/*.js"):
-    SCHEDULES.inclusive += ['test-verification']
+    SCHEDULES.inclusive += ['test-verify']
 
 with Files("**/*.html"):
-    SCHEDULES.inclusive += ['test-verification']
+    SCHEDULES.inclusive += ['test-verify']
 
 with Files("**/*.xhtml"):
-    SCHEDULES.inclusive += ['test-verification']
+    SCHEDULES.inclusive += ['test-verify']
 
 with Files("**/*.xul"):
-    SCHEDULES.inclusive += ['test-verification']
+    SCHEDULES.inclusive += ['test-verify']
 
 FILES_PER_UNIFIED_FILE = 1
 
 CONFIGURE_SUBST_FILES += [
     'config/autoconf.mk',
     'config/emptyvars.mk',
 ]
 
--- a/python/mozbuild/mozbuild/schedules.py
+++ b/python/mozbuild/mozbuild/schedules.py
@@ -12,17 +12,17 @@ from __future__ import absolute_import, 
 # TODO: ideally these lists could be specified in moz.build itself
 
 INCLUSIVE_COMPONENTS = [
     'py-lint',
     'js-lint',
     'yaml-lint',
     # test suites that only run when certain files have changed
     'jittest',
-    'test-verification',
+    'test-verify',
     # test flavors (narrower than suites)
     'jsreftest',
 ]
 INCLUSIVE_COMPONENTS = sorted(INCLUSIVE_COMPONENTS)
 
 EXCLUSIVE_COMPONENTS = [
     # os families
     'android',
--- a/taskcluster/ci/test/misc.yml
+++ b/taskcluster/ci/test/misc.yml
@@ -45,17 +45,17 @@ telemetry-tests-client:
         config:
             by-test-platform:
                 linux.*:
                     - remove_executables.py
                 windows.*: []
 
 test-verify:
     description: "Extra verification of tests modified on this push"
-    suite: test-verification
+    suite: test-verify
     treeherder-symbol: tc(TV)
     loopback-video: true
     max-run-time: 5400
     allow-software-gl-layers: false
     run-on-projects:
         by-test-platform:
             # do not run on ccov; see also the enable_code_coverage transform
             linux64-ccov/.*: []
--- a/taskcluster/taskgraph/transforms/tests.py
+++ b/taskcluster/taskgraph/transforms/tests.py
@@ -635,17 +635,17 @@ def handle_suite_category(config, tests)
             suite = flavor = test['suite']
 
         test.setdefault('attributes', {})
         test['attributes']['unittest_suite'] = suite
         test['attributes']['unittest_flavor'] = flavor
 
         script = test['mozharness']['script']
         category_arg = None
-        if suite == 'test-verification':
+        if suite == 'test-verify':
             pass
         elif script == 'android_emulator_unittest.py':
             category_arg = '--test-suite'
         elif script == 'desktop_unittest.py':
             category_arg = '--{}-suite'.format(suite)
 
         if category_arg:
             test['mozharness'].setdefault('extra-options', [])
--- a/testing/mach_commands.py
+++ b/testing/mach_commands.py
@@ -383,16 +383,19 @@ class MachCommands(MachCommandBase):
     def run_android_test(self, tests, symbols_path, manifest_path, log):
         import remotecppunittests as remotecppunittests
         from mozlog import commandline
 
         parser = remotecppunittests.RemoteCPPUnittestOptions()
         commandline.add_logging_group(parser)
         options, args = parser.parse_args()
 
+        if not options.adb_path:
+            from mozrunner.devices.android_device import get_adb_path
+            options.adb_path = get_adb_path(self)
         options.symbols_path = symbols_path
         options.manifest_path = manifest_path
         options.xre_path = self.bindir
         options.local_lib = self.bindir.replace('bin', 'fennec')
         for file in os.listdir(os.path.join(self.topobjdir, "dist")):
             if file.endswith(".apk") and file.startswith("fennec"):
                 options.local_apk = os.path.join(self.topobjdir, "dist", file)
                 log.info("using APK: " + options.local_apk)
--- a/testing/mochitest/mach_commands.py
+++ b/testing/mochitest/mach_commands.py
@@ -167,16 +167,20 @@ class MochitestRunner(MozbuildObject):
 
         import imp
         path = os.path.join(self.mochitest_dir, 'runtestsremote.py')
         with open(path, 'r') as fh:
             imp.load_module('runtestsremote', fh, path,
                             ('.py', 'r', imp.PY_SOURCE))
         import runtestsremote
 
+        from mozrunner.devices.android_device import get_adb_path
+        if not kwargs['adbPath']:
+            kwargs['adbPath'] = get_adb_path(self)
+
         options = Namespace(**kwargs)
 
         from manifestparser import TestManifest
         if tests and not options.manifestFile:
             manifest = TestManifest()
             manifest.tests.extend(tests)
             options.manifestFile = manifest
 
@@ -450,19 +454,22 @@ class RobocopCommands(MachCommandBase):
         driver = self._spawn(BuildDriver)
         driver.install_tests(tests)
 
         if len(tests) < 1:
             print(ROBOCOP_TESTS_NOT_FOUND.format('\n'.join(
                 sorted(list(test_paths)))))
             return 1
 
-        from mozrunner.devices.android_device import grant_runtime_permissions
+        from mozrunner.devices.android_device import grant_runtime_permissions, get_adb_path
         grant_runtime_permissions(self)
 
+        if not kwargs['adbPath']:
+            kwargs['adbPath'] = get_adb_path(self)
+
         mochitest = self._spawn(MochitestRunner)
         return mochitest.run_robocop_test(self._mach_context, tests, 'robocop', **kwargs)
 
 
 # NOTE python/mach/mach/commands/commandinfo.py references this function
 #      by name. If this function is renamed or removed, that file should
 #      be updated accordingly as well.
 def REMOVED(cls):
--- a/testing/mozbase/mozrunner/mozrunner/devices/android_device.py
+++ b/testing/mozbase/mozrunner/mozrunner/devices/android_device.py
@@ -243,16 +243,20 @@ def verify_android_device(build_obj, ins
             _update_gdbinit(build_obj.substs, os.path.join(jimdb_utils_path, "gdbinit.local"))
             # ensure JimDB is in system path, so that mozdebug can find it
             bin_path = os.path.join(jimdb_path, 'bin')
             os.environ['PATH'] = "%s:%s" % (bin_path, os.environ['PATH'])
 
     return device_verified
 
 
+def get_adb_path(build_obj):
+    return _find_sdk_exe(build_obj.substs, 'adb', False)
+
+
 def run_firefox_for_android(build_obj, params):
     """
        Launch Firefox for Android on the connected device.
        Optional 'params' allow parameters to be passed to Firefox.
     """
     adb_path = _find_sdk_exe(build_obj.substs, 'adb', False)
     if not adb_path:
         adb_path = 'adb'
--- a/testing/remotecppunittests.py
+++ b/testing/remotecppunittests.py
@@ -173,16 +173,21 @@ class RemoteCPPUnittestOptions(cppunitte
                         help="ip address of remote device to test")
         defaults["device_ip"] = None
 
         self.add_option("--devicePort", action="store",
                         type="string", dest="device_port",
                         help="port of remote device to test")
         defaults["device_port"] = 20701
 
+        self.add_option("--adbPath", action="store",
+                        type="string", dest="adb_path",
+                        help="Path to adb")
+        defaults["adb_path"] = None
+
         self.add_option("--noSetup", action="store_false",
                         dest="setup",
                         help="do not copy any files to device (to be used only if "
                         "device is already setup)")
         defaults["setup"] = True
 
         self.add_option("--localLib", action="store",
                         type="string", dest="local_lib",
@@ -233,16 +238,18 @@ def run_test_harness(options, args):
     else:
         retryLimit = 5
     try:
         dm_args = {'deviceRoot': options.remote_test_root}
         dm_args['retryLimit'] = retryLimit
         if options.device_ip:
             dm_args['host'] = options.device_ip
             dm_args['port'] = options.device_port
+        if options.adb_path:
+            dm_args['adbPath'] = options.adb_path
         if options.log_tbpl_level == 'debug' or options.log_mach_level == 'debug':
             dm_args['logLevel'] = logging.DEBUG # noqa python 2 / 3
         dm = devicemanagerADB.DeviceManagerADB(**dm_args)
     except:
         if options.with_b2g_emulator:
             runner.cleanup()
             runner.wait()
         raise
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/testrunner.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/testrunner.py
@@ -243,17 +243,17 @@ class _RunnerManagerState(object):
     stop = namedtuple("stop", [])
 
 
 RunnerManagerState = _RunnerManagerState()
 
 
 class TestRunnerManager(threading.Thread):
     def __init__(self, suite_name, test_queue, test_source_cls, browser_cls, browser_kwargs,
-                 executor_cls, executor_kwargs, stop_flag, pause_after_test=False,
+                 executor_cls, executor_kwargs, stop_flag, rerun=1, pause_after_test=False,
                  pause_on_unexpected=False, restart_on_unexpected=True, debug_info=None):
         """Thread that owns a single TestRunner process and any processes required
         by the TestRunner (e.g. the Firefox binary).
 
         TestRunnerManagers are responsible for launching the browser process and the
         runner process, and for logging the test progress. The actual test running
         is done by the TestRunner. In particular they:
 
@@ -274,16 +274,18 @@ class TestRunnerManager(threading.Thread
 
         self.executor_cls = executor_cls
         self.executor_kwargs = executor_kwargs
 
         # Flags used to shut down this thread if we get a sigint
         self.parent_stop_flag = stop_flag
         self.child_stop_flag = multiprocessing.Event()
 
+        self.rerun = rerun
+        self.run_count = 0
         self.pause_after_test = pause_after_test
         self.pause_on_unexpected = pause_on_unexpected
         self.restart_on_unexpected = restart_on_unexpected
         self.debug_info = debug_info
 
         self.manager_number = next_manager_number()
 
         self.command_queue = Queue()
@@ -497,30 +499,33 @@ class TestRunnerManager(threading.Thread
         test = None
         while test is None:
             while test_group is None or len(test_group) == 0:
                 test_group, group_metadata = self.test_source.group()
                 if test_group is None:
                     self.logger.info("No more tests")
                     return None, None, None
             test = test_group.popleft()
+        self.run_count = 0
         return test, test_group, group_metadata
 
-
     def run_test(self):
         assert isinstance(self.state, RunnerManagerState.running)
         assert self.state.test is not None
 
         if self.browser.update_settings(self.state.test):
             self.logger.info("Restarting browser for new test environment")
             return RunnerManagerState.restarting(self.state.test,
                                                  self.state.test_group,
                                                  self.state.group_metadata)
 
         self.logger.test_start(self.state.test.id)
+        if self.rerun > 1:
+            self.logger.info("Run %d/%d" % (self.run_count, self.rerun))
+        self.run_count += 1
         self.send_message("run_test", self.state.test)
 
     def test_ended(self, test, results):
         """Handle the end of a test.
 
         Output the result of each subtest, and the result of the overall
         harness to the logs.
         """
@@ -574,34 +579,39 @@ class TestRunnerManager(threading.Thread
                                ((subtest_unexpected or is_unexpected)
                                 and self.restart_on_unexpected))
 
         if (self.pause_after_test or
             (self.pause_on_unexpected and (subtest_unexpected or is_unexpected))):
             self.logger.info("Pausing until the browser exits")
             self.send_message("wait")
         else:
-            return self.after_test_end(restart_before_next)
+            return self.after_test_end(test, restart_before_next)
 
     def wait_finished(self):
         assert isinstance(self.state, RunnerManagerState.running)
         # The browser should be stopped already, but this ensures we do any post-stop
         # processing
         self.logger.debug("Wait finished")
 
         return self.after_test_end(True)
 
-    def after_test_end(self, restart):
+    def after_test_end(self, test, restart):
         assert isinstance(self.state, RunnerManagerState.running)
-        test, test_group, group_metadata = self.get_next_test()
-        if test is None:
-            return RunnerManagerState.stop()
-        if test_group != self.state.test_group:
-            # We are starting a new group of tests, so force a restart
-            restart = True
+        if self.run_count == self.rerun:
+            test, test_group, group_metadata = self.get_next_test()
+            if test is None:
+                return RunnerManagerState.stop()
+            if test_group != self.state.test_group:
+                # We are starting a new group of tests, so force a restart
+                restart = True
+        else:
+            test = test
+            test_group = self.state.test_group
+            group_metadata = self.state.group_metadata
         if restart:
             return RunnerManagerState.restarting(test, test_group, group_metadata)
         else:
             return RunnerManagerState.running(test, test_group, group_metadata)
 
     def restart_runner(self):
         """Stop and restart the TestRunner"""
         assert isinstance(self.state, RunnerManagerState.restarting)
@@ -680,16 +690,17 @@ def make_test_queue(tests, test_source_c
     assert not queue.empty()
     return queue
 
 
 class ManagerGroup(object):
     def __init__(self, suite_name, size, test_source_cls, test_source_kwargs,
                  browser_cls, browser_kwargs,
                  executor_cls, executor_kwargs,
+                 rerun=1,
                  pause_after_test=False,
                  pause_on_unexpected=False,
                  restart_on_unexpected=True,
                  debug_info=None):
         """Main thread object that owns all the TestManager threads."""
         self.suite_name = suite_name
         self.size = size
         self.test_source_cls = test_source_cls
@@ -697,16 +708,17 @@ class ManagerGroup(object):
         self.browser_cls = browser_cls
         self.browser_kwargs = browser_kwargs
         self.executor_cls = executor_cls
         self.executor_kwargs = executor_kwargs
         self.pause_after_test = pause_after_test
         self.pause_on_unexpected = pause_on_unexpected
         self.restart_on_unexpected = restart_on_unexpected
         self.debug_info = debug_info
+        self.rerun = rerun
 
         self.pool = set()
         # Event that is polled by threads so that they can gracefully exit in the face
         # of sigint
         self.stop_flag = threading.Event()
         self.logger = structuredlog.StructuredLogger(suite_name)
 
     def __enter__(self):
@@ -729,16 +741,17 @@ class ManagerGroup(object):
             manager = TestRunnerManager(self.suite_name,
                                         test_queue,
                                         self.test_source_cls,
                                         self.browser_cls,
                                         self.browser_kwargs,
                                         self.executor_cls,
                                         self.executor_kwargs,
                                         self.stop_flag,
+                                        self.rerun,
                                         self.pause_after_test,
                                         self.pause_on_unexpected,
                                         self.restart_on_unexpected,
                                         self.debug_info)
             manager.start()
             self.pool.add(manager)
         self.wait()
 
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/wptcommandline.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/wptcommandline.py
@@ -69,16 +69,19 @@ scheme host and port.""")
                             default=False,
                             help="List the top level directories containing tests that will run.")
     mode_group.add_argument("--list-disabled", action="store_true",
                             default=False,
                             help="List the tests that are disabled on the current platform")
     mode_group.add_argument("--list-tests", action="store_true",
                             default=False,
                             help="List all tests that will run")
+    mode_group.add_argument("--verify", action="store_true",
+                            default=False,
+                            help="Run a stability check on the selected tests")
 
     test_selection_group = parser.add_argument_group("Test Selection")
     test_selection_group.add_argument("--test-types", action="store",
                                       nargs="*", default=wpttest.enabled_tests,
                                       choices=wpttest.enabled_tests,
                                       help="Test types to run")
     test_selection_group.add_argument("--include", action="append",
                                       help="URL prefix to include")
@@ -88,18 +91,20 @@ scheme host and port.""")
                                       help="Path to manifest listing tests to include")
     test_selection_group.add_argument("--tag", action="append", dest="tags",
                                       help="Labels applied to tests to include in the run. Labels starting dir: are equivalent to top-level directories.")
 
     debugging_group = parser.add_argument_group("Debugging")
     debugging_group.add_argument('--debugger', const="__default__", nargs="?",
                                  help="run under a debugger, e.g. gdb or valgrind")
     debugging_group.add_argument('--debugger-args', help="arguments to the debugger")
+    debugging_group.add_argument("--rerun", action="store", type=int, default=1,
+                                 help="Number of times to re run each test without restarts")
     debugging_group.add_argument("--repeat", action="store", type=int, default=1,
-                                 help="Number of times to run the tests")
+                                 help="Number of times to run the tests, restarting between each run")
     debugging_group.add_argument("--repeat-until-unexpected", action="store_true", default=None,
                                  help="Run tests in a loop until one returns an unexpected result")
     debugging_group.add_argument('--pause-after-test', action="store_true", default=None,
                                  help="Halt the test runner after each test (this happens by default if only a single test is run)")
     debugging_group.add_argument('--no-pause-after-test', dest="pause_after_test", action="store_false",
                                  help="Don't halt the test runner irrespective of the number of tests run")
 
     debugging_group.add_argument('--pause-on-unexpected', action="store_true",
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/wptrunner.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/wptrunner.py
@@ -117,17 +117,17 @@ def list_tests(test_paths, product, **kw
         print test
 
 
 def get_pause_after_test(test_loader, **kwargs):
     total_tests = sum(len(item) for item in test_loader.tests.itervalues())
     if kwargs["pause_after_test"] is None:
         if kwargs["repeat_until_unexpected"]:
             return False
-        if kwargs["repeat"] == 1 and total_tests == 1:
+        if kwargs["repeat"] == 1 and kwargs["rerun"] == 1 and total_tests == 1:
             return True
         return False
     return kwargs["pause_after_test"]
 
 
 def run_tests(config, test_paths, product, **kwargs):
     with wptlogging.CaptureIO(logger, not kwargs["no_capture_stdio"]):
         env.do_delayed_imports(logger, test_paths)
@@ -236,16 +236,17 @@ def run_tests(config, test_paths, produc
                     with ManagerGroup("web-platform-tests",
                                       kwargs["processes"],
                                       test_source_cls,
                                       test_source_kwargs,
                                       browser_cls,
                                       browser_kwargs,
                                       executor_cls,
                                       executor_kwargs,
+                                      kwargs["rerun"],
                                       kwargs["pause_after_test"],
                                       kwargs["pause_on_unexpected"],
                                       kwargs["restart_on_unexpected"],
                                       kwargs["debug_info"]) as manager_group:
                         try:
                             manager_group.run(test_type, test_loader.tests)
                         except KeyboardInterrupt:
                             logger.critical("Main thread got signal")
@@ -255,26 +256,35 @@ def run_tests(config, test_paths, produc
 
                 unexpected_total += unexpected_count
                 logger.info("Got %i unexpected results" % unexpected_count)
                 if repeat_until_unexpected and unexpected_total > 0:
                     break
                 logger.suite_end()
     return unexpected_total == 0
 
+
+def check_stability(**kwargs):
+    import stability
+    return stability.check_stability(logger, **kwargs)
+
+
 def start(**kwargs):
     if kwargs["list_test_groups"]:
         list_test_groups(**kwargs)
     elif kwargs["list_disabled"]:
         list_disabled(**kwargs)
     elif kwargs["list_tests"]:
         list_tests(**kwargs)
+    elif kwargs["verify"]:
+        check_stability(**kwargs)
     else:
         return not run_tests(**kwargs)
 
+
 def main():
     """Main entry point when calling from the command line"""
     kwargs = wptcommandline.parse_args()
 
     try:
         if kwargs["prefs_root"] is None:
             kwargs["prefs_root"] = os.path.abspath(os.path.join(here, "prefs"))
 
--- a/testing/xpcshell/mach_commands.py
+++ b/testing/xpcshell/mach_commands.py
@@ -140,36 +140,38 @@ class XPCShellRunner(MozbuildObject):
         if not result and not xpcshell.sequential:
             print("Tests were run in parallel. Try running with --sequential "
                   "to make sure the failures were not caused by this.")
         return int(not result)
 
 
 class AndroidXPCShellRunner(MozbuildObject):
     """Get specified DeviceManager"""
-    def get_devicemanager(self, ip, port, remote_test_root):
+    def get_devicemanager(self, ip, port, remote_test_root, adb_path):
         import mozdevice
         dm = None
         if ip:
-            dm = mozdevice.DroidADB(ip, port, packageName=None, deviceRoot=remote_test_root)
+            dm = mozdevice.DroidADB(ip, port, packageName=None, deviceRoot=remote_test_root,
+                                    adbPath=adb_path)
         else:
-            dm = mozdevice.DroidADB(packageName=None, deviceRoot=remote_test_root)
+            dm = mozdevice.DroidADB(packageName=None, deviceRoot=remote_test_root,
+                                    adbPath=adb_path)
         return dm
 
     """Run Android xpcshell tests."""
     def run_test(self, **kwargs):
         # TODO Bug 794506 remove once mach integrates with virtualenv.
         build_path = os.path.join(self.topobjdir, 'build')
         if build_path not in sys.path:
             sys.path.append(build_path)
 
         import remotexpcshelltests
 
         dm = self.get_devicemanager(kwargs["deviceIP"], kwargs["devicePort"],
-                                    kwargs["remoteTestRoot"])
+                                    kwargs["remoteTestRoot"], kwargs["adbPath"])
 
         log = kwargs.pop("log")
         self.log_manager.enable_unstructured()
 
         if kwargs["xpcshell"] is None:
             kwargs["xpcshell"] = "xpcshell"
 
         if not kwargs["objdir"]:
@@ -250,18 +252,20 @@ class MachCommands(MachCommandBase):
                                                              params,
                                                              {"mach": sys.stdout},
                                                              {"verbose": True})
 
         if not params['threadCount']:
             params['threadCount'] = int((cpu_count() * 3) / 2)
 
         if conditions.is_android(self):
-            from mozrunner.devices.android_device import verify_android_device
+            from mozrunner.devices.android_device import verify_android_device, get_adb_path
             verify_android_device(self)
+            if not params['adbPath']:
+                params['adbPath'] = get_adb_path(self)
             xpcshell = self._spawn(AndroidXPCShellRunner)
         else:
             xpcshell = self._spawn(XPCShellRunner)
         xpcshell.cwd = self._mach_context.cwd
 
         try:
             return xpcshell.run_test(**params)
         except InvalidTestPathError as e:
--- a/testing/xpcshell/remotexpcshelltests.py
+++ b/testing/xpcshell/remotexpcshelltests.py
@@ -596,16 +596,18 @@ def main():
                                     {"tbpl": sys.stdout})
 
     dm_args = {'deviceRoot': options['remoteTestRoot']}
     if options['deviceIP']:
         dm_args['host'] = options['deviceIP']
         dm_args['port'] = options['devicePort']
     if options['log_tbpl_level'] == 'debug' or options['log_mach_level'] == 'debug':
         dm_args['logLevel'] = logging.DEBUG
+    if options['adbPath']:
+        dm_args['adbPath'] = options['adbPath']
     dm = mozdevice.DroidADB(**dm_args)
 
     if options['interactive'] and not options['testPath']:
         print("Error: You must specify a test filename in interactive mode!", file=sys.stderr)
         sys.exit(1)
 
     if options['xpcshell'] is None:
         options['xpcshell'] = "xpcshell"
--- a/testing/xpcshell/xpcshellcommandline.py
+++ b/testing/xpcshell/xpcshellcommandline.py
@@ -144,16 +144,19 @@ def add_remote_arguments(parser):
                         default=20701, help="port of remote device to test")
 
     parser.add_argument("--objdir", action="store", type=str, dest="objdir",
                         help="local objdir, containing xpcshell binaries")
 
     parser.add_argument("--apk", action="store", type=str, dest="localAPK",
                         help="local path to Fennec APK")
 
+    parser.add_argument("--adbPath", action="store", type=str, dest="adbPath",
+                        help="Path to adb")
+
     parser.add_argument("--noSetup", action="store_false", dest="setup", default=True,
                         help="do not copy any files to device (to be used only if "
                              "device is already setup)")
 
     parser.add_argument("--local-lib-dir", action="store", type=str, dest="localLib",
                         help="local path to library directory")
 
     parser.add_argument("--local-bin-dir", action="store", type=str, dest="localBin",
--- a/toolkit/content/aboutTelemetry.js
+++ b/toolkit/content/aboutTelemetry.js
@@ -207,35 +207,37 @@ var Settings = {
   detachObservers() {
     for (let setting of this.SETTINGS) {
       Preferences.ignore(setting.pref, this.render, this);
     }
   },
 
   getStatusStringForSetting(setting) {
     let enabled = Preferences.get(setting.pref, setting.defaultPrefValue);
-    let status = bundle.GetStringFromName(enabled ? "telemetryEnabled" : "telemetryDisabled");
+    let status = bundle.GetStringFromName(enabled ? "telemetryUploadEnabled" : "telemetryUploadDisabled");
     return status;
   },
 
   /**
    * Updates the button & text at the top of the page to reflect Telemetry state.
    */
   render() {
-    let homeExplanation = document.getElementById("home-explanation");
-    let fhrEnabled = Preferences.get(this.SETTINGS[0].pref, this.SETTINGS[0].defaultPrefValue);
-    fhrEnabled = bundle.GetStringFromName(fhrEnabled ? "telemetryEnabled" : "telemetryDisabled");
-    let extendedEnabled = Preferences.get(this.SETTINGS[1].pref, this.SETTINGS[1].defaultPrefValue);
-    extendedEnabled = bundle.GetStringFromName(extendedEnabled ? "extendedTelemetryEnabled" : "extendedTelemetryDisabled");
-    let parameters = [fhrEnabled, extendedEnabled].map(this.convertStringToLink);
+    let settingsExplanation = document.getElementById("settings-explanation");
+    let uploadEnabled = this.getStatusStringForSetting(this.SETTINGS[0]);
+    let extendedEnabled = Services.telemetry.canRecordExtended;
+    let collectedData = bundle.GetStringFromName(extendedEnabled ? "prereleaseData" : "releaseData");
 
-    let explanation = bundle.formatStringFromName("homeExplanation", parameters, 2);
+    let parameters = [
+      collectedData,
+      this.convertStringToLink(uploadEnabled),
+    ];
+    let explanation = bundle.formatStringFromName("settingsExplanation", parameters, 2);
 
     // eslint-disable-next-line no-unsanitized/property
-    homeExplanation.innerHTML = explanation;
+    settingsExplanation.innerHTML = explanation;
     this.attachObservers();
   },
 
   convertStringToLink(string) {
     return "<a href=\"\" class=\"change-data-choices-link\">" + string + "</a>";
   },
 };
 
--- a/toolkit/content/aboutTelemetry.xhtml
+++ b/toolkit/content/aboutTelemetry.xhtml
@@ -140,17 +140,17 @@
 
       <div id="no-search-results" hidden="true" class="hidden">
         <span id="no-search-results-text"></span>
         <div class="no-search-results-image"></div>
       </div>
 
       <section id="home-section" class="active">
         <p id="page-subtitle"></p>
-        <p id="home-explanation"></p>
+        <p id="settings-explanation"></p>
         <p id="ping-explanation"></p>
         <p>&aboutTelemetry.moreInformations;</p>
         <ul>
           <li>&aboutTelemetry.firefoxDataDoc;</li>
           <li>&aboutTelemetry.telemetryClientDoc;</li>
           <li>&aboutTelemetry.telemetryDashboard;</li>
         </ul>
       </section>
--- a/toolkit/locales/en-US/chrome/global/aboutTelemetry.properties
+++ b/toolkit/locales/en-US/chrome/global/aboutTelemetry.properties
@@ -2,24 +2,24 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # LOCALIZATION NOTE(pageSubtitle):
 # - %1$S is replaced by the value of the toolkit.telemetry.server_owner preference
 # - %2$S is replaced by brandFullName
 pageSubtitle = This page shows the information about performance, hardware, usage and customizations collected by Telemetry. This information is submitted to %1$S to help improve %2$S.
 
-# LOCALIZATION NOTE(homeExplanation):
-# - %1$S is either telemetryEnabled or telemetryDisabled
-# - %2$S is either extendedTelemetryEnabled or extendedTelemetryDisabled
-homeExplanation = Telemetry is %1$S and extended telemetry is %2$S.
-telemetryEnabled = enabled
-telemetryDisabled = disabled
-extendedTelemetryEnabled = enabled
-extendedTelemetryDisabled = disabled
+# LOCALIZATION NOTE(settingsExplanation):
+# - %1$S is either releaseData or prereleaseData
+# - %2$S is either telemetryUploadEnabled or telemetryUploadDisabled
+settingsExplanation = Telemetry is collecting %1$S and upload is %2$S.
+releaseData = release data
+prereleaseData = pre-release data
+telemetryUploadEnabled = enabled
+telemetryUploadDisabled = disabled
 
 # LOCALIZATION NOTE(pingDetails):
 # - %1$S is replaced by a link with pingExplanationLink as text
 # - %2$S is replaced by namedPing
 pingDetails = Each piece of information is sent bundled into “%1$S”. You are looking at the %2$S ping.
 # LOCALIZATION NOTE(namedPing):
 # - %1$S is replaced by the ping localized timestamp, e.g. “2017/07/08 10:40:46”
 # - %2$S is replaced by the ping name, e.g. “saved-session”