Bug 1077651 Measure frame uniformity by synthesizing native events. r=kats,mrbkap
authorMason Chang <mchang@mozilla.com>
Mon, 08 Jun 2015 09:53:41 -0700
changeset 247631 a9cff1d9e7c6a9a698522ee4cd9ddbac39cb0b07
parent 247630 73dda324715fb3accbeb88076a3aaeed0714931a
child 247632 3abb08512b2435c53e63dcba2843d0df66391a55
push id28874
push userryanvm@gmail.com
push dateTue, 09 Jun 2015 16:52:27 +0000
treeherdermozilla-central@76ae244b637a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats, mrbkap
bugs1077651
milestone41.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
Bug 1077651 Measure frame uniformity by synthesizing native events. r=kats,mrbkap
dom/base/nsDOMWindowUtils.cpp
dom/interfaces/base/nsIDOMWindowUtils.idl
dom/webidl/APZTestData.webidl
gfx/layers/Layers.h
gfx/layers/apz/test/chrome.ini
gfx/layers/apz/test/test_smoothness.html
gfx/layers/client/ClientLayerManager.cpp
gfx/layers/client/ClientLayerManager.h
gfx/layers/composite/AsyncCompositionManager.cpp
gfx/layers/composite/AsyncCompositionManager.h
gfx/layers/composite/FrameUniformityData.cpp
gfx/layers/composite/FrameUniformityData.h
gfx/layers/ipc/CompositorParent.cpp
gfx/layers/ipc/CompositorParent.h
gfx/layers/ipc/PCompositor.ipdl
gfx/layers/moz.build
gfx/thebes/gfxPrefs.h
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -65,28 +65,29 @@
 #include <algorithm>
 
 #if defined(MOZ_X11) && defined(MOZ_WIDGET_GTK)
 #include <gdk/gdk.h>
 #include <gdk/gdkx.h>
 #endif
 
 #include "Layers.h"
-#include "mozilla/layers/ShadowLayers.h"
 #include "gfxPrefs.h"
 
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/IDBFactoryBinding.h"
 #include "mozilla/dom/IDBMutableFileBinding.h"
 #include "mozilla/dom/indexedDB/IDBMutableFile.h"
 #include "mozilla/dom/indexedDB/IndexedDatabaseManager.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
 #include "mozilla/dom/quota/PersistenceType.h"
 #include "mozilla/dom/quota/QuotaManager.h"
+#include "mozilla/layers/FrameUniformityData.h"
+#include "mozilla/layers/ShadowLayers.h"
 #include "nsPrintfCString.h"
 #include "nsViewportInfo.h"
 #include "nsIFormControl.h"
 #include "nsIScriptError.h"
 #include "nsIAppShell.h"
 #include "nsWidgetsCID.h"
 #include "FrameLayerBuilder.h"
 #include "nsDisplayList.h"
