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

Tomaž Vajngerl tomaz.vajngerl at collabora.com
Fri Sep 26 14:15:57 PDT 2014


 android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java                    |   96 ------
 android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitTileProvider.java              |   30 -
 android/experimental/LOAndroid3/src/java/org/libreoffice/MockTileProvider.java               |   11 
 android/experimental/LOAndroid3/src/java/org/libreoffice/TileProvider.java                   |    4 
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/DisplayPortCalculator.java    |   48 +--
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GLController.java             |   48 ---
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java         |   94 ++---
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ImmutableViewportMetrics.java |   58 ++-
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/Layer.java                    |   10 
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java          |   38 --
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java            |   37 --
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java                |    7 
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/MultiTileLayer.java           |  160 +++++++---
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/NinePatchTileLayer.java       |   28 -
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScreenshotLayer.java          |  147 +--------
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScrollbarLayer.java           |   12 
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/SingleTileLayer.java          |    2 
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/SubTile.java                  |   14 
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TouchEventHandler.java        |   84 ++---
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewportMetrics.java          |  113 +++----
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java                      |   19 -
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java         |  129 ++++----
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/SubdocumentScrollHelper.java   |    3 
 23 files changed, 529 insertions(+), 663 deletions(-)

New commits:
commit 93e7ffbd1bd38ae9fad63a0a8abd2deb7fab9543
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Fri Sep 26 23:15:29 2014 +0200

    android: PanZoomControler updates from Fennec
    
    Change-Id: Ic338221ced6133771af8f46ae4a257e043a1709f

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 59fc1fe..8a39b82 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
@@ -80,6 +80,10 @@ public class LayerController {
         layerClient.setLayerController(this);
     }
 
+    public void destroy() {
+        mPanZoomController.destroy();
+    }
+
     public void setForceRedraw() {
         mForceRedraw = true;
     }
@@ -93,26 +97,10 @@ public class LayerController {
         return mViewportMetrics.getViewport();
     }
 
-    public RectF getCssViewport() {
-        return mViewportMetrics.getCssViewport();
-    }
-
     public FloatSize getViewportSize() {
         return mViewportMetrics.getSize();
     }
 
-    public RectF getPageRect() {
-        return mViewportMetrics.getPageRect();
-    }
-
-    public RectF getCssPageRect() {
-        return mViewportMetrics.getCssPageRect();
-    }
-
-    public PointF getOrigin() {
-        return mViewportMetrics.getOrigin();
-    }
-
     public float getZoomFactor() {
         return mViewportMetrics.zoomFactor;
     }
