Bug 749063 - Replace shouldAbort with generic callback for progressive updates. r=bgirard
authorChris Lord <chrislord.net@gmail.com>
Mon, 15 Oct 2012 09:33:34 +0100
changeset 110395 f8114d23854fe8d7db4dcdc224dd5a1d475bd884
parent 110394 9ca0df7f08fceb479a33092fa95184c9964d52d8
child 110396 9706761f3533c2f7bff82e47a0171fc6c19eebff
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewersbgirard
bugs749063
milestone19.0a1
Bug 749063 - Replace shouldAbort with generic callback for progressive updates. r=bgirard Replace ShouldAbortProgressiveUpdate with ProgressiveUpdateCallback, that provides more contextual information about how the update will get used.
gfx/layers/basic/BasicLayerManager.cpp
gfx/layers/basic/BasicLayers.h
gfx/layers/basic/BasicTiledThebesLayer.cpp
mobile/android/base/Makefile.in
mobile/android/base/gfx/GeckoLayerClient.java
mobile/android/base/gfx/ProgressiveUpdateData.java
widget/android/AndroidBridge.cpp
widget/android/AndroidBridge.h
widget/android/AndroidJavaWrappers.cpp
widget/android/AndroidJavaWrappers.h
--- a/gfx/layers/basic/BasicLayerManager.cpp
+++ b/gfx/layers/basic/BasicLayerManager.cpp
@@ -1273,35 +1273,39 @@ BasicShadowLayerManager::IsCompositingCh
 
 void
 BasicShadowLayerManager::SetIsFirstPaint()
 {
   ShadowLayerForwarder::SetIsFirstPaint();
 }
 
 bool
-BasicShadowLayerManager::ShouldAbortProgressiveUpdate(bool aHasPendingNewThebesContent)
+BasicShadowLayerManager::ProgressiveUpdateCallback(bool aHasPendingNewThebesContent,
+                                                   gfx::Rect& aViewport,
+                                                   float& aScaleX,
+                                                   float& aScaleY)
 {
 #ifdef MOZ_WIDGET_ANDROID
   Layer* primaryScrollable = GetPrimaryScrollableLayer();
   if (primaryScrollable) {
     const FrameMetrics& metrics = primaryScrollable->AsContainerLayer()->GetFrameMetrics();
 
     // This is derived from the code in
     // gfx/layers/ipc/CompositorParent.cpp::TransformShadowTree.
     const gfx3DMatrix& rootTransform = GetRoot()->GetTransform();
     float devPixelRatioX = 1 / rootTransform.GetXScale();
     float devPixelRatioY = 1 / rootTransform.GetYScale();
     gfx::Rect displayPort((metrics.mDisplayPort.x + metrics.mScrollOffset.x) * devPixelRatioX,
                           (metrics.mDisplayPort.y + metrics.mScrollOffset.y) * devPixelRatioY,
                           metrics.mDisplayPort.width * devPixelRatioX,
                           metrics.mDisplayPort.height * devPixelRatioY);
 
-    return AndroidBridge::Bridge()->ShouldAbortProgressiveUpdate(
-      aHasPendingNewThebesContent, displayPort, devPixelRatioX);
+    return AndroidBridge::Bridge()->ProgressiveUpdateCallback(
+      aHasPendingNewThebesContent, displayPort, devPixelRatioX,
+      aViewport, aScaleX, aScaleY);
   }
 #endif
 
   return false;
 }
 
 already_AddRefed<ThebesLayer>
 BasicShadowLayerManager::CreateThebesLayer()
--- a/gfx/layers/basic/BasicLayers.h
+++ b/gfx/layers/basic/BasicLayers.h
@@ -268,22 +268,29 @@ public:
   virtual bool IsCompositingCheap();
   virtual bool HasShadowManagerInternal() const { return HasShadowManager(); }
 
   virtual void SetIsFirstPaint() MOZ_OVERRIDE;
 
   void SetRepeatTransaction() { mRepeatTransaction = true; }
 
   /**
-   * Determines if a progressive update should be cancelled. This is only called
-   * if gfxPlatform::UseProgressiveTilePainting() returns true.
-   * aHasPendingNewThebesContent is true if there is a Thebes layer update
-   * that will cause its valid region to expand.
+   * Called for each iteration of a progressive tile update. Fills
+   * aViewport, aScaleX and aScaleY with the current scale and viewport
+   * being used to composite the layers in this manager, to determine what area
+   * intersects with the target render rectangle.
+   * Returns true if the update should continue, or false if it should be
+   * cancelled.
+   * This is only called if gfxPlatform::UseProgressiveTilePainting() returns
+   * true.
    */