@@ -3712,16 +3713,37 @@ nsDOMWindowUtils::SetChromeMargin(int32_
       }
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDOMWindowUtils::GetFrameUniformityTestData(JSContext* aContext,
+                                             JS::MutableHandleValue aOutFrameUniformity)
+{
+  MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+  nsIWidget* widget = GetWidget();
+  if (!widget) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsRefPtr<LayerManager> manager = widget->GetLayerManager();
+  if (!manager) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  FrameUniformityData outData;
+  manager->GetFrameUniformity(&outData);
+  outData.ToJS(aOutFrameUniformity, aContext);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMWindowUtils::XpconnectArgument(nsIDOMWindowUtils* aThis)
 {
   // Do nothing.
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::AskPermission(nsIContentPermissionRequest* aRequest)
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -44,17 +44,17 @@ interface nsIDOMClientRect;
 interface nsIURI;
 interface nsIDOMEventTarget;
 interface nsIRunnable;
 interface nsITranslationNodeList;
 interface nsIJSRAIIHelper;
 interface nsIContentPermissionRequest;
 interface nsIObserver;
 
-[scriptable, uuid(098d9f0d-7809-4d3c-8fc6-e5b3fb71835b)]
+[scriptable, uuid(ec176f3b-2886-4090-938e-dded103c5f1c)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -1809,16 +1809,24 @@ interface nsIDOMWindowUtils : nsISupport
                        in int32_t aLeft);
 
   /**
    * Enable some service workers testing features.
    */
   attribute boolean serviceWorkersTestingEnabled;
 
   /**
+   * Returns a JSObject which contains a list of frame uniformities
+   * when the pref gfx.vsync.collect-scroll-data is enabled.
+   * Every result contains a layer address and a frame uniformity for that layer.
+   * A negative frame uniformity value indicates an invalid frame uniformity and an error has occured.
+   */
+  [implicit_jscontext] jsval getFrameUniformityTestData();
+
+  /*
    * Increase the chaos mode activation level. An equivalent number of
    * calls to leaveChaosMode must be made in order to restore the original
    * chaos mode state. If the activation level is nonzero all chaos mode
    * features are activated.
    */
   void enterChaosMode();
 
   /**
--- a/dom/webidl/APZTestData.webidl
+++ b/dom/webidl/APZTestData.webidl
@@ -29,9 +29,19 @@ dictionary APZBucket {
   unsigned long sequenceNumber;
   sequence<ScrollFrameData> scrollFrames;
 };
 
 // All the paints and repaint requests. This is the top-level data structure.
 dictionary APZTestData {
   sequence<APZBucket> paints;
   sequence<APZBucket> repaintRequests;
-};
\ No newline at end of file
+};
+
+// A frame uniformity measurement for every scrollable layer
+dictionary FrameUniformity {
+  unsigned long layerAddress;
+  float frameUniformity;
+};
+
+dictionary FrameUniformityResults {
+  sequence<FrameUniformity> layerUniformities;
+};
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -87,16 +87,17 @@ class ReadbackLayer;
 class ReadbackProcessor;
 class RefLayer;
 class LayerComposite;
 class ShadowableLayer;
 class ShadowLayerForwarder;
 class LayerManagerComposite;
 class SpecificLayerAttributes;
 class Compositor;
+class FrameUniformityData;
 
 namespace layerscope {
 class LayersPacket;
 }
 
 #define MOZ_LAYER_DECL_NAME(n, e)                              \
   virtual const char* Name() const override { return n; }  \
   virtual LayerType GetType() const override { return e; }
@@ -638,16 +639,17 @@ public:
     // LayersBackend::LAYERS_NONE is an error state, but in that case we should try to
     // avoid loading the compositor!
     return LayersBackend::LAYERS_BASIC != aBackend && LayersBackend::LAYERS_NONE != aBackend;
   }
 
   virtual bool IsCompositingCheap() { return true; }
 
   bool IsInTransaction() const { return mInTransaction; }
+  virtual void GetFrameUniformity(FrameUniformityData* aOutData) { }
   virtual bool RequestOverfill(mozilla::dom::OverfillCallback* aCallback) { return true; }
   virtual void RunOverfillCallback(const uint32_t aOverfill) { }
 
   virtual void SetRegionToClear(const nsIntRegion& aRegion)
   {
     mRegionToClear = aRegion;
   }
 
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/chrome.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+  apz_test_native_event_utils.js
+tags = apz-chrome
+
+[test_smoothness.html]
+# hardware vsync only on win/mac
+# e10s only since APZ is only enabled on e10s
+skip-if = debug || (os != 'mac' && os != 'win') || !e10s
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/test_smoothness.html
@@ -0,0 +1,83 @@
+<html>
+<head>
+  <title>Test Frame Uniformity While Scrolling</title>
+  <script type="text/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+
+  <style>
+  #content {
+    height: 5000px;
+    background: repeating-linear-gradient(#EEE, #EEE 100px, #DDD 100px, #DDD 200px);
+  }
+  </style>
+  <script type="text/javascript">
+    var scrollEvents = 100;
+    var i = 0;
+    var testPref = "gfx.vsync.collect-scroll-transforms";
+    // Scroll points
+    var x = 100;
+    var y = 150;
+
+    SimpleTest.waitForExplicitFinish();
+    var utils = _getDOMWindowUtils(window);
+
+    function sendScrollEvent(aRafTimestamp) {
+      var scrollDiv = document.getElementById("content");
+
+      if (i < scrollEvents) {
+        i++;
+        // Scroll diff
+        var dx = 0;
+        var dy = -10; // Negative to scroll down
+        synthesizeNativeWheelAndWaitForEvent(scrollDiv, x, y, dx, dy);
+        window.requestAnimationFrame(sendScrollEvent);
+      } else {
+        // Locally, with silk and apz + e10s, retina 15" mbp usually get ~1.0 - 1.5
+        // w/o silk + e10s + apz, I get up to 7. Lower is better.
+        // Windows, I get ~3. Values are not valid w/o hardware vsync
+        var uniformities = _getDOMWindowUtils().getFrameUniformityTestData();
+        for (var j = 0; j < uniformities.layerUniformities.length; j++) {
+          var layerResult = uniformities.layerUniformities[j];
+          var layerAddr = layerResult.layerAddress;
+          var uniformity = layerResult.frameUniformity;
+          var msg = "Layer: " + layerAddr.toString(16) + " Uniformity: " + uniformity;
+          SimpleTest.ok((uniformity >= 0) && (uniformity < 4.0), msg);
+        }
+        SimpleTest.finish();
+      }
+    }
+
+    function startTest() {
+      window.requestAnimationFrame(sendScrollEvent);
+    }
+
+    window.onload = function() {
+      var apzEnabled = SpecialPowers.getBoolPref("layers.async-pan-zoom.enabled");
+      if (!apzEnabled) {
+        SimpleTest.ok(true, "APZ not enabled, skipping test");
+        SimpleTest.finish();
+      }
+
+      var hwVsyncEnabled = SpecialPowers.getBoolPref("gfx.vsync.hw-vsync.enabled");
+      if (!hwVsyncEnabled) {
+        SimpleTest.ok(true, "Hardware vsync not enabled, skipping test");
+        SimpleTest.finish();
+      }
+
+      SpecialPowers.pushPrefEnv({
+        "set" : [
+          [testPref, true]
+        ]
+      }, startTest);
+    }
+    </script>
+</head>
+
+<body>
+  <div id="content">
+  </div>
+</body>
+</html>
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -9,16 +9,17 @@
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc
 #include "mozilla/Hal.h"
 #include "mozilla/dom/ScreenOrientation.h"  // for ScreenOrientation
 #include "mozilla/dom/TabChild.h"       // for TabChild
 #include "mozilla/hal_sandbox/PHal.h"   // for ScreenConfiguration
 #include "mozilla/layers/CompositableClient.h"
 #include "mozilla/layers/CompositorChild.h" // for CompositorChild
 #include "mozilla/layers/ContentClient.h"
+#include "mozilla/layers/FrameUniformityData.h"
 #include "mozilla/layers/ISurfaceAllocator.h"
 #include "mozilla/layers/LayersMessages.h"  // for EditReply, etc
 #include "mozilla/layers/LayersSurfaces.h"  // for SurfaceDescriptor
 #include "mozilla/layers/PLayerChild.h"  // for PLayerChild
 #include "mozilla/layers/LayerTransactionChild.h"
 #include "mozilla/layers/TextureClientPool.h" // for TextureClientPool
 #include "ClientReadbackLayer.h"        // for ClientReadbackLayer
 #include "nsAString.h"
@@ -421,16 +422,30 @@ ClientLayerManager::RequestProperty(cons
 void
 ClientLayerManager::StartNewRepaintRequest(SequenceNumber aSequenceNumber)
 {
   if (gfxPrefs::APZTestLoggingEnabled()) {
     mApzTestData.StartNewRepaintRequest(aSequenceNumber);
   }
 }
 
+void
+ClientLayerManager::GetFrameUniformity(FrameUniformityData* aOutData)
+{
+  MOZ_ASSERT(XRE_IsParentProcess(), "Frame Uniformity only supported in parent process");
+
+  if (HasShadowManager()) {
+    CompositorChild* child = GetRemoteRenderer();
+    child->SendGetFrameUniformity(aOutData);
+    return;
+  }
+
+  return LayerManager::GetFrameUniformity(aOutData);
+}
+
 bool
 ClientLayerManager::RequestOverfill(mozilla::dom::OverfillCallback* aCallback)
 {
   MOZ_ASSERT(aCallback != nullptr);
   MOZ_ASSERT(HasShadowManager(), "Request Overfill only supported on b2g for now");
 
   if (HasShadowManager()) {
     CompositorChild* child = GetRemoteRenderer();
--- a/gfx/layers/client/ClientLayerManager.h
+++ b/gfx/layers/client/ClientLayerManager.h
@@ -29,16 +29,17 @@
 
 namespace mozilla {
 namespace layers {
 
 class ClientPaintedLayer;
 class CompositorChild;
 class ImageLayer;
 class PLayerChild;
+class FrameUniformityData;
 class TextureClientPool;
 
 class ClientLayerManager final : public LayerManager
 {
   typedef nsTArray<nsRefPtr<Layer> > LayerRefArray;
 
 public:
   explicit ClientLayerManager(nsIWidget* aWidget);
@@ -195,16 +196,17 @@ public:
 
   void SetNeedsComposite(bool aNeedsComposite)
   {
     mNeedsComposite = aNeedsComposite;
   }
   bool NeedsComposite() const { return mNeedsComposite; }
 
   virtual void Composite() override;
+  virtual void GetFrameUniformity(FrameUniformityData* aFrameUniformityData) override;
   virtual bool RequestOverfill(mozilla::dom::OverfillCallback* aCallback) override;
   virtual void RunOverfillCallback(const uint32_t aOverfill) override;
 
   virtual void DidComposite(uint64_t aTransactionId);
 
   virtual bool SupportsMixBlendModes(EnumSet<gfx::CompositionOp>& aMixBlendModes) override
   {
    return (GetTextureFactoryIdentifier().mSupportedBlendModes & aMixBlendModes) == aMixBlendModes;
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -32,16 +32,17 @@
 #include "nsTArray.h"                   // for nsTArray, nsTArray_Impl, etc
 #include "nsTArrayForwardDeclare.h"     // for InfallibleTArray
 #include "UnitTransforms.h"             // for TransformTo
 #if defined(MOZ_WIDGET_ANDROID)
 # include <android/log.h>
 # include "AndroidBridge.h"
 #endif
 #include "GeckoProfiler.h"
+#include "FrameUniformityData.h"
 
 struct nsCSSValueSharedList;
 
 namespace mozilla {
 namespace layers {
 
 using namespace mozilla::gfx;
 
@@ -89,16 +90,28 @@ WalkTheTree(Layer* aLayer,
     }
   }
   for (Layer* child = aLayer->GetFirstChild();
        child; child = child->GetNextSibling()) {
     WalkTheTree<OP>(child, aReady, aTargetConfig);
   }
 }
 
+AsyncCompositionManager::AsyncCompositionManager(LayerManagerComposite* aManager)
+  : mLayerManager(aManager)
+  , mIsFirstPaint(true)
+  , mLayersUpdated(false)
+  , mReadyForCompose(true)
+{
+}
+
+AsyncCompositionManager::~AsyncCompositionManager()
+{
+}
+
 void
 AsyncCompositionManager::ResolveRefLayers()
 {
   if (!mLayerManager->GetRoot()) {
     return;
   }
 
   mReadyForCompose = true;
@@ -540,16 +553,46 @@ SampleAPZAnimations(const LayerMetricsWr
 
   if (AsyncPanZoomController* apzc = aLayer.GetApzc()) {
     activeAnimations |= apzc->AdvanceAnimations(aSampleTime);
   }
 
   return activeAnimations;
 }
 
+void
+AsyncCompositionManager::RecordShadowTransforms(Layer* aLayer)
+{
+  MOZ_ASSERT(gfxPrefs::CollectScrollTransforms());
+  MOZ_ASSERT(CompositorParent::IsInCompositorThread());
+
+  for (Layer* child = aLayer->GetFirstChild();
+      child; child = child->GetNextSibling()) {
+      RecordShadowTransforms(child);
+  }
+
+  for (uint32_t i = 0; i < aLayer->GetFrameMetricsCount(); i++) {
+    AsyncPanZoomController* apzc = aLayer->GetAsyncPanZoomController(i);
+    if (!apzc) {
+      continue;
+    }
+    gfx::Matrix4x4 shadowTransform = aLayer->AsLayerComposite()->GetShadowTransform();
+    if (!shadowTransform.Is2D()) {
+      continue;
+    }
+
+    Matrix transform = shadowTransform.As2D();
+    if (transform.IsTranslation() && !shadowTransform.IsIdentity()) {
+      Point translation = transform.GetTranslation();
+      mLayerTransformRecorder.RecordTransform(aLayer, translation);
+      return;
+    }
+  }
+}
+
 Matrix4x4
 AdjustForClip(const Matrix4x4& asyncTransform, Layer* aLayer)
 {
   Matrix4x4 result = asyncTransform;
 
   // Container layers start at the origin, but they are clipped to where they
   // actually have content on the screen. The tree transform is meant to apply
   // to the clipped area. If the tree transform includes a scale component,
@@ -1046,16 +1089,23 @@ AsyncCompositionManager::TransformScroll
   oldTransform.PreScale(underZoomScale.width, underZoomScale.height, 1);
 
   // Make sure fixed position layers don't move away from their anchor points
   // when we're asynchronously panning or zooming
   AlignFixedAndStickyLayers(aLayer, aLayer, metrics.GetScrollId(), oldTransform,
                             aLayer->GetLocalTransform(), fixedLayerMargins);
 }
 
+void
+AsyncCompositionManager::GetFrameUniformity(FrameUniformityData* aOutData)
+{
+  MOZ_ASSERT(CompositorParent::IsInCompositorThread());
+  mLayerTransformRecorder.EndTest(aOutData);
+}
+
 bool
 AsyncCompositionManager::TransformShadowTree(TimeStamp aCurrentFrame,
                                              TransformsToSkip aSkip)
 {
   PROFILER_LABEL("AsyncCompositionManager", "TransformShadowTree",
     js::ProfileEntry::Category::GRAPHICS);
 
   Layer* root = mLayerManager->GetRoot();
@@ -1098,16 +1148,19 @@ AsyncCompositionManager::TransformShadow
   }
 
   LayerComposite* rootComposite = root->AsLayerComposite();
 
   gfx::Matrix4x4 trans = rootComposite->GetShadowTransform();
   trans *= gfx::Matrix4x4::From2D(mWorldTransform);
   rootComposite->SetShadowTransform(trans);
 
+  if (gfxPrefs::CollectScrollTransforms()) {
+    RecordShadowTransforms(root);
+  }
 
   return wantNextFrame;
 }
 
 void
 AsyncCompositionManager::SetFirstPaintViewport(const LayerIntPoint& aOffset,
                                                const CSSToLayerScale& aZoom,
                                                const CSSRect& aCssPageRect)
--- a/gfx/layers/composite/AsyncCompositionManager.h
+++ b/gfx/layers/composite/AsyncCompositionManager.h
@@ -9,16 +9,17 @@
 #include "Units.h"                      // for ScreenPoint, etc
 #include "mozilla/layers/LayerManagerComposite.h"  // for LayerManagerComposite
 #include "mozilla/Attributes.h"         // for final, etc
 #include "mozilla/RefPtr.h"             // for RefCounted
 #include "mozilla/TimeStamp.h"          // for TimeStamp
 #include "mozilla/dom/ScreenOrientation.h"  // for ScreenOrientation
 #include "mozilla/gfx/BasePoint.h"      // for BasePoint
 #include "mozilla/gfx/Matrix.h"         // for Matrix4x4
+#include "mozilla/layers/FrameUniformityData.h" // For FrameUniformityData
 #include "mozilla/layers/LayersMessages.h"  // for TargetConfig
 #include "nsRefPtr.h"                   // for nsRefPtr
 #include "nsISupportsImpl.h"            // for LayerManager::AddRef, etc
 
 namespace mozilla {
 namespace layers {
 
 class AsyncPanZoomController;
@@ -65,29 +66,22 @@ struct ViewTransform {
  * (LayerManagerComposite) which deals with elements of composition which are
  * usually dealt with by dom or layout when main thread rendering, but which can
  * short circuit that stuff to directly affect layers as they are composited,
  * for example, off-main thread animation, async video, async pan/zoom.
  */
 class AsyncCompositionManager final
 {
   friend class AutoResolveRefLayers;
-  ~AsyncCompositionManager()
-  {
-  }
+  ~AsyncCompositionManager();
+
 public:
   NS_INLINE_DECL_REFCOUNTING(AsyncCompositionManager)
 
-  explicit AsyncCompositionManager(LayerManagerComposite* aManager)
-    : mLayerManager(aManager)
-    , mIsFirstPaint(true)
-    , mLayersUpdated(false)
-    , mReadyForCompose(true)
-  {
-  }
+  explicit AsyncCompositionManager(LayerManagerComposite* aManager);
 
   /**
    * This forces the is-first-paint flag to true. This is intended to
    * be called by the widget code when it loses its viewport information
    * (or for whatever reason wants to refresh the viewport information).
    * The information refresh happens because the compositor will call
    * SetFirstPaintViewport on the next frame of composition.
    */
@@ -118,16 +112,20 @@ public:
 
   // True if the underlying layer tree is ready to be composited.
   bool ReadyForCompose() { return mReadyForCompose; }
 
   // Returns true if the next composition will be the first for a
   // particular document.
   bool IsFirstPaint() { return mIsFirstPaint; }
 
+  // GetFrameUniformity will return the frame uniformity for each layer attached to an APZ
+  // from the recorded data in RecordShadowTransform
+  void GetFrameUniformity(FrameUniformityData* aFrameUniformityData);
+
 private:
   void TransformScrollableLayer(Layer* aLayer);
   // Return true if an AsyncPanZoomController content transform was
   // applied for |aLayer|.
   bool ApplyAsyncContentTransformToTree(Layer* aLayer);
   /**
    * Update the shadow transform for aLayer assuming that is a scrollbar,
    * so that it stays in sync with the content that is being scrolled by APZ.
@@ -185,16 +183,19 @@ private:
   void ResolveRefLayers();
   /**
    * Detaches all referents resolved by ResolveRefLayers.
    * Assumes that mLayerManager->GetRoot() and mTargetConfig have not changed
    * since ResolveRefLayers was called.
    */
   void DetachRefLayers();
 
+  // Records the shadow transforms for the tree of layers rooted at the given layer
+  void RecordShadowTransforms(Layer* aLayer);
+
   TargetConfig mTargetConfig;
   CSSRect mContentRect;
 
   nsRefPtr<LayerManagerComposite> mLayerManager;
   // When this flag is set, the next composition will be the first for a
   // particular document (i.e. the document displayed on the screen will change).
   // This happens when loading a new page or switching tabs. We notify the
   // front-end (e.g. Java on Android) about this so that it take the new page
@@ -203,16 +204,17 @@ private:
 
   // This flag is set during a layers update, so that the first composition
   // after a layers update has it set. It is cleared after that first composition.
   bool mLayersUpdated;
 
   bool mReadyForCompose;
 
   gfx::Matrix mWorldTransform;
+  LayerTransformRecorder mLayerTransformRecorder;
 };
 
 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(AsyncCompositionManager::TransformsToSkip)
 
 class MOZ_STACK_CLASS AutoResolveRefLayers {
 public:
   explicit AutoResolveRefLayers(AsyncCompositionManager* aManager) : mManager(aManager)
   {
new file mode 100644
--- /dev/null
+++ b/gfx/layers/composite/FrameUniformityData.cpp
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FrameUniformityData.h"
+
+#include <map>
+
+#include "Units.h"
+#include "gfxPoint.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/dom/APZTestDataBinding.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace layers {
+
+using namespace gfx;
+
+Point
+LayerTransforms::GetAverage()
+{
+  MOZ_ASSERT(!mTransforms.IsEmpty());
+
+  Point current = mTransforms[0];
+  Point average;
+  size_t length = mTransforms.Length();
+
+  for (size_t i = 1; i < length; i++) {
+    Point nextTransform = mTransforms[i];
+    Point movement = nextTransform - current;
+    average += Point(std::fabs(movement.x), std::fabs(movement.y));
+    current = nextTransform;
+  }
+
+  average = average / (float) length;
+  return average;
+}
+
+Point
+LayerTransforms::GetStdDev()
+{
+  Point average = GetAverage();
+  Point stdDev;
+  Point current = mTransforms[0];
+
+  for (size_t i = 1; i < mTransforms.Length(); i++) {
+    Point next = mTransforms[i];
+    Point move = next - current;
+    move.x = fabs(move.x);
+    move.y = fabs(move.y);
+
+    Point diff = move - average;
+    diff.x = diff.x * diff.x;
+    diff.y = diff.y * diff.y;
+    stdDev += diff;
+
+    current = next;
+  }
+
+  stdDev = stdDev / mTransforms.Length();
+  stdDev.x = sqrt(stdDev.x);
+  stdDev.y = sqrt(stdDev.y);
+  return stdDev;
+}
+
+LayerTransformRecorder::~LayerTransformRecorder()
+{
+  Reset();
+}
+
+void
+LayerTransformRecorder::RecordTransform(Layer* aLayer, const Point& aTransform)
+{
+  LayerTransforms* layerTransforms = GetLayerTransforms((uintptr_t) aLayer);
+  layerTransforms->mTransforms.AppendElement(aTransform);
+}
+
+void
+LayerTransformRecorder::EndTest(FrameUniformityData* aOutData)
+{
+  for (auto iter = mFrameTransforms.begin(); iter != mFrameTransforms.end(); ++iter) {
+    uintptr_t layer = iter->first;
+    float uniformity = CalculateFrameUniformity(layer);
+
+    std::pair<uintptr_t,float> result(layer, uniformity);
+    aOutData->mUniformities.insert(result);
+  }
+
+  Reset();
+}
+
+LayerTransforms*
+LayerTransformRecorder::GetLayerTransforms(uintptr_t aLayer)
+{
+  if (!mFrameTransforms.count(aLayer)) {
+    LayerTransforms* newTransform = new LayerTransforms();
+    std::pair<uintptr_t, LayerTransforms*> newLayer(aLayer, newTransform);
+    mFrameTransforms.insert(newLayer);
+  }
+
+  return mFrameTransforms.find(aLayer)->second;
+}
+
+void
+LayerTransformRecorder::Reset()
+{
+  for (auto iter = mFrameTransforms.begin(); iter != mFrameTransforms.end(); ++iter) {
+    LayerTransforms* layerTransforms = iter->second;
+    delete layerTransforms;
+  }
+
+  mFrameTransforms.clear();
+}
+
+float
+LayerTransformRecorder::CalculateFrameUniformity(uintptr_t aLayer)
+{
+  LayerTransforms* layerTransform = GetLayerTransforms(aLayer);
+  float yUniformity = -1;
+  if (!layerTransform->mTransforms.IsEmpty()) {
+    Point stdDev = layerTransform->GetStdDev();
+    yUniformity = stdDev.y;
+  }
+  return yUniformity;
+}
+
+bool
+FrameUniformityData::ToJS(JS::MutableHandleValue aOutValue, JSContext* aContext)
+{
+  dom::FrameUniformityResults results;
+  dom::Sequence<dom::FrameUniformity>& layers = results.mLayerUniformities.Construct();
+
+  for (auto iter = mUniformities.begin(); iter != mUniformities.end(); ++iter) {
+    uintptr_t layerAddr = iter->first;
+    float uniformity = iter->second;
+
+    layers.AppendElement();
+    dom::FrameUniformity& entry = layers.LastElement();
+
+    entry.mLayerAddress.Construct() = layerAddr;
+    entry.mFrameUniformity.Construct() = uniformity;
+  }
+
+  return dom::ToJSValue(aContext, results, aOutValue);
+}
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/gfx/layers/composite/FrameUniformityData.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_FrameUniformityData_h_
+#define mozilla_layers_FrameUniformityData_h_
+
+#include "ipc/IPCMessageUtils.h"
+#include "js/TypeDecls.h"
+#include "nsRefPtr.h"
+
+namespace mozilla {
+namespace layers {
+class Layer;
+
+class FrameUniformityData {
+  friend struct IPC::ParamTraits<FrameUniformityData>;
+
+public:
+  bool ToJS(JS::MutableHandleValue aOutValue, JSContext* aContext);
+  // Contains the calculated frame uniformities
+  std::map<uintptr_t,float> mUniformities;
+};
+
+struct LayerTransforms {
+  LayerTransforms() {}
+
+  gfx::Point GetAverage();
+  gfx::Point GetStdDev();
+
+  // 60 fps * 5 seconds worth of data
+  nsAutoTArray<gfx::Point, 300> mTransforms;
+};
+
+class LayerTransformRecorder {
+public:
+  LayerTransformRecorder() {}
+  ~LayerTransformRecorder();
+
+  void RecordTransform(Layer* aLayer, const gfx::Point& aTransform);
+  void Reset();
+  void EndTest(FrameUniformityData* aOutData);
+
+private:
+  float CalculateFrameUniformity(uintptr_t aLayer);
+  LayerTransforms* GetLayerTransforms(uintptr_t aLayer);
+  std::map<uintptr_t,LayerTransforms*> mFrameTransforms;
+};
+
+} // mozilla
+} // layers
+
+namespace IPC {
+template<>
+struct ParamTraits<mozilla::layers::FrameUniformityData>
+{
+  typedef mozilla::layers::FrameUniformityData paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mUniformities);
+  }
+
+  static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
+  {
+    return ParamTraitsStd<std::map<uintptr_t,float>>::Read(aMsg, aIter, &aResult->mUniformities);
+  }
+};
+
+}// ipc
+
+#endif // mozilla_layers_FrameUniformityData_h_
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -32,16 +32,17 @@
 #include "mozilla/layers/APZCTreeManager.h"  // for APZCTreeManager
 #include "mozilla/layers/APZThreadUtils.h"  // for APZCTreeManager
 #include "mozilla/layers/AsyncCompositionManager.h"
 #include "mozilla/layers/BasicCompositor.h"  // for BasicCompositor
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/CompositorLRU.h"  // for CompositorLRU
 #include "mozilla/layers/CompositorOGL.h"  // for CompositorOGL
 #include "mozilla/layers/CompositorTypes.h"
+#include "mozilla/layers/FrameUniformityData.h"
 #include "mozilla/layers/LayerManagerComposite.h"
 #include "mozilla/layers/LayersTypes.h"
 #include "mozilla/layers/PLayerTransactionParent.h"
 #include "mozilla/layers/ShadowLayersManager.h" // for ShadowLayersManager
 #include "mozilla/mozalloc.h"           // for operator new, etc
 #include "mozilla/Telemetry.h"
 #ifdef MOZ_WIDGET_GTK
 #include "basic/X11BasicCompositor.h" // for X11BasicCompositor
@@ -1320,16 +1321,23 @@ CompositorParent::ApplyAsyncProperties(L
       CancelCurrentCompositeTask();
       // Pretend we composited in case someone is waiting for this event.
       DidComposite();
     }
   }
 }
 
 bool