@@ -321,11 +309,6 @@ public class LayerController {
 
     public void setAllowZoom(final boolean aValue) {
         mAllowZoom = aValue;
-        mView.post(new Runnable() {
-            public void run() {
-                mView.getTouchEventHandler().setDoubleTapEnabled(aValue);
-            }
-        });
     }
 
     public boolean getAllowZoom() {
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 f7d8c4e..c6f11f4 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
@@ -188,11 +188,9 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
     @Override
     protected void finalize() throws Throwable {
         try {
-            if (mCoordByteBuffer != null) {
-                DirectBufferAllocator.free(mCoordByteBuffer);
-                mCoordByteBuffer = null;
-                mCoordBuffer = null;
-            }
+            DirectBufferAllocator.free(mCoordByteBuffer);
+            mCoordByteBuffer = null;
+            mCoordBuffer = null;
         } finally {
             super.finalize();
         }
@@ -333,14 +331,6 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
             moveFrameRateLayer(width, height);
         }
 
-        // updating the state in the view/controller/client should be
-        // done on the main UI thread, not the GL renderer thread
-        mView.post(new Runnable() {
-            public void run() {
-                mView.setViewportSize(new IntSize(width, height));
-            }
-        });
-
         /* TODO: Throw away tile images? */
     }
 
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java
index 9c6a616..18f6328 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java
@@ -74,6 +74,13 @@ public class LayerView extends SurfaceView implements SurfaceHolder.Callback {
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
+        if (event.getActionMasked() == MotionEvent.ACTION_DOWN)
+            requestFocus();
+        return mTouchEventHandler.handleEvent(event);
+    }
+
+    @Override
+    public boolean onHoverEvent(MotionEvent event) {
         return mTouchEventHandler.handleEvent(event);
     }
 
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TouchEventHandler.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TouchEventHandler.java
index 78a141e..b93b5f0 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TouchEventHandler.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TouchEventHandler.java
@@ -7,6 +7,7 @@ package org.mozilla.gecko.gfx;
 
 import android.content.Context;
 import android.os.SystemClock;
+import android.util.Log;
 import android.view.GestureDetector;
 import android.view.MotionEvent;
 import android.view.View.OnTouchListener;
@@ -58,7 +59,6 @@ public final class TouchEventHandler {
     private final GestureDetector mGestureDetector;
     private final SimpleScaleGestureDetector mScaleGestureDetector;
     private final PanZoomController mPanZoomController;
-    private final GestureDetector.OnDoubleTapListener mDoubleTapListener;
 
     // the queue of events that we are holding on to while waiting for a preventDefault
     // notification
@@ -133,8 +133,7 @@ public final class TouchEventHandler {
         mListenerTimeoutProcessor = new ListenerTimeoutProcessor();
         mDispatchEvents = true;
 
-        mDoubleTapListener = controller.getDoubleTapListener();
-        setDoubleTapEnabled(true);
+        mGestureDetector.setOnDoubleTapListener(controller.getDoubleTapListener());
     }
 
     /* This function MUST be called on the UI thread */
@@ -146,6 +145,12 @@ public final class TouchEventHandler {
             return true;
         }
 
+        // if this is a hover event just notify gecko, we don't have any interest in the java layer.
+        if (isHoverEvent(event)) {
+            mOnTouchListener.onTouch(mView, event);
+            return true;
+        }
+
         if (isDownEvent(event)) {
             // this is the start of a new block of events! whee!
             mHoldInQueue = mWaitForTouchListeners;
@@ -159,18 +164,20 @@ public final class TouchEventHandler {
                 // other blocks waiting in the queue, then we should let the pan/zoom controller
                 // know we are waiting for the touch listeners to run
                 if (mEventQueue.isEmpty()) {
-                    mPanZoomController.waitingForTouchListeners(event);
+                    mPanZoomController.startingNewEventBlock(event, true);
                 }
-                // if we're holding the events in the queue, set the timeout so that
-                // we dispatch these events if we don't get a default-prevented notification
-                mView.postDelayed(mListenerTimeoutProcessor, EVENT_LISTENER_TIMEOUT);
             } else {
-                // if we're not holding these events, then we still need to pretend like
-                // we did and had a ListenerTimeoutProcessor fire so that when we get
-                // the default-prevented notification for this block, it doesn't accidentally
-                // act upon some other block
-                mProcessingBalance++;
+                // we're not going to be holding this block of events in the queue, but we need
+                // a marker of some sort so that the processEventBlock loop deals with the blocks
+                // in the right order as notifications come in. we use a single null event in
+                // the queue as a placeholder for a block of events that has already been dispatched.
+                mEventQueue.add(null);
+                mPanZoomController.startingNewEventBlock(event, false);
             }
+
+            // set the timeout so that we dispatch these events and update mProcessingBalance
+            // if we don't get a default-prevented notification
+            mView.postDelayed(mListenerTimeoutProcessor, EVENT_LISTENER_TIMEOUT);
         }
 
         // if we need to hold the events, add it to the queue. if we need to dispatch
@@ -213,11 +220,6 @@ public final class TouchEventHandler {
     }
 
     /* This function MUST be called on the UI thread. */
-    public void setDoubleTapEnabled(boolean aValue) {
-        mGestureDetector.setOnDoubleTapListener(aValue ? mDoubleTapListener : null);
-    }
-
-    /* This function MUST be called on the UI thread. */
     public void setWaitForTouchListeners(boolean aValue) {
         mWaitForTouchListeners = aValue;
     }
@@ -227,6 +229,11 @@ public final class TouchEventHandler {
         mOnTouchListener = onTouchListener;
     }
 
+    private boolean isHoverEvent(MotionEvent event) {
+        int action = (event.getAction() & MotionEvent.ACTION_MASK);
+        return (action == MotionEvent.ACTION_HOVER_ENTER || action == MotionEvent.ACTION_HOVER_MOVE || action == MotionEvent.ACTION_HOVER_EXIT);
+    }
+
     private boolean isDownEvent(MotionEvent event) {
         int action = (event.getAction() & MotionEvent.ACTION_MASK);
         return (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN);
@@ -242,16 +249,7 @@ public final class TouchEventHandler {
      */
     private void dispatchEvent(MotionEvent event) {
         if (mGestureDetector.onTouchEvent(event)) {
-            // An up/cancel event should get passed to both detectors, in
-            // case it comes from a pointer the scale detector is tracking.
-            switch (event.getAction() & MotionEvent.ACTION_MASK) {
-                case MotionEvent.ACTION_POINTER_UP:
-                case MotionEvent.ACTION_UP:
-                case MotionEvent.ACTION_CANCEL:
-                    break;
-                default:
-                    return;
-            }
+            return;
         }
         mScaleGestureDetector.onTouchEvent(event);
         if (mScaleGestureDetector.isInProgress()) {
@@ -272,6 +270,11 @@ public final class TouchEventHandler {
             dispatchEvent(MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0));
         }
 
+        if (mEventQueue.isEmpty()) {
+            Log.e(LOGTAG, "Unexpected empty event queue in processEventBlock!", new Exception());
+            return;
+        }
+
         // the odd loop condition is because the first event in the queue will
         // always be a DOWN or POINTER_DOWN event, and we want to process all
         // the events in the queue starting at that one, up to but not including
@@ -279,15 +282,19 @@ public final class TouchEventHandler {
 
         MotionEvent event = mEventQueue.poll();
         while (true) {
-            // for each event we process, only dispatch it if the block hasn't been
-            // default-prevented.
-            if (allowDefaultAction) {
-                dispatchEvent(event);
-            } else if (touchFinished(event)) {
-                mPanZoomController.preventedTouchFinished();
+            // event being null here is valid and represents a block of events
+            // that has already been dispatched.
+
+            if (event != null) {
+                // for each event we process, only dispatch it if the block hasn't been
+                // default-prevented.
+                if (allowDefaultAction) {
+                    dispatchEvent(event);
+                } else if (touchFinished(event)) {
+                    mPanZoomController.preventedTouchFinished();
+                }
             }
-            event = mEventQueue.peek();
-            if (event == null) {
+            if (mEventQueue.isEmpty()) {
                 // we have processed the backlog of events, and are all caught up.
                 // now we can set clear the hold flag and set the dispatch flag so
                 // that the handleEvent() function can do the right thing for all
@@ -297,10 +304,13 @@ public final class TouchEventHandler {
                 mDispatchEvents = allowDefaultAction;
                 break;
             }
-            if (isDownEvent(event)) {
+            event = mEventQueue.peek();
+            if (event == null || isDownEvent(event)) {
                 // we have finished processing the block we were interested in.
                 // now we wait for the next call to processEventBlock
-                mPanZoomController.waitingForTouchListeners(event);
+                if (event != null) {
+                    mPanZoomController.startingNewEventBlock(event, true);
+                }
                 break;
             }
             // pop the event we peeked above, as it is still part of the block and
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 0cb83ef..243fa75 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
@@ -14,6 +14,7 @@ import android.view.MotionEvent;
 
 import org.libreoffice.LOKitShell;
 import org.libreoffice.LibreOfficeMainActivity;
+import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.gfx.LayerController;
 import org.mozilla.gecko.gfx.ViewportMetrics;
 import org.mozilla.gecko.util.FloatUtils;
@@ -117,7 +118,19 @@ public class PanZoomController
         mMainThread = LibreOfficeMainActivity.mAppContext.getMainLooper().getThread();
         checkMainThread();
 
-        mState = PanZoomState.NOTHING;
+        setState(PanZoomState.NOTHING);
+    }
+
+    public void destroy() {
+        mSubscroller.destroy();
+    }
+
+    private void setState(PanZoomState state) {
+        mState = state;
+    }
+
+    private ImmutableViewportMetrics getMetrics() {
+        return mController.getViewportMetrics();
     }
 
     // for debugging bug 713011; it can be taken out once that is resolved.
@@ -171,7 +184,7 @@ public class PanZoomController
         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
-            mState = PanZoomState.NOTHING;
+            setState(PanZoomState.NOTHING);
             // fall through
         case NOTHING:
             // Don't do animations here; they're distracting and can cause flashes on page
@@ -185,14 +198,14 @@ public class PanZoomController
     }
 
     /** This function must be called on the UI thread. */
-    public void waitingForTouchListeners(MotionEvent event) {
+    public void startingNewEventBlock(MotionEvent event, boolean waitingForTouchListeners) {
         checkMainThread();
-        if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
+        mSubscroller.cancel();
+        if (waitingForTouchListeners && (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
             // this is the first touch point going down, so we enter the pending state
-            mSubscroller.cancel();
             // seting the state will kill any animations in progress, possibly leaving
             // the page in overscroll
-            mState = PanZoomState.WAITING_LISTENERS;
+            setState(PanZoomState.WAITING_LISTENERS);
         }
     }
 
@@ -203,7 +216,6 @@ public class PanZoomController
             // if we enter here, we just finished a block of events whose default actions
             // were prevented by touch listeners. Now there are no touch points left, so
             // we need to reset our state and re-bounce because we might be in overscroll
-            mState = PanZoomState.NOTHING;
             bounce();
         }
     }
@@ -213,7 +225,7 @@ public class PanZoomController
         if (mState == PanZoomState.NOTHING) {
             synchronized (mController) {
                 ViewportMetrics validated = getValidViewportMetrics();
-                if (! (new ViewportMetrics(mController.getViewportMetrics())).fuzzyEquals(validated)) {
+                if (! (new ViewportMetrics(getMetrics())).fuzzyEquals(validated)) {
                     // page size changed such that we are now in overscroll. snap to the
                     // the nearest valid viewport
                     mController.setViewportMetrics(validated);
@@ -231,7 +243,6 @@ public class PanZoomController
         // user is taking control of movement, so stop
         // any auto-movement we have going
         stopAnimationTimer();
-        mSubscroller.cancel();
 
         switch (mState) {
         case ANIMATED_ZOOM:
@@ -285,14 +296,14 @@ public class PanZoomController
             return true;
 
         case PANNING_HOLD_LOCKED:
-            mState = PanZoomState.PANNING_LOCKED;
+            setState(PanZoomState.PANNING_LOCKED);
             // fall through
         case PANNING_LOCKED:
             track(event);
             return true;
 
         case PANNING_HOLD:
-            mState = PanZoomState.PANNING;
+            setState(PanZoomState.PANNING);
             // fall through
         case PANNING:
             track(event);
@@ -322,7 +333,6 @@ public class PanZoomController
             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
@@ -333,12 +343,12 @@ public class PanZoomController
         case PANNING_LOCKED:
         case PANNING_HOLD:
         case PANNING_HOLD_LOCKED:
-            mState = PanZoomState.FLING;
+            setState(PanZoomState.FLING);
             fling();
             return true;
 
         case PINCHING:
-            mState = PanZoomState.NOTHING;
+            setState(PanZoomState.NOTHING);
             return true;
         }
         Log.e(LOGTAG, "Unhandled case " + mState + " in onTouchEnd");
@@ -357,7 +367,6 @@ public class PanZoomController
         }
 
         cancelTouch();
-        mState = PanZoomState.NOTHING;
         // ensure we snap back if we're overscrolled
         bounce();
         return false;
@@ -366,7 +375,7 @@ public class PanZoomController
     private void startTouch(float x, float y, long time) {
         mX.startTouch(x);
         mY.startTouch(y);
-        mState = PanZoomState.TOUCHING;
+        setState(PanZoomState.TOUCHING);
         mLastEventTime = time;
     }
 
@@ -384,12 +393,12 @@ public class PanZoomController
 
         if (angle < AXIS_LOCK_ANGLE || angle > (Math.PI - AXIS_LOCK_ANGLE)) {
             mY.setScrollingDisabled(true);
-            mState = PanZoomState.PANNING_LOCKED;
+            setState(PanZoomState.PANNING_LOCKED);
         } else if (Math.abs(angle - (Math.PI / 2)) < AXIS_LOCK_ANGLE) {
             mX.setScrollingDisabled(true);
-            mState = PanZoomState.PANNING_LOCKED;
+            setState(PanZoomState.PANNING_LOCKED);
         } else {
-            mState = PanZoomState.PANNING;
+            setState(PanZoomState.PANNING);
         }
     }
 
@@ -425,13 +434,13 @@ public class PanZoomController
 
         if (stopped()) {
             if (mState == PanZoomState.PANNING) {
-                mState = PanZoomState.PANNING_HOLD;
+                setState(PanZoomState.PANNING_HOLD);
             } else if (mState == PanZoomState.PANNING_LOCKED) {
-                mState = PanZoomState.PANNING_HOLD_LOCKED;
+                setState(PanZoomState.PANNING_HOLD_LOCKED);
             } else {
                 // should never happen, but handle anyway for robustness
                 Log.e(LOGTAG, "Impossible case " + mState + " when stopped in track");
-                mState = PanZoomState.PANNING_HOLD_LOCKED;
+                setState(PanZoomState.PANNING_HOLD_LOCKED);
             }
         }
 
@@ -456,9 +465,9 @@ public class PanZoomController
     private void bounce(ViewportMetrics metrics) {
         stopAnimationTimer();
 
-        ViewportMetrics bounceStartMetrics = new ViewportMetrics(mController.getViewportMetrics());
+        ViewportMetrics bounceStartMetrics = new ViewportMetrics(getMetrics());
         if (bounceStartMetrics.fuzzyEquals(metrics)) {
-            mState = PanZoomState.NOTHING;
+            setState(PanZoomState.NOTHING);
             return;
         }
 
@@ -472,7 +481,7 @@ public class PanZoomController
 
     /* Performs a bounce-back animation to the nearest valid viewport metrics. */
     private void bounce() {
-        mState = PanZoomState.BOUNCE;
+        setState(PanZoomState.BOUNCE);
         bounce(getValidViewportMetrics());
     }
 
@@ -596,7 +605,7 @@ public class PanZoomController
             /* Finally, if there's nothing else to do, complete the animation and go to sleep. */
             finishBounce();
             finishAnimation();
-            mState = PanZoomState.NOTHING;
+            setState(PanZoomState.NOTHING);
         }
 
         /* Performs one frame of a bounce animation. */
@@ -664,7 +673,7 @@ public class PanZoomController
                 bounce();
             } else {
                 finishAnimation();
-                mState = PanZoomState.NOTHING;
+                setState(PanZoomState.NOTHING);
             }
         }
     }
@@ -672,7 +681,6 @@ public class PanZoomController
     private void finishAnimation() {
         checkMainThread();
 
-        Log.d(LOGTAG, "Finishing animation at " + mController.getViewportMetrics());
         stopAnimationTimer();
 
         // Force a viewport synchronisation
@@ -682,12 +690,10 @@ public class PanZoomController
 
     /* Returns the nearest viewport metrics with no overscroll visible. */
     private ViewportMetrics getValidViewportMetrics() {
-        return getValidViewportMetrics(new ViewportMetrics(mController.getViewportMetrics()));
+        return getValidViewportMetrics(new ViewportMetrics(getMetrics()));
     }
 
     private ViewportMetrics getValidViewportMetrics(ViewportMetrics viewportMetrics) {
-        Log.d(LOGTAG, "generating valid viewport using " + viewportMetrics);
-
         /* First, we adjust the zoom factor so that we can make no overscrolled area visible. */
         float zoomFactor = viewportMetrics.getZoomFactor();
         RectF pageRect = viewportMetrics.getPageRect();
@@ -740,7 +746,6 @@ public class PanZoomController
 
         /* Now we pan to the right origin. */
         viewportMetrics.setViewport(viewportMetrics.getClampedViewport());
-        Log.d(LOGTAG, "generated valid viewport as " + viewportMetrics);
 
         return viewportMetrics;
     }
@@ -748,25 +753,25 @@ public class PanZoomController
     private class AxisX extends Axis {
         AxisX(SubdocumentScrollHelper subscroller) { super(subscroller); }
         @Override
-        public float getOrigin() { return mController.getOrigin().x; }
+        public float getOrigin() { return getMetrics().viewportRectLeft; }
         @Override
-        protected float getViewportLength() { return mController.getViewportSize().width; }
+        protected float getViewportLength() { return getMetrics().getWidth(); }
         @Override
-        protected float getPageStart() { return mController.getPageRect().left; }
+        protected float getPageStart() { return getMetrics().pageRectLeft; }
         @Override
-        protected float getPageLength() { return mController.getPageRect().width(); }
+        protected float getPageLength() { return getMetrics().getPageWidth(); }
     }
 
     private class AxisY extends Axis {
         AxisY(SubdocumentScrollHelper subscroller) { super(subscroller); }
         @Override
-        public float getOrigin() { return mController.getOrigin().y; }
+        public float getOrigin() { return getMetrics().viewportRectTop; }
         @Override
-        protected float getViewportLength() { return mController.getViewportSize().height; }
+        protected float getViewportLength() { return getMetrics().getHeight(); }
         @Override
-        protected float getPageStart() { return mController.getPageRect().top; }
+        protected float getPageStart() { return getMetrics().pageRectTop; }
         @Override
-        protected float getPageLength() { return mController.getPageRect().height(); }
+        protected float getPageLength() { return getMetrics().getPageHeight(); }
     }
 
     /*
@@ -774,15 +779,13 @@ public class PanZoomController
      */
     @Override
     public boolean onScaleBegin(SimpleScaleGestureDetector detector) {
-        Log.d(LOGTAG, "onScaleBegin in " + mState);
-
         if (mState == PanZoomState.ANIMATED_ZOOM)
             return false;
 
         if (!mController.getAllowZoom())
             return false;
 
-        mState = PanZoomState.PINCHING;
+        setState(PanZoomState.PINCHING);
         mLastZoomFocus = new PointF(detector.getFocusX(), detector.getFocusY());
         cancelTouch();
 
@@ -791,9 +794,6 @@ public class PanZoomController
 
     @Override
     public boolean onScale(SimpleScaleGestureDetector detector) {
-        Log.d(LOGTAG, "onScale in state " + mState);
-
-
         if (mState != PanZoomState.PINCHING)
             return false;
 
@@ -857,8 +857,6 @@ public class PanZoomController
 
     @Override
     public void onScaleEnd(SimpleScaleGestureDetector detector) {
-        Log.d(LOGTAG, "onScaleEnd in " + mState);
-
         if (mState == PanZoomState.ANIMATED_ZOOM)
             return;
 
@@ -892,23 +890,17 @@ public class PanZoomController
     @Override
     public boolean onSingleTapUp(MotionEvent motionEvent) {
         // When zooming is enabled, wait to see if there's a double-tap.
-        if (mController.getAllowZoom())
-            return false;
-        return true;
+        return false;
     }
 
     @Override
     public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
         // When zooming is disabled, we handle this in onSingleTapUp.
-        if (!mController.getAllowZoom())
-            return false;
         return true;
     }
 
     @Override
     public boolean onDoubleTap(MotionEvent motionEvent) {
-        if (!mController.getAllowZoom())
-            return false;
         return true;
     }
 
@@ -922,7 +914,7 @@ public class PanZoomController
      * pixels.
      */
     private boolean animatedZoomTo(RectF zoomToRect) {
-        mState = PanZoomState.ANIMATED_ZOOM;
+        setState(PanZoomState.ANIMATED_ZOOM);
         final float startZoom = mController.getZoomFactor();
 
         RectF viewport = mController.getViewport();
@@ -948,7 +940,7 @@ public class PanZoomController
 
         float finalZoom = viewport.width() / zoomToRect.width();
 
-        ViewportMetrics finalMetrics = new ViewportMetrics(mController.getViewportMetrics());
+        ViewportMetrics finalMetrics = new ViewportMetrics(getMetrics());
         finalMetrics.setOrigin(new PointF(zoomToRect.left * finalMetrics.getZoomFactor(),
                                           zoomToRect.top * finalMetrics.getZoomFactor()));
         finalMetrics.scaleTo(finalZoom, new PointF(0.0f, 0.0f));
@@ -960,4 +952,10 @@ public class PanZoomController
         bounce(finalMetrics);
         return true;
     }
+
+    /** This function must be called from the UI thread. */
+    public void abortPanning() {
+        checkMainThread();
+        bounce();
+    }
 }
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/SubdocumentScrollHelper.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/SubdocumentScrollHelper.java
index 8e7de55..0e74456 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/SubdocumentScrollHelper.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/SubdocumentScrollHelper.java
@@ -45,6 +45,9 @@ class SubdocumentScrollHelper {
         mPendingDisplacement = new PointF();
     }
 
+    void destroy() {
+    }
+
     boolean scrollBy(PointF displacement) {
         if (! mOverridePanning) {
             return false;
commit dc169a05cfa11bbf63294b4a930f51c330d363ed
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Fri Sep 26 22:14:11 2014 +0200

    android: remove unused screenSize (Fennec import)
    
    Change-Id: I0b8d1b29ab3219bc75de6c087e410523c18dbeef

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 9700d2a..3eab452 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
@@ -157,17 +157,15 @@ public abstract class Layer {
     public static class RenderContext {
         public final RectF viewport;
         public final RectF pageRect;
-        public final IntSize screenSize;
         public final float zoomFactor;
         public final int positionHandle;
         public final int textureHandle;
         public final FloatBuffer coordBuffer;
 
-        public RenderContext(RectF aViewport, RectF aPageRect, IntSize aScreenSize, float aZoomFactor,
+        public RenderContext(RectF aViewport, RectF aPageRect, float aZoomFactor,
                              int aPositionHandle, int aTextureHandle, FloatBuffer aCoordBuffer) {
             viewport = aViewport;
             pageRect = aPageRect;
-            screenSize = aScreenSize;
             zoomFactor = aZoomFactor;
             positionHandle = aPositionHandle;
             textureHandle = aTextureHandle;
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 c0bef87..f7d8c4e 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
@@ -319,7 +319,7 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
     }
 
     private RenderContext createContext(RectF viewport, RectF pageRect, float zoomFactor) {
-        return new RenderContext(viewport, pageRect, new IntSize(mSurfaceWidth, mSurfaceHeight), zoomFactor, mPositionHandle, mTextureHandle,
+        return new RenderContext(viewport, pageRect, zoomFactor, mPositionHandle, mTextureHandle,
                                  mCoordBuffer);
     }
 
commit c6ed6a2db827f5432d7ee33548ba1ee0c3a3691d
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Fri Sep 26 22:12:34 2014 +0200

    android: change pageSize to pageRect (Fennec import)
    
    Change-Id: Ia3958b174e7190171cbd006550e47677a1ba0966

diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
index 2b77143..42935562 100644
--- a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
@@ -2,9 +2,9 @@ package org.libreoffice;
 
 import android.graphics.Bitmap;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.util.Log;
 
-import org.mozilla.gecko.gfx.FloatSize;
 import org.mozilla.gecko.gfx.GeckoLayerClient;
 import org.mozilla.gecko.gfx.ViewportMetrics;
 
@@ -27,9 +27,9 @@ public class LOKitThread extends Thread {
         int pageWidth = mTileProvider.getPageWidth();
         int pageHeight = mTileProvider.getPageHeight();
 
-        FloatSize size = new FloatSize(pageWidth, pageHeight);
+        RectF rect = new RectF(0, 0, pageWidth, pageHeight);
         mViewportMetrics = new ViewportMetrics();
-        mViewportMetrics.setPageSize(size, size);
+        mViewportMetrics.setPageRect(rect, rect);
 
         GeckoLayerClient layerClient = mApplication.getLayerClient();
 
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/DisplayPortCalculator.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/DisplayPortCalculator.java
index 88507e5..4298586 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/DisplayPortCalculator.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/DisplayPortCalculator.java
@@ -36,7 +36,7 @@ final class DisplayPortCalculator {
     private static final String PREF_DISPLAYPORT_VB_DANGER_Y_INCR = "gfx.displayport.strategy_vb.danger_y_incr";
     private static final String PREF_DISPLAYPORT_PB_VELOCITY_THRESHOLD = "gfx.displayport.strategy_pb.threshold";
 
-    private static DisplayPortStrategy sStrategy = new DynamicResolutionStrategy(null);
+    private static DisplayPortStrategy sStrategy = new VelocityBiasStrategy(null);
 
     static DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
         return sStrategy.calculate(metrics, (velocity == null ? ZERO_VELOCITY : velocity));
@@ -131,15 +131,15 @@ final class DisplayPortCalculator {
      */
     private static FloatSize reshapeForPage(float width, float height, ImmutableViewportMetrics metrics) {
         // figure out how much of the desired buffer amount we can actually use on the horizontal axis
-        float usableWidth = Math.min(width, metrics.pageSizeWidth);
+        float usableWidth = Math.min(width, metrics.getPageWidth());
         // if we reduced the buffer amount on the horizontal axis, we should take that saved memory and
         // use it on the vertical axis
         float extraUsableHeight = (float)Math.floor(((width - usableWidth) * height) / usableWidth);
-        float usableHeight = Math.min(height + extraUsableHeight, metrics.pageSizeHeight);
+        float usableHeight = Math.min(height + extraUsableHeight, metrics.getPageHeight());
         if (usableHeight < height && usableWidth == width) {
             // and the reverse - if we shrunk the buffer on the vertical axis we can add it to the horizontal
             float extraUsableWidth = (float)Math.floor(((height - usableHeight) * width) / usableHeight);
-            usableWidth = Math.min(width + extraUsableWidth, metrics.pageSizeWidth);
+            usableWidth = Math.min(width + extraUsableWidth, metrics.getPageWidth());
         }
         return new FloatSize(usableWidth, usableHeight);
     }
@@ -155,11 +155,7 @@ final class DisplayPortCalculator {
         float dangerZoneY = metrics.getHeight() * dangerZoneYMultiplier;
         rect = RectUtils.expand(rect, dangerZoneX, dangerZoneY);
         // clamp to page bounds
-        if (rect.top < 0) rect.top = 0;
-        if (rect.left < 0) rect.left = 0;
-        if (rect.right > metrics.pageSizeWidth) rect.right = metrics.pageSizeWidth;
-        if (rect.bottom > metrics.pageSizeHeight) rect.bottom = metrics.pageSizeHeight;
-        return rect;
+        return clampToPageBounds(rect, metrics);
     }
 
     /**
@@ -173,10 +169,10 @@ final class DisplayPortCalculator {
         float top = metrics.viewportRectTop - margins.top;
         float right = metrics.viewportRectRight + margins.right;
         float bottom = metrics.viewportRectBottom + margins.bottom;
-        left = Math.max(0.0f, TILE_SIZE * FloatMath.floor(left / TILE_SIZE));
-        top = Math.max(0.0f, TILE_SIZE * FloatMath.floor(top / TILE_SIZE));
-        right = Math.min(metrics.pageSizeWidth, TILE_SIZE * FloatMath.ceil(right / TILE_SIZE));
-        bottom = Math.min(metrics.pageSizeHeight, TILE_SIZE * FloatMath.ceil(bottom / TILE_SIZE));
+        left = Math.max(metrics.pageRectLeft, TILE_SIZE * FloatMath.floor(left / TILE_SIZE));
+        top = Math.max(metrics.pageRectTop, TILE_SIZE * FloatMath.floor(top / TILE_SIZE));
+        right = Math.min(metrics.pageRectRight, TILE_SIZE * FloatMath.ceil(right / TILE_SIZE));
+        bottom = Math.min(metrics.pageRectBottom, TILE_SIZE * FloatMath.ceil(bottom / TILE_SIZE));
         return new DisplayPortMetrics(left, top, right, bottom, zoom);
     }
 
@@ -184,16 +180,16 @@ final class DisplayPortCalculator {
      * Adjust the given margins so if they are applied on the viewport in the metrics, the resulting rect
      * does not exceed the page bounds. This code will maintain the total margin amount for a given axis;
      * it assumes that margins.left + metrics.getWidth() + margins.right is less than or equal to
-     * metrics.pageSizeWidth; and the same for the y axis.
+     * metrics.getPageWidth(); and the same for the y axis.
      */
     private static RectF shiftMarginsForPageBounds(RectF margins, ImmutableViewportMetrics metrics) {
         // check how much we're overflowing in each direction. note that at most one of leftOverflow
         // and rightOverflow can be greater than zero, and at most one of topOverflow and bottomOverflow
         // can be greater than zero, because of the assumption described in the method javadoc.
-        float leftOverflow = margins.left - metrics.viewportRectLeft;
-        float rightOverflow = margins.right - (metrics.pageSizeWidth - metrics.viewportRectRight);
-        float topOverflow = margins.top - metrics.viewportRectTop;
-        float bottomOverflow = margins.bottom - (metrics.pageSizeHeight - metrics.viewportRectBottom);
+        float leftOverflow = metrics.pageRectLeft - (metrics.viewportRectLeft - margins.left);
+        float rightOverflow = (metrics.viewportRectRight + margins.right) - metrics.pageRectRight;
+        float topOverflow = metrics.pageRectTop - (metrics.viewportRectTop - margins.top);
+        float bottomOverflow = (metrics.viewportRectBottom + margins.bottom) - metrics.pageRectBottom;
 
         // if the margins overflow the page bounds, shift them to other side on the same axis
         if (leftOverflow > 0) {
@@ -217,10 +213,10 @@ final class DisplayPortCalculator {
      * Clamp the given rect to the page bounds and return it.
      */
     private static RectF clampToPageBounds(RectF rect, ImmutableViewportMetrics metrics) {
-        rect.left = Math.max(rect.left, 0);
-        rect.top = Math.max(rect.top, 0);
-        rect.right = Math.min(rect.right, metrics.pageSizeWidth);
-        rect.bottom = Math.min(rect.bottom, metrics.pageSizeHeight);
+        if (rect.top < metrics.pageRectTop) rect.top = metrics.pageRectTop;
+        if (rect.left < metrics.pageRectLeft) rect.left = metrics.pageRectLeft;
+        if (rect.right > metrics.pageRectRight) rect.right = metrics.pageRectRight;
+        if (rect.bottom > metrics.pageRectBottom) rect.bottom = metrics.pageRectBottom;
         return rect;
     }
 
@@ -397,8 +393,8 @@ final class DisplayPortCalculator {
 
             // 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).
-            displayPortWidth = Math.min(displayPortWidth, metrics.pageSizeWidth);
-            displayPortHeight = Math.min(displayPortHeight, metrics.pageSizeHeight);
+            displayPortWidth = Math.min(displayPortWidth, metrics.getPageWidth());
+            displayPortHeight = Math.min(displayPortHeight, metrics.getPageHeight());
             float horizontalBuffer = displayPortWidth - metrics.getWidth();
             float verticalBuffer = displayPortHeight - metrics.getHeight();
 
@@ -416,8 +412,8 @@ final class DisplayPortCalculator {
             float dangerZoneY = metrics.getHeight() * (DANGER_ZONE_BASE_Y_MULTIPLIER + (velocity.y * DANGER_ZONE_INCR_Y_MULTIPLIER));
             // clamp it such that when added to the viewport, they don't exceed page size.
             // this is a prerequisite to calling shiftMarginsForPageBounds as we do below.
-            dangerZoneX = Math.min(dangerZoneX, metrics.pageSizeWidth - metrics.getWidth());
-            dangerZoneY = Math.min(dangerZoneY, metrics.pageSizeHeight - metrics.getHeight());
+            dangerZoneX = Math.min(dangerZoneX, metrics.getPageWidth() - metrics.getWidth());
+            dangerZoneY = Math.min(dangerZoneY, metrics.getPageHeight() - metrics.getHeight());
 
             // split the danger zone into margins based on velocity, and ensure it doesn't exceed
             // page bounds.
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GLController.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GLController.java
index 14cea1e..9bd7d2f 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GLController.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GLController.java
@@ -1,39 +1,7 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla Android code.
- *
- * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2011-2012
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Patrick Walton <pcwalton at mozilla.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
+ * 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;
 
@@ -196,8 +164,7 @@ public class GLController {
 
         int[] version = new int[2];
         if (!mEGL.eglInitialize(mEGLDisplay, version)) {
-            throw new GLControllerException("eglInitialize() failed " +
-                                            getEGLError());
+            throw new GLControllerException("eglInitialize() failed " + getEGLError());
         }
 
         mEGLConfig = chooseConfig();
@@ -213,6 +180,13 @@ public class GLController {
             throw new GLControllerException("createContext() failed " +
                                             getEGLError());
         }
+
+        mGL = mEGLContext.getGL();
+
+        if (mView.getRenderer() != null) {
+            mView.getRenderer().onSurfaceCreated((GL10)mGL, mEGLConfig);
+            mView.getRenderer().onSurfaceChanged((GL10)mGL, mWidth, mHeight);
+        }
     }
 
     private EGLConfig chooseConfig() {
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 d9c594e..ffce4c4 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
@@ -141,7 +141,7 @@ public class GeckoLayerClient implements LayerView.Listener {
             // Don't adjust page size when zooming unless zoom levels are
             // approximately equal.
             if (FloatUtils.fuzzyEquals(mLayerController.getZoomFactor(), mGeckoViewport.getZoomFactor())) {
-                mLayerController.setPageSize(mGeckoViewport.getPageSize(), mGeckoViewport.getPageSize());
+                mLayerController.setPageRect(mGeckoViewport.getPageRect(), mGeckoViewport.getCssPageRect());
             }
         } else {
             mLayerController.setViewportMetrics(mGeckoViewport);
@@ -215,19 +215,25 @@ public class GeckoLayerClient implements LayerView.Listener {
         }
     }
 
+    /** This function is invoked by Gecko via JNI; be careful when modifying signature.
+     * The compositor invokes this function whenever it determines that the page size
+     * has changed (based on the information it gets from layout). If setFirstPaintViewport
+     * is invoked on a frame, then this function will not be. For any given frame, this
+     * function will be invoked before syncViewportInfo.
+     */
     public void setPageSize(float zoom, float pageWidth, float pageHeight, float cssPageWidth, float cssPageHeight) {
         synchronized (mLayerController) {
-        // adjust the page dimensions to account for differences in zoom
-        // between the rendered content (which is what the compositor tells us)
-        // and our zoom level (which may have diverged).
-        float ourZoom = mLayerController.getZoomFactor();
-        pageWidth = pageWidth * ourZoom / zoom;
-        pageHeight = pageHeight * ourZoom /zoom;
-        mLayerController.setPageSize(new FloatSize(pageWidth, pageHeight), new FloatSize(cssPageWidth, cssPageHeight));
-        // Here the page size of the document has changed, but the document being displayed
-        // is still the same. Therefore, we don't need to send anything to browser.js; any
-        // changes we need to make to the display port will get sent the next time we call
-        // adjustViewport().
+            // adjust the page dimensions to account for differences in zoom
+            // between the rendered content (which is what the compositor tells us)
+            // and our zoom level (which may have diverged).
+            RectF pageRect = new RectF(0.0f, 0.0f, pageWidth, pageHeight);
+            RectF cssPageRect = new RectF(0.0f, 0.0f, cssPageWidth, cssPageHeight);
+            float ourZoom = mLayerController.getZoomFactor();
+            mLayerController.setPageRect(RectUtils.scale(pageRect, ourZoom / zoom), cssPageRect);
+            // Here the page size of the document has changed, but the document being displayed
+            // is still the same. Therefore, we don't need to send anything to browser.js; any
+            // changes we need to make to the display port will get sent the next time we call
+            // adjustViewport().
         }
     }
 
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
index 067c532..5403c80 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ImmutableViewportMetrics.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ImmutableViewportMetrics.java
@@ -17,30 +17,38 @@ 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 cssPageSizeWidth;
-    public final float cssPageSizeHeight;
-    public final float viewportRectBottom;
+    public final float pageRectLeft;
+    public final float pageRectTop;
+    public final float pageRectRight;
+    public final float pageRectBottom;
+    public final float cssPageRectLeft;
+    public final float cssPageRectTop;
+    public final float cssPageRectRight;
+    public final float cssPageRectBottom;
     public final float viewportRectLeft;
-    public final float viewportRectRight;
     public final float viewportRectTop;
+    public final float viewportRectRight;
+    public final float viewportRectBottom;
     public final float zoomFactor;
 
     public ImmutableViewportMetrics(ViewportMetrics m) {
         RectF viewportRect = m.getViewport();
-        viewportRectBottom = viewportRect.bottom;
         viewportRectLeft = viewportRect.left;
-        viewportRectRight = viewportRect.right;
         viewportRectTop = viewportRect.top;
+        viewportRectRight = viewportRect.right;
+        viewportRectBottom = viewportRect.bottom;
 
-        FloatSize pageSize = m.getPageSize();
-        pageSizeWidth = pageSize.width;
-        pageSizeHeight = pageSize.height;
+        RectF pageRect = m.getPageRect();
+        pageRectLeft = pageRect.left;
+        pageRectTop = pageRect.top;
+        pageRectRight = pageRect.right;
+        pageRectBottom = pageRect.bottom;
 
-        FloatSize cssPageSize = m.getCssPageSize();
-        cssPageSizeWidth = cssPageSize.width;
-        cssPageSizeHeight = cssPageSize.height;
+        RectF cssPageRect = m.getCssPageRect();
+        cssPageRectLeft = cssPageRect.left;
+        cssPageRectTop = cssPageRect.top;
+        cssPageRectRight = cssPageRect.right;
+        cssPageRectBottom = cssPageRect.bottom;
 
         zoomFactor = m.getZoomFactor();
     }
@@ -74,18 +82,28 @@ public class ImmutableViewportMetrics {
         return RectUtils.scale(getViewport(), 1/zoomFactor);
     }
 
-    public FloatSize getPageSize() {
-        return new FloatSize(pageSizeWidth, pageSizeHeight);
+    public RectF getPageRect() {
+        return new RectF(pageRectLeft, pageRectTop, pageRectRight, pageRectBottom);
+    }
+
+    public float getPageWidth() {
+        return pageRectRight - pageRectLeft;
+    }
+
+    public float getPageHeight() {
+        return pageRectBottom - pageRectTop;
     }
 
-    public FloatSize getCssPageSize() {
-        return new FloatSize(cssPageSizeWidth, cssPageSizeHeight);
+    public RectF getCssPageRect() {
+        return new RectF(cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom);
     }
 
     @Override
     public String toString() {
         return "ImmutableViewportMetrics v=(" + viewportRectLeft + "," + viewportRectTop + ","
-                + viewportRectRight + "," + viewportRectBottom + ") p=(" + pageSizeWidth + ","
-                + pageSizeHeight + ") z=" + zoomFactor;
+                + viewportRectRight + "," + viewportRectBottom + ") p=(" + pageRectLeft + ","
+                + pageRectTop + "," + pageRectRight + "," + pageRectBottom + ") c=("
+                + cssPageRectLeft + "," + cssPageRectTop + "," + cssPageRectRight + ","
+                + cssPageRectBottom + ") z=" + zoomFactor;
     }
 }
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 0ab4e6a..9700d2a 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
@@ -156,17 +156,17 @@ public abstract class Layer {
 
     public static class RenderContext {
         public final RectF viewport;
-        public final FloatSize pageSize;
+        public final RectF pageRect;
         public final IntSize screenSize;
         public final float zoomFactor;
         public final int positionHandle;
         public final int textureHandle;
         public final FloatBuffer coordBuffer;
 
-        public RenderContext(RectF aViewport, FloatSize aPageSize, IntSize aScreenSize, float aZoomFactor,
+        public RenderContext(RectF aViewport, RectF aPageRect, IntSize aScreenSize, float aZoomFactor,
                              int aPositionHandle, int aTextureHandle, FloatBuffer aCoordBuffer) {
             viewport = aViewport;
-            pageSize = aPageSize;
+            pageRect = aPageRect;
             screenSize = aScreenSize;
             zoomFactor = aZoomFactor;
             positionHandle = aPositionHandle;
@@ -179,7 +179,7 @@ public abstract class Layer {
                 return false;
             }
             return RectUtils.fuzzyEquals(viewport, other.viewport)
-                && pageSize.fuzzyEquals(other.pageSize)
+                && RectUtils.fuzzyEquals(pageRect, other.pageRect)
                 && 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 277ed42..59fc1fe 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
@@ -101,12 +101,12 @@ public class LayerController {
         return mViewportMetrics.getSize();
     }
 
-    public FloatSize getPageSize() {
-        return mViewportMetrics.getPageSize();
+    public RectF getPageRect() {
+        return mViewportMetrics.getPageRect();
     }
 
-    public FloatSize getCssPageSize() {
-        return mViewportMetrics.getCssPageSize();
+    public RectF getCssPageRect() {
+        return mViewportMetrics.getCssPageRect();
     }
 
     public PointF getOrigin() {
@@ -165,13 +165,16 @@ public class LayerController {
         mView.requestRender();
     }
 
-    /** Sets the current page size. You must hold the monitor while calling this. */
-    public void setPageSize(FloatSize size, FloatSize cssSize) {
-        if (mViewportMetrics.getCssPageSize().equals(cssSize))
+    /** Sets the current page rect. You must hold the monitor while calling this. */
+    public void setPageRect(RectF rect, RectF cssRect) {
+        // Since the "rect" is always just a multiple of "cssRect" we don't need to
+        // check both; this function assumes that both "rect" and "cssRect" are relative
+        // the zoom factor in mViewportMetrics.
+        if (mViewportMetrics.getCssPageRect().equals(cssRect))
             return;
 
         ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
-        viewportMetrics.setPageSize(size, cssSize);
+        viewportMetrics.setPageRect(rect, cssRect);
         mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics);
 
         // Page size is owned by the layer client, so no need to notify it of
@@ -179,7 +182,7 @@ public class LayerController {
 
         mView.post(new Runnable() {
             public void run() {
-                mPanZoomController.pageSizeUpdated();
+                mPanZoomController.pageRectUpdated();
                 mView.requestRender();
             }
         });
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 34c5c61..c0bef87 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
@@ -307,19 +307,19 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
 
     private RenderContext createScreenContext(ImmutableViewportMetrics metrics) {
         RectF viewport = new RectF(0.0f, 0.0f, metrics.getWidth(), metrics.getHeight());
-        FloatSize pageSize = new FloatSize(metrics.getPageSize());
-        return createContext(viewport, pageSize, 1.0f);
+        RectF pageRect = new RectF(metrics.getPageRect());
+        return createContext(viewport, pageRect, 1.0f);
     }
 
     private RenderContext createPageContext(ImmutableViewportMetrics metrics) {
         Rect viewport = RectUtils.round(metrics.getViewport());
-        FloatSize pageSize = metrics.getPageSize();
+        RectF pageRect = metrics.getPageRect();
         float zoomFactor = metrics.zoomFactor;
-        return createContext(new RectF(viewport), pageSize, zoomFactor);
+        return createContext(new RectF(viewport), pageRect, zoomFactor);
     }
 
-    private RenderContext createContext(RectF viewport, FloatSize pageSize, float zoomFactor) {
-        return new RenderContext(viewport, pageSize, new IntSize(mSurfaceWidth, mSurfaceHeight), zoomFactor, mPositionHandle, mTextureHandle,
+    private RenderContext createContext(RectF viewport, RectF pageRect, float zoomFactor) {
+        return new RenderContext(viewport, pageRect, new IntSize(mSurfaceWidth, mSurfaceHeight), zoomFactor, mPositionHandle, mTextureHandle,
                                  mCoordBuffer);
     }
 
@@ -486,12 +486,9 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
 
         private Rect getPageRect() {
             Point origin = PointUtils.round(mFrameMetrics.getOrigin());
-            IntSize pageSize = new IntSize(mFrameMetrics.getPageSize());
-
-            origin.negate();
-
-            return new Rect(origin.x, origin.y,
-                            origin.x + pageSize.width, origin.y + pageSize.height);
+            Rect pageRect = RectUtils.round(mFrameMetrics.getPageRect());
+            pageRect.offset(-origin.x, -origin.y);
+            return pageRect;
         }
 
         /** This function is invoked via JNI; be careful when modifying signature. */
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/MultiTileLayer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/MultiTileLayer.java
index 8647ce6..c4a445d 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/MultiTileLayer.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/MultiTileLayer.java
@@ -62,8 +62,8 @@ public class MultiTileLayer extends Layer {
     private TileProvider tileProvider;
     private float currentZoomFactor;
     private RectF tileViewPort = new RectF();
-    private FloatSize currentPageSize = new FloatSize(0, 0);
     private boolean shouldRefreshZoom = true;
+    private RectF currentPageRect = new RectF();
 
     public MultiTileLayer() {
         super();
@@ -202,7 +202,7 @@ public class MultiTileLayer extends Layer {
             return;
         }
 
-        currentPageSize = context.pageSize;
+        currentPageRect = context.pageRect;
 
         for (SubTile layer : mTiles) {
             // Avoid work, only draw tiles that intersect with the viewport
@@ -258,11 +258,11 @@ public class MultiTileLayer extends Layer {
 
     private void addNewTiles() {
         for (float y = tileViewPort.top; y < tileViewPort.bottom; y += TILE_SIZE) {
-            if (y > currentPageSize.height) {
+            if (y > currentPageRect.height()) {
                 continue;
             }
             for (float x = tileViewPort.left; x < tileViewPort.right; x += TILE_SIZE) {
-                if (x > currentPageSize.width) {
+                if (x > currentPageRect.width()) {
                     continue;
                 }
                 boolean contains = false;
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/NinePatchTileLayer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/NinePatchTileLayer.java
index f3201cf..99f2039 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/NinePatchTileLayer.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/NinePatchTileLayer.java
@@ -49,33 +49,33 @@ public class NinePatchTileLayer extends TileLayer {
          *    +---+---+---+
          */
 
-        FloatSize size = context.pageSize;
-        float width = size.width, height = size.height;
+        // page is the rect of the "missing" center spot in the picture above
+        RectF page = context.pageRect;
 
         drawPatch(context, 0, PATCH_SIZE * 3,                                              /* 0 */
-                  0.0f, 0.0f, PATCH_SIZE, PATCH_SIZE);
-        drawPatch(context, PATCH_SIZE, PATCH_SIZE*3,                                       /* 1 */
-                  PATCH_SIZE, 0.0f, width, PATCH_SIZE);
-        drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE*3,                                   /* 2 */
-                  PATCH_SIZE + width, 0.0f, PATCH_SIZE, PATCH_SIZE);
+                  page.left - PATCH_SIZE, page.top - PATCH_SIZE, PATCH_SIZE, PATCH_SIZE);
+        drawPatch(context, PATCH_SIZE, PATCH_SIZE * 3,                                     /* 1 */
+                  page.left, page.top - PATCH_SIZE, page.width(), PATCH_SIZE);
+        drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE * 3,                                 /* 2 */
+                  page.right, page.top - PATCH_SIZE, PATCH_SIZE, PATCH_SIZE);
         drawPatch(context, 0, PATCH_SIZE * 2,                                              /* 3 */
-                  0.0f, PATCH_SIZE, PATCH_SIZE, height);
+                  page.left - PATCH_SIZE, page.top, PATCH_SIZE, page.height());
         drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE * 2,                                 /* 4 */
-                  PATCH_SIZE + width, PATCH_SIZE, PATCH_SIZE, height);
+                  page.right, page.top, PATCH_SIZE, page.height());
         drawPatch(context, 0, PATCH_SIZE,                                                  /* 5 */
-                  0.0f, PATCH_SIZE + height, PATCH_SIZE, PATCH_SIZE);
+                  page.left - PATCH_SIZE, page.bottom, PATCH_SIZE, PATCH_SIZE);
         drawPatch(context, PATCH_SIZE, PATCH_SIZE,                                         /* 6 */
-                  PATCH_SIZE, PATCH_SIZE + height, width, PATCH_SIZE);
+                  page.left, page.bottom, page.width(), PATCH_SIZE);
         drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE,                                     /* 7 */
-                  PATCH_SIZE + width, PATCH_SIZE + height, PATCH_SIZE, PATCH_SIZE);
+                  page.right, page.bottom, PATCH_SIZE, PATCH_SIZE);
     }
 
     private void drawPatch(RenderContext context, int textureX, int textureY,
                            float tileX, float tileY, float tileWidth, float tileHeight) {
         RectF viewport = context.viewport;
         float viewportHeight = viewport.height();
-        float drawX = tileX - viewport.left - PATCH_SIZE;
-        float drawY = viewportHeight - (tileY + tileHeight - viewport.top - PATCH_SIZE);
+        float drawX = tileX - viewport.left;
+        float drawY = viewportHeight - (tileY + tileHeight - viewport.top);
 
         float[] coords = {
             //x, y, z, texture_x, texture_y
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScreenshotLayer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScreenshotLayer.java
index 4552e36..5f82213 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScreenshotLayer.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScreenshotLayer.java
@@ -7,15 +7,11 @@ package org.mozilla.gecko.gfx;
 
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Paint;
-import android.graphics.Rect;
-import android.opengl.GLES20;
 
 import org.libreoffice.kit.DirectBufferAllocator;
 
 import java.nio.ByteBuffer;
-import java.nio.FloatBuffer;
 
 public class ScreenshotLayer extends SingleTileLayer {
     private static final int SCREENSHOT_SIZE_LIMIT = 1048576;
@@ -25,32 +21,24 @@ public class ScreenshotLayer extends SingleTileLayer {
     // The size of the bitmap painted in the buffer
     // (may be smaller than mBufferSize due to power of 2 padding)
     private IntSize mImageSize;
-    // Special case to show the page background color prior to painting a screenshot
-    private boolean mIsSingleColor = true;
-    // Force single color, needed for testing
-    private boolean mForceSingleColor = false;
-    // Cache the passed in background color to determine if we need to update
-    // initialized to 0 so it lets the code run to set it to white on init
-    private int mCurrentBackgroundColor = 0;
+    // Whether we have an up-to-date image to draw
+    private boolean mHasImage;
 
     public static int getMaxNumPixels() {
         return SCREENSHOT_SIZE_LIMIT;
     }
 
     public void reset() {
-        mIsSingleColor = true;
-        updateBackground(mForceSingleColor, Color.WHITE);
+        mHasImage = false;
     }
 
     void setBitmap(Bitmap bitmap) {
-        if (mForceSingleColor)
-            return;
         mImageSize = new IntSize(bitmap.getWidth(), bitmap.getHeight());
         int width = IntSize.nextPowerOfTwo(bitmap.getWidth());
         int height = IntSize.nextPowerOfTwo(bitmap.getHeight());
         mBufferSize = new IntSize(width, height);
         mImage.setBitmap(bitmap, width, height, CairoImage.FORMAT_RGB16_565);
-        mIsSingleColor = false;
+        mHasImage = true;
     }
 
     public void updateBitmap(Bitmap bitmap, float x, float y, float width, float height) {
@@ -58,9 +46,7 @@ public class ScreenshotLayer extends SingleTileLayer {
     }
 
     public static ScreenshotLayer create() {
-        // 3 x 3 min for the single color case. Less than 3x3 will blend
-        // the colors from outside this single color block when scaled
-        return ScreenshotLayer.create(new IntSize(3, 3));
+        return ScreenshotLayer.create(new IntSize(4, 4));
     }
 
     public static ScreenshotLayer create(IntSize size) {
@@ -82,135 +68,37 @@ public class ScreenshotLayer extends SingleTileLayer {
     }
 
     private ScreenshotLayer(ScreenshotImage image, IntSize size) {
-        super(image, TileLayer.PaintMode.STRETCH);
+        super(image, TileLayer.PaintMode.NORMAL);
         mBufferSize = size;
         mImage = image;
     }
 
     @Override
     public void draw(RenderContext context) {
-        // mTextureIDs may be null here during startup if Layer.java's draw method
-        // failed to acquire the transaction lock and call performUpdates.
-        if (!initialized())
-            return;
-
-        float txl, txr, txb, txt;
-        if (mIsSingleColor) {
-            txt = 1.0f;
-            txr = 0.5f / mBufferSize.width;;
-            txb = 1.0f - 0.5f / mBufferSize.height;
-            txl = 0.0f;
-        } else {
-            Rect position = getPosition();
-
-            float bw = mBufferSize.width;
-            float bh = mBufferSize.height;
-            float iw = mImageSize.width;
-            float ih = mImageSize.height;
-
-            float pw = context.pageSize.width;
-            float ph = context.pageSize.height;
-
-            float vl = context.viewport.left;
-            float vr = context.viewport.right;
-            float vt = context.viewport.top;
-            float vb = context.viewport.bottom;
-
-            float vw =  vr - vl;
-            float vh =  vb - vt;
-
-            txl = (iw/bw) * (vl / pw);
-            txr = (iw/bw) * (vr / pw);
-            txt = 1.0f - ((ih/bh) * (vt / ph));
-            txb = 1.0f - ((ih/bh) * (vb / ph));
-        }
-        float[] coords = {
-                0.0f, 0.0f, 0.0f, txl, txb,
-                0.0f, 1.0f, 0.0f, txl, txt,
-                1.0f, 0.0f, 0.0f, txr, txb,
-                1.0f, 1.0f, 0.0f, txr, txt,
-        };
-
-        FloatBuffer coordBuffer = context.coordBuffer;
-        int positionHandle = context.positionHandle;
-        int textureHandle = context.textureHandle;
-
-        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTextureID());
-
-        // Make sure we are at position zero in the buffer
-        coordBuffer.position(0);
-        coordBuffer.put(coords);
-
-        // Vertex coordinates are x,y,z starting at position 0 into the buffer.
-        coordBuffer.position(0);
-        GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer);
-
-        // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer.
-        coordBuffer.position(3);
-        GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer);
-        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
-    }
-
-    public boolean updateBackground(boolean showChecks, int color) {
-        if (!showChecks) {
-            mIsSingleColor = true;
-            mForceSingleColor = true;
-        } else {
-            mForceSingleColor = false;
-        }
-
-        if (!mIsSingleColor || color == mCurrentBackgroundColor)
-            return false;
-
-        mCurrentBackgroundColor = color;
-
-        /* mask each component of the 8888 color and bit shift to least
-         * sigificant. Then for red and blue multiply by (2^5 -1) and (2^6 - 1)
-         * for green. Finally, divide by (2^8 - 1) for all color values. This
-         * scales the 8 bit color values to 5 or 6 bits
-         */
-        int red =   ((color & 0x00FF0000 >> 16)* 31 / 255);
-        int green = ((color & 0x0000FF00 >> 8) * 63 / 255);
-        int blue =   (color & 0x000000FF) * 31 / 255;
-        /* For the first byte left shift red by 3 positions such that it is the
-         * top 5 bits, right shift green by 3 so its 3 most significant are the
-         * 3 least significant. For the second byte, left shift green by 3 so
-         * its 3 least significant bits are the 3 most significant bits of the
-         * byte. Finally, set the 5 least significant bits to blue's value.
-         */
-        byte byte1 = (byte)((red << 3 | green >> 3) & 0x0000FFFF);
-        byte byte2 = (byte)((green << 5 | blue) & 0x0000FFFF);
-        mImage.mBuffer.put(1, byte1);
-        mImage.mBuffer.put(0, byte2);
-        mImage.mBuffer.put(3, byte1);
-        mImage.mBuffer.put(2, byte2);
-        mImage.mBuffer.put(5, byte1);
-        mImage.mBuffer.put(4, byte2);
-        mImage.mBuffer.put(mImageSize.width + 1, byte1);
-        mImage.mBuffer.put(mImageSize.width + 0, byte2);
-        mImage.mBuffer.put(mImageSize.width + 3, byte1);
-        mImage.mBuffer.put(mImageSize.width + 2, byte2);
-        mImage.mBuffer.put(mImageSize.width + 5, byte1);
-        mImage.mBuffer.put(mImageSize.width + 4, byte2);
-        return true;
+        if (mHasImage)
+            super.draw(context);
     }
 
     /** A Cairo image that simply saves a buffer of pixel data. */
     static class ScreenshotImage extends CairoImage {
-        ByteBuffer mBuffer;
+        private ByteBuffer mBuffer;
         private IntSize mSize;
         private int mFormat;
 
         /** Creates a buffered Cairo image from a byte buffer. */
         public ScreenshotImage(ByteBuffer inBuffer, int inWidth, int inHeight, int inFormat) {
-            mBuffer = inBuffer; mSize = new IntSize(inWidth, inHeight); mFormat = inFormat;
+            mBuffer = inBuffer;
+            mSize = new IntSize(inWidth, inHeight);
+            mFormat = inFormat;
         }
 
         @Override
         protected void finalize() throws Throwable {
             try {
-                if (mBuffer != null)
+                if (mBuffer != null) {
                     DirectBufferAllocator.free(mBuffer);
+                    mBuffer = null;
+                }
             } finally {
                 super.finalize();
             }
@@ -242,7 +130,10 @@ public class ScreenshotLayer extends SingleTileLayer {
 
         @Override
         public void destroy() {
-
+            if (mBuffer != null) {
+                DirectBufferAllocator.free(mBuffer);
+                mBuffer = null;
+            }
         }
 
         @Override
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScrollbarLayer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScrollbarLayer.java
index 51ba946..ddd4a0a 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScrollbarLayer.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScrollbarLayer.java
@@ -421,9 +421,9 @@ public class ScrollbarLayer extends TileLayer {
 
     private RectF getVerticalRect(RenderContext context) {
         RectF viewport = context.viewport;
-        FloatSize pageSize = context.pageSize;
-        float barStart = (viewport.height() * viewport.top / pageSize.height) + CAP_RADIUS;
-        float barEnd = (viewport.height() * viewport.bottom / pageSize.height) - CAP_RADIUS;
+        RectF pageRect = context.pageRect;
+        float barStart = ((viewport.top - pageRect.top) * (viewport.height() / pageRect.height())) + CAP_RADIUS;
+        float barEnd = ((viewport.bottom - pageRect.top) * (viewport.height() / pageRect.height())) - CAP_RADIUS;
         if (barStart > barEnd) {
             float middle = (barStart + barEnd) / 2.0f;
             barStart = barEnd = middle;
@@ -434,9 +434,9 @@ public class ScrollbarLayer extends TileLayer {
 
     private RectF getHorizontalRect(RenderContext context) {
         RectF viewport = context.viewport;
-        FloatSize pageSize = context.pageSize;
-        float barStart = (viewport.width() * viewport.left / pageSize.width) + CAP_RADIUS;
-        float barEnd = (viewport.width() * viewport.right / pageSize.width) - CAP_RADIUS;
+        RectF pageRect = context.pageRect;
+        float barStart = ((viewport.left - pageRect.left) * (viewport.width() / pageRect.width())) + CAP_RADIUS;
+        float barEnd = ((viewport.right - pageRect.left) * (viewport.width() / pageRect.width())) - CAP_RADIUS;
         if (barStart > barEnd) {
             float middle = (barStart + barEnd) / 2.0f;
             barStart = barEnd = middle;
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/SingleTileLayer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/SingleTileLayer.java
index 160a40b..cc1988f 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/SingleTileLayer.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/SingleTileLayer.java
@@ -61,7 +61,7 @@ public class SingleTileLayer extends TileLayer {
         } else if (stretches()) {
             // If we're stretching, we just want the bounds and texture bounds
             // to fit to the page.
-            bounds = new RectF(0.0f, 0.0f, context.pageSize.width, context.pageSize.height);
+            bounds = new RectF(context.pageRect);
             textureBounds = bounds;
         } else {
             bounds = getBounds(context);
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 f21f499..f7b84bf 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
@@ -21,8 +21,8 @@ import org.mozilla.gecko.util.FloatUtils;
 public class ViewportMetrics {
     private static final String LOGTAG = "GeckoViewportMetrics";
 
-    private FloatSize mPageSize;
-    private FloatSize mCssPageSize;
+    private RectF mPageRect;
+    private RectF mCssPageRect;
     private RectF mViewportRect;
     private float mZoomFactor;
 
@@ -30,26 +30,32 @@ public class ViewportMetrics {
         DisplayMetrics metrics = new DisplayMetrics();
         LibreOfficeMainActivity.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
 
-        mPageSize = new FloatSize(metrics.widthPixels, metrics.heightPixels);
-        mCssPageSize = new FloatSize(metrics.widthPixels, metrics.heightPixels);
+        mPageRect = new RectF(0, 0, metrics.widthPixels, metrics.heightPixels);
+        mCssPageRect = new RectF(0, 0, metrics.widthPixels, metrics.heightPixels);
         mViewportRect = new RectF(0, 0, metrics.widthPixels, metrics.heightPixels);
         mZoomFactor = 1.0f;
     }
 
     public ViewportMetrics(ViewportMetrics viewport) {
-        mPageSize = new FloatSize(viewport.getPageSize());
-        mCssPageSize = new FloatSize(viewport.getCssPageSize());
+        mPageRect = new RectF(viewport.getPageRect());
+        mCssPageRect = new RectF(viewport.getCssPageRect());
         mViewportRect = new RectF(viewport.getViewport());
         mZoomFactor = viewport.getZoomFactor();
     }
 
     public ViewportMetrics(ImmutableViewportMetrics viewport) {
-        mPageSize = new FloatSize(viewport.pageSizeWidth, viewport.pageSizeHeight);
-        mCssPageSize = new FloatSize(viewport.cssPageSizeWidth, viewport.cssPageSizeHeight);
+        mPageRect = new RectF(viewport.pageRectLeft,
+                viewport.pageRectTop,
+                viewport.pageRectRight,
+                viewport.pageRectBottom);
+        mCssPageRect = new RectF(viewport.cssPageRectLeft,
+                viewport.cssPageRectTop,
+                viewport.cssPageRectRight,
+                viewport.cssPageRectBottom);
         mViewportRect = new RectF(viewport.viewportRectLeft,
-                                  viewport.viewportRectTop,
-                                  viewport.viewportRectRight,
-                                  viewport.viewportRectBottom);
+                viewport.viewportRectTop,
+                viewport.viewportRectRight,
+                viewport.viewportRectBottom);
         mZoomFactor = viewport.zoomFactor;
     }
 
@@ -59,14 +65,18 @@ public class ViewportMetrics {
         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 cssPageWidth = (float)json.getDouble("cssPageWidth");
-        float cssPageHeight = (float)json.getDouble("cssPageHeight");
+        float pageLeft = 0.0f;
+        float pageTop = 0.0f;
+        float pageRight = (float)json.getDouble("pageWidth");
+        float pageBottom = (float)json.getDouble("pageHeight");
+        float cssPageLeft = 0.0f;
+        float cssPageTop = 0.0f;
+        float cssPageRight = (float)json.getDouble("cssPageWidth");
+        float cssPageBottom = (float)json.getDouble("cssPageHeight");
         float zoom = (float)json.getDouble("zoom");
 
-        mPageSize = new FloatSize(pageWidth, pageHeight);
-        mCssPageSize = new FloatSize(cssPageWidth, cssPageHeight);
+        mPageRect = new RectF(pageLeft, pageTop, pageRight, pageBottom);
+        mCssPageRect = new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom);
         mViewportRect = new RectF(x, y, x + width, y + height);
         mZoomFactor = zoom;
     }
@@ -91,38 +101,35 @@ public class ViewportMetrics {
     public RectF getClampedViewport() {
         RectF clampedViewport = new RectF(mViewportRect);
 
-        // While the viewport size ought to never exceed the page size, we
-        // do the clamping in this order to make sure that the origin is
-        // never negative.
-        if (clampedViewport.right > mPageSize.width)
-            clampedViewport.offset(mPageSize.width - clampedViewport.right, 0);
-        if (clampedViewport.left < 0)
-            clampedViewport.offset(-clampedViewport.left, 0);
+        // The viewport bounds ought to never exceed the page bounds.
+        if (clampedViewport.right > mPageRect.right)
+            clampedViewport.offset(mPageRect.right - clampedViewport.right, 0);
+        if (clampedViewport.left < mPageRect.left)
+            clampedViewport.offset(mPageRect.left - clampedViewport.left, 0);
 
-        if (clampedViewport.bottom > mPageSize.height)
-            clampedViewport.offset(0, mPageSize.height - clampedViewport.bottom);
-        if (clampedViewport.top < 0)
-            clampedViewport.offset(0, -clampedViewport.top);
+        if (clampedViewport.bottom > mPageRect.bottom)
+            clampedViewport.offset(0, mPageRect.bottom - clampedViewport.bottom);
+        if (clampedViewport.top < mPageRect.top)
+            clampedViewport.offset(0, mPageRect.top - clampedViewport.top);
 
         return clampedViewport;
     }
 
-    public FloatSize getPageSize() {
-        return mPageSize;
+    public RectF getPageRect() {
+        return mPageRect;
     }
 
-    public FloatSize getCssPageSize() {
-        return mCssPageSize;
+    public RectF getCssPageRect() {
+        return mCssPageRect;
     }
 
-
     public float getZoomFactor() {
         return mZoomFactor;
     }
 
-    public void setPageSize(FloatSize pageSize, FloatSize cssPageSize) {
-        mPageSize = pageSize;
-        mCssPageSize = cssPageSize;
+    public void setPageRect(RectF pageRect, RectF cssPageRect) {
+        mPageRect = pageRect;
+        mCssPageRect = cssPageRect;
     }
 
     public void setViewport(RectF viewport) {
@@ -149,9 +156,9 @@ public class ViewportMetrics {
      * after scaling.
      */
     public void scaleTo(float newZoomFactor, PointF focus) {
-        // mCssPageSize is invariant, since we're setting the scale factor
-        // here. The page size is based on the CSS page size.
-        mPageSize = mCssPageSize.scale(newZoomFactor);
+        // mCssPageRect is invariant, since we're setting the scale factor
+        // here. The page rect is based on the CSS page rect.
+        mPageRect = RectUtils.scale(mCssPageRect, newZoomFactor);
 
         float scaleFactor = newZoomFactor / mZoomFactor;
         PointF origin = getOrigin();
@@ -172,16 +179,16 @@ public class ViewportMetrics {
      */
     public ViewportMetrics interpolate(ViewportMetrics to, float t) {
         ViewportMetrics result = new ViewportMetrics();
-        result.mPageSize = mPageSize.interpolate(to.mPageSize, t);
-        result.mCssPageSize = mCssPageSize.interpolate(to.mCssPageSize, t);
+        result.mPageRect = RectUtils.interpolate(mPageRect, to.mPageRect, t);
+        result.mCssPageRect = RectUtils.interpolate(mCssPageRect, to.mCssPageRect, t);
         result.mZoomFactor = FloatUtils.interpolate(mZoomFactor, to.mZoomFactor, t);
         result.mViewportRect = RectUtils.interpolate(mViewportRect, to.mViewportRect, t);
         return result;
     }
 
     public boolean fuzzyEquals(ViewportMetrics other) {
-        return mPageSize.fuzzyEquals(other.mPageSize)
-            && mCssPageSize.fuzzyEquals(other.mCssPageSize)
+        return RectUtils.fuzzyEquals(mPageRect, other.mPageRect)
+            && RectUtils.fuzzyEquals(mCssPageRect, other.mCssPageRect)
             && RectUtils.fuzzyEquals(mViewportRect, other.mViewportRect)
             && FloatUtils.fuzzyEquals(mZoomFactor, other.mZoomFactor);
     }
@@ -192,15 +199,19 @@ public class ViewportMetrics {
         int height = Math.round(mViewportRect.height());
         int width = Math.round(mViewportRect.width());
 
-        StringBuffer sb = new StringBuffer(256);
+        StringBuffer sb = new StringBuffer(512);
         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(", \"cssPageWidth\" : ").append(mCssPageSize.width)
-          .append(", \"cssPageHeight\" : ").append(mCssPageSize.height)
+          .append(", \"pageLeft\" : ").append(mPageRect.left)
+          .append(", \"pageTop\" : ").append(mPageRect.top)
+          .append(", \"pageRight\" : ").append(mPageRect.right)
+          .append(", \"pageBottom\" : ").append(mPageRect.bottom)
+          .append(", \"cssPageLeft\" : ").append(mCssPageRect.left)
+          .append(", \"cssPageTop\" : ").append(mCssPageRect.top)
+          .append(", \"cssPageRight\" : ").append(mCssPageRect.right)
+          .append(", \"cssPageBottom\" : ").append(mCssPageRect.bottom)
           .append(", \"zoom\" : ").append(mZoomFactor)
           .append(" }");
         return sb.toString();
@@ -208,10 +219,10 @@ public class ViewportMetrics {
 
     @Override
     public String toString() {
-        StringBuffer buff = new StringBuffer(128);
+        StringBuffer buff = new StringBuffer(256);
         buff.append("v=").append(mViewportRect.toString())
-            .append(" p=").append(mPageSize.toString())
-            .append(" c=").append(mCssPageSize.toString())
+            .append(" p=").append(mPageRect.toString())
+            .append(" c=").append(mCssPageRect.toString())
             .append(" z=").append(mZoomFactor);
         return buff.toString();
     }
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java
index d5e3de8..fcdcd72 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java
@@ -116,6 +116,7 @@ abstract class Axis {
 
     protected abstract float getOrigin();
     protected abstract float getViewportLength();
+    protected abstract float getPageStart();
     protected abstract float getPageLength();
 
     Axis(SubdocumentScrollHelper subscroller) {
@@ -126,6 +127,10 @@ abstract class Axis {
         return getOrigin() + getViewportLength();
     }
 
+    private float getPageEnd() {
+        return getPageStart() + getPageLength();
+    }
+
     void startTouch(float pos) {
         mVelocity = 0.0f;
         mScrollingDisabled = false;
@@ -167,8 +172,8 @@ abstract class Axis {
     }
 
     private Overscroll getOverscroll() {
-        boolean minus = (getOrigin() < 0.0f);
-        boolean plus = (getViewportEnd() > getPageLength());
+        boolean minus = (getOrigin() < getPageStart());
+        boolean plus = (getViewportEnd() > getPageEnd());
         if (minus && plus) {
             return Overscroll.BOTH;
         } else if (minus) {
@@ -184,10 +189,10 @@ abstract class Axis {
     // overscrolled on this axis, returns 0.
     private float getExcess() {
         switch (getOverscroll()) {
-            case MINUS:     return -getOrigin();
-            case PLUS:      return getViewportEnd() - getPageLength();
-            case BOTH:      return getViewportEnd() - getPageLength() - getOrigin();
-            default:        return 0.0f;
+        case MINUS:     return getPageStart() - getOrigin();
+        case PLUS:      return getViewportEnd() - getPageEnd();
+        case BOTH:      return (getViewportEnd() - getPageEnd()) + (getPageStart() - getOrigin());
+        default:        return 0.0f;
         }
     }
 
@@ -202,7 +207,7 @@ abstract class Axis {
             return !mScrollingDisabled;
         } else {
             return getViewportLength() <= getPageLength() - MIN_SCROLLABLE_DISTANCE &&
-                    !mScrollingDisabled;
+                   !mScrollingDisabled;
         }
     }
 
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 8f81b5d..0cb83ef 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
@@ -14,7 +14,6 @@ import android.view.MotionEvent;
 
 import org.libreoffice.LOKitShell;
 import org.libreoffice.LibreOfficeMainActivity;
-import org.mozilla.gecko.gfx.FloatSize;
 import org.mozilla.gecko.gfx.LayerController;
 import org.mozilla.gecko.gfx.ViewportMetrics;
 import org.mozilla.gecko.util.FloatUtils;
@@ -210,7 +209,7 @@ public class PanZoomController
     }
 
     /** This must be called on the UI thread. */
-    public void pageSizeUpdated() {
+    public void pageRectUpdated() {
         if (mState == PanZoomState.NOTHING) {
             synchronized (mController) {
                 ViewportMetrics validated = getValidViewportMetrics();
@@ -691,7 +690,7 @@ public class PanZoomController
 
         /* First, we adjust the zoom factor so that we can make no overscrolled area visible. */
         float zoomFactor = viewportMetrics.getZoomFactor();
-        FloatSize pageSize = viewportMetrics.getPageSize();
+        RectF pageRect = viewportMetrics.getPageRect();
         RectF viewport = viewportMetrics.getViewport();
 
         float focusX = viewport.width() / 2.0f;
@@ -711,16 +710,16 @@ public class PanZoomController
         }
 
         // Ensure minZoomFactor keeps the page at least as big as the viewport.
-        if (pageSize.width > 0) {
-            float scaleFactor = viewport.width() / pageSize.width;
+        if (pageRect.width() > 0) {
+            float scaleFactor = viewport.width() / pageRect.width();
             minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor);
-            if (viewport.width() > pageSize.width)
+            if (viewport.width() > pageRect.width())
                 focusX = 0.0f;
         }
-        if (pageSize.height > 0) {
-            float scaleFactor = viewport.height() / pageSize.height;
+        if (pageRect.height() > 0) {
+            float scaleFactor = viewport.height() / pageRect.height();
             minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor);
-            if (viewport.height() > pageSize.height)
+            if (viewport.height() > pageRect.height())
                 focusY = 0.0f;
         }
 
@@ -753,7 +752,9 @@ public class PanZoomController
         @Override
         protected float getViewportLength() { return mController.getViewportSize().width; }
         @Override
-        protected float getPageLength() { return mController.getPageSize().width; }
+        protected float getPageStart() { return mController.getPageRect().left; }
+        @Override
+        protected float getPageLength() { return mController.getPageRect().width(); }
     }
 
     private class AxisY extends Axis {
@@ -763,7 +764,9 @@ public class PanZoomController
         @Override
         protected float getViewportLength() { return mController.getViewportSize().height; }
         @Override
-        protected float getPageLength() { return mController.getPageSize().height; }
+        protected float getPageStart() { return mController.getPageRect().top; }
+        @Override
+        protected float getPageLength() { return mController.getPageRect().height(); }
     }
 
     /*
commit 40ca1cea9b195b59923954e3466a285b2ae46384
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Fri Sep 26 20:57:56 2014 +0200

    android: remove tiles delayed by one redraw call
    
    Change-Id: I5ab5e4a0ba9cdf3f3115c4913b4cf04e7f062755

diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/MultiTileLayer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/MultiTileLayer.java
index eafbf32..8647ce6 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/MultiTileLayer.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/MultiTileLayer.java
@@ -45,6 +45,7 @@ import android.graphics.Region;
 import android.util.Log;
 
 import org.libreoffice.TileProvider;
+import org.mozilla.gecko.util.FloatUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -230,7 +231,6 @@ public class MultiTileLayer extends Layer {
     public void reevaluateTiles(ImmutableViewportMetrics viewportMetrics) {
         if (currentZoomFactor != viewportMetrics.zoomFactor) {
             currentZoomFactor = viewportMetrics.zoomFactor;
-            mTiles.clear();
         }
 
         RectF newTileViewPort = inflate(roundToTileSize(viewportMetrics.getViewport(), TILE_SIZE), TILE_SIZE);
@@ -239,11 +239,23 @@ public class MultiTileLayer extends Layer {
 
         if (tileViewPort != newTileViewPort) {
             tileViewPort = newTileViewPort;
-            clearTiles();
+            cleanTiles();
             addNewTiles();
+            markTiles();
         }
     }
 
+    private void cleanTiles() {
+        List<SubTile> tilesToRemove = new ArrayList<SubTile>();
+        for(SubTile tile : mTiles) {
+            if (tile.markedForRemoval) {
+                tile.destroy();
+                tilesToRemove.add(tile);
+            }
+        }
+        mTiles.removeAll(tilesToRemove);
+    }
+
     private void addNewTiles() {
         for (float y = tileViewPort.top; y < tileViewPort.bottom; y += TILE_SIZE) {
             if (y > currentPageSize.height) {
@@ -269,17 +281,17 @@ public class MultiTileLayer extends Layer {
         }
     }
 
-    private void clearTiles() {
-        ArrayList<SubTile> removeTiles = new ArrayList<SubTile>();
+    private void markTiles() {
         for (SubTile tile : mTiles) {
-            RectF tileRect = new RectF(tile.x, tile.y, tile.x + TILE_SIZE, tile.y + TILE_SIZE);
-            if (!RectF.intersects(tileViewPort, tileRect)) {
-                tile.destroy();
-                removeTiles.add(tile);
+            if (FloatUtils.fuzzyEquals(tile.zoom, currentZoomFactor)) {
+                RectF tileRect = new RectF(tile.x, tile.y, tile.x + TILE_SIZE, tile.y + TILE_SIZE);
+                if (!RectF.intersects(tileViewPort, tileRect)) {
+                    tile.markForRemoval();
+                }
+            } else {
+                tile.markForRemoval();
             }
         }
-
-        mTiles.removeAll(removeTiles);
     }
 }
 
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/SubTile.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/SubTile.java
index 9ec98ec..5ab4f0d 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/SubTile.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/SubTile.java
@@ -10,10 +10,16 @@ public class SubTile extends SingleTileLayer {
     public int y;
     public float zoom;
 
+    public boolean markedForRemoval = false;
+
     public SubTile(CairoImage mImage, int x, int y, float zoom) {
         super(mImage);
         this.x = x;
         this.y = y;
         this.zoom = zoom;
     }
+
+    public void markForRemoval() {
+        markedForRemoval = true;
+    }
 }
commit fd08f4fc4be60c99e83ea6e0aafc5c617c2c6ac0
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Fri Sep 26 13:56:13 2014 +0200

    android: rerender tiles when the zoom changes
    
    Initial implementation which needs a lots of polish. Sometimes
    the app crashes (for yet unknown reasons). Sometimes a gap
    between tiles is visible probably because of rounding errors.
    Tile handling and releasing after zoom is quite naive for now
    (just clearing all the tiles) which needs to be improved to be
    more seamless.
    
    Other changes: The responsibility of LOKitThread to handle tiles
    was moved to MultiTileLayer. TileProvider now takes into account
    the current zoom level when requesting tile rerendering.
    
    Change-Id: Ic188c03bfadf3a3dd2f79b04f07668eb63b705fb

diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
index 8ba5156..2b77143 100644
--- a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
@@ -2,123 +2,42 @@ package org.libreoffice;
 
 import android.graphics.Bitmap;
 import android.graphics.Rect;
-import android.graphics.RectF;
 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;
 
-import java.util.ArrayList;
 import java.util.concurrent.LinkedBlockingQueue;
 
 public class LOKitThread extends Thread {
     private static final String LOGTAG = LOKitThread.class.getSimpleName();
 
-    private static final int TILE_SIZE = 256;
     public LinkedBlockingQueue<LOEvent> mEventQueue = new LinkedBlockingQueue<LOEvent>();
     private LibreOfficeMainActivity mApplication;
     private TileProvider mTileProvider;
     private ViewportMetrics mViewportMetrics;
-    private Rect mOldRect;
     private boolean mCheckboardImageSet = false;
 
     public LOKitThread() {
         TileProviderFactory.initialize();
     }
 
-    private RectF normlizeRect(ImmutableViewportMetrics metrics) {
-        RectF rect = metrics.getViewport();
-        float zoomFactor = metrics.zoomFactor;
-        return new RectF(rect.left / zoomFactor, rect.top / zoomFactor, rect.right / zoomFactor, rect.bottom / zoomFactor);
-    }
-
-    private Rect roundToTileSize(RectF input, int tileSize) {
-        int minX = (Math.round(input.left) / tileSize) * tileSize;
-        int minY = (Math.round(input.top) / tileSize) * tileSize;
-        int maxX = ((Math.round(input.right) / tileSize) + 1) * tileSize;
-        int maxY = ((Math.round(input.bottom) / tileSize) + 1) * tileSize;
-        return new Rect(minX, minY, maxX, maxY);
-    }
-
-    private Rect inflate(Rect rect, int inflateSize) {
-        Rect newRect = new Rect(rect);
-        newRect.left -= inflateSize;
-        newRect.left = newRect.left < 0 ? 0 : newRect.left;
-
-        newRect.top -= inflateSize;
-        newRect.top = newRect.top < 0 ? 0 : newRect.top;
-
-        newRect.right += inflateSize;
-        newRect.bottom += inflateSize;
-
-        return newRect;
-    }
-
     private boolean draw() throws InterruptedException {
         int pageWidth = mTileProvider.getPageWidth();
         int pageHeight = mTileProvider.getPageHeight();
 
-        mViewportMetrics = new ViewportMetrics();
         FloatSize size = new FloatSize(pageWidth, pageHeight);
+        mViewportMetrics = new ViewportMetrics();
         mViewportMetrics.setPageSize(size, size);
 
         GeckoLayerClient layerClient = mApplication.getLayerClient();
-        layerClient.beginDrawing(mViewportMetrics);
 
-        ImmutableViewportMetrics metrics = mApplication.getLayerController().getViewportMetrics();
-        RectF viewport = normlizeRect(metrics);
-        Rect rect = inflate(roundToTileSize(viewport, TILE_SIZE), TILE_SIZE);
+        layerClient.beginDrawing();
 
-        mOldRect = rect;
+        layerClient.reevaluateTiles();
 
-        Log.i(LOGTAG, "tilerender RECT: " + rect);
-
-        long start = System.currentTimeMillis();
-        int noOfRemoved = 0;
-
-        ArrayList<SubTile> removeTiles = new ArrayList<SubTile>();
-        for (SubTile tile : layerClient.getTiles()) {
-            Rect tileRect = new Rect(tile.x, tile.y, tile.x + TILE_SIZE, tile.y + TILE_SIZE);
-            if (!Rect.intersects(rect, tileRect)) {
-                tile.destroy();
-                removeTiles.add(tile);
-                noOfRemoved++;
-            }
-        }
-
-        layerClient.getTiles().removeAll(removeTiles);
-
-        Log.i(LOGTAG, "TileRendering Clear: " + noOfRemoved + " in " + (System.currentTimeMillis() - start) + "ms");
-        start = System.currentTimeMillis();
-        int noOfAdded = 0;
-        for (int y = rect.top; y < rect.bottom; y += TILE_SIZE) {
-            for (int x = rect.left; x < rect.right; x += TILE_SIZE) {
-                if (x > pageWidth) {
-                    continue;
-                }
-                if (y > pageHeight) {
-                    continue;
-                }
-                boolean contains = false;
-                for (SubTile tile : layerClient.getTiles()) {
-                    if (tile.x == x && tile.y == y) {
-                        contains = true;
-                    }
-                }

... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list