-  bool ShouldAbortProgressiveUpdate(bool aHasPendingNewThebesContent);
+  bool ProgressiveUpdateCallback(bool aHasPendingNewThebesContent,
+                                 gfx::Rect& aViewport,
+                                 float& aScaleX,
+                                 float& aScaleY);
 
 private:
   /**
    * Forward transaction results to the parent context.
    */
   void ForwardTransaction();
 
   // The bounds of |mTarget| in device pixels.
--- a/gfx/layers/basic/BasicTiledThebesLayer.cpp
+++ b/gfx/layers/basic/BasicTiledThebesLayer.cpp
@@ -280,19 +280,22 @@ BasicTiledThebesLayer::PaintThebes(gfxCo
     // Paint tiles that have no content before tiles that only have stale content.
     nsIntRegion staleRegion = mTiledBuffer.GetValidRegion();
     staleRegion.And(staleRegion, regionToPaint);
     bool hasNewContent = !staleRegion.Contains(regionToPaint);
     if (!staleRegion.IsEmpty() && hasNewContent) {
       regionToPaint.Sub(regionToPaint, staleRegion);
     }
 
-    // Find out if we should just abort this paint, usually due to there being
-    // an incoming, more relevant paint.
-    if (BasicManager()->ShouldAbortProgressiveUpdate(hasNewContent)) {
+    // Find out the current view transform to determine which tiles to draw
+    // first, and see if we should just abort this paint. Aborting is usually
+    // caused by there being an incoming, more relevant paint.
+    gfx::Rect viewport;
+    float scaleX, scaleY;
+    if (BasicManager()->ProgressiveUpdateCallback(hasNewContent, viewport, scaleX, scaleY)) {
       return;
     }
 
     // The following code decides what order to draw tiles in, based on the
     // current scroll direction of the primary scrollable layer.
     // XXX While this code is of a reasonable size currently, it is likely
     //     we'll want to add more comprehensive methods of deciding what
     //     tiles to draw. This is a good candidate for splitting out into a
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -149,16 +149,17 @@ FENNEC_JAVA_FILES = \
   gfx/IntSize.java \
   gfx/Layer.java \
   gfx/LayerRenderer.java \
   gfx/LayerView.java \
   gfx/PluginLayer.java \
   gfx/NinePatchTileLayer.java \
   gfx/PanningPerfAPI.java \
   gfx/PointUtils.java \
+  gfx/ProgressiveUpdateData.java \
   gfx/RectUtils.java \
   gfx/ScreenshotLayer.java \
   gfx/ScrollbarLayer.java \
   gfx/SingleTileLayer.java \
   gfx/TextLayer.java \
   gfx/TextureGenerator.java \
   gfx/TextureReaper.java \
   gfx/TileLayer.java \
--- a/mobile/android/base/gfx/GeckoLayerClient.java
+++ b/mobile/android/base/gfx/GeckoLayerClient.java
@@ -67,16 +67,19 @@ public class GeckoLayerClient
     private ImmutableViewportMetrics mFrameMetrics;
 
     /* Used by robocop for testing purposes */
     private DrawListener mDrawListener;
 
     /* Used as a temporary ViewTransform by syncViewportInfo */
     private final ViewTransform mCurrentViewTransform;
 
+    /* Used as the return value of progressiveUpdateCallback */
+    private final ProgressiveUpdateData mProgressiveUpdateData;
+
     /* This is written by the compositor thread and read by the UI thread. */
     private volatile boolean mCompositorCreated;
 
     private boolean mForceRedraw;
 
     /* The current viewport metrics.
      * This is volatile so that we can read and write to it from different threads.
      * We avoid synchronization to make getting the viewport metrics from
@@ -104,16 +107,17 @@ public class GeckoLayerClient
         mEventDispatcher = eventDispatcher;
         mContext = context;
         mScreenSize = new IntSize(0, 0);
         mWindowSize = new IntSize(0, 0);
         mDisplayPort = new DisplayPortMetrics();
         mRecordDrawTimes = true;
         mDrawTimingQueue = new DrawTimingQueue();
         mCurrentViewTransform = new ViewTransform(0, 0, 1);
+        mProgressiveUpdateData = new ProgressiveUpdateData();
         mCompositorCreated = false;
 
         mForceRedraw = true;
         DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
         mViewportMetrics = new ImmutableViewportMetrics(new ViewportMetrics(displayMetrics));
         mZoomConstraints = new ZoomConstraints(false);
 
         mPanZoomController = new PanZoomController(this, mEventDispatcher);
@@ -357,72 +361,76 @@ public class GeckoLayerClient
         }
     }
 
     // This is called on the Gecko thread to determine if we're still interested
     // in the update of this display-port to continue. We can return true here
     // to abort the current update and continue with any subsequent ones. This
     // is useful for slow-to-render pages when the display-port starts lagging
     // behind enough that continuing to draw it is wasted effort.
-    public boolean shouldAbortProgressiveUpdate(boolean aHasPendingNewThebesContent,
-                                                float x, float y, float width, float height, float resolution) {
-        // XXX Grab a local copy of the last display-port sent to Gecko to
-        //     avoid races when accessing it.
+    public ProgressiveUpdateData progressiveUpdateCallback(boolean aHasPendingNewThebesContent,
+                                                           float x, float y, float width, float height, float resolution) {
+        // Grab a local copy of the last display-port sent to Gecko and the
+        // current viewport metrics to avoid races when accessing them.
         DisplayPortMetrics displayPort = mDisplayPort;
+        ImmutableViewportMetrics viewportMetrics = mViewportMetrics;
+        mProgressiveUpdateData.setViewport(viewportMetrics);
+        mProgressiveUpdateData.abort = false;
 
         // Always abort updates if the resolution has changed. There's no use
         // in drawing at the incorrect resolution.
         if (!FloatUtils.fuzzyEquals(resolution, displayPort.resolution)) {
             Log.d(LOGTAG, "Aborting draw due to resolution change");
-            return true;
+            mProgressiveUpdateData.abort = true;
+            return mProgressiveUpdateData;
         }
 
         // XXX All sorts of rounding happens inside Gecko that becomes hard to
         //     account exactly for. Given we align the display-port to tile
         //     boundaries (and so they rarely vary by sub-pixel amounts), just
         //     check that values are within a pixel of the display-port bounds.
 
         // Never abort drawing if we can't be sure we've sent a more recent
         // display-port. If we abort updating when we shouldn't, we can end up
         // with blank regions on the screen and we open up the risk of entering
         // an endless updating cycle.
         if (Math.abs(displayPort.getLeft() - x) <= 1 &&
             Math.abs(displayPort.getTop() - y) <= 1 &&
             Math.abs(displayPort.getBottom() - (y + height)) <= 1 &&
             Math.abs(displayPort.getRight() - (x + width)) <= 1) {
-            return false;
+            return mProgressiveUpdateData;
         }
 
         // Abort updates when the display-port no longer contains the visible
         // area of the page (that is, the viewport cropped by the page
         // boundaries).
         // XXX This makes the assumption that we never let the visible area of
         //     the page fall outside of the display-port.
-        // Grab a local copy of the viewport metrics to avoid races.
-        ImmutableViewportMetrics viewportMetrics = mViewportMetrics;
         if (Math.max(viewportMetrics.viewportRectLeft, viewportMetrics.pageRectLeft) + 1 < x ||
             Math.max(viewportMetrics.viewportRectTop, viewportMetrics.pageRectTop) + 1 < y ||
             Math.min(viewportMetrics.viewportRectRight, viewportMetrics.pageRectRight) - 1 > x + width ||
             Math.min(viewportMetrics.viewportRectBottom, viewportMetrics.pageRectBottom) - 1 > y + height) {
             Log.d(LOGTAG, "Aborting update due to viewport not in display-port");
-            return true;
+            mProgressiveUpdateData.abort = true;
+            return mProgressiveUpdateData;
         }
 
         // There's no new content (where new content is considered to be an
         // update in a region that wasn't previously visible), and we've sent a
         // more recent display-port.
         // Aborting in this situation helps us recover more quickly when the
         // user starts scrolling on a page that contains animated content that
         // is slow to draw.
         if (!aHasPendingNewThebesContent) {
             Log.d(LOGTAG, "Aborting update due to more relevant display-port in event queue");
-            return true;
+            mProgressiveUpdateData.abort = true;
+            return mProgressiveUpdateData;
         }
 
-        return false;
+        return mProgressiveUpdateData;
     }
 
     /** Implementation of GeckoEventResponder/GeckoEventListener. */
     public void handleMessage(String event, JSONObject message) {
         try {
             if ("Checkerboard:Toggle".equals(event)) {
                 mView.setCheckerboardShouldShowChecks(message.getBoolean("value"));
             }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/ProgressiveUpdateData.java
@@ -0,0 +1,30 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.gfx;
+
+/**
+ * This is the data structure that's returned by the progressive tile update
+ * callback function. It encompasses the current viewport and a boolean value
+ * representing whether the front-end is interested in the current progressive
+ * update continuing.
+ */
+public class ProgressiveUpdateData {
+    public float x;
+    public float y;
+    public float width;
+    public float height;
+    public float scale;
+    public boolean abort;
+
+    public void setViewport(ImmutableViewportMetrics viewport) {
+        this.x = viewport.viewportRectLeft;
+        this.y = viewport.viewportRectTop;
+        this.width = viewport.viewportRectRight - this.x;
+        this.height = viewport.viewportRectBottom - this.x;
+        this.scale = viewport.zoomFactor;
+    }
+}
+
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -2522,23 +2522,23 @@ AndroidBridge::GetDisplayPort(bool aPage
         return NS_OK;
     AutoLocalJNIFrame jniFrame(env, 0);
     mLayerClient->GetDisplayPort(&jniFrame, aPageSizeUpdate, aIsBrowserContentDisplayed, tabId, metrics, displayPort);
 
     return NS_OK;
 }
 
 bool
-AndroidBridge::ShouldAbortProgressiveUpdate(bool aHasPendingNewThebesContent, const gfx::Rect& aDisplayPort, float aDisplayResolution)
+AndroidBridge::ProgressiveUpdateCallback(bool aHasPendingNewThebesContent, const gfx::Rect& aDisplayPort, float aDisplayResolution, gfx::Rect& aViewport, float& aScaleX, float& aScaleY)
 {
     AndroidGeckoLayerClient *client = mLayerClient;
     if (!client)
         return false;
 
-    return client->ShouldAbortProgressiveUpdate(aHasPendingNewThebesContent, aDisplayPort, aDisplayResolution);
+    return client->ProgressiveUpdateCallback(aHasPendingNewThebesContent, aDisplayPort, aDisplayResolution, aViewport, aScaleX, aScaleY);
 }
 
 void
 AndroidBridge::NotifyPaintedRect(float top, float left, float bottom, float right)
 {
     JNIEnv* env = GetJNIEnv();
     if (!env)
         return;
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -151,17 +151,17 @@ public:
     static void NotifyIMEEnabled(int aState, const nsAString& aTypeHint,
                                  const nsAString& aModeHint, const nsAString& aActionHint);
 
     static void NotifyIMEChange(const PRUnichar *aText, uint32_t aTextLen, int aStart, int aEnd, int aNewEnd);
 
     nsresult TakeScreenshot(nsIDOMWindow *window, int32_t srcX, int32_t srcY, int32_t srcW, int32_t srcH, int32_t dstY, int32_t dstX, int32_t dstW, int32_t dstH, int32_t bufW, int32_t bufH, int32_t tabId, int32_t token, jobject buffer);
     nsresult GetDisplayPort(bool aPageSizeUpdate, bool aIsBrowserContentDisplayed, int32_t tabId, nsIAndroidViewport* metrics, nsIAndroidDisplayport** displayPort);
 
-    bool ShouldAbortProgressiveUpdate(bool aHasPendingNewThebesContent, const gfx::Rect& aDisplayPort, float aDisplayResolution);
+    bool ProgressiveUpdateCallback(bool aHasPendingNewThebesContent, const gfx::Rect& aDisplayPort, float aDisplayResolution, gfx::Rect& aViewport, float& aScaleX, float& aScaleY);
 
     static void NotifyPaintedRect(float top, float left, float bottom, float right);
 
     void AcknowledgeEventSync();
 
     void EnableLocation(bool aEnable);
     void EnableLocationHighAccuracy(bool aEnable);
 
--- a/widget/android/AndroidJavaWrappers.cpp
+++ b/widget/android/AndroidJavaWrappers.cpp
@@ -79,29 +79,37 @@ jmethodID AndroidGeckoLayerClient::jSetP
 jmethodID AndroidGeckoLayerClient::jSyncViewportInfoMethod = 0;
 jmethodID AndroidGeckoLayerClient::jCreateFrameMethod = 0;
 jmethodID AndroidGeckoLayerClient::jActivateProgramMethod = 0;
 jmethodID AndroidGeckoLayerClient::jDeactivateProgramMethod = 0;
 jmethodID AndroidGeckoLayerClient::jGetDisplayPort = 0;
 jmethodID AndroidGeckoLayerClient::jViewportCtor = 0;
 jfieldID AndroidGeckoLayerClient::jDisplayportPosition = 0;
 jfieldID AndroidGeckoLayerClient::jDisplayportResolution = 0;
-jmethodID AndroidGeckoLayerClient::jShouldAbortProgressiveUpdate = 0;
+jmethodID AndroidGeckoLayerClient::jProgressiveUpdateCallbackMethod = 0;
 
 jclass AndroidLayerRendererFrame::jLayerRendererFrameClass = 0;
 jmethodID AndroidLayerRendererFrame::jBeginDrawingMethod = 0;
 jmethodID AndroidLayerRendererFrame::jDrawBackgroundMethod = 0;
 jmethodID AndroidLayerRendererFrame::jDrawForegroundMethod = 0;
 jmethodID AndroidLayerRendererFrame::jEndDrawingMethod = 0;
 
 jclass AndroidViewTransform::jViewTransformClass = 0;
 jfieldID AndroidViewTransform::jXField = 0;
 jfieldID AndroidViewTransform::jYField = 0;
 jfieldID AndroidViewTransform::jScaleField = 0;
 
+jclass AndroidProgressiveUpdateData::jProgressiveUpdateDataClass = 0;
+jfieldID AndroidProgressiveUpdateData::jXField = 0;
+jfieldID AndroidProgressiveUpdateData::jYField = 0;
+jfieldID AndroidProgressiveUpdateData::jWidthField = 0;
+jfieldID AndroidProgressiveUpdateData::jHeightField = 0;
+jfieldID AndroidProgressiveUpdateData::jScaleField = 0;
+jfieldID AndroidProgressiveUpdateData::jShouldAbortField = 0;
+
 jclass AndroidGeckoSurfaceView::jGeckoSurfaceViewClass = 0;
 jmethodID AndroidGeckoSurfaceView::jBeginDrawingMethod = 0;
 jmethodID AndroidGeckoSurfaceView::jEndDrawingMethod = 0;
 jmethodID AndroidGeckoSurfaceView::jDraw2DBitmapMethod = 0;
 jmethodID AndroidGeckoSurfaceView::jDraw2DBufferMethod = 0;
 jmethodID AndroidGeckoSurfaceView::jGetSoftwareDrawBitmapMethod = 0;
 jmethodID AndroidGeckoSurfaceView::jGetSoftwareDrawBufferMethod = 0;
 jmethodID AndroidGeckoSurfaceView::jGetSurfaceMethod = 0;
@@ -182,16 +190,17 @@ mozilla::InitAndroidJavaWrappers(JNIEnv 
     AndroidGeckoEvent::InitGeckoEventClass(jEnv);
     AndroidPoint::InitPointClass(jEnv);
     AndroidLocation::InitLocationClass(jEnv);
     AndroidRect::InitRectClass(jEnv);
     AndroidRectF::InitRectFClass(jEnv);
     AndroidGeckoLayerClient::InitGeckoLayerClientClass(jEnv);
     AndroidLayerRendererFrame::InitLayerRendererFrameClass(jEnv);
     AndroidViewTransform::InitViewTransformClass(jEnv);
+    AndroidProgressiveUpdateData::InitProgressiveUpdateDataClass(jEnv);
     AndroidGeckoSurfaceView::InitGeckoSurfaceViewClass(jEnv);
 }
 
 void
 AndroidGeckoEvent::InitGeckoEventClass(JNIEnv *jEnv)
 {
     initInit();
 
@@ -340,24 +349,25 @@ AndroidGeckoLayerClient::InitGeckoLayerC
     jSetFirstPaintViewport = getMethod("setFirstPaintViewport", "(FFFFFFFFFFF)V");
     jSetPageRect = getMethod("setPageRect", "(FFFF)V");
     jSyncViewportInfoMethod = getMethod("syncViewportInfo",
                                         "(IIIIFZ)Lorg/mozilla/gecko/gfx/ViewTransform;");
     jCreateFrameMethod = getMethod("createFrame", "()Lorg/mozilla/gecko/gfx/LayerRenderer$Frame;");
     jActivateProgramMethod = getMethod("activateProgram", "()V");
     jDeactivateProgramMethod = getMethod("deactivateProgram", "()V");
     jGetDisplayPort = getMethod("getDisplayPort", "(ZZILorg/mozilla/gecko/gfx/ViewportMetrics;)Lorg/mozilla/gecko/gfx/DisplayPortMetrics;");
-    jShouldAbortProgressiveUpdate = getMethod("shouldAbortProgressiveUpdate", "(ZFFFFF)Z");
 
     jViewportClass = GetClassGlobalRef(jEnv, "org/mozilla/gecko/gfx/ViewportMetrics");
     jViewportCtor = GetMethodID(jEnv, jViewportClass, "<init>", "(FFFFFFFFFFFFF)V");
 
     jDisplayportClass = GetClassGlobalRef(jEnv, "org/mozilla/gecko/gfx/DisplayPortMetrics");
     jDisplayportPosition = GetFieldID(jEnv, jDisplayportClass, "mPosition", "Landroid/graphics/RectF;");
     jDisplayportResolution = GetFieldID(jEnv, jDisplayportClass, "resolution", "F");
+    jProgressiveUpdateCallbackMethod = getMethod("progressiveUpdateCallback",
+                                                 "(ZFFFFF)Lorg/mozilla/gecko/gfx/ProgressiveUpdateData;");
 
 #endif
 }
 
 void
 AndroidLayerRendererFrame::InitLayerRendererFrameClass(JNIEnv *jEnv)
 {
 #ifdef MOZ_ANDROID_OMTC
@@ -381,16 +391,33 @@ AndroidViewTransform::InitViewTransformC
     jViewTransformClass = getClassGlobalRef("org/mozilla/gecko/gfx/ViewTransform");
 
     jXField = getField("x", "F");
     jYField = getField("y", "F");
     jScaleField = getField("scale", "F");
 #endif
 }
 
+void
+AndroidProgressiveUpdateData::InitProgressiveUpdateDataClass(JNIEnv *jEnv)
+{
+#ifdef MOZ_ANDROID_OMTC
+    initInit();
+
+    jProgressiveUpdateDataClass = getClassGlobalRef("org/mozilla/gecko/gfx/ProgressiveUpdateData");
+
+    jXField = getField("x", "F");
+    jYField = getField("y", "F");
+    jWidthField = getField("width", "F");
+    jHeightField = getField("height", "F");
+    jScaleField = getField("scale", "F");
+    jShouldAbortField = getField("abort", "Z");
+#endif
+}
+
 #undef initInit
 #undef initClassGlobalRef
 #undef getField
 #undef getMethod
 
 void
 AndroidGeckoEvent::ReadPointArray(nsTArray<nsIntPoint> &points,
                                   JNIEnv *jenv,
@@ -697,16 +724,23 @@ AndroidLayerRendererFrame::Dispose(JNIEn
 void
 AndroidViewTransform::Init(jobject jobj)
 {
     NS_ABORT_IF_FALSE(wrapped_obj == nullptr, "Init called on non-null wrapped_obj!");
     wrapped_obj = jobj;
 }
 
 void
+AndroidProgressiveUpdateData::Init(jobject jobj)
+{
+    NS_ABORT_IF_FALSE(wrapped_obj == nullptr, "Init called on non-null wrapped_obj!");
+    wrapped_obj = jobj;
+}
+
+void
 AndroidGeckoSurfaceView::Init(jobject jobj)
 {
     NS_ASSERTION(wrapped_obj == nullptr, "Init called on non-null wrapped_obj!");
 
     wrapped_obj = jobj;
 }
 
 int
@@ -810,37 +844,51 @@ AndroidGeckoLayerClient::SyncViewportInf
     AndroidViewTransform viewTransform;
     viewTransform.Init(viewTransformJObj);
 
     aScrollOffset = nsIntPoint(viewTransform.GetX(env), viewTransform.GetY(env));
     aScaleX = aScaleY = viewTransform.GetScale(env);
 }
 
 bool
-AndroidGeckoLayerClient::ShouldAbortProgressiveUpdate(bool aHasPendingNewThebesContent,
-                                                      const gfx::Rect& aDisplayPort,
-                                                      float aDisplayResolution)
+AndroidGeckoLayerClient::ProgressiveUpdateCallback(bool aHasPendingNewThebesContent,
+                                                   const gfx::Rect& aDisplayPort,
+                                                   float aDisplayResolution,
+                                                   gfx::Rect& aViewport,
+                                                   float& aScaleX,
+                                                   float& aScaleY)
 {
     JNIEnv *env = AndroidBridge::GetJNIEnv();
     if (!env)
         return false;
 
     AutoLocalJNIFrame jniFrame(env);
 
-    bool ret = env->CallBooleanMethod(wrapped_obj, jShouldAbortProgressiveUpdate,
-                                      aHasPendingNewThebesContent,
-                                      (float)aDisplayPort.x,
-                                      (float)aDisplayPort.y,
-                                      (float)aDisplayPort.width,
-                                      (float)aDisplayPort.height,
-                                      aDisplayResolution);
+    jobject progressiveUpdateDataJObj = env->CallObjectMethod(wrapped_obj,
+                                                              jProgressiveUpdateCallbackMethod,
+                                                              aHasPendingNewThebesContent,
+                                                              (float)aDisplayPort.x,
+                                                              (float)aDisplayPort.y,
+                                                              (float)aDisplayPort.width,
+                                                              (float)aDisplayPort.height,
+                                                              aDisplayResolution);
     if (jniFrame.CheckForException())
         return false;
 
-    return ret;
+    NS_ABORT_IF_FALSE(progressiveUpdateDataJObj, "No progressive update data!");
+
+    AndroidProgressiveUpdateData progressiveUpdateData(progressiveUpdateDataJObj);
+
+    aViewport.x = progressiveUpdateData.GetX(env);
+    aViewport.y = progressiveUpdateData.GetY(env);
+    aViewport.width = progressiveUpdateData.GetWidth(env);
+    aViewport.height = progressiveUpdateData.GetHeight(env);
+    aScaleX = aScaleY = progressiveUpdateData.GetScale(env);
+
+    return progressiveUpdateData.GetShouldAbort(env);
 }
 
 jobject ConvertToJavaViewportMetrics(JNIEnv* env, nsIAndroidViewport* metrics) {
     float x, y, width, height,
         pageLeft, pageTop, pageRight, pageBottom,
         cssPageLeft, cssPageTop, cssPageRight, cssPageBottom,
         zoom;
     metrics->GetX(&x);
@@ -1075,16 +1123,64 @@ AndroidViewTransform::GetY(JNIEnv *env)
 float
 AndroidViewTransform::GetScale(JNIEnv *env)
 {
     if (!env)
         return 0.0f;
     return env->GetFloatField(wrapped_obj, jScaleField);
 }
 
+float
+AndroidProgressiveUpdateData::GetX(JNIEnv *env)
+{
+    if (!env)
+        return 0.0f;
+    return env->GetFloatField(wrapped_obj, jXField);
+}
+
+float
+AndroidProgressiveUpdateData::GetY(JNIEnv *env)
+{
+    if (!env)
+        return 0.0f;
+    return env->GetFloatField(wrapped_obj, jYField);
+}
+
+float
+AndroidProgressiveUpdateData::GetWidth(JNIEnv *env)
+{
+    if (!env)
+        return 0.0f;
+    return env->GetFloatField(wrapped_obj, jWidthField);
+}
+
+float
+AndroidProgressiveUpdateData::GetHeight(JNIEnv *env)
+{
+    if (!env)
+        return 0.0f;
+    return env->GetFloatField(wrapped_obj, jHeightField);
+}
+
+float
+AndroidProgressiveUpdateData::GetScale(JNIEnv *env)
+{
+    if (!env)
+        return 0.0f;
+    return env->GetFloatField(wrapped_obj, jScaleField);
+}
+
+bool
+AndroidProgressiveUpdateData::GetShouldAbort(JNIEnv *env)
+{
+    if (!env)
+        return false;
+    return env->GetBooleanField(wrapped_obj, jShouldAbortField);
+}
+
 void
 AndroidRect::Init(JNIEnv *jenv, jobject jobj)
 {
     NS_ASSERTION(wrapped_obj == nullptr, "Init called on non-null wrapped_obj!");
 
     wrapped_obj = jobj;
 
     if (jobj) {
--- a/widget/android/AndroidJavaWrappers.h
+++ b/widget/android/AndroidJavaWrappers.h
@@ -195,16 +195,42 @@ public:
 
 private:
     static jclass jViewTransformClass;
     static jfieldID jXField;
     static jfieldID jYField;
     static jfieldID jScaleField;
 };
 
+class AndroidProgressiveUpdateData : public WrappedJavaObject {
+public:
+    static void InitProgressiveUpdateDataClass(JNIEnv *jEnv);
+
+    void Init(jobject jobj);
+
+    AndroidProgressiveUpdateData() {}
+    AndroidProgressiveUpdateData(jobject jobj) { Init(jobj); }
+
+    float GetX(JNIEnv *env);
+    float GetY(JNIEnv *env);
+    float GetWidth(JNIEnv *env);
+    float GetHeight(JNIEnv *env);
+    float GetScale(JNIEnv *env);
+    bool GetShouldAbort(JNIEnv *env);
+
+private:
+    static jclass jProgressiveUpdateDataClass;
+    static jfieldID jXField;
+    static jfieldID jYField;
+    static jfieldID jWidthField;
+    static jfieldID jHeightField;
+    static jfieldID jScaleField;
+    static jfieldID jShouldAbortField;
+};
+
 class AndroidLayerRendererFrame : public WrappedJavaObject {
 public:
     static void InitLayerRendererFrameClass(JNIEnv *jEnv);
 
     void Init(JNIEnv *env, jobject jobj);
     void Dispose(JNIEnv *env);
 
     bool BeginDrawing(AutoLocalJNIFrame *jniFrame);
@@ -228,32 +254,32 @@ public:
 
     AndroidGeckoLayerClient() {}
     AndroidGeckoLayerClient(jobject jobj) { Init(jobj); }
 
     void SetFirstPaintViewport(const nsIntPoint& aOffset, float aZoom, const nsIntRect& aPageRect, const gfx::Rect& aCssPageRect);
     void SetPageRect(const gfx::Rect& aCssPageRect);
     void SyncViewportInfo(const nsIntRect& aDisplayPort, float aDisplayResolution, bool aLayersUpdated,
                           nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY);
-    bool ShouldAbortProgressiveUpdate(bool aHasPendingNewThebesContent, const gfx::Rect& aDisplayPort, float aDisplayResolution);
+    bool ProgressiveUpdateCallback(bool aHasPendingNewThebesContent, const gfx::Rect& aDisplayPort, float aDisplayResolution, gfx::Rect& aViewport, float& aScaleX, float& aScaleY);
     bool CreateFrame(AutoLocalJNIFrame *jniFrame, AndroidLayerRendererFrame& aFrame);
     bool ActivateProgram(AutoLocalJNIFrame *jniFrame);
     bool DeactivateProgram(AutoLocalJNIFrame *jniFrame);
     void GetDisplayPort(AutoLocalJNIFrame *jniFrame, bool aPageSizeUpdate, bool aIsBrowserContentDisplayed, int32_t tabId, nsIAndroidViewport* metrics, nsIAndroidDisplayport** displayPort);
 
 protected:
     static jclass jGeckoLayerClientClass;
     static jmethodID jSetFirstPaintViewport;
     static jmethodID jSetPageRect;
     static jmethodID jSyncViewportInfoMethod;
     static jmethodID jCreateFrameMethod;
     static jmethodID jActivateProgramMethod;
     static jmethodID jDeactivateProgramMethod;
     static jmethodID jGetDisplayPort;
-    static jmethodID jShouldAbortProgressiveUpdate;
+    static jmethodID jProgressiveUpdateCallbackMethod;
 
 public:
     static jclass jViewportClass;
     static jclass jDisplayportClass;
     static jmethodID jViewportCtor;
     static jfieldID jDisplayportPosition;
     static jfieldID jDisplayportResolution;
 };