+CompositorParent::RecvGetFrameUniformity(FrameUniformityData* aOutData)
+{
+  mCompositionManager->GetFrameUniformity(aOutData);
+  return true;
+}
+
+bool
 CompositorParent::RecvRequestOverfill()
 {
   uint32_t overfillRatio = mCompositor->GetFillRatio();
   unused << SendOverfill(overfillRatio);
   return true;
 }
 
 void
@@ -1708,16 +1716,23 @@ public:
   virtual bool RecvStopFrameTimeRecording(const uint32_t& aStartIndex, InfallibleTArray<float>* intervals) override  { return true; }
   virtual bool RecvGetTileSize(int32_t* aWidth, int32_t* aHeight) override
   {
     *aWidth = gfxPlatform::GetPlatform()->GetTileWidth();
     *aHeight = gfxPlatform::GetPlatform()->GetTileHeight();
     return true;
   }
 
+  virtual bool RecvGetFrameUniformity(FrameUniformityData* aOutData) override
+  {
+    // Don't support calculating frame uniformity on the child process and
+    // this is just a stub for now.
+    MOZ_ASSERT(false);
+    return true;
+  }
 
   /**
    * Tells this CompositorParent to send a message when the compositor has received the transaction.
    */
   virtual bool RecvRequestNotifyAfterRemotePaint() override;
 
   virtual PLayerTransactionParent*
     AllocPLayerTransactionParent(const nsTArray<LayersBackend>& aBackendHints,
--- a/gfx/layers/ipc/CompositorParent.h
+++ b/gfx/layers/ipc/CompositorParent.h
@@ -224,16 +224,17 @@ public:
                             int aSurfaceWidth = -1, int aSurfaceHeight = -1);
 
   // IToplevelProtocol::CloneToplevel()
   virtual IToplevelProtocol*
   CloneToplevel(const InfallibleTArray<mozilla::ipc::ProtocolFdMapping>& aFds,
                 base::ProcessHandle aPeerProcess,
                 mozilla::ipc::ProtocolCloneContext* aCtx) override;
 
