Stand up async panning and zooming
authorPatrick Walton <pwalton@mozilla.com>
Fri, 03 Feb 2012 23:31:05 -0800
changeset 92386 d678113069ca2b8d5b9e96596576cc4526d6a049
parent 92385 e1f70b352cad41eaf7377f6566ab6723e7c59a47
child 92387 d721b3df0c6bdd8e0ea83ec816b94453d13ca49b
push id886
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 19:57:52 +0000
treeherdermozilla-beta@bbd8d5efd6d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone12.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
Stand up async panning and zooming
gfx/layers/ipc/CompositorParent.cpp
gfx/layers/ipc/CompositorParent.h
mobile/android/base/gfx/GeckoGLLayerClient.java
mobile/android/base/gfx/GeckoLayerClient.java
mobile/android/base/gfx/GeckoSoftwareLayerClient.java
mobile/android/base/gfx/VirtualLayer.java
mobile/android/chrome/content/browser.js
widget/android/AndroidBridge.cpp
widget/android/AndroidBridge.h
widget/android/AndroidJavaWrappers.cpp
widget/android/AndroidJavaWrappers.h
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -77,20 +77,20 @@ CompositorParent::RecvStop()
 {
   mStopped = true;
   Destroy();
   return true;
 }
 
 
 void
-CompositorParent::ScheduleCompositionOnCompositorThread(::base::Thread &aCompositorThread)
+CompositorParent::ScheduleRenderOnCompositorThread(::base::Thread &aCompositorThread)
 {
-  CancelableTask *composeTask = NewRunnableMethod(this, &CompositorParent::Composite);
-  aCompositorThread.message_loop()->PostTask(FROM_HERE, composeTask);
+  CancelableTask *renderTask = NewRunnableMethod(this, &CompositorParent::AsyncRender);
+  aCompositorThread.message_loop()->PostTask(FROM_HERE, renderTask);
 }
 
 void
 CompositorParent::ScheduleComposition()
 {
   printf_stderr("Schedule composition\n");
   CancelableTask *composeTask = NewRunnableMethod(this, &CompositorParent::Composite);
   MessageLoop::current()->PostTask(FROM_HERE, composeTask);
@@ -218,54 +218,66 @@ CompositorParent::TransformShadowTree(La
 void
 CompositorParent::AsyncRender()
 {
   if (mStopped || !mLayerManager) {
     return;
   }
 
   Layer* root = mLayerManager->GetRoot();
+
+/*
   ContainerLayer* container = root->AsContainerLayer();
   if (!container)
     return;
 
   FrameMetrics metrics = container->GetFrameMetrics();
-/*
     printf("FrameMetrics: mViewPort: X: %d, Y: %d, Width: %d, Height: %d ",
             metrics.mViewport.X(), metrics.mViewport.Y(), metrics.mViewport.Width(),
             metrics.mViewport.Height());
     printf("mDisplayPort: X: %d, Y: %d, Width: %d, Height: %d ",
             metrics.mDisplayPort.X(), metrics.mDisplayPort.Y(), metrics.mDisplayPort.Width(),
             metrics.mDisplayPort.Height());
     printf("mContentSize: width: %d, height: %d ", metrics.mContentSize.width,
            metrics. mContentSize.height);
     printf("mViewPortScrollOffset: x: %d, y: %d\n",
             metrics.mViewportScrollOffset.x,
             metrics.mViewportScrollOffset.y);
-*/
     // Modify framemetrics here, just as a test.
   metrics.mScrollId = FrameMetrics::ROOT_SCROLL_ID;
   container->SetFrameMetrics(metrics);
+*/
 
 #ifdef MOZ_WIDGET_ANDROID
   RequestViewTransform();
 #endif
 
+  gfx3DMatrix worldTransform;
+  gfxPoint3D offset(-mScrollOffset.x, -mScrollOffset.y, 0.0f);
+  worldTransform.Translate(offset);
+  worldTransform.Scale(mXScale, mYScale, 1.0f);
+  root->AsShadowLayer()->SetShadowTransform(worldTransform);
+
+#if 0
   ViewTransform transform;
   TransformShadowTree(root, transform);
+#endif
 
   Composite();
 }
 
 #ifdef MOZ_WIDGET_ANDROID
 void
 CompositorParent::RequestViewTransform()
 {
-  mozilla::AndroidBridge::Bridge()->GetViewTransform(mScrollOffset, mXScale,
-                                                     mYScale);
+  mozilla::AndroidBridge::Bridge()->GetViewTransform(mScrollOffset, mXScale, mYScale);
+
+  __android_log_print(ANDROID_LOG_ERROR, "Gecko", "### mScrollOffset=%g %g "
+                      "mXScale=%g mYScale=%g", (float)mScrollOffset.x, (float)mScrollOffset.y,
+                      (float)mXScale, (float)mYScale);
 }
 #endif
 
 void
 CompositorParent::ShadowLayersUpdated()
 {
   printf_stderr("ShadowLayersUpdated\n");
   const nsTArray<PLayersParent*>& shadowParents = ManagedPLayersParent();
--- a/gfx/layers/ipc/CompositorParent.h
+++ b/gfx/layers/ipc/CompositorParent.h
@@ -85,17 +85,17 @@ public:
 
   virtual void ShadowLayersUpdated() MOZ_OVERRIDE;
   void Destroy();
 
   LayerManager* GetLayerManager() { return mLayerManager; }
 
   void SetTransformation(float aScale, nsIntPoint aScrollOffset);
   void AsyncRender();
-  void ScheduleCompositionOnCompositorThread(::base::Thread &aCompositorThread);
+  void ScheduleRenderOnCompositorThread(::base::Thread &aCompositorThread);
 
 protected:
   virtual PLayersParent* AllocPLayers(const LayersBackend &backendType);
   virtual bool DeallocPLayers(PLayersParent* aLayers);
 
 private:
   void ScheduleComposition();
   void Composite();
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/GeckoGLLayerClient.java
@@ -0,0 +1,197 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoEvent;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.View;
+
+public class GeckoGLLayerClient extends GeckoLayerClient
+                                implements FlexibleGLSurfaceView.Listener, VirtualLayer.Listener {
+    private static final String LOGTAG = "GeckoGLLayerClient";
+
+    public GeckoGLLayerClient(Context context) {
+        super(context);
+    }
+
+    @Override
+    public boolean beginDrawing(int width, int height, int tileWidth, int tileHeight,
+                                String metadata, boolean hasDirectTexture) {
+        if (!super.beginDrawing(width, height, tileWidth, tileHeight, metadata,
+                                hasDirectTexture)) {
+            return false;
+        }
+
+        // Be sure to adjust the buffer size; if it's not at least as large as the viewport size,
+        // ViewportMetrics.getOptimumViewportOffset() gets awfully confused and severe display
+        // corruption results!
+        if (mBufferSize.width != width || mBufferSize.height != height) {
+            mBufferSize = new IntSize(width, height);
+        }
+
+        return true;
+    }
+
+    @Override
+    protected boolean handleDirectTextureChange(boolean hasDirectTexture) {
+        Log.e(LOGTAG, "### handleDirectTextureChange");
+        if (mTileLayer != null) {
+            return false;
+        }
+
+        Log.e(LOGTAG, "### Creating virtual layer");
+        VirtualLayer virtualLayer = new VirtualLayer();
+        virtualLayer.setListener(this);
+        virtualLayer.setSize(getBufferSize());
+        getLayerController().setRoot(virtualLayer);
+        mTileLayer = virtualLayer;
+
+        sendResizeEventIfNecessary(true);
+        return true;
+    }
+
+    @Override
+    public void setLayerController(LayerController layerController) {
+        super.setLayerController(layerController);
+
+        ((FlexibleGLSurfaceView)layerController.getView()).setListener(this);
+    }
+
+    @Override
+    protected boolean shouldDrawProceed(int tileWidth, int tileHeight) {
+        Log.e(LOGTAG, "### shouldDrawProceed");
+        // Always draw.
+        return true;
+    }
+
+    @Override
+    protected void updateLayerAfterDraw(Rect updatedRect) {
+        Log.e(LOGTAG, "### updateLayerAfterDraw");
+        // Nothing to do.
+    }
+
+    @Override
+    protected IntSize getBufferSize() {
+        View view = (View)getLayerController().getView();
+        IntSize size = new IntSize(view.getWidth(), view.getHeight());
+        Log.e(LOGTAG, "### getBufferSize " + size);
+        return size;
+    }
+
+    @Override
+    protected IntSize getTileSize() {
+        Log.e(LOGTAG, "### getTileSize " + getBufferSize());
+        return getBufferSize();
+    }
+
+    @Override
+    protected void tileLayerUpdated() {
+        // Set the new origin and resolution instantly.
+        mTileLayer.performUpdates(null, null);
+    }
+
+    @Override
+    public Bitmap getBitmap() {
+        Log.e(LOGTAG, "### getBitmap");
+        IntSize size = getBufferSize();
+        return Bitmap.createBitmap(size.width, size.height, Bitmap.Config.RGB_565);
+    }
+
+    @Override
+    public int getType() {
+        Log.e(LOGTAG, "### getType");
+        return LAYER_CLIENT_TYPE_GL;
+    }
+
+    public void dimensionsChanged(Point newOrigin, float newResolution) {
+        Log.e(LOGTAG, "### dimensionsChanged " + newOrigin + " " + newResolution);
+    }
+
+    /* Informs Gecko that the screen size has changed. */
+    @Override
+    protected void sendResizeEventIfNecessary(boolean force) {
+        Log.e(LOGTAG, "### sendResizeEventIfNecessary " + force);
+
+        IntSize newSize = getBufferSize();
+        if (!force && mScreenSize != null && mScreenSize.equals(newSize)) {
+            return;
+        }
+
+        mScreenSize = newSize;
+
+        Log.e(LOGTAG, "### Screen-size changed to " + mScreenSize);
+        GeckoEvent event = new GeckoEvent(GeckoEvent.SIZE_CHANGED,
+                                          mScreenSize.width, mScreenSize.height,
+                                          mScreenSize.width, mScreenSize.height,
+                                          mScreenSize.width, mScreenSize.height);
+        GeckoAppShell.sendEventToGecko(event);
+    }
+
+    /** For Gecko to use. */
+    public ViewTransform getViewTransform() {
+        Log.e(LOGTAG, "### getViewTransform()");
+
+        // NB: We don't begin a transaction here because this can be called in a synchronous
+        // manner between beginDrawing() and endDrawing(), and that will cause a deadlock.
+
+        LayerController layerController = getLayerController();
+        synchronized (layerController) {
+            ViewportMetrics viewportMetrics = layerController.getViewportMetrics();
+            PointF viewportOrigin = viewportMetrics.getOrigin();
+            Point tileOrigin = mTileLayer.getOrigin();
+            float scrollX = viewportOrigin.x - tileOrigin.x;
+            float scrollY = viewportOrigin.y - tileOrigin.y;
+            float zoomFactor = viewportMetrics.getZoomFactor() / mTileLayer.getResolution();
+            Log.e(LOGTAG, "### Viewport metrics = " + viewportMetrics + " tile reso = " +
+                  mTileLayer.getResolution());
+            return new ViewTransform(scrollX, scrollY, zoomFactor);
+        }
+    }
+
+    public void renderRequested() {
+        Log.e(LOGTAG, "### Render requested, scheduling composite");
+        GeckoAppShell.scheduleComposite();
+    }
+}
+
--- a/mobile/android/base/gfx/GeckoLayerClient.java
+++ b/mobile/android/base/gfx/GeckoLayerClient.java
@@ -69,17 +69,17 @@ public abstract class GeckoLayerClient e
     protected Layer mTileLayer;
 
     /* The viewport that Gecko is currently displaying. */
     protected ViewportMetrics mGeckoViewport;
 
     /* The viewport that Gecko will display when drawing is finished */
     protected ViewportMetrics mNewGeckoViewport;
 
-    private static final long MIN_VIEWPORT_CHANGE_DELAY = 15L;
+    private static final long MIN_VIEWPORT_CHANGE_DELAY = 200L;
     private long mLastViewportChangeTime;
     private boolean mPendingViewportAdjust;
     private boolean mViewportSizeChanged;
 
     // mUpdateViewportOnEndDraw is used to indicate that we received a
     // viewport update notification while drawing. therefore, when the
     // draw finishes, we need to update the entire viewport rather than
     // just the page size. this boolean should always be accessed from
@@ -91,16 +91,17 @@ public abstract class GeckoLayerClient e
     /* Used by robocop for testing purposes */
     private DrawListener mDrawListener;
 
     protected abstract boolean handleDirectTextureChange(boolean hasDirectTexture);
     protected abstract boolean shouldDrawProceed(int tileWidth, int tileHeight);
     protected abstract void updateLayerAfterDraw(Rect updatedRect);
     protected abstract IntSize getBufferSize();
     protected abstract IntSize getTileSize();
+    protected abstract void tileLayerUpdated();
     public abstract Bitmap getBitmap();
     public abstract int getType();
 
     public GeckoLayerClient(Context context) {
         mScreenSize = new IntSize(0, 0);
         mBufferSize = new IntSize(0, 0);
     }
 
@@ -135,16 +136,18 @@ public abstract class GeckoLayerClient e
             Log.e(LOGTAG, "### Cancelling draw due to shouldDrawProceed()");
             return false;
         }
 
         try {
             JSONObject viewportObject = new JSONObject(metadata);
             mNewGeckoViewport = new ViewportMetrics(viewportObject);
 
+            Log.e(LOGTAG, "### beginDrawing new Gecko viewport " + mNewGeckoViewport);
+
             // Update the background color, if it's present.
             String backgroundColorString = viewportObject.optString("backgroundColor");
             if (backgroundColorString != null) {
                 LayerController controller = getLayerController();
                 controller.setCheckerboardColor(parseColorFromGecko(backgroundColorString));
             }
         } catch (JSONException e) {
             Log.e(LOGTAG, "Aborting draw, bad viewport description: " + metadata);
@@ -188,16 +191,20 @@ public abstract class GeckoLayerClient e
         mGeckoViewport = mNewGeckoViewport;
         mGeckoViewport.setSize(viewportSize);
 
         LayerController controller = getLayerController();
         PointF displayportOrigin = mGeckoViewport.getDisplayportOrigin();
         mTileLayer.setOrigin(PointUtils.round(displayportOrigin));
         mTileLayer.setResolution(mGeckoViewport.getZoomFactor());
 
+        this.tileLayerUpdated();
+        Log.e(LOGTAG, "### updateViewport onlyUpdatePageSize=" + onlyUpdatePageSize +
+              " getTileViewport " + mGeckoViewport);
+
         if (onlyUpdatePageSize) {
             // Don't adjust page size when zooming unless zoom levels are
             // approximately equal.
             if (FloatUtils.fuzzyEquals(controller.getZoomFactor(),
                     mGeckoViewport.getZoomFactor()))
                 controller.setPageSize(mGeckoViewport.getPageSize());
         } else {
             controller.setViewportMetrics(mGeckoViewport);
@@ -299,22 +306,24 @@ public abstract class GeckoLayerClient e
             GeckoAppShell.viewSizeChanged();
         }
 
         mLastViewportChangeTime = System.currentTimeMillis();
     }
 
     public void handleMessage(String event, JSONObject message) {
         if ("Viewport:UpdateAndDraw".equals(event)) {
+            Log.e(LOGTAG, "### Java side Viewport:UpdateAndDraw()!");
             mUpdateViewportOnEndDraw = true;
 
             // Redraw everything.
             Rect rect = new Rect(0, 0, mBufferSize.width, mBufferSize.height);
             GeckoAppShell.sendEventToGecko(new GeckoEvent(GeckoEvent.DRAW, rect));
         } else if ("Viewport:UpdateLater".equals(event)) {
+            Log.e(LOGTAG, "### Java side Viewport:UpdateLater()!");
             mUpdateViewportOnEndDraw = true;
         }
     }
 
     @Override
     public void geometryChanged() {
         /* Let Gecko know if the screensize has changed */
         sendResizeEventIfNecessary();
--- a/mobile/android/base/gfx/GeckoSoftwareLayerClient.java
+++ b/mobile/android/base/gfx/GeckoSoftwareLayerClient.java
@@ -240,16 +240,21 @@ public class GeckoSoftwareLayerClient ex
                 // Progress the buffer to the next tile
                 tileBuffer.position(TILE_SIZE.getArea() * bpp);
                 tileBuffer = tileBuffer.slice();
             }
         }
     }
 
     @Override
