[Libreoffice-commits] core.git: 7 commits - android/experimental

Tomaž Vajngerl tomaz.vajngerl at collabora.com
Thu Sep 18 14:03:34 PDT 2014


 android/experimental/LOAndroid3/AndroidManifest.xml                                          |    3 
 android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java                    |   34 
 android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitTileProvider.java              |   39 
 android/experimental/LOAndroid3/src/java/org/libreoffice/LibreOfficeMainActivity.java        |   53 
 android/experimental/LOAndroid3/src/java/org/libreoffice/MockTileProvider.java               |    5 
 android/experimental/LOAndroid3/src/java/org/libreoffice/TileProvider.java                   |    5 
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java         |   97 +
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ImmutableViewportMetrics.java |   70 +
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/Layer.java                    |  116 -
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java          |  384 ++---
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java            |  378 +++--
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java                |   60 
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/MultiTileLayer.java           |   66 -
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/NinePatchTileLayer.java       |   99 +
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScreenshotLayer.java          |  253 +++
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScrollbarLayer.java           |  544 ++++----
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/SingleTileLayer.java          |  119 +
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TileLayer.java                |  119 -
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewportMetrics.java          |  173 --
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/VirtualLayer.java             |   80 -
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java         |  653 ++++------
 21 files changed, 1830 insertions(+), 1520 deletions(-)

New commits:
commit f02cef962b4f930eb5637dea1bb9d8c382839286
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Thu Sep 18 22:22:08 2014 +0200

    android: convert to ImmutableViewportMetrics
    
    Change-Id: Idd5e604541577f6b812a971e585cee9b089d2b4b

diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
index c5918e69..c12170f 100644
--- a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
@@ -7,6 +7,7 @@ import android.util.Log;
 
 import org.mozilla.gecko.gfx.FloatSize;
 import org.mozilla.gecko.gfx.GeckoLayerClient;
+import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.gfx.SubTile;
 import org.mozilla.gecko.gfx.ViewportMetrics;
 
@@ -29,9 +30,9 @@ public class LOKitThread extends Thread {
         mInputFile = inputFile;
     }
 