+  virtual bool RecvGetFrameUniformity(FrameUniformityData* aOutData) override;
   virtual bool RecvRequestOverfill() override;
   virtual bool RecvWillStop() override;
   virtual bool RecvStop() override;
   virtual bool RecvPause() override;
   virtual bool RecvResume() override;
   virtual bool RecvNotifyHidden(const uint64_t& id) override { return true; }
   virtual bool RecvNotifyVisible(const uint64_t& id) override { return true; }
   virtual bool RecvNotifyChildCreated(const uint64_t& child) override;
--- a/gfx/layers/ipc/PCompositor.ipdl
+++ b/gfx/layers/ipc/PCompositor.ipdl
@@ -14,16 +14,17 @@ include "nsRegion.h";
 using struct mozilla::null_t from "ipc/IPCMessageUtils.h";
 using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h";
 using struct mozilla::layers::FrameMetrics from "FrameMetrics.h";
 using mozilla::layers::FrameMetrics::ViewID from "FrameMetrics.h";
 using mozilla::layers::LayersBackend from "mozilla/layers/LayersTypes.h";
 using mozilla::CrossProcessMutexHandle from "mozilla/ipc/CrossProcessMutex.h";
 using mozilla::ipc::SharedMemoryBasic::Handle from "mozilla/ipc/SharedMemoryBasic.h";
 using class mozilla::TimeStamp from "mozilla/TimeStamp.h";