+    protected void tileLayerUpdated() {
+        /* No-op. */
+    }
+
+    @Override
     public Bitmap getBitmap() {
         if (mTileLayer == null)
             return null;
 
         // Begin a tile transaction, otherwise the buffer can be destroyed while
         // we're reading from it.
         beginTransaction(mTileLayer);
         try {
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/VirtualLayer.java
@@ -0,0 +1,81 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *   Chris Lord <chrislord.net@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import android.graphics.Point;
+import javax.microedition.khronos.opengles.GL10;
+
+public class VirtualLayer extends Layer {
+    private Listener mListener;
+    private IntSize mSize;
+
+    public void setListener(Listener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public void draw(RenderContext context) {
+        // No-op.
+    }
+
+    @Override
+    public IntSize getSize() {
+        return mSize;
+    }
+
+    public void setSize(IntSize size) {
+        mSize = size;
+    }
+
+    @Override
+    protected boolean performUpdates(GL10 gl, RenderContext context) {
+        boolean dimensionsChanged = dimensionChangesPending();
+        boolean result = super.performUpdates(gl, context);
+        if (dimensionsChanged && mListener != null) {
+            mListener.dimensionsChanged(getOrigin(), getResolution());
+        }
+
+        return result;
+    }
+
+    public interface Listener {
+        void dimensionsChanged(Point newOrigin, float newResolution);
+    }
+}
+
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -1545,16 +1545,17 @@ Tab.prototype = {
 
     // If we've been asked to over-scroll, do it via the transformation
     // and store it separately to the viewport.
     let excessX = aViewport.x - win.scrollX;
     let excessY = aViewport.y - win.scrollY;
 
     this._viewport.width = gScreenWidth = aViewport.width;
     this._viewport.height = gScreenHeight = aViewport.height;
+    dump("### gScreenWidth = " + gScreenWidth + "\n");
 
     let transformChanged = false;
 
     if ((aViewport.offsetX != this._viewport.offsetX) ||
         (excessX != this.viewportExcess.x)) {
       this._viewport.offsetX = aViewport.offsetX;
       this.viewportExcess.x = excessX;
       transformChanged = true;
@@ -1659,16 +1660,18 @@ Tab.prototype = {
         this._viewport.pageHeight = pageHeight;
       }
     }
 
     return this._viewport;
   },
 
   updateViewport: function(aReset, aZoomLevel) {
+    dump("### JS side updateViewport " + aReset + " zoom " + aZoomLevel + "\n");
+
     if (!aZoomLevel)
       aZoomLevel = this.getDefaultZoomLevel();
 
     let win = this.browser.contentWindow;
     let zoom = (aReset ? aZoomLevel : this._viewport.zoom);
     let xpos = ((aReset && win) ? win.scrollX * zoom : this._viewport.x);
     let ypos = ((aReset && win) ? win.scrollY * zoom : this._viewport.y);
 
@@ -2066,16 +2069,17 @@ Tab.prototype = {
   },
 
   getDefaultZoomLevel: function getDefaultZoomLevel() {
     let md = this.metadata;
     if ("defaultZoom" in md && md.defaultZoom)
       return md.defaultZoom;
 
     let browserWidth = parseInt(this.browser.style.width);
+    dump("### getDefaultZoomLevel gScreenWidth=" + gScreenWidth);
     return gScreenWidth / browserWidth;
   },
 
   getPageZoomLevel: function getPageZoomLevel() {
     // This may get called during a Viewport:Change message while the document
     // has not loaded yet.
     if (!this.browser.contentDocument || !this.browser.contentDocument.body)
       return 1.0;
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -1860,29 +1860,40 @@ AndroidBridge::SetCompositorParent(mozil
     mCompositorParent = aCompositorParent;
     mCompositorThread = aCompositorThread;
 }
 
 void
 AndroidBridge::ScheduleComposite()
 {
     if (mCompositorParent) {
-        mCompositorParent->ScheduleCompositionOnCompositorThread(*mCompositorThread);
+        mCompositorParent->ScheduleRenderOnCompositorThread(*mCompositorThread);
     }
 }
 
 void
+AndroidBridge::SetViewTransformGetter(AndroidViewTransformGetter& aViewTransformGetter)
+{
+    __android_log_print(ANDROID_LOG_ERROR, "Gecko", "### SetViewTransformGetter()");
+    mViewTransformGetter = &aViewTransformGetter;
+}
+
+void
 AndroidBridge::GetViewTransform(nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY)
 {
-    __android_log_print(ANDROID_LOG_ERROR, "Gecko", "### GetViewTransform() TODO");
+    __android_log_print(ANDROID_LOG_ERROR, "Gecko", "### GetViewTransform()");
+    if (mViewTransformGetter) {
+        (*mViewTransformGetter)(aScrollOffset, aScaleX, aScaleY);
+    }
 }
 
 AndroidBridge::AndroidBridge()
 : mLayerClient(NULL)
 , mLayerClientType(0)
+, mViewTransformGetter(NULL)
 {
 }
 
 AndroidBridge::~AndroidBridge()
 {
 }
 
 /* Implementation file */
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -398,16 +398,17 @@ public:
 
     void GetCurrentNetworkInformation(hal::NetworkInformation* aNetworkInfo);
     void EnableNetworkNotifications();
     void DisableNetworkNotifications();
 
     void SetCompositorParent(mozilla::layers::CompositorParent* aCompositorParent,
                              base::Thread* aCompositorThread);
     void ScheduleComposite();
+    void SetViewTransformGetter(AndroidViewTransformGetter& aViewTransformGetter);
     void GetViewTransform(nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY);
 
 protected:
     static AndroidBridge *sBridge;
 
     // the global JavaVM
     JavaVM *mJavaVM;
 
@@ -418,16 +419,17 @@ protected:
     // the GeckoSurfaceView
     AndroidGeckoSurfaceView mSurfaceView;
 
     AndroidGeckoLayerClient *mLayerClient;
     int mLayerClientType;
 
     nsRefPtr<mozilla::layers::CompositorParent> mCompositorParent;
     base::Thread *mCompositorThread;
+    AndroidViewTransformGetter *mViewTransformGetter;
 
     // the GeckoAppShell java class
     jclass mGeckoAppShellClass;
 
     AndroidBridge();
     ~AndroidBridge();
 
     bool Init(JNIEnv *jEnv, jclass jGeckoApp);
--- a/widget/android/AndroidJavaWrappers.cpp
+++ b/widget/android/AndroidJavaWrappers.cpp
@@ -113,16 +113,22 @@ jmethodID AndroidGeckoLayerClient::jBegi
 jmethodID AndroidGeckoLayerClient::jEndDrawingMethod = 0;
 
 jclass AndroidGeckoSoftwareLayerClient::jGeckoSoftwareLayerClientClass = 0;
 jmethodID AndroidGeckoSoftwareLayerClient::jLockBufferMethod = 0;
 jmethodID AndroidGeckoSoftwareLayerClient::jUnlockBufferMethod = 0;
 jmethodID AndroidGeckoSoftwareLayerClient::jGetRenderOffsetMethod = 0;
 
 jclass AndroidGeckoGLLayerClient::jGeckoGLLayerClientClass = 0;
+jmethodID AndroidGeckoGLLayerClient::jGetViewTransformMethod = 0;
+
+jclass AndroidViewTransform::jViewTransformClass = 0;
+jfieldID AndroidViewTransform::jXField = 0;
+jfieldID AndroidViewTransform::jYField = 0;
+jfieldID AndroidViewTransform::jScaleField = 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;
@@ -146,16 +152,18 @@ mozilla::InitAndroidJavaWrappers(JNIEnv 
 {
     AndroidGeckoEvent::InitGeckoEventClass(jEnv);
     AndroidPoint::InitPointClass(jEnv);
     AndroidLocation::InitLocationClass(jEnv);
     AndroidAddress::InitAddressClass(jEnv);
     AndroidRect::InitRectClass(jEnv);
     AndroidGeckoLayerClient::InitGeckoLayerClientClass(jEnv);
     AndroidGeckoSoftwareLayerClient::InitGeckoSoftwareLayerClientClass(jEnv);
+    AndroidGeckoGLLayerClient::InitGeckoGLLayerClientClass(jEnv);
+    AndroidViewTransform::InitViewTransformClass(jEnv);
     AndroidGeckoSurfaceView::InitGeckoSurfaceViewClass(jEnv);
 }
 
 void
 AndroidGeckoEvent::InitGeckoEventClass(JNIEnv *jEnv)
 {
     initInit();
 
@@ -355,16 +363,33 @@ AndroidGeckoSoftwareLayerClient::InitGec
 
 void
 AndroidGeckoGLLayerClient::InitGeckoGLLayerClientClass(JNIEnv *jEnv)
 {
 #ifdef MOZ_JAVA_COMPOSITOR
     initInit();
 
     jGeckoGLLayerClientClass = getClassGlobalRef("org/mozilla/gecko/gfx/GeckoGLLayerClient");
+
+    jGetViewTransformMethod = getMethod("getViewTransform",
+                                        "()Lorg/mozilla/gecko/gfx/ViewTransform;");
+#endif
+}
+
+void
+AndroidViewTransform::InitViewTransformClass(JNIEnv *jEnv)
+{
+#ifdef MOZ_JAVA_COMPOSITOR
+    initInit();
+
+    jViewTransformClass = getClassGlobalRef("org/mozilla/gecko/gfx/ViewTransform");
+
+    jXField = getField("x", "F");
+    jYField = getField("y", "F");
+    jScaleField = getField("scale", "F");
 #endif
 }
 
 #undef initInit
 #undef initClassGlobalRef
 #undef getField
 #undef getMethod
 
@@ -620,16 +645,26 @@ AndroidGeckoSoftwareLayerClient::Init(jo
     wrapped_obj = jobj;
 }
 
 void
 AndroidGeckoGLLayerClient::Init(jobject jobj)
 {
     NS_ASSERTION(wrapped_obj == nsnull, "Init called on non-null wrapped_obj!");
     wrapped_obj = jobj;
+
+    // Register the view transform getter.
+    AndroidBridge::Bridge()->SetViewTransformGetter(mViewTransformGetter);
+}
+
+void
+AndroidViewTransform::Init(jobject jobj)
+{
+    NS_ABORT_IF_FALSE(wrapped_obj == nsnull, "Init called on non-null wrapped_obj!");
+    wrapped_obj = jobj;
 }
 
 void
 AndroidGeckoSurfaceView::Init(jobject jobj)
 {
     NS_ASSERTION(wrapped_obj == nsnull, "Init called on non-null wrapped_obj!");
 
     wrapped_obj = jobj;
@@ -790,16 +825,70 @@ AndroidGeckoSurfaceView::GetSurface()
 
 jobject
 AndroidGeckoSurfaceView::GetSurfaceHolder()
 {
     return GetJNIForThread()->CallObjectMethod(wrapped_obj, jGetHolderMethod);
 }
 
 void
+AndroidGeckoGLLayerClient::GetViewTransform(AndroidViewTransform& aViewTransform)
+{
+    JNIEnv *env = GetJNIForThread();
+    NS_ABORT_IF_FALSE(env, "No JNI environment at GetViewTransform()!");
+    if (!env) {
+        return;
+    }
+
+    jobject viewTransformJObj = env->CallObjectMethod(wrapped_obj, jGetViewTransformMethod);
+    NS_ABORT_IF_FALSE(viewTransformJObj, "No view transform object!");
+    aViewTransform.Init(viewTransformJObj);
+}
+
+float
+AndroidViewTransform::GetX()
+{
+    JNIEnv *env = GetJNIForThread();
+    if (!env)
+        return 0.0f;
+    return env->GetFloatField(wrapped_obj, jXField);
+}
+
+float
+AndroidViewTransform::GetY()
+{
+    JNIEnv *env = GetJNIForThread();
+    if (!env)
+        return 0.0f;
+    return env->GetFloatField(wrapped_obj, jYField);
+}
+
+float
+AndroidViewTransform::GetScale()
+{
+    JNIEnv *env = GetJNIForThread();
+    if (!env)
+        return 0.0f;
+    return env->GetFloatField(wrapped_obj, jScaleField);
+}
+
+void
+AndroidGeckoGLLayerClientViewTransformGetter::operator()(nsIntPoint& aScrollOffset, float& aScaleX,
+                                                         float& aScaleY)
+{
+    AndroidViewTransform viewTransform;
+
+    AndroidBridge::AutoLocalJNIFrame jniFrame(GetJNIForThread());
+    mLayerClient.GetViewTransform(viewTransform);
+
+    aScrollOffset = nsIntPoint(viewTransform.GetX(), viewTransform.GetY());
+    aScaleX = aScaleY = viewTransform.GetScale();
+}
+
+void
 AndroidRect::Init(JNIEnv *jenv, jobject jobj)
 {
     NS_ASSERTION(wrapped_obj == nsnull, "Init called on non-null wrapped_obj!");
 
     wrapped_obj = jobj;
 
     if (jobj) {
         mTop = jenv->GetIntField(jobj, jTopField);
--- a/widget/android/AndroidJavaWrappers.h
+++ b/widget/android/AndroidJavaWrappers.h
@@ -53,16 +53,18 @@
 #define ALOG(args...)  __android_log_print(ANDROID_LOG_INFO, "Gecko" , ## args)
 #else
 #define ALOG(args...)
 #endif
 #endif
 
 namespace mozilla {
 
+class AndroidGeckoGLLayerClient;
+
 void InitAndroidJavaWrappers(JNIEnv *jEnv);
 
 /*
  * Note: do not store global refs to any WrappedJavaObject;
  * these are live only during a particular JNI method, as
  * NewGlobalRef is -not- called on the jobject.
  *
  * If this is needed, WrappedJavaObject can be extended to
@@ -188,27 +190,73 @@ private:
     static jclass jGeckoSoftwareLayerClientClass;
     static jmethodID jLockBufferMethod;
     static jmethodID jUnlockBufferMethod;
 
 protected:
     static jmethodID jGetRenderOffsetMethod;
 };
 
+/** A callback that retrieves the view transform. */
+class AndroidViewTransformGetter
+{
+public:
+    virtual void operator()(nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY) = 0;
+};
+
+class AndroidGeckoGLLayerClientViewTransformGetter : public AndroidViewTransformGetter {
+public:
+    AndroidGeckoGLLayerClientViewTransformGetter(AndroidGeckoGLLayerClient& aLayerClient)
+    : mLayerClient(aLayerClient) {}
+
+    virtual void operator()(nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY);
+
+private:
+    AndroidGeckoGLLayerClient& mLayerClient;
+};
+
+class AndroidViewTransform : public WrappedJavaObject {
+public:
+    static void InitViewTransformClass(JNIEnv *jEnv);
+
+    void Init(jobject jobj);
+
+    AndroidViewTransform() {}
+    AndroidViewTransform(jobject jobj) { Init(jobj); }
+
+    float GetX();
+    float GetY();
+    float GetScale();
+
+private:
+    static jclass jViewTransformClass;
+    static jfieldID jXField;
+    static jfieldID jYField;
+    static jfieldID jScaleField;
+};
+
 class AndroidGeckoGLLayerClient : public AndroidGeckoLayerClient {
 public:
     static void InitGeckoGLLayerClientClass(JNIEnv *jEnv);
 
     void Init(jobject jobj);
 
-    AndroidGeckoGLLayerClient() {}
-    AndroidGeckoGLLayerClient(jobject jobj) { Init(jobj); }
+    AndroidGeckoGLLayerClient()
+    : mViewTransformGetter(*this) {}
+
+    AndroidGeckoGLLayerClient(jobject jobj)
+    : mViewTransformGetter(*this) { Init(jobj); }
+
+    void GetViewTransform(AndroidViewTransform& aViewTransform);
 
 private:
     static jclass jGeckoGLLayerClientClass;
+    static jmethodID jGetViewTransformMethod;
+
+    AndroidGeckoGLLayerClientViewTransformGetter mViewTransformGetter;
 };
 
 class AndroidGeckoSurfaceView : public WrappedJavaObject
 {
 public:
     static void InitGeckoSurfaceViewClass(JNIEnv *jEnv);
 
     AndroidGeckoSurfaceView() { }