-    RectF normlizeRect(ViewportMetrics metrics) {
+    RectF normlizeRect(ImmutableViewportMetrics metrics) {
         RectF rect = metrics.getViewport();
-        float zoomFactor = metrics.getZoomFactor();
+        float zoomFactor = metrics.zoomFactor;
         return new RectF(rect.left / zoomFactor, rect.top / zoomFactor, rect.right / zoomFactor, rect.bottom / zoomFactor);
     }
 
@@ -68,7 +69,7 @@ public class LOKitThread extends Thread {
         GeckoLayerClient layerClient = mApplication.getLayerClient();
         layerClient.beginDrawing(mViewportMetrics);
 
-        ViewportMetrics metrics = mApplication.getLayerController().getViewportMetrics();
+        ImmutableViewportMetrics metrics = mApplication.getLayerController().getViewportMetrics();
         RectF viewport = normlizeRect(metrics);
         Rect rect = inflate(roundToTileSize(viewport, TILE_SIZE), TILE_SIZE);
 
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
index 1ebb9a1..e7a9059 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
@@ -149,7 +149,7 @@ public class GeckoLayerClient {
         mGeckoViewport = mNewGeckoViewport;
         mGeckoViewport.setSize(viewportSize);
 
-        PointF displayportOrigin = mGeckoViewport.getDisplayportOrigin();
+        PointF displayportOrigin = mGeckoViewport.getOrigin();
         RectF position = mGeckoViewport.getViewport();
         mTileLayer.setPosition(RectUtils.round(position));
         mTileLayer.setResolution(mGeckoViewport.getZoomFactor());
@@ -288,7 +288,7 @@ public class GeckoLayerClient {
 
         viewportMetrics.setViewport(viewportMetrics.getClampedViewport());
 
-        mDisplayPort = calculateDisplayPort(new ImmutableViewportMetrics(mLayerController.getViewportMetrics()));
+        mDisplayPort = calculateDisplayPort(mLayerController.getViewportMetrics());
 
         LOKitShell.sendEvent(LOEvent.viewport(viewportMetrics));
         if (mViewportSizeChanged) {
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java
index 9c497f7..e9d0cb6 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java
@@ -70,7 +70,20 @@ public class LayerController {
     private Layer mRootLayer;                   /* The root layer. */
     private LayerView mView;                    /* The main rendering view. */
     private Context mContext;                   /* The current context. */
-    private ViewportMetrics mViewportMetrics;   /* 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
+     * the compositor as cheap as possible. The viewport is immutable so
+     * we don't need to worry about anyone mutating it while we're reading from it.
+     * Specifically:
+     * 1) reading mViewportMetrics from any thread is fine without synchronization
+     * 2) writing to mViewportMetrics requires synchronizing on the layer controller object
+     * 3) whenver reading multiple fields from mViewportMetrics without synchronization (i.e. in
+     *    case 1 above) you should always frist grab a local copy of the reference, and then use
+     *    that because mViewportMetrics might get reassigned in between reading the different
+     *    fields. */
+    private volatile ImmutableViewportMetrics mViewportMetrics;   /* The current viewport metrics. */
+
     private boolean mWaitForTouchListeners;
 
     private PanZoomController mPanZoomController;
@@ -84,7 +97,7 @@ public class LayerController {
 
     /* The new color for the checkerboard. */
     private int mCheckerboardColor;
-    private boolean mCheckerboardShouldShowChecks;
+    private boolean mCheckerboardShouldShowChecks = true;
 
     private boolean mForceRedraw;
 
@@ -113,10 +126,9 @@ public class LayerController {
         mContext = context;
 
         mForceRedraw = true;
-        mViewportMetrics = new ViewportMetrics();
+        mViewportMetrics = new ImmutableViewportMetrics(new ViewportMetrics());
         mPanZoomController = new PanZoomController(this);
         mView = new LayerView(context, this);
-        mCheckerboardShouldShowChecks = true;
     }
 
     public void onDestroy() {
@@ -136,7 +148,7 @@ public class LayerController {
     public Layer getRoot()                        { return mRootLayer; }
     public LayerView getView()                    { return mView; }
     public Context getContext()                   { return mContext; }
-    public ViewportMetrics getViewportMetrics()   { return mViewportMetrics; }
+    public ImmutableViewportMetrics getViewportMetrics()   { return mViewportMetrics; }
 
     public RectF getViewport() {
         return mViewportMetrics.getViewport();
@@ -155,7 +167,7 @@ public class LayerController {
     }
 
     public float getZoomFactor() {
-        return mViewportMetrics.getZoomFactor();
+        return mViewportMetrics.zoomFactor;
     }
 
     public Bitmap getBackgroundPattern()    { return getDrawable("background"); }
@@ -185,13 +197,49 @@ public class LayerController {
      * result in an infinite loop.
      */
     public void setViewportSize(FloatSize size) {
+        // Resize the viewport, and modify its zoom factor so that the page retains proportionally
+        // zoomed relative to the screen.
         ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
+        float oldHeight = viewportMetrics.getSize().height;
+        float oldWidth = viewportMetrics.getSize().width;
+        float oldZoomFactor = viewportMetrics.getZoomFactor();
         viewportMetrics.setSize(size);
-        mViewportMetrics = new ViewportMetrics(viewportMetrics);
+
+        // if the viewport got larger (presumably because the vkb went away), and the page
+        // is smaller than the new viewport size, increase the page size so that the panzoomcontroller
+        // doesn't zoom in to make it fit (bug 718270). this page size change is in anticipation of
+        // gecko increasing the page size to match the new viewport size, which will happen the next
+        // time we get a draw update.
+        if (size.width >= oldWidth && size.height >= oldHeight) {
+            FloatSize pageSize = viewportMetrics.getPageSize();
+            if (pageSize.width < size.width || pageSize.height < size.height) {
+                viewportMetrics.setPageSize(new FloatSize(Math.max(pageSize.width, size.width),
+                                                           Math.max(pageSize.height, size.height)));
+            }
+        }
+
+        // For rotations, we want the focus point to be at the top left.
+        boolean rotation = (size.width > oldWidth && size.height < oldHeight) ||
+                           (size.width < oldWidth && size.height > oldHeight);
+        PointF newFocus;
+        if (rotation) {
+            newFocus = new PointF(0, 0);
+        } else {
+            newFocus = new PointF(size.width / 2.0f, size.height / 2.0f);
+        }
+        float newZoomFactor = size.width * oldZoomFactor / oldWidth;
+        viewportMetrics.scaleTo(newZoomFactor, newFocus);
+        mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics);
+
+        setForceRedraw();
 
         if (mLayerClient != null) {
             mLayerClient.viewportSizeChanged();
+            notifyLayerClientOfGeometryChange();
         }
+
+        mPanZoomController.abortAnimation();
+        mView.requestRender();
     }
 
     /** Scrolls the viewport by the given offset. You must hold the monitor while calling this. */
@@ -200,7 +248,7 @@ public class LayerController {
         PointF origin = viewportMetrics.getOrigin();
         origin.offset(point.x, point.y);
         viewportMetrics.setOrigin(origin);
-        mViewportMetrics = new ViewportMetrics(viewportMetrics);
+        mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics);
 
         notifyLayerClientOfGeometryChange();
         mView.requestRender();
@@ -213,7 +261,7 @@ public class LayerController {
 
         ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
         viewportMetrics.setPageSize(size);
-        mViewportMetrics = new ViewportMetrics(viewportMetrics);
+        mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics);
 
         // Page size is owned by the layer client, so no need to notify it of
         // this change.
@@ -233,7 +281,7 @@ public class LayerController {
      * while calling this.
      */
     public void setViewportMetrics(ViewportMetrics viewport) {
-        mViewportMetrics = new ViewportMetrics(viewport);
+        mViewportMetrics = new ImmutableViewportMetrics(viewport);
         Log.d(LOGTAG, "setViewportMetrics: " + mViewportMetrics);
         mView.requestRender();
     }
@@ -245,7 +293,7 @@ public class LayerController {
     public void scaleWithFocus(float zoomFactor, PointF focus) {
         ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
         viewportMetrics.scaleTo(zoomFactor, focus);
-        mViewportMetrics = new ViewportMetrics(viewportMetrics);
+        mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics);
         Log.d(LOGTAG, "scaleWithFocus: " + mViewportMetrics + "; zf=" + zoomFactor);
 
         // We assume the zoom level will only be modified by the
@@ -317,31 +365,26 @@ public class LayerController {
      * Converts a point from layer view coordinates to layer coordinates. In other words, given a
      * point measured in pixels from the top left corner of the layer view, returns the point in
      * pixels measured from the top left corner of the root layer, in the coordinate system of the
-     * layer itself (CSS pixels). This method is used as part of the process of translating touch
-     * events to Gecko's coordinate system.
+     * layer itself. This method is used by the viewport controller as part of the process of
+     * translating touch events to Gecko's coordinate system.
      */
     public PointF convertViewPointToLayerPoint(PointF viewPoint) {
         if (mRootLayer == null)
             return null;
 
-        ViewportMetrics viewportMetrics = mViewportMetrics;
+        ImmutableViewportMetrics viewportMetrics = mViewportMetrics;
+        // Undo the transforms.
         PointF origin = viewportMetrics.getOrigin();
         PointF newPoint = new PointF(origin.x, origin.y);
-        float zoom = viewportMetrics.getZoomFactor();
+        float zoom = viewportMetrics.zoomFactor;
+        viewPoint.x /= zoom;
+        viewPoint.y /= zoom;
+        newPoint.offset(viewPoint.x, viewPoint.y);
 
         Rect rootPosition = mRootLayer.getPosition();
-        float rootScale = mRootLayer.getResolution();
-
-        // viewPoint + origin gives the coordinate in device pixels from the top-left corner of the page.
-        // Divided by zoom, this gives us the coordinate in CSS pixels from the top-left corner of the page.
-        // rootPosition / rootScale is where Gecko thinks it is (scrollTo position) in CSS pixels from
-        // the top-left corner of the page. Subtracting the two gives us the offset of the viewPoint from
-        // the current Gecko coordinate in CSS pixels.
-        PointF layerPoint = new PointF(
-                ((viewPoint.x + origin.x) / zoom) - (rootPosition.left / rootScale),
-                ((viewPoint.y + origin.y) / zoom) - (rootPosition.top / rootScale));
-
-        return layerPoint;
+        newPoint.offset(-rootPosition.left, -rootPosition.top);
+
+        return newPoint;
     }
 
     /*
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java
index 44973bf..6ff9a8a 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java
@@ -286,7 +286,7 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
      * Called whenever a new frame is about to be drawn.
      */
     public void onDrawFrame(GL10 gl) {
-        Frame frame = createFrame(new ImmutableViewportMetrics(mView.getController().getViewportMetrics()));
+        Frame frame = createFrame(mView.getController().getViewportMetrics());
         synchronized (mView.getController()) {
             frame.beginDrawing();
             frame.drawBackground();
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewportMetrics.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewportMetrics.java
index d98b474..8c3004e 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewportMetrics.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewportMetrics.java
@@ -53,14 +53,10 @@ import org.mozilla.gecko.util.FloatUtils;
  */
 public class ViewportMetrics {
     private static final String LOGTAG = "GeckoViewportMetrics";
-    private static final float MAX_BIAS = 0.8f;
+
     private FloatSize mPageSize;
     private RectF mViewportRect;
-    private PointF mViewportOffset;
     private float mZoomFactor;
-    // A scale from -1,-1 to 1,1 that represents what edge of the displayport
-    // we want the viewport to be biased towards.
-    private PointF mViewportBias;
 
     public ViewportMetrics() {
         DisplayMetrics metrics = new DisplayMetrics();
@@ -68,119 +64,52 @@ public class ViewportMetrics {
 
         mPageSize = new FloatSize(metrics.widthPixels, metrics.heightPixels);
         mViewportRect = new RectF(0, 0, metrics.widthPixels, metrics.heightPixels);
-        mViewportOffset = new PointF(0, 0);
         mZoomFactor = 1.0f;
-        mViewportBias = new PointF(0.0f, 0.0f);
     }
 
     public ViewportMetrics(ViewportMetrics viewport) {
         mPageSize = new FloatSize(viewport.getPageSize());
         mViewportRect = new RectF(viewport.getViewport());
-        PointF offset = viewport.getViewportOffset();
-        mViewportOffset = new PointF(offset.x, offset.y);
         mZoomFactor = viewport.getZoomFactor();
-        mViewportBias = viewport.mViewportBias;
     }
 
+    public ViewportMetrics(ImmutableViewportMetrics viewport) {
+        mPageSize = new FloatSize(viewport.pageSizeWidth, viewport.pageSizeHeight);
+        mViewportRect = new RectF(viewport.viewportRectLeft,
+                                  viewport.viewportRectTop,
+                                  viewport.viewportRectRight,
+                                  viewport.viewportRectBottom);
+        mZoomFactor = viewport.zoomFactor;
+    }
+
+
     public ViewportMetrics(JSONObject json) throws JSONException {
-        float x = (float) json.getDouble("x");
-        float y = (float) json.getDouble("y");
-        float width = (float) json.getDouble("width");
-        float height = (float) json.getDouble("height");
-        float pageWidth = (float) json.getDouble("pageWidth");
-        float pageHeight = (float) json.getDouble("pageHeight");
-        float offsetX = (float) json.getDouble("offsetX");
-        float offsetY = (float) json.getDouble("offsetY");
-        float zoom = (float) json.getDouble("zoom");
+        float x = (float)json.getDouble("x");
+        float y = (float)json.getDouble("y");
+        float width = (float)json.getDouble("width");
+        float height = (float)json.getDouble("height");
+        float pageWidth = (float)json.getDouble("pageWidth");
+        float pageHeight = (float)json.getDouble("pageHeight");
+        float zoom = (float)json.getDouble("zoom");
 
         mPageSize = new FloatSize(pageWidth, pageHeight);
         mViewportRect = new RectF(x, y, x + width, y + height);
-        mViewportOffset = new PointF(offsetX, offsetY);
         mZoomFactor = zoom;
-        mViewportBias = new PointF(0.0f, 0.0f);
-    }
-
-    public PointF getOptimumViewportOffset(IntSize displayportSize) {
-        /* XXX Until bug #524925 is fixed, changing the viewport origin will
-         *     cause unnecessary relayouts. This may cause rendering time to
-         *     increase and should be considered.
-         */
-        RectF viewport = getClampedViewport();
-
-        FloatSize bufferSpace = new FloatSize(displayportSize.width - viewport.width(),
-                displayportSize.height - viewport.height());
-        PointF optimumOffset =
-                new PointF(bufferSpace.width * ((mViewportBias.x + 1.0f) / 2.0f),
-                        bufferSpace.height * ((mViewportBias.y + 1.0f) / 2.0f));
-
-        // Make sure this offset won't cause wasted pixels in the displayport
-        // (i.e. make sure the resultant displayport intersects with the page
-        //  as much as possible)
-        if (viewport.left - optimumOffset.x < 0)
-            optimumOffset.x = viewport.left;
-        else if ((bufferSpace.width - optimumOffset.x) + viewport.right > mPageSize.width)
-            optimumOffset.x = bufferSpace.width - (mPageSize.width - viewport.right);
-
-        if (viewport.top - optimumOffset.y < 0)
-            optimumOffset.y = viewport.top;
-        else if ((bufferSpace.height - optimumOffset.y) + viewport.bottom > mPageSize.height)
-            optimumOffset.y = bufferSpace.height - (mPageSize.height - viewport.bottom);
-
-        return new PointF(Math.round(optimumOffset.x), Math.round(optimumOffset.y));
     }
 
     public PointF getOrigin() {
         return new PointF(mViewportRect.left, mViewportRect.top);
     }
 
-    public void setOrigin(PointF origin) {
-        // When the origin is set, we compare it with the last value set and
-        // change the viewport bias accordingly, so that any viewport based
-        // on these metrics will have a larger buffer in the direction of
-        // movement.
-
-        // XXX Note the comment about bug #524925 in getOptimumViewportOffset.
-        //     Ideally, the viewport bias would be a sliding scale, but we
-        //     don't want to change it too often at the moment.
-        if (FloatUtils.fuzzyEquals(origin.x, mViewportRect.left))
-            mViewportBias.x = 0;
-        else
-            mViewportBias.x = ((mViewportRect.left - origin.x) > 0) ? MAX_BIAS : -MAX_BIAS;
-        if (FloatUtils.fuzzyEquals(origin.y, mViewportRect.top))
-            mViewportBias.y = 0;
-        else
-            mViewportBias.y = ((mViewportRect.top - origin.y) > 0) ? MAX_BIAS : -MAX_BIAS;
-
-        mViewportRect.set(origin.x, origin.y,
-                origin.x + mViewportRect.width(),
-                origin.y + mViewportRect.height());
-    }
-
-    public PointF getDisplayportOrigin() {
-        return new PointF(mViewportRect.left - mViewportOffset.x,
-                mViewportRect.top - mViewportOffset.y);
-    }
-
     public FloatSize getSize() {
         return new FloatSize(mViewportRect.width(), mViewportRect.height());
     }
 
-    public void setSize(FloatSize size) {
-        mViewportRect.right = mViewportRect.left + size.width;
-        mViewportRect.bottom = mViewportRect.top + size.height;
-    }
-
     public RectF getViewport() {
         return mViewportRect;
     }
 
-    public void setViewport(RectF viewport) {
-        mViewportRect = viewport;
-    }
-
-    /**
-     * Returns the viewport rectangle, clamped within the page-size.
-     */
+    /** Returns the viewport rectangle, clamped within the page-size. */
     public RectF getClampedViewport() {
         RectF clampedViewport = new RectF(mViewportRect);
 
@@ -200,24 +129,31 @@ public class ViewportMetrics {
         return clampedViewport;
     }
 
-    public PointF getViewportOffset() {
-        return mViewportOffset;
-    }
-
-    public void setViewportOffset(PointF offset) {
-        mViewportOffset = offset;
-    }
-
     public FloatSize getPageSize() {
         return mPageSize;
     }
 
+    public float getZoomFactor() {
+        return mZoomFactor;
+    }
+
     public void setPageSize(FloatSize pageSize) {
         mPageSize = pageSize;
     }
 
-    public float getZoomFactor() {
-        return mZoomFactor;
+    public void setViewport(RectF viewport) {
+        mViewportRect = viewport;
+    }
+
+    public void setOrigin(PointF origin) {
+        mViewportRect.set(origin.x, origin.y,
+                          origin.x + mViewportRect.width(),
+                          origin.y + mViewportRect.height());
+    }
+
+    public void setSize(FloatSize size) {
+        mViewportRect.right = mViewportRect.left + size.width;
+        mViewportRect.bottom = mViewportRect.top + size.height;
     }
 
     public void setZoomFactor(float zoomFactor) {
@@ -240,15 +176,6 @@ public class ViewportMetrics {
         setOrigin(origin);
 
         mZoomFactor = newZoomFactor;
-
-        // Similar to setOrigin, set the viewport bias based on the focal point
-        // of the zoom so that a viewport based on these metrics will have a
-        // larger buffer based on the direction of movement when scaling.
-        //
-        // This is biased towards scaling outwards, as zooming in doesn't
-        // really require a viewport bias.
-        mViewportBias.set(((focus.x / mViewportRect.width()) * (2.0f * MAX_BIAS)) - MAX_BIAS,
-                ((focus.y / mViewportRect.height()) * (2.0f * MAX_BIAS)) - MAX_BIAS);
     }
 
     /*
@@ -261,15 +188,13 @@ public class ViewportMetrics {
         result.mPageSize = mPageSize.interpolate(to.mPageSize, t);
         result.mZoomFactor = FloatUtils.interpolate(mZoomFactor, to.mZoomFactor, t);
         result.mViewportRect = RectUtils.interpolate(mViewportRect, to.mViewportRect, t);
-        result.mViewportOffset = PointUtils.interpolate(mViewportOffset, to.mViewportOffset, t);
         return result;
     }
 
     public boolean fuzzyEquals(ViewportMetrics other) {
         return mPageSize.fuzzyEquals(other.mPageSize)
-                && RectUtils.fuzzyEquals(mViewportRect, other.mViewportRect)
-                && FloatUtils.fuzzyEquals(mViewportOffset, other.mViewportOffset)
-                && FloatUtils.fuzzyEquals(mZoomFactor, other.mZoomFactor);
+            && RectUtils.fuzzyEquals(mViewportRect, other.mViewportRect)
+            && FloatUtils.fuzzyEquals(mZoomFactor, other.mZoomFactor);
     }
 
     public String toJSON() {
@@ -280,15 +205,13 @@ public class ViewportMetrics {
 
         StringBuffer sb = new StringBuffer(256);
         sb.append("{ \"x\" : ").append(mViewportRect.left)
-                .append(", \"y\" : ").append(mViewportRect.top)
-                .append(", \"width\" : ").append(width)
-                .append(", \"height\" : ").append(height)
-                .append(", \"pageWidth\" : ").append(mPageSize.width)
-                .append(", \"pageHeight\" : ").append(mPageSize.height)
-                .append(", \"offsetX\" : ").append(mViewportOffset.x)
-                .append(", \"offsetY\" : ").append(mViewportOffset.y)
-                .append(", \"zoom\" : ").append(mZoomFactor)
-                .append(" }");
+          .append(", \"y\" : ").append(mViewportRect.top)
+          .append(", \"width\" : ").append(width)
+          .append(", \"height\" : ").append(height)
+          .append(", \"pageWidth\" : ").append(mPageSize.width)
+          .append(", \"pageHeight\" : ").append(mPageSize.height)
+          .append(", \"zoom\" : ").append(mZoomFactor)
+          .append(" }");
         return sb.toString();
     }
 
@@ -296,10 +219,8 @@ public class ViewportMetrics {
     public String toString() {
         StringBuffer buff = new StringBuffer(128);
         buff.append("v=").append(mViewportRect.toString())
-                .append(" p=").append(mPageSize.toString())
-                .append(" z=").append(mZoomFactor)
-                .append(" o=").append(mViewportOffset.x)
-                .append(',').append(mViewportOffset.y);
+            .append(" p=").append(mPageSize.toString())
+            .append(" z=").append(mZoomFactor);
         return buff.toString();
     }
 }
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java
index 7d15bdb..1eb0331 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java
@@ -61,45 +61,69 @@ import java.util.TimerTask;
  * Many ideas are from Joe Hewitt's Scrollability:
  *   https://github.com/joehewitt/scrollability/
  */
-public class PanZoomController extends GestureDetector.SimpleOnGestureListener implements SimpleScaleGestureDetector.SimpleScaleGestureListener {
-    // The distance the user has to pan before we recognize it as such (e.g. to avoid 1-pixel pans
-    // between the touch-down and touch-up of a click). In units of density-independent pixels.
-    public static final float PAN_THRESHOLD = 1 / 16f * LOKitShell.getDpi();
+public class PanZoomController
+    extends GestureDetector.SimpleOnGestureListener
+    implements SimpleScaleGestureDetector.SimpleScaleGestureListener
+{
     private static final String LOGTAG = "GeckoPanZoomController";
+
+
     // Animation stops if the velocity is below this value when overscrolled or panning.
     private static final float STOPPED_THRESHOLD = 4.0f;
+
     // Animation stops is the velocity is below this threshold when flinging.
     private static final float FLING_STOPPED_THRESHOLD = 0.1f;
+
+    // The distance the user has to pan before we recognize it as such (e.g. to avoid 1-pixel pans
+    // between the touch-down and touch-up of a click). In units of density-independent pixels.
+    public static final float PAN_THRESHOLD = 1/16f * LOKitShell.getDpi();
+
     // Angle from axis within which we stay axis-locked
     private static final double AXIS_LOCK_ANGLE = Math.PI / 6.0; // 30 degrees
+
     // The maximum amount we allow you to zoom into a page
     private static final float MAX_ZOOM = 4.0f;
+
     /* 16 precomputed frames of the _ease-out_ animation from the CSS Transitions specification. */
     private static final float[] EASE_OUT_ANIMATION_FRAMES = {
-            0.00000f,   /* 0 */
-            0.10211f,   /* 1 */
-            0.19864f,   /* 2 */
-            0.29043f,   /* 3 */
-            0.37816f,   /* 4 */
-            0.46155f,   /* 5 */
-            0.54054f,   /* 6 */
-            0.61496f,   /* 7 */
-            0.68467f,   /* 8 */
-            0.74910f,   /* 9 */
-            0.80794f,   /* 10 */
-            0.86069f,   /* 11 */
-            0.90651f,   /* 12 */
-            0.94471f,   /* 13 */
-            0.97401f,   /* 14 */
-            0.99309f,   /* 15 */
+        0.00000f,   /* 0 */
+        0.10211f,   /* 1 */
+        0.19864f,   /* 2 */
+        0.29043f,   /* 3 */
+        0.37816f,   /* 4 */
+        0.46155f,   /* 5 */
+        0.54054f,   /* 6 */
+        0.61496f,   /* 7 */
+        0.68467f,   /* 8 */
+        0.74910f,   /* 9 */
+        0.80794f,   /* 10 */
+        0.86069f,   /* 11 */
+        0.90651f,   /* 12 */
+        0.94471f,   /* 13 */
+        0.97401f,   /* 14 */
+        0.99309f,   /* 15 */
     };
-    private static String MESSAGE_ZOOM_RECT = "Browser:ZoomToRect";
-    private static String MESSAGE_ZOOM_PAGE = "Browser:ZoomToPageWidth";
+
+    private enum PanZoomState {
+        NOTHING,        /* no touch-start events received */
+        FLING,          /* all touches removed, but we're still scrolling page */
+        TOUCHING,       /* one touch-start event received */
+        PANNING_LOCKED, /* touch-start followed by move (i.e. panning with axis lock) */
+        PANNING,        /* panning without axis lock */
+        PANNING_HOLD,   /* in panning, but not moving.
+                         * similar to TOUCHING but after starting a pan */
+        PANNING_HOLD_LOCKED, /* like PANNING_HOLD, but axis lock still in effect */
+        PINCHING,       /* nth touch-start, where n > 1. this mode allows pan and zoom */
+        ANIMATED_ZOOM   /* animated zoom to a new rect */
+    }
+
     private final LayerController mController;
     private final SubdocumentScrollHelper mSubscroller;
     private final Axis mX;
     private final Axis mY;
+
     private Thread mMainThread;
+
     /* The timer that handles flings or bounces. */
     private Timer mAnimationTimer;
     /* The runnable being scheduled by the animation timer. */
@@ -118,59 +142,48 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i
         mY = new AxisY(mSubscroller);
 
         mMainThread = LibreOfficeMainActivity.mAppContext.getMainLooper().getThread();
+        checkMainThread();
 
         mState = PanZoomState.NOTHING;
     }
 
+    // for debugging bug 713011; it can be taken out once that is resolved.
+    private void checkMainThread() {
+        if (mMainThread != Thread.currentThread()) {
+            // log with full stack trace
+            Log.e(LOGTAG, "Uh-oh, we're running on the wrong thread!", new Exception());
+        }
+    }
+
     public boolean onTouchEvent(MotionEvent event) {
         switch (event.getAction() & MotionEvent.ACTION_MASK) {
-            case MotionEvent.ACTION_DOWN:
-                return onTouchStart(event);
-            case MotionEvent.ACTION_MOVE:
-                return onTouchMove(event);
-            case MotionEvent.ACTION_UP:
-                return onTouchEnd(event);
-            case MotionEvent.ACTION_CANCEL:
-                return onTouchCancel(event);
-            default:
-                return false;
+        case MotionEvent.ACTION_DOWN:   return onTouchStart(event);
+        case MotionEvent.ACTION_MOVE:   return onTouchMove(event);
+        case MotionEvent.ACTION_UP:     return onTouchEnd(event);
+        case MotionEvent.ACTION_CANCEL: return onTouchCancel(event);
+        default:                        return false;
         }
     }
 
-    /**
-     * This function must be called from the UI thread.
-     */
+    /** This function must be called from the UI thread. */
     public void abortAnimation() {
+        checkMainThread();
         // this happens when gecko changes the viewport on us or if the device is rotated.
         // if that's the case, abort any animation in progress and re-zoom so that the page
         // snaps to edges. for other cases (where the user's finger(s) are down) don't do
         // anything special.
-        switch (mState) {
-            case FLING:
-                mX.stopFling();
-                mY.stopFling();
-                mState = PanZoomState.NOTHING;
-                // fall through
-            case ANIMATED_ZOOM:
-                // the zoom that's in progress likely makes no sense any more (such as if
-                // the screen orientation changed) so abort it
-                // fall through
-            case NOTHING:
-                // Don't do animations here; they're distracting and can cause flashes on page
-                // transitions.
-                mController.setViewportMetrics(getValidViewportMetrics());
-                mController.notifyLayerClientOfGeometryChange();
-                break;
+        if (mState == PanZoomState.FLING) {
+            mX.stopFling();
+            mY.stopFling();
+            mState = PanZoomState.NOTHING;
         }
     }
 
-    /**
-     * This must be called on the UI thread.
-     */
+    /** This must be called on the UI thread. */
     public void pageSizeUpdated() {
         if (mState == PanZoomState.NOTHING) {
             ViewportMetrics validated = getValidViewportMetrics();
-            if (!mController.getViewportMetrics().fuzzyEquals(validated)) {
+            if (! (new ViewportMetrics(mController.getViewportMetrics())).fuzzyEquals(validated)) {
                 // page size changed such that we are now in overscroll. snap to the
                 // the nearest valid viewport
                 mController.setViewportMetrics(validated);
@@ -179,116 +192,110 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i
         }
     }
 
+    /*
+     * Panning/scrolling
+     */
+
     private boolean onTouchStart(MotionEvent event) {
-        Log.d(LOGTAG, "onTouchStart in state " + mState);
         // user is taking control of movement, so stop
         // any auto-movement we have going
         stopAnimationTimer();
         mSubscroller.cancel();
 
         switch (mState) {
-            case ANIMATED_ZOOM:
-                return false;
-            case FLING:
-            case NOTHING:
-                startTouch(event.getX(0), event.getY(0), event.getEventTime());
-                return false;
-            case TOUCHING:
-            case PANNING:
-            case PANNING_LOCKED:
-            case PANNING_HOLD:
-            case PANNING_HOLD_LOCKED:
-            case PINCHING:
-                Log.e(LOGTAG, "Received impossible touch down while in " + mState);
-                return false;
+        case ANIMATED_ZOOM:
+            return false;
+        case FLING:
+        case NOTHING:
+            startTouch(event.getX(0), event.getY(0), event.getEventTime());
+            return false;
+        case TOUCHING:
+        case PANNING:
+        case PANNING_LOCKED:
+        case PANNING_HOLD:
+        case PANNING_HOLD_LOCKED:
+        case PINCHING:
+            Log.e(LOGTAG, "Received impossible touch down while in " + mState);
+            return false;
         }
         Log.e(LOGTAG, "Unhandled case " + mState + " in onTouchStart");
         return false;
     }
 
-    /*
-     * Panning/scrolling
-     */
     private boolean onTouchMove(MotionEvent event) {
-        Log.d(LOGTAG, "onTouchMove in state " + mState);
 
         switch (mState) {
-            case NOTHING:
-            case FLING:
-                // should never happen
-                Log.e(LOGTAG, "Received impossible touch move while in " + mState);
-                return false;
+        case NOTHING:
+        case FLING:
+            // should never happen
+            Log.e(LOGTAG, "Received impossible touch move while in " + mState);
+            return false;
 
-            case TOUCHING:
-                if (panDistance(event) < PAN_THRESHOLD) {
-                    return false;
-                }
-                cancelTouch();
-                startPanning(event.getX(0), event.getY(0), event.getEventTime());
-                track(event);
-                return true;
-
-            case PANNING_HOLD_LOCKED:
-                //GeckoApp.mAutoCompletePopup.hide();
-                mState = PanZoomState.PANNING_LOCKED;
-                // fall through
-            case PANNING_LOCKED:
-                track(event);
-                return true;
-
-            case PANNING_HOLD:
-                //GeckoApp.mAutoCompletePopup.hide();
-                mState = PanZoomState.PANNING;
-                // fall through
-            case PANNING:
-                track(event);
-                return true;
-
-            case ANIMATED_ZOOM:
-            case PINCHING:
-                // scale gesture listener will handle this
+        case TOUCHING:
+            if (panDistance(event) < PAN_THRESHOLD) {
                 return false;
+            }
+            cancelTouch();
+            startPanning(event.getX(0), event.getY(0), event.getEventTime());
+            track(event);
+            return true;
+
+        case PANNING_HOLD_LOCKED:
+            mState = PanZoomState.PANNING_LOCKED;
+            // fall through
+        case PANNING_LOCKED:
+            track(event);
+            return true;
+
+        case PANNING_HOLD:
+            mState = PanZoomState.PANNING;
+            // fall through
+        case PANNING:
+            track(event);
+            return true;
+
+        case ANIMATED_ZOOM:
+        case PINCHING:
+            // scale gesture listener will handle this
+            return false;
         }
         Log.e(LOGTAG, "Unhandled case " + mState + " in onTouchMove");
         return false;
     }
 
     private boolean onTouchEnd(MotionEvent event) {
-        Log.d(LOGTAG, "onTouchEnd in " + mState);
 
         switch (mState) {
-            case NOTHING:
-            case FLING:
-                // should never happen
-                Log.e(LOGTAG, "Received impossible touch end while in " + mState);
-                return false;
-            case TOUCHING:
-                mState = PanZoomState.NOTHING;
-                // the switch into TOUCHING might have happened while the page was
-                // snapping back after overscroll. we need to finish the snap if that
-                // was the case
-                bounce();
-                return false;
-            case PANNING:
-            case PANNING_LOCKED:
-            case PANNING_HOLD:
-            case PANNING_HOLD_LOCKED:
-                mState = PanZoomState.FLING;
-                fling();
-                return true;
-            case PINCHING:
-                mState = PanZoomState.NOTHING;
-                return true;
-            case ANIMATED_ZOOM:
-                return false;
+        case NOTHING:
+        case FLING:
+            // should never happen
+            Log.e(LOGTAG, "Received impossible touch end while in " + mState);
+            return false;
+        case TOUCHING:
+            mState = PanZoomState.NOTHING;
+            // the switch into TOUCHING might have happened while the page was
+            // snapping back after overscroll. we need to finish the snap if that
+            // was the case
+            bounce();
+            return false;
+        case PANNING:
+        case PANNING_LOCKED:
+        case PANNING_HOLD:
+        case PANNING_HOLD_LOCKED:
+            mState = PanZoomState.FLING;
+            fling();
+            return true;
+        case PINCHING:
+            mState = PanZoomState.NOTHING;
+            return true;
+        case ANIMATED_ZOOM:
+            return false;
         }
         Log.e(LOGTAG, "Unhandled case " + mState + " in onTouchEnd");
         return false;
     }
 
     private boolean onTouchCancel(MotionEvent event) {
-        Log.d(LOGTAG, "onTouchCancel in " + mState);
-
         mState = PanZoomState.NOTHING;
         // ensure we snap back if we're overscrolled
         bounce();
@@ -332,7 +339,7 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i
     }
 
     private void track(float x, float y, long time) {
-        float timeDelta = (float) (time - mLastEventTime);
+        float timeDelta = (float)(time - mLastEventTime);
         if (FloatUtils.fuzzyEquals(timeDelta, 0)) {
             // probably a duplicate event, ignore it. using a zero timeDelta will mess
             // up our velocity
@@ -350,8 +357,8 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i
 
         for (int i = 0; i < event.getHistorySize(); i++) {
             track(event.getHistoricalX(0, i),
-                    event.getHistoricalY(0, i),
-                    event.getHistoricalEventTime(i));
+                  event.getHistoricalY(0, i),
+                  event.getHistoricalEventTime(i));
         }
         track(event.getX(0), event.getY(0), event.getEventTime());
 
@@ -395,7 +402,6 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i
         }
 
         mState = PanZoomState.FLING;
-        Log.d(LOGTAG, "end bounce at " + metrics);
 
         startAnimationTimer(new BounceRunnable(bounceStartMetrics, metrics));
     }
@@ -412,16 +418,12 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i
             stopAnimationTimer();
         }
 
-        //GeckoApp.mAppContext.hidePlugins(false /* don't hide layers */);
-
         mAnimationTimer = new Timer("Animation Timer");
         mAnimationRunnable = runnable;
         mAnimationTimer.scheduleAtFixedRate(new TimerTask() {
             @Override
-            public void run() {
-                mController.post(runnable);
-            }
-        }, 0, 1000L / 60L);
+            public void run() { mController.post(runnable); }
+        }, 0, 1000L/60L);
     }
 
     /* Stops the fling or bounce animation. */
@@ -437,9 +439,9 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i
     }
 
     private float getVelocity() {
-        float xVelocity = mX.getRealVelocity();
-        float yVelocity = mY.getRealVelocity();
-        return FloatMath.sqrt(xVelocity * xVelocity + yVelocity * yVelocity);
+        float xvel = mX.getRealVelocity();
+        float yvel = mY.getRealVelocity();
+        return FloatMath.sqrt(xvel * xvel + yvel * yvel);
     }
 
     private boolean stopped() {
@@ -454,14 +456,150 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i
         mX.displace();
         mY.displace();
         PointF displacement = getDisplacement();
-        if (!mSubscroller.scrollBy(displacement)) {
+        if (! mSubscroller.scrollBy(displacement)) {
             synchronized (mController) {
                 mController.scrollBy(displacement);
             }
         }
     }
 
+    private abstract class AnimationRunnable implements Runnable {
+        private boolean mAnimationTerminated;
+
+        /* This should always run on the UI thread */
+        public final void run() {
+            /*
+             * Since the animation timer queues this runnable on the UI thread, it
+             * is possible that even when the animation timer is cancelled, there
+             * are multiple instances of this queued, so we need to have another
+             * mechanism to abort. This is done by using the mAnimationTerminated flag.
+             */
+            if (mAnimationTerminated) {
+                return;
+            }
+            animateFrame();
+        }
+
+        protected abstract void animateFrame();
+
+        /* This should always run on the UI thread */
+        protected final void terminate() {
+            mAnimationTerminated = true;
+        }
+    }
+
+    /* The callback that performs the bounce animation. */
+    private class BounceRunnable extends AnimationRunnable {
+        /* The current frame of the bounce-back animation */
+        private int mBounceFrame;
+        /*
+         * The viewport metrics that represent the start and end of the bounce-back animation,
+         * respectively.
+         */
+        private ViewportMetrics mBounceStartMetrics;
+        private ViewportMetrics mBounceEndMetrics;
+
+        BounceRunnable(ViewportMetrics startMetrics, ViewportMetrics endMetrics) {
+            mBounceStartMetrics = startMetrics;
+            mBounceEndMetrics = endMetrics;
+        }
+
+        protected void animateFrame() {
+            /*
+             * The pan/zoom controller might have signaled to us that it wants to abort the
+             * animation by setting the state to PanZoomState.NOTHING. Handle this case and bail
+             * out.
+             */
+            if (mState != PanZoomState.FLING) {
+                finishAnimation();
+                return;
+            }
+
+            /* Perform the next frame of the bounce-back animation. */
+            if (mBounceFrame < EASE_OUT_ANIMATION_FRAMES.length) {
+                advanceBounce();
+                return;
+            }
+
+            /* Finally, if there's nothing else to do, complete the animation and go to sleep. */
+            finishBounce();
+            finishAnimation();
+            mState = PanZoomState.NOTHING;
+        }
+
+        /* Performs one frame of a bounce animation. */
+        private void advanceBounce() {
+            synchronized (mController) {
+                float t = EASE_OUT_ANIMATION_FRAMES[mBounceFrame];
+                ViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t);
+                mController.setViewportMetrics(newMetrics);
+                mController.notifyLayerClientOfGeometryChange();
+                mBounceFrame++;
+            }
+        }
+
+        /* Concludes a bounce animation and snaps the viewport into place. */
+        private void finishBounce() {
+            synchronized (mController) {
+                mController.setViewportMetrics(mBounceEndMetrics);
+                mController.notifyLayerClientOfGeometryChange();
+                mBounceFrame = -1;
+            }
+        }
+    }
+
+    // The callback that performs the fling animation.
+    private class FlingRunnable extends AnimationRunnable {
+        protected void animateFrame() {
+            /*
+             * The pan/zoom controller might have signaled to us that it wants to abort the
+             * animation by setting the state to PanZoomState.NOTHING. Handle this case and bail
+             * out.
+             */
+            if (mState != PanZoomState.FLING) {
+                finishAnimation();
+                return;
+            }
+
+            /* Advance flings, if necessary. */
+            boolean flingingX = mX.advanceFling();
+            boolean flingingY = mY.advanceFling();
+
+            boolean overscrolled = (mX.overscrolled() || mY.overscrolled());
+
+            /* If we're still flinging in any direction, update the origin. */
+            if (flingingX || flingingY) {
+                updatePosition();
+
+                /*
+                 * Check to see if we're still flinging with an appreciable velocity. The threshold is
+                 * higher in the case of overscroll, so we bounce back eagerly when overscrolling but
+                 * coast smoothly to a stop when not. In other words, require a greater velocity to
+                 * maintain the fling once we enter overscroll.
+                 */
+                float threshold = (overscrolled && !mSubscroller.scrolling() ? STOPPED_THRESHOLD : FLING_STOPPED_THRESHOLD);
+                if (getVelocity() >= threshold) {
+                    // we're still flinging
+                    return;
+                }
+
+                mX.stopFling();
+                mY.stopFling();
+            }
+
+            /* Perform a bounce-back animation if overscrolled. */
+            if (overscrolled) {
+                bounce();
+            } else {
+                finishAnimation();
+                mState = PanZoomState.NOTHING;
+            }
+        }
+    }
+
     private void finishAnimation() {
+        checkMainThread();
+
         Log.d(LOGTAG, "Finishing animation at " + mController.getViewportMetrics());
         stopAnimationTimer();
 
@@ -517,6 +655,26 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i
         return viewportMetrics;
     }
 
+    private class AxisX extends Axis {
+        AxisX(SubdocumentScrollHelper subscroller) { super(subscroller); }
+        @Override
+        public float getOrigin() { return mController.getOrigin().x; }
+        @Override
+        protected float getViewportLength() { return mController.getViewportSize().width; }
+        @Override
+        protected float getPageLength() { return mController.getPageSize().width; }
+    }
+
+    private class AxisY extends Axis {
+        AxisY(SubdocumentScrollHelper subscroller) { super(subscroller); }
+        @Override
+        public float getOrigin() { return mController.getOrigin().y; }
+        @Override
+        protected float getViewportLength() { return mController.getViewportSize().height; }
+        @Override
+        protected float getPageLength() { return mController.getPageSize().height; }
+    }
+
     /*
      * Zooming
      */
@@ -555,11 +713,10 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i
          * factor toward 1.0.
          */
         float resistance = Math.min(mX.getEdgeResistance(), mY.getEdgeResistance());
-        if (spanRatio > 1.0f) {
+        if (spanRatio > 1.0f)
             spanRatio = 1.0f + (spanRatio - 1.0f) * resistance;
-        } else {
+        else
             spanRatio = 1.0f - (1.0f - spanRatio) * resistance;
-        }
 
         synchronized (mController) {
             float newZoomFactor = mController.getZoomFactor() * spanRatio;
@@ -568,12 +725,12 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i
                 // such that it asymptotically reaches MAX_ZOOM + 1.0
                 // but never exceeds that
                 float excessZoom = newZoomFactor - MAX_ZOOM;
-                excessZoom = 1.0f - (float) Math.exp(-excessZoom);
+                excessZoom = 1.0f - (float)Math.exp(-excessZoom);
                 newZoomFactor = MAX_ZOOM + excessZoom;
             }
 
             mController.scrollBy(new PointF(mLastZoomFocus.x - detector.getFocusX(),
-                    mLastZoomFocus.y - detector.getFocusY()));
+                                            mLastZoomFocus.y - detector.getFocusY()));
             PointF focus = new PointF(detector.getFocusX(), detector.getFocusY());
             mController.scaleWithFocus(newZoomFactor, focus);
         }
@@ -594,7 +751,6 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i
         startTouch(detector.getFocusX(), detector.getFocusY(), detector.getEventTime());
 
         // Force a viewport synchronisation
-        //GeckoApp.mAppContext.showPlugins();
         mController.setForceRedraw();
         mController.notifyLayerClientOfGeometryChange();
     }
@@ -663,193 +819,4 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i
         bounce(finalMetrics);
         return true;
     }
-
-    private enum PanZoomState {
-        NOTHING,        /* no touch-start events received */
-        FLING,          /* all touches removed, but we're still scrolling page */
-        TOUCHING,       /* one touch-start event received */
-        PANNING_LOCKED, /* touch-start followed by move (i.e. panning with axis lock) */
-        PANNING,        /* panning without axis lock */
-        PANNING_HOLD,   /* in panning, but not moving.
-                         * similar to TOUCHING but after starting a pan */
-        PANNING_HOLD_LOCKED, /* like PANNING_HOLD, but axis lock still in effect */
-        PINCHING,       /* nth touch-start, where n > 1. this mode allows pan and zoom */
-        ANIMATED_ZOOM   /* animated zoom to a new rect */
-    }
-
-    private abstract class AnimationRunnable implements Runnable {
-        private boolean mAnimationTerminated;
-
-        /* This should always run on the UI thread */
-        public final void run() {
-            /*
-             * Since the animation timer queues this runnable on the UI thread, it
-             * is possible that even when the animation timer is cancelled, there
-             * are multiple instances of this queued, so we need to have another
-             * mechanism to abort. This is done by using the mAnimationTerminated flag.
-             */
-            if (mAnimationTerminated) {
-                return;
-            }
-            animateFrame();
-        }
-
-        protected abstract void animateFrame();
-
-        /* This should always run on the UI thread */
-        protected final void terminate() {
-            mAnimationTerminated = true;
-        }
-    }
-
-    /* The callback that performs the bounce animation. */
-    private class BounceRunnable extends AnimationRunnable {
-        /* The current frame of the bounce-back animation */
-        private int mBounceFrame;
-        /*
-         * The viewport metrics that represent the start and end of the bounce-back animation,
-         * respectively.
-         */
-        private ViewportMetrics mBounceStartMetrics;
-        private ViewportMetrics mBounceEndMetrics;
-
-        BounceRunnable(ViewportMetrics startMetrics, ViewportMetrics endMetrics) {
-            mBounceStartMetrics = startMetrics;
-            mBounceEndMetrics = endMetrics;
-        }
-
-        protected void animateFrame() {
-            /*
-             * The pan/zoom controller might have signaled to us that it wants to abort the
-             * animation by setting the state to PanZoomState.NOTHING. Handle this case and bail
-             * out.
-             */
-            if (mState != PanZoomState.FLING) {
-                finishAnimation();
-                return;
-            }
-
-            /* Perform the next frame of the bounce-back animation. */
-            if (mBounceFrame < EASE_OUT_ANIMATION_FRAMES.length) {
-                advanceBounce();
-                return;
-            }
-
-            /* Finally, if there's nothing else to do, complete the animation and go to sleep. */
-            finishBounce();
-            finishAnimation();
-            mState = PanZoomState.NOTHING;
-        }
-
-        /* Performs one frame of a bounce animation. */
-        private void advanceBounce() {
-            synchronized (mController) {
-                float t = EASE_OUT_ANIMATION_FRAMES[mBounceFrame];
-                ViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t);
-                mController.setViewportMetrics(newMetrics);
-                mController.notifyLayerClientOfGeometryChange();
-                mBounceFrame++;
-            }
-        }
-
-        /* Concludes a bounce animation and snaps the viewport into place. */
-        private void finishBounce() {
-            synchronized (mController) {
-                mController.setViewportMetrics(mBounceEndMetrics);
-                mController.notifyLayerClientOfGeometryChange();
-                mBounceFrame = -1;
-            }
-        }
-    }
-
-    // The callback that performs the fling animation.
-    private class FlingRunnable extends AnimationRunnable {
-        protected void animateFrame() {
-            /*
-             * The pan/zoom controller might have signaled to us that it wants to abort the
-             * animation by setting the state to PanZoomState.NOTHING. Handle this case and bail
-             * out.
-             */
-            if (mState != PanZoomState.FLING) {
-                finishAnimation();
-                return;
-            }
-
-            /* Advance flings, if necessary. */
-            boolean flingingX = mX.advanceFling();
-            boolean flingingY = mY.advanceFling();
-
-            boolean overscrolled = (mX.overscrolled() || mY.overscrolled());
-
-            /* If we're still flinging in any direction, update the origin. */
-            if (flingingX || flingingY) {
-                updatePosition();
-
-                /*
-                 * Check to see if we're still flinging with an appreciable velocity. The threshold is
-                 * higher in the case of overscroll, so we bounce back eagerly when overscrolling but
-                 * coast smoothly to a stop when not. In other words, require a greater velocity to
-                 * maintain the fling once we enter overscroll.
-                 */
-                float threshold = (overscrolled && !mSubscroller.scrolling() ? STOPPED_THRESHOLD : FLING_STOPPED_THRESHOLD);
-                if (getVelocity() >= threshold) {
-                    // we're still flinging
-                    return;
-                }
-
-                mX.stopFling();
-                mY.stopFling();
-            }
-
-            /* Perform a bounce-back animation if overscrolled. */
-            if (overscrolled) {
-                bounce();
-            } else {
-                finishAnimation();
-                mState = PanZoomState.NOTHING;
-            }
-        }
-    }
-
-    private class AxisX extends Axis {
-        AxisX(SubdocumentScrollHelper subscroller) {
-            super(subscroller);
-        }
-
-        @Override
-        public float getOrigin() {
-            return mController.getOrigin().x;
-        }
-
-        @Override
-        protected float getViewportLength() {
-            return mController.getViewportSize().width;
-        }
-
-        @Override
-        protected float getPageLength() {
-            return mController.getPageSize().width;
-        }
-    }
-
-    private class AxisY extends Axis {
-        AxisY(SubdocumentScrollHelper subscroller) {
-            super(subscroller);
-        }
-
-        @Override
-        public float getOrigin() {
-            return mController.getOrigin().y;
-        }
-
-        @Override
-        protected float getViewportLength() {
-            return mController.getViewportSize().height;
-        }
-
-        @Override
-        protected float getPageLength() {
-            return mController.getPageSize().height;
-        }
-    }
 }
commit 046e19ef2cb02d11560b5812364aa5211b20e4ac
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Thu Sep 18 22:10:49 2014 +0200

    android: thumbnail as background when tile is not available
    
    Change-Id: Id38e40b3fdb46adeb19e6a1e106775391c5da455

diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
index e75276d..c5918e69 100644
--- a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
@@ -1,5 +1,6 @@
 package org.libreoffice;
 
+import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.Log;
@@ -22,6 +23,7 @@ public class LOKitThread extends Thread {
     private ViewportMetrics mViewportMetrics;
     private String mInputFile;
     private Rect mOldRect;
+    private boolean mCheckboardImageSet = false;
 
     LOKitThread(String inputFile) {
         mInputFile = inputFile;
@@ -115,6 +117,7 @@ public class LOKitThread extends Thread {
 
         layerClient.endDrawing();
         Log.i(LOGTAG, "tilerender end draw");
+
         return true;
     }
 
@@ -128,7 +131,22 @@ public class LOKitThread extends Thread {
     private boolean initialize() {
         mApplication = LibreOfficeMainActivity.mAppContext;
         mTileProvider = new LOKitTileProvider(mApplication.getLayerController(), mInputFile);
-        return mTileProvider.isReady();
+        boolean isReady = mTileProvider.isReady();
+        if (isReady)
+        {
+            if (!mCheckboardImageSet) {
+                Log.i(LOGTAG, "Generate thumbnail!");
+                Bitmap bitmap = mTileProvider.thumbnail();
+                Log.i(LOGTAG, "Done generate thumbnail!");
+                if (bitmap != null) {
+                    Log.i(LOGTAG, "Setting checkboard image!");
+                    mApplication.getLayerController().getView().changeCheckerboardBitmap(bitmap);
+                    Log.i(LOGTAG, "Done setting checkboard image!!");
+                    mCheckboardImageSet = true;
+                }
+            }
+        }
+        return isReady;
     }
 
     public void run() {
commit 6ca57cb25ef8826aac5584dee5b41b6ad6114555
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Thu Sep 18 22:09:48 2014 +0200

    android: fix thumbnail() to produce a valid bitmap
    
    Change-Id: I578ac9482f334765c71a66421a3fa2dfb85e22b3

diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitTileProvider.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitTileProvider.java
index 5a906c9..f13dd8a 100644
--- a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitTileProvider.java
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitTileProvider.java
@@ -122,9 +122,6 @@ public class LOKitTileProvider implements TileProvider {
 
     @Override
     public Bitmap thumbnail() {
-        ByteBuffer buffer = ByteBuffer.allocateDirect(TILE_SIZE * TILE_SIZE * 4);
-        Bitmap bitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Bitmap.Config.ARGB_8888);
-
         int widthPixel = getPageWidth();
         int heightPixel = getPageHeight();
 
@@ -138,8 +135,14 @@ public class LOKitTileProvider implements TileProvider {
             widthPixel = (int) (heightPixel * ratio);
         }
 
+        ByteBuffer buffer = ByteBuffer.allocateDirect(widthPixel * heightPixel * 4);
         mDocument.paintTile(buffer, widthPixel, heightPixel, 0, 0, (int) mWidthTwip, (int) mHeightTwip);
 
+        Bitmap bitmap = Bitmap.createBitmap(widthPixel, heightPixel, Bitmap.Config.ARGB_8888);
+        bitmap.copyPixelsFromBuffer(buffer);
+        if (bitmap == null) {
+            Log.w(LOGTAG, "Thumbnail not created!");
+        }
         return bitmap;
     }
 
diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/MockTileProvider.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/MockTileProvider.java
index 60abbdf..2a89e77 100644
--- a/android/experimental/LOAndroid3/src/java/org/libreoffice/MockTileProvider.java
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/MockTileProvider.java
@@ -63,7 +63,7 @@ public class MockTileProvider implements TileProvider {
 
     @Override
     public Bitmap thumbnail() {
-        return null;
+        return layerController.getDrawable("dummy_page");
     }
 
     @Override
commit 58f5e531f792567beb99a6eee346ac61e6f94938
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Thu Sep 18 22:08:01 2014 +0200

    android: import changes from Fennec to get ScreenShotLayer
    
    Change-Id: I1b72af85a906aef289d1be086274941094df4f96

diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
index cf13afb..1ebb9a1 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
@@ -40,6 +40,7 @@ package org.mozilla.gecko.gfx;
 
 import android.content.Context;
 import android.graphics.PointF;
+import android.graphics.RectF;
 import android.os.SystemClock;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -53,11 +54,13 @@ import java.util.List;
 
 public class GeckoLayerClient {
     private static final String LOGTAG = "GeckoLayerClient";
-    private static final long MIN_VIEWPORT_CHANGE_DELAY = 25L;
+    private static final int DEFAULT_DISPLAY_PORT_MARGIN = 300;
 
+    private static final long MIN_VIEWPORT_CHANGE_DELAY = 25L;
     private static final IntSize TILE_SIZE = new IntSize(256, 256);
 
     protected IntSize mScreenSize;
+    private RectF mDisplayPort;
     protected Layer mTileLayer;
     /* The viewport that Gecko is currently displaying. */
     protected ViewportMetrics mGeckoViewport;
@@ -79,6 +82,7 @@ public class GeckoLayerClient {
     public GeckoLayerClient(Context context) {
         mContext = context;
         mScreenSize = new IntSize(0, 0);
+        mDisplayPort = new RectF();
     }
 
     protected void setupLayer() {
@@ -110,7 +114,7 @@ public class GeckoLayerClient {
             layerController.setViewportMetrics(mGeckoViewport);
         }
 
-        sendResizeEventIfNecessary(false);
+        sendResizeEventIfNecessary(true);
     }
 
     public void beginDrawing(ViewportMetrics viewportMetrics) {
@@ -132,6 +136,10 @@ public class GeckoLayerClient {
         Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - endDrawing");
     }
 
+    RectF getDisplayPort() {
+        return mDisplayPort;
+    }
+
     protected void updateViewport(boolean onlyUpdatePageSize) {
         // save and restore the viewport size stored in java; never let the
         // JS-side viewport dimensions override the java-side ones because
@@ -142,7 +150,8 @@ public class GeckoLayerClient {
         mGeckoViewport.setSize(viewportSize);
 
         PointF displayportOrigin = mGeckoViewport.getDisplayportOrigin();
-        mTileLayer.setOrigin(PointUtils.round(displayportOrigin));
+        RectF position = mGeckoViewport.getViewport();
+        mTileLayer.setPosition(RectUtils.round(position));
         mTileLayer.setResolution(mGeckoViewport.getZoomFactor());
 
         Log.e(LOGTAG, "### updateViewport onlyUpdatePageSize=" + onlyUpdatePageSize + " getTileViewport " + mGeckoViewport);
@@ -160,7 +169,7 @@ public class GeckoLayerClient {
     }
 
     /* Informs Gecko that the screen size has changed. */
-    protected void sendResizeEventIfNecessary(boolean force) {
+    private void sendResizeEventIfNecessary(boolean force) {
         Log.e(LOGTAG, "### sendResizeEventIfNecessary " + force);
 
         DisplayMetrics metrics = new DisplayMetrics();
@@ -172,9 +181,8 @@ public class GeckoLayerClient {
         // size is zero (which indicates that the rendering surface hasn't been
         // allocated yet).
         boolean screenSizeChanged = !mScreenSize.equals(newScreenSize);
-        boolean viewportSizeValid = (mLayerController != null && mLayerController.getViewportSize().isPositive());
 
-        if (!(force || (screenSizeChanged && viewportSizeValid))) {
+        if (!force && !screenSizeChanged) {
             return;
         }
 
@@ -213,11 +221,75 @@ public class GeckoLayerClient {
         mViewportSizeChanged = true;
     }
 
+    private static RectF calculateDisplayPort(ImmutableViewportMetrics metrics) {
+        float desiredXMargins = 2 * DEFAULT_DISPLAY_PORT_MARGIN;
+        float desiredYMargins = 2 * DEFAULT_DISPLAY_PORT_MARGIN;
+
+        // we need to avoid having a display port that is larger than the page, or we will end up
+        // painting things outside the page bounds (bug 729169). we simultaneously need to make
+        // the display port as large as possible so that we redraw less.
+
+        // figure out how much of the desired buffer amount we can actually use on the horizontal axis
+        float xBufferAmount = Math.min(desiredXMargins, metrics.pageSizeWidth - metrics.getWidth());
+        // if we reduced the buffer amount on the horizontal axis, we should take that saved memory and
+        // use it on the vertical axis
+        float savedPixels = (desiredXMargins - xBufferAmount) * (metrics.getHeight() + desiredYMargins);
+        float extraYAmount = (float)Math.floor(savedPixels / (metrics.getWidth() + xBufferAmount));
+        float yBufferAmount = Math.min(desiredYMargins + extraYAmount, metrics.pageSizeHeight - metrics.getHeight());
+        // and the reverse - if we shrunk the buffer on the vertical axis we can add it to the horizontal
+        if (xBufferAmount == desiredXMargins && yBufferAmount < desiredYMargins) {
+            savedPixels = (desiredYMargins - yBufferAmount) * (metrics.getWidth() + xBufferAmount);
+            float extraXAmount = (float)Math.floor(savedPixels / (metrics.getHeight() + yBufferAmount));
+            xBufferAmount = Math.min(xBufferAmount + extraXAmount, metrics.pageSizeWidth - metrics.getWidth());
+        }
+
+        // and now calculate the display port margins based on how much buffer we've decided to use and
+        // the page bounds, ensuring we use all of the available buffer amounts on one side or the other
+        // on any given axis. (i.e. if we're scrolled to the top of the page, the vertical buffer is
+        // entirely below the visible viewport, but if we're halfway down the page, the vertical buffer
+        // is split).
+        float leftMargin = Math.min(DEFAULT_DISPLAY_PORT_MARGIN, metrics.viewportRectLeft);
+        float rightMargin = Math.min(DEFAULT_DISPLAY_PORT_MARGIN, metrics.pageSizeWidth - (metrics.viewportRectLeft + metrics.getWidth()));
+        if (leftMargin < DEFAULT_DISPLAY_PORT_MARGIN) {
+            rightMargin = xBufferAmount - leftMargin;
+        } else if (rightMargin < DEFAULT_DISPLAY_PORT_MARGIN) {
+            leftMargin = xBufferAmount - rightMargin;
+        } else if (!FloatUtils.fuzzyEquals(leftMargin + rightMargin, xBufferAmount)) {
+            float delta = xBufferAmount - leftMargin - rightMargin;
+            leftMargin += delta / 2;
+            rightMargin += delta / 2;
+        }
+
+        float topMargin = Math.min(DEFAULT_DISPLAY_PORT_MARGIN, metrics.viewportRectTop);
+        float bottomMargin = Math.min(DEFAULT_DISPLAY_PORT_MARGIN, metrics.pageSizeHeight - (metrics.viewportRectTop + metrics.getHeight()));
+        if (topMargin < DEFAULT_DISPLAY_PORT_MARGIN) {
+            bottomMargin = yBufferAmount - topMargin;
+        } else if (bottomMargin < DEFAULT_DISPLAY_PORT_MARGIN) {
+            topMargin = yBufferAmount - bottomMargin;
+        } else if (!FloatUtils.fuzzyEquals(topMargin + bottomMargin, yBufferAmount)) {
+            float delta = yBufferAmount - topMargin - bottomMargin;
+            topMargin += delta / 2;
+            bottomMargin += delta / 2;
+        }
+
+        // note that unless the viewport size changes, or the page dimensions change (either because of
+        // content changes or zooming), the size of the display port should remain constant. this
+        // is intentional to avoid re-creating textures and all sorts of other reallocations in the
+        // draw and composition code.
+        return new RectF(metrics.viewportRectLeft - leftMargin,
+                         metrics.viewportRectTop - topMargin,
+                         metrics.viewportRectRight + rightMargin,
+                         metrics.viewportRectBottom + bottomMargin);
+    }
+
     private void adjustViewport() {
-        ViewportMetrics viewportMetrics = new ViewportMetrics(mLayerController.getViewportMetrics());
+        ViewportMetrics viewportMetrics =
+            new ViewportMetrics(mLayerController.getViewportMetrics());
 
         viewportMetrics.setViewport(viewportMetrics.getClampedViewport());
 
+        mDisplayPort = calculateDisplayPort(new ImmutableViewportMetrics(mLayerController.getViewportMetrics()));
+
         LOKitShell.sendEvent(LOEvent.viewport(viewportMetrics));
         if (mViewportSizeChanged) {
             mViewportSizeChanged = false;
@@ -228,19 +300,18 @@ public class GeckoLayerClient {
     }
 
     public void geometryChanged() {
-        sendResizeEventIfNecessary();
-        render();
+        sendResizeEventIfNecessary(false);
+        if (mLayerController.getRedrawHint())
+            adjustViewport();
     }
 
     public ViewportMetrics getGeckoViewportMetrics() {
+        // Return a copy, as we modify this inside the Gecko thread
         if (mGeckoViewport != null)
             return new ViewportMetrics(mGeckoViewport);
         return null;
     }
 
-    private void sendResizeEventIfNecessary() {
-        sendResizeEventIfNecessary(false);
-    }
 
     public void addTile(SubTile tile) {
         if (mTileLayer instanceof MultiTileLayer) {
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ImmutableViewportMetrics.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ImmutableViewportMetrics.java
new file mode 100644
index 0000000..b9eb34d
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ImmutableViewportMetrics.java
@@ -0,0 +1,70 @@
+/* -*- 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;
+
+import android.graphics.PointF;
+import android.graphics.RectF;
+
+/**
+ * ImmutableViewportMetrics are used to store the viewport metrics
+ * in way that we can access a version of them from multiple threads
+ * without having to take a lock
+ */
+public class ImmutableViewportMetrics {
+
+    // We need to flatten the RectF and FloatSize structures
+    // because Java doesn't have the concept of const classes
+    public final float pageSizeWidth;
+    public final float pageSizeHeight;
+    public final float viewportRectBottom;
+    public final float viewportRectLeft;
+    public final float viewportRectRight;
+    public final float viewportRectTop;
+    public final float zoomFactor;
+
+    public ImmutableViewportMetrics(ViewportMetrics m) {
+        RectF viewportRect = m.getViewport();
+        viewportRectBottom = viewportRect.bottom;
+        viewportRectLeft = viewportRect.left;
+        viewportRectRight = viewportRect.right;
+        viewportRectTop = viewportRect.top;
+
+        FloatSize pageSize = m.getPageSize();
+        pageSizeWidth = pageSize.width;
+        pageSizeHeight = pageSize.height;
+
+        zoomFactor = m.getZoomFactor();
+    }
+
+    public float getWidth() {
+        return viewportRectRight - viewportRectLeft;
+    }
+
+    public float getHeight() {
+        return viewportRectBottom - viewportRectTop;
+    }
+
+    // some helpers to make ImmutableViewportMetrics act more like ViewportMetrics
+
+    public PointF getOrigin() {
+        return new PointF(viewportRectLeft, viewportRectTop);
+    }
+
+    public FloatSize getSize() {
+        return new FloatSize(viewportRectRight - viewportRectLeft, viewportRectBottom - viewportRectTop);
+    }
+
+    public RectF getViewport() {
+        return new RectF(viewportRectLeft,
+                viewportRectTop,
+                viewportRectRight,
+                viewportRectBottom);
+    }
+
+    public FloatSize getPageSize() {
+        return new FloatSize(pageSizeWidth, pageSizeHeight);
+    }
+}
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/Layer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/Layer.java
index cc689ed..7e575b5 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/Layer.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/Layer.java
@@ -39,7 +39,7 @@
 
 package org.mozilla.gecko.gfx;
 
-import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
 
@@ -50,16 +50,24 @@ import java.util.concurrent.locks.ReentrantLock;
 
 public abstract class Layer {
     private final ReentrantLock mTransactionLock;
-    protected Point mOrigin;
-    protected float mResolution;
     private boolean mInTransaction;
-    private Point mNewOrigin;
+    private Rect mNewPosition;
     private float mNewResolution;
-    private LayerView mView;
+
+    protected Rect mPosition;
+    protected float mResolution;
 
     public Layer() {
+        this(null);
+    }
+
+    public Layer(IntSize size) {
         mTransactionLock = new ReentrantLock();
-        mOrigin = new Point(0, 0);
+        if (size == null) {
+            mPosition = new Rect();
+        } else {
+            mPosition = new Rect(0, 0, size.width, size.height);
+        }
         mResolution = 1.0f;
     }
 
@@ -69,12 +77,14 @@ public abstract class Layer {
      */
     public final boolean update(RenderContext context) {
         if (mTransactionLock.isHeldByCurrentThread()) {
-            throw new RuntimeException("draw() called while transaction lock held by this thread?!");
+            throw new RuntimeException("draw() called while transaction lock held by this " +
+                                       "thread?!");
         }
 
         if (mTransactionLock.tryLock()) {
             try {
-                return performUpdates(context);
+                performUpdates(context);
+                return true;
             } finally {
                 mTransactionLock.unlock();
             }
@@ -83,24 +93,12 @@ public abstract class Layer {
         return false;
     }
 
-    /**
-     * Subclasses override this function to draw the layer.
-     */
+    /** Subclasses override this function to draw the layer. */
     public abstract void draw(RenderContext context);
 
-    /**
-     * Subclasses override this function to provide access to the size of the layer.
-     */
-    public abstract IntSize getSize();
-
-    /**
-     * Given the intrinsic size of the layer, returns the pixel boundaries of the layer rect.
-     */
-    protected RectF getBounds(RenderContext context, FloatSize size) {
-        float scaleFactor = context.zoomFactor / mResolution;
-        float x = mOrigin.x * scaleFactor, y = mOrigin.y * scaleFactor;
-        float width = size.width * scaleFactor, height = size.height * scaleFactor;
-        return new RectF(x, y, x + width, y + height);
+    /** Given the intrinsic size of the layer, returns the pixel boundaries of the layer rect. */
+    protected RectF getBounds(RenderContext context) {
+        return RectUtils.scale(new RectF(mPosition), context.zoomFactor / mResolution);
     }
 
     /**
@@ -109,68 +107,50 @@ public abstract class Layer {
      * may be overridden.
      */
     public Region getValidRegion(RenderContext context) {
-        return new Region(RectUtils.round(getBounds(context, new FloatSize(getSize()))));
+        return new Region(RectUtils.round(getBounds(context)));
     }
 
     /**
      * Call this before modifying the layer. Note that, for TileLayers, "modifying the layer"
      * includes altering the underlying CairoImage in any way. Thus you must call this function
      * before modifying the byte buffer associated with this layer.
-     * <p/>
+     *
      * This function may block, so you should never call this on the main UI thread.
      */
-    public void beginTransaction(LayerView aView) {
-        //if (mTransactionLock.isHeldByCurrentThread())
-        //    throw new RuntimeException("Nested transactions are not supported");
+    public void beginTransaction() {
+        if (mTransactionLock.isHeldByCurrentThread())
+            throw new RuntimeException("Nested transactions are not supported");
         mTransactionLock.lock();
-        mView = aView;
         mInTransaction = true;
         mNewResolution = mResolution;
     }
 
-    public void beginTransaction() {
-        beginTransaction(null);
-    }
-
-    /**
-     * Call this when you're done modifying the layer.
-     */
+    /** Call this when you're done modifying the layer. */
     public void endTransaction() {
         if (!mInTransaction)
             throw new RuntimeException("endTransaction() called outside a transaction");
         mInTransaction = false;
         mTransactionLock.unlock();
-
-        if (mView != null)
-            mView.requestRender();
     }
 
-    /**
-     * Returns true if the layer is currently in a transaction and false otherwise.
-     */
+    /** Returns true if the layer is currently in a transaction and false otherwise. */
     protected boolean inTransaction() {
         return mInTransaction;
     }
 
-    /**
-     * Returns the current layer origin.
-     */
-    public Point getOrigin() {
-        return mOrigin;
+    /** Returns the current layer position. */
+    public Rect getPosition() {
+        return mPosition;
     }
 
-    /**
-     * Sets the origin. Only valid inside a transaction.
-     */
-    public void setOrigin(Point newOrigin) {
+    /** Sets the position. Only valid inside a transaction. */
+    public void setPosition(Rect newPosition) {
         if (!mInTransaction)
-            throw new RuntimeException("setOrigin() is only valid inside a transaction");
-        mNewOrigin = newOrigin;
+            throw new RuntimeException("setPosition() is only valid inside a transaction");
+        mNewPosition = newPosition;
     }
 
-    /**
-     * Returns the current layer's resolution.
-     */
+    /** Returns the current layer's resolution. */
     public float getResolution() {
         return mResolution;
     }
@@ -179,8 +159,7 @@ public abstract class Layer {
      * Sets the layer resolution. This value is used to determine how many pixels per
      * device pixel this layer was rendered at. This will be reflected by scaling by
      * the reciprocal of the resolution in the layer's transform() function.
-     * Only valid inside a transaction.
-     */
+     * Only valid inside a transaction. */
     public void setResolution(float newResolution) {
         if (!mInTransaction)
             throw new RuntimeException("setResolution() is only valid inside a transaction");
@@ -193,22 +172,15 @@ public abstract class Layer {
      * superclass implementation. Returns false if there is still work to be done after this
      * update is complete.
      */
-    protected boolean performUpdates(RenderContext context) {
-
-        if (mNewOrigin != null) {
-            mOrigin = mNewOrigin;
-            mNewOrigin = null;
+    protected void performUpdates(RenderContext context) {
+        if (mNewPosition != null) {
+            mPosition = mNewPosition;
+            mNewPosition = null;
         }
         if (mNewResolution != 0.0f) {
             mResolution = mNewResolution;
             mNewResolution = 0.0f;
         }
-
-        return true;
-    }
-
-    protected boolean dimensionChangesPending() {
-        return (mNewOrigin != null) || (mNewResolution != 0.0f);
     }
 
     public static class RenderContext {
@@ -234,8 +206,8 @@ public abstract class Layer {
                 return false;
             }
             return RectUtils.fuzzyEquals(viewport, other.viewport)
-                    && pageSize.fuzzyEquals(other.pageSize)
-                    && FloatUtils.fuzzyEquals(zoomFactor, other.zoomFactor);
+                && pageSize.fuzzyEquals(other.pageSize)
+                && FloatUtils.fuzzyEquals(zoomFactor, other.zoomFactor);
         }
     }
 }
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java
index 54def4a..9c497f7 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java
@@ -42,8 +42,8 @@ import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
-import android.graphics.Point;
 import android.graphics.PointF;
+import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.Log;
 import android.view.GestureDetector;
@@ -55,172 +55,167 @@ import org.mozilla.gecko.ui.SimpleScaleGestureDetector;
 
 import java.util.Timer;
 import java.util.TimerTask;
+import java.util.regex.Pattern;
 
 /**
  * The layer controller manages a tile that represents the visible page. It does panning and
  * zooming natively by delegating to a panning/zooming controller. Touch events can be dispatched
  * to a higher-level view.
- * <p/>
+ *
  * Many methods require that the monitor be held, with a synchronized (controller) { ... } block.
  */
 public class LayerController {
-    /* The extra area on the sides of the page that we want to buffer to help with
-     * smooth, asynchronous scrolling. Depending on a device's support for NPOT
-     * textures, this may be rounded up to the nearest power of two.
-     */
     private static final String LOGTAG = "GeckoLayerController";
-    /* If the visible rect is within the danger zone (measured in pixels from each edge of a tile),
-     * we start aggressively redrawing to minimize checkerboarding. */
-    private static final int DANGER_ZONE_X = 75;
-    private static final int DANGER_ZONE_Y = 150;
-    /* The time limit for pages to respond with preventDefault on touchevents
-     * before we begin panning the page */
-    private static final int PREVENT_DEFAULT_TIMEOUT = 200;
+
     private Layer mRootLayer;                   /* The root layer. */
     private LayerView mView;                    /* The main rendering view. */
-
     private Context mContext;                   /* The current context. */
     private ViewportMetrics mViewportMetrics;   /* The current viewport metrics. */
     private boolean mWaitForTouchListeners;
+
     private PanZoomController mPanZoomController;
+    /*
+     * The panning and zooming controller, which interprets pan and zoom gestures for us and
+     * updates our visible rect appropriately.
+     */
+
     private OnTouchListener mOnTouchListener;       /* The touch listener. */
-    private GeckoLayerClient mLayerClient;               /* The layer client. */
+    private GeckoLayerClient mLayerClient;          /* The layer client. */
+
     /* The new color for the checkerboard. */
     private int mCheckerboardColor;
     private boolean mCheckerboardShouldShowChecks;
+
     private boolean mForceRedraw;
+
+    /* The extra area on the sides of the page that we want to buffer to help with
+     * smooth, asynchronous scrolling. Depending on a device's support for NPOT
+     * textures, this may be rounded up to the nearest power of two.
+     */
+    public static final IntSize MIN_BUFFER = new IntSize(512, 1024);
+
+    /* If the visible rect is within the danger zone (measured in pixels from each edge of a tile),
+     * we start aggressively redrawing to minimize checkerboarding. */
+    private static final int DANGER_ZONE_X = 75;
+    private static final int DANGER_ZONE_Y = 150;
+
+    /* The time limit for pages to respond with preventDefault on touchevents
+     * before we begin panning the page */
+    private int mTimeout = 200;
+
     private boolean allowDefaultActions = true;
-    private Timer allowDefaultTimer = null;
-    private boolean inTouchSession = false;
+    private Timer allowDefaultTimer =  null;
     private PointF initialTouchLocation = null;
 
+    private static Pattern sColorPattern;
+
     public LayerController(Context context) {
         mContext = context;
+
         mForceRedraw = true;
         mViewportMetrics = new ViewportMetrics();
         mPanZoomController = new PanZoomController(this);
         mView = new LayerView(context, this);
+        mCheckerboardShouldShowChecks = true;
     }
 
-    public void setForceRedraw() {
-        mForceRedraw = true;
+    public void onDestroy() {
     }
 
-    public GeckoLayerClient getLayerClient() {
-        return mLayerClient;
-    }
+    public void setRoot(Layer layer) { mRootLayer = layer; }
 
     public void setLayerClient(GeckoLayerClient layerClient) {
         mLayerClient = layerClient;
         layerClient.setLayerController(this);
     }
 
-    public Layer getRoot() {
-        return mRootLayer;
+    public void setForceRedraw() {
+        mForceRedraw = true;
     }
 
-    public void setRoot(Layer layer) {
-        mRootLayer = layer;
+    public Layer getRoot()                        { return mRootLayer; }
+    public LayerView getView()                    { return mView; }
+    public Context getContext()                   { return mContext; }
+    public ViewportMetrics getViewportMetrics()   { return mViewportMetrics; }
+
+    public RectF getViewport() {
+        return mViewportMetrics.getViewport();
     }
 
-    public LayerView getView() {
-        return mView;
+    public FloatSize getViewportSize() {
+        return mViewportMetrics.getSize();
     }
 
-    public Context getContext() {
-        return mContext;
+    public FloatSize getPageSize() {
+        return mViewportMetrics.getPageSize();
     }
 
-    public ViewportMetrics getViewportMetrics() {
-        return mViewportMetrics;
+    public PointF getOrigin() {
+        return mViewportMetrics.getOrigin();
     }
 
-    /**
-     * Sets the entire viewport metrics at once. This function does not notify the layer client or
-     * the pan/zoom controller, so you will need to call notifyLayerClientOfGeometryChange() or
-     * notifyPanZoomControllerOfGeometryChange() after calling this. You must hold the monitor
-     * while calling this.
-     */
-    public void setViewportMetrics(ViewportMetrics viewport) {
-        mViewportMetrics = new ViewportMetrics(viewport);
-        Log.d(LOGTAG, "setViewportMetrics: " + mViewportMetrics);
-        // this function may or may not be called on the UI thread,
-        // but repositionPluginViews must only be called on the UI thread.
-        //GeckoApp.mAppContext.runOnUiThread(new Runnable() {
-        //    public void run() {
-        //        GeckoApp.mAppContext.repositionPluginViews(false);
-        //    }
-        //});
-        mView.requestRender();
+    public float getZoomFactor() {
+        return mViewportMetrics.getZoomFactor();
     }
 
-    public RectF getViewport() {
-        return mViewportMetrics.getViewport();
+    public Bitmap getBackgroundPattern()    { return getDrawable("background"); }
+    public Bitmap getShadowPattern()        { return getDrawable("shadow"); }
+
+    public PanZoomController getPanZoomController()                                 { return mPanZoomController; }
+    public GestureDetector.OnGestureListener getGestureListener()                   { return mPanZoomController; }
+    public SimpleScaleGestureDetector.SimpleScaleGestureListener getScaleGestureListener() {
+        return mPanZoomController;
     }
+    public GestureDetector.OnDoubleTapListener getDoubleTapListener()               { return mPanZoomController; }
 
-    public FloatSize getViewportSize() {
-        return mViewportMetrics.getSize();
+    public Bitmap getDrawable(String name) {
+        Resources resources = mContext.getResources();
+        int resourceID = resources.getIdentifier(name, "drawable", mContext.getPackageName());
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inScaled = false;
+        return BitmapFactory.decodeResource(mContext.getResources(), resourceID, options);
     }
 
     /**
      * The view calls this function to indicate that the viewport changed size. It must hold the
      * monitor while calling it.
-     * <p/>
+     *
      * TODO: Refactor this to use an interface. Expose that interface only to the view and not
      * to the layer client. That way, the layer client won't be tempted to call this, which might
      * result in an infinite loop.
      */
     public void setViewportSize(FloatSize size) {
-        // Resize the viewport, and modify its zoom factor so that the page retains proportionally
-        // zoomed relative to the screen.
-        float oldHeight = mViewportMetrics.getSize().height;
-        float oldWidth = mViewportMetrics.getSize().width;
-        float oldZoomFactor = mViewportMetrics.getZoomFactor();
-        mViewportMetrics.setSize(size);
-
-        // if the viewport got larger (presumably because the vkb went away), and the page
-        // is smaller than the new viewport size, increase the page size so that the panzoomcontroller
-        // doesn't zoom in to make it fit (bug 718270). this page size change is in anticipation of
-        // gecko increasing the page size to match the new viewport size, which will happen the next
-        // time we get a draw update.
-        if (size.width >= oldWidth && size.height >= oldHeight) {
-            FloatSize pageSize = mViewportMetrics.getPageSize();
-            if (pageSize.width < size.width || pageSize.height < size.height) {
-                mViewportMetrics.setPageSize(new FloatSize(Math.max(pageSize.width, size.width),
-                        Math.max(pageSize.height, size.height)));
-            }
-        }
-
-        PointF newFocus = new PointF(size.width / 2.0f, size.height / 2.0f);
-        float newZoomFactor = size.width * oldZoomFactor / oldWidth;
-        mViewportMetrics.scaleTo(newZoomFactor, newFocus);
+        ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
+        viewportMetrics.setSize(size);
+        mViewportMetrics = new ViewportMetrics(viewportMetrics);
 
-        Log.d(LOGTAG, "setViewportSize: " + mViewportMetrics);
-        setForceRedraw();
-
-        if (mLayerClient != null)
+        if (mLayerClient != null) {
             mLayerClient.viewportSizeChanged();
+        }
+    }
+
+    /** Scrolls the viewport by the given offset. You must hold the monitor while calling this. */
+    public void scrollBy(PointF point) {
+        ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
+        PointF origin = viewportMetrics.getOrigin();
+        origin.offset(point.x, point.y);
+        viewportMetrics.setOrigin(origin);
+        mViewportMetrics = new ViewportMetrics(viewportMetrics);
 
         notifyLayerClientOfGeometryChange();
-        mPanZoomController.abortAnimation();
         mView.requestRender();
     }
 
-    public FloatSize getPageSize() {
-        return mViewportMetrics.getPageSize();
-    }
-
-    /**
-     * Sets the current page size. You must hold the monitor while calling this.
-     */
+    /** Sets the current page size. You must hold the monitor while calling this. */
     public void setPageSize(FloatSize size) {
         if (mViewportMetrics.getPageSize().fuzzyEquals(size))
             return;
 
-        mViewportMetrics.setPageSize(size);
-        Log.d(LOGTAG, "setPageSize: " + mViewportMetrics);
+        ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
+        viewportMetrics.setPageSize(size);
+        mViewportMetrics = new ViewportMetrics(viewportMetrics);
 
-        // Page size is owned by the LayerClient, so no need to notify it of
+        // Page size is owned by the layer client, so no need to notify it of
         // this change.
 
         mView.post(new Runnable() {
@@ -231,59 +226,15 @@ public class LayerController {
         });
     }
 
-    public PointF getOrigin() {
-        return mViewportMetrics.getOrigin();
-    }
-
-    public float getZoomFactor() {
-        return mViewportMetrics.getZoomFactor();
-    }
-
-    public Bitmap getBackgroundPattern() {
-        return getDrawable("background");
-    }
-
-    public Bitmap getShadowPattern() {
-        return getDrawable("shadow");
-    }
-
-    public PanZoomController getPanZoomController() {
-        return mPanZoomController;
-    }
-
-    public GestureDetector.OnGestureListener getGestureListener() {
-        return mPanZoomController;
-    }
-
-    public SimpleScaleGestureDetector.SimpleScaleGestureListener getScaleGestureListener() {
-        return mPanZoomController;
-    }
-
-    public GestureDetector.OnDoubleTapListener getDoubleTapListener() {
-        return mPanZoomController;
-    }
-
-    public Bitmap getDrawable(String name) {
-        Resources resources = mContext.getResources();
-        int resourceID = resources.getIdentifier(name, "drawable", mContext.getPackageName());
-        BitmapFactory.Options options = new BitmapFactory.Options();
-        options.inScaled = false;
-        return BitmapFactory.decodeResource(mContext.getResources(), resourceID, options);
-    }
-
     /**
-     * Scrolls the viewport by the given offset. You must hold the monitor while calling this.
+     * Sets the entire viewport metrics at once. This function does not notify the layer client or
+     * the pan/zoom controller, so you will need to call notifyLayerClientOfGeometryChange() or
+     * notifyPanZoomControllerOfGeometryChange() after calling this. You must hold the monitor
+     * while calling this.
      */
-    public void scrollBy(PointF point) {
-        if (point.equals(0,0)) {
-            return;
-        }
-
-        PointF origin = mViewportMetrics.getOrigin();
-        origin.offset(point.x, point.y);
-        mViewportMetrics.setOrigin(origin);
-        Log.d(LOGTAG, "scrollBy: " + mViewportMetrics);
-        notifyLayerClientOfGeometryChange();
+    public void setViewportMetrics(ViewportMetrics viewport) {
+        mViewportMetrics = new ViewportMetrics(viewport);
+        Log.d(LOGTAG, "setViewportMetrics: " + mViewportMetrics);
         mView.requestRender();
     }
 
@@ -292,7 +243,9 @@ public class LayerController {
      * scale operation. You must hold the monitor while calling this.
      */
     public void scaleWithFocus(float zoomFactor, PointF focus) {
-        mViewportMetrics.scaleTo(zoomFactor, focus);
+        ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
+        viewportMetrics.scaleTo(zoomFactor, focus);
+        mViewportMetrics = new ViewportMetrics(viewportMetrics);
         Log.d(LOGTAG, "scaleWithFocus: " + mViewportMetrics + "; zf=" + zoomFactor);
 
         // We assume the zoom level will only be modified by the
@@ -301,9 +254,7 @@ public class LayerController {
         mView.requestRender();
     }
 
-    public boolean post(Runnable action) {
-        return mView.post(action);
-    }
+    public boolean post(Runnable action) { return mView.post(action); }
 
     public void setOnTouchListener(OnTouchListener onTouchListener) {
         mOnTouchListener = onTouchListener;
@@ -314,14 +265,11 @@ public class LayerController {
      * the geometry changed.
      */
     public void notifyLayerClientOfGeometryChange() {
-        if (mLayerClient != null) {
+        if (mLayerClient != null)
             mLayerClient.geometryChanged();
-        }
     }
 
-    /**
-     * Aborts any pan/zoom animation that is currently in progress.
-     */
+    /** Aborts any pan/zoom animation that is currently in progress. */
     public void abortPanZoomAnimation() {
         if (mPanZoomController != null) {
             mView.post(new Runnable() {
@@ -337,24 +285,16 @@ public class LayerController {
      * would prefer that the action didn't take place.
      */
     public boolean getRedrawHint() {
-        // FIXME: Allow redraw while a finger is down, but only if we're about to checkerboard.
-        // This requires fixing aboutToCheckerboard() to know about the new buffer size.
-
         if (mForceRedraw) {
             mForceRedraw = false;
             return true;
         }
 
-        return mPanZoomController.getRedrawHint();
-    }
-
-    private RectF getTileRect() {
-        if (mRootLayer == null)
-            return new RectF();
+        if (!mPanZoomController.getRedrawHint()) {
+            return false;
+        }
 
-        float x = mRootLayer.getOrigin().x, y = mRootLayer.getOrigin().y;
-        IntSize layerSize = mRootLayer.getSize();
-        return new RectF(x, y, x + layerSize.width, y + layerSize.height);
+        return aboutToCheckerboard();
     }
 
     // Returns true if a checkerboard is about to be visible.

... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list