+using class mozilla::layers::FrameUniformityData from "mozilla/layers/FrameUniformityData.h";
 
 namespace mozilla {
 namespace layers {
 
 
 /**
  * The PCompositor protocol is used to manage communication between
  * the main thread and the compositor thread context. It's primary
@@ -75,16 +76,19 @@ child:
    * side.
    */
   async ClearCachedResources(uint64_t id);
 
 parent:
   // Child sends the parent a request for fill ratio numbers.
   async RequestOverfill();
 
+  // Child requests frame uniformity measurements
+  sync GetFrameUniformity() returns (FrameUniformityData data);
+
   // The child is about to be destroyed, so perform any necessary cleanup.
   sync WillStop();
 
   // Clean up in preparation for own destruction.
   sync Stop();
 
   // Pause/resume the compositor. These are intended to be used on mobile, when
   // the compositor needs to pause/resume in lockstep with the application.
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -120,16 +120,17 @@ EXPORTS.mozilla.layers += [
     'client/TextureClientRecycleAllocator.h',
     'client/TextureClientSharedSurface.h',
     'client/TiledContentClient.h',
     'composite/AsyncCompositionManager.h',
     'composite/CanvasLayerComposite.h',
     'composite/ColorLayerComposite.h',
     'composite/ContainerLayerComposite.h',
     'composite/ContentHost.h',
+    'composite/FrameUniformityData.h',
     'composite/ImageHost.h',
     'composite/ImageLayerComposite.h',
     'composite/LayerManagerComposite.h',
     'composite/PaintedLayerComposite.h',
     'composite/TextureHost.h',
     'Compositor.h',
     'CompositorTypes.h',
     'D3D11ShareHandleImage.h',
@@ -272,16 +273,17 @@ UNIFIED_SOURCES += [
     'client/TiledContentClient.cpp',
     'composite/AsyncCompositionManager.cpp',
     'composite/CanvasLayerComposite.cpp',
     'composite/ColorLayerComposite.cpp',
     'composite/CompositableHost.cpp',
     'composite/ContainerLayerComposite.cpp',
     'composite/ContentHost.cpp',
     'composite/FPSCounter.cpp',
+    'composite/FrameUniformityData.cpp',
     'composite/ImageHost.cpp',
     'composite/ImageLayerComposite.cpp',
     'composite/LayerManagerComposite.cpp',
     'composite/PaintedLayerComposite.cpp',
     'composite/TextRenderer.cpp',
     'composite/TextureHost.cpp',
     'composite/TiledContentHost.cpp',
     'Compositor.cpp',
@@ -386,11 +388,12 @@ CXXFLAGS += [
         'frameworks/base/include/media/stagefright',
         'frameworks/base/include/media/stagefright/openmax',
         'frameworks/av/include/media/stagefright',
         'frameworks/native/include/media/openmax',
     ]
 ]
 
 MOCHITEST_MANIFESTS += ['apz/test/mochitest.ini']
+MOCHITEST_CHROME_MANIFESTS += ['apz/test/chrome.ini']
 
 CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
 CXXFLAGS += CONFIG['TK_CFLAGS']
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -235,16 +235,17 @@ private:
   DECL_GFX_PREF(Once, "gfx.touch.resample",                    TouchResampling, bool, false);
 
   // These times should be in milliseconds
   DECL_GFX_PREF(Once, "gfx.touch.resample.delay-threshold",    TouchResampleVsyncDelayThreshold, int32_t, 20);
   DECL_GFX_PREF(Once, "gfx.touch.resample.max-predict",        TouchResampleMaxPredict, int32_t, 8);
   DECL_GFX_PREF(Once, "gfx.touch.resample.old-touch-threshold",TouchResampleOldTouchThreshold, int32_t, 17);
   DECL_GFX_PREF(Once, "gfx.touch.resample.vsync-adjust",       TouchVsyncSampleAdjust, int32_t, 5);
 
+  DECL_GFX_PREF(Live, "gfx.vsync.collect-scroll-transforms",   CollectScrollTransforms, bool, false);
   DECL_GFX_PREF(Once, "gfx.vsync.compositor",                  VsyncAlignedCompositor, bool, false);
   // On b2g, in really bad cases, I've seen up to 80 ms delays between touch events and the main thread
   // processing them. So 80 ms / 16 = 5 vsync events. Double it up just to be on the safe side, so 10.
   DECL_GFX_PREF(Once, "gfx.vsync.compositor.unobserve-count",  CompositorUnobserveCount, int32_t, 10);
   // Use vsync events generated by hardware
   DECL_GFX_PREF(Once, "gfx.vsync.hw-vsync.enabled",            HardwareVsyncEnabled, bool, false);
   DECL_GFX_PREF(Once, "gfx.vsync.refreshdriver",               VsyncAlignedRefreshDriver, bool, false);
   DECL_GFX_PREF(Once, "gfx.work-around-driver-bugs",           WorkAroundDriverBugs, bool, true);