Pull parts of GeckoSoftwareLayerClient that will be used for GL layers into a generic GeckoLayerClient class
authorPatrick Walton <pwalton@mozilla.com>
Thu, 02 Feb 2012 22:07:05 -0800
changeset 90876 5e47633414952424bf3fe0eeac621bea5ff587ce
parent 90875 6a17b93d69336766e3407e720a2367611cd7027d
child 90877 ba16d096e3d87b774c0834914c003eaa4cbfd261
push idunknown
push userunknown
push dateunknown
milestone12.0a1
Pull parts of GeckoSoftwareLayerClient that will be used for GL layers into a generic GeckoLayerClient class
mobile/android/base/Makefile.in
mobile/android/base/gfx/GeckoLayerClient.java
mobile/android/base/gfx/GeckoSoftwareLayerClient.java
widget/android/AndroidJavaWrappers.cpp
widget/android/AndroidJavaWrappers.h
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -105,16 +105,17 @@ FENNEC_JAVA_FILES = \
   gfx/BitmapUtils.java \
   gfx/BufferedCairoImage.java \
   gfx/CairoGLInfo.java \
   gfx/CairoImage.java \
   gfx/CairoUtils.java \
   gfx/CheckerboardImage.java \
   gfx/FlexibleGLSurfaceView.java \
   gfx/FloatSize.java \
+  gfx/GeckoLayerClient.java \
   gfx/GeckoSoftwareLayerClient.java \
   gfx/GLController.java \
   gfx/GLThread.java \
   gfx/InputConnectionHandler.java \
   gfx/IntSize.java \
   gfx/Layer.java \
   gfx/LayerClient.java \
   gfx/LayerController.java \
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/GeckoLayerClient.java
@@ -0,0 +1,324 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *   Chris Lord <chrislord.net@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.FloatUtils;
+import org.mozilla.gecko.GeckoApp;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoEvent;
+import org.mozilla.gecko.GeckoEventListener;
+import org.json.JSONException;
+import org.json.JSONObject;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public abstract class GeckoLayerClient extends LayerClient implements GeckoEventListener {
+    private static final String LOGTAG = "GeckoLayerClient";
+
+    protected IntSize mScreenSize;
+    protected IntSize mBufferSize;
+
+    protected Layer mTileLayer;
+
+    /* The viewport that Gecko is currently displaying. */
+    protected ViewportMetrics mGeckoViewport;
+
+    /* The viewport that Gecko will display when drawing is finished */
+    protected ViewportMetrics mNewGeckoViewport;
+
+    private static final long MIN_VIEWPORT_CHANGE_DELAY = 350L;
+    private long mLastViewportChangeTime;
+    private boolean mPendingViewportAdjust;
+    private boolean mViewportSizeChanged;
+
+    // mUpdateViewportOnEndDraw is used to indicate that we received a
+    // viewport update notification while drawing. therefore, when the
+    // draw finishes, we need to update the entire viewport rather than
+    // just the page size. this boolean should always be accessed from
+    // inside a transaction, so no synchronization is needed.
+    private boolean mUpdateViewportOnEndDraw;
+
+    private static Pattern sColorPattern;
+
+    /* Used by robocop for testing purposes */
+    private DrawListener mDrawListener;
+
+    protected abstract boolean handleDirectTextureChange(boolean hasDirectTexture);
+    protected abstract boolean shouldDrawProceed(int tileWidth, int tileHeight);
+    protected abstract void updateLayerAfterDraw(Rect updatedRect);
+    protected abstract IntSize getBufferSize();
+    protected abstract IntSize getTileSize();
+
+    public GeckoLayerClient(Context context) {
+        mScreenSize = new IntSize(0, 0);
+        mBufferSize = new IntSize(0, 0);
+    }
+
+    /** Attaches the root layer to the layer controller so that Gecko appears. */
+    @Override
+    public void setLayerController(LayerController layerController) {
+        super.setLayerController(layerController);
+
+        layerController.setRoot(mTileLayer);
+        if (mGeckoViewport != null) {
+            layerController.setViewportMetrics(mGeckoViewport);
+        }
+
+        GeckoAppShell.registerGeckoEventListener("Viewport:UpdateAndDraw", this);
+        GeckoAppShell.registerGeckoEventListener("Viewport:UpdateLater", this);
+
+        sendResizeEventIfNecessary();
+    }
+
+    public boolean beginDrawing(int width, int height, int tileWidth, int tileHeight,
+                                String metadata, boolean hasDirectTexture) {
+        // If we've changed surface types, cancel this draw
+        if (handleDirectTextureChange(hasDirectTexture)) {
+            return false;
+        }
+
+        if (!shouldDrawProceed(tileWidth, tileHeight)) {
+            return false;
+        }
+
+        try {
+            JSONObject viewportObject = new JSONObject(metadata);
+            mNewGeckoViewport = new ViewportMetrics(viewportObject);
+
+            // Update the background color, if it's present.
+            String backgroundColorString = viewportObject.optString("backgroundColor");
+            if (backgroundColorString != null) {
+                LayerController controller = getLayerController();
+                controller.setCheckerboardColor(parseColorFromGecko(backgroundColorString));
+            }
+        } catch (JSONException e) {
+            Log.e(LOGTAG, "Aborting draw, bad viewport description: " + metadata);
+            return false;
+        }
+
+        beginTransaction(mTileLayer);
+        return true;
+    }
+
+    /*
+     * TODO: Would be cleaner if this took an android.graphics.Rect instead, but that would require
+     * a little more JNI magic.
+     */
+    public void endDrawing(int x, int y, int width, int height) {
+        synchronized (getLayerController()) {
+            try {
+                updateViewport(!mUpdateViewportOnEndDraw);
+                mUpdateViewportOnEndDraw = false;
+
+                Rect rect = new Rect(x, y, x + width, y + height);
+                updateLayerAfterDraw(rect);
+            } finally {
+                endTransaction(mTileLayer);
+            }
+        }
+        Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - endDrawing");
+
+        /* Used by robocop for testing purposes */
+        if (mDrawListener != null) {
+            mDrawListener.drawFinished(x, y, width, height);
+        }
+    }
+
+    protected void updateViewport(boolean onlyUpdatePageSize) {
+        // save and restore the viewport size stored in java; never let the
+        // JS-side viewport dimensions override the java-side ones because
+        // java is the One True Source of this information, and allowing JS
+        // to override can lead to race conditions where this data gets clobbered.
+        FloatSize viewportSize = getLayerController().getViewportSize();
+        mGeckoViewport = mNewGeckoViewport;
+        mGeckoViewport.setSize(viewportSize);
+
+        LayerController controller = getLayerController();
+        PointF displayportOrigin = mGeckoViewport.getDisplayportOrigin();
+        mTileLayer.setOrigin(PointUtils.round(displayportOrigin));
+        mTileLayer.setResolution(mGeckoViewport.getZoomFactor());
+
+        if (onlyUpdatePageSize) {
+            // Don't adjust page size when zooming unless zoom levels are
+            // approximately equal.
+            if (FloatUtils.fuzzyEquals(controller.getZoomFactor(),
+                    mGeckoViewport.getZoomFactor()))
+                controller.setPageSize(mGeckoViewport.getPageSize());
+        } else {
+            controller.setViewportMetrics(mGeckoViewport);
+            controller.abortPanZoomAnimation();
+        }
+    }
+
+    /* Informs Gecko that the screen size has changed. */
+    protected void sendResizeEventIfNecessary(boolean force) {
+        DisplayMetrics metrics = new DisplayMetrics();
+        GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+
+        // Return immediately if the screen size hasn't changed or the viewport
+        // size is zero (which indicates that the rendering surface hasn't been
+        // allocated yet).
+        boolean screenSizeChanged = (metrics.widthPixels != mScreenSize.width ||
+                                     metrics.heightPixels != mScreenSize.height);
+        boolean viewportSizeValid = (getLayerController() != null &&
+                                     getLayerController().getViewportSize().isPositive());
+        if (!(force || (screenSizeChanged && viewportSizeValid))) {
+            return;
+        }
+
+        mScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels);
+        IntSize bufferSize = getBufferSize(), tileSize = getTileSize();
+
+        Log.i(LOGTAG, "Screen-size changed to " + mScreenSize);
+        GeckoEvent event = new GeckoEvent(GeckoEvent.SIZE_CHANGED,
+                                          bufferSize.width, bufferSize.height,
+                                          metrics.widthPixels, metrics.heightPixels,
+                                          tileSize.width, tileSize.height);
+        GeckoAppShell.sendEventToGecko(event);
+    }
+
+    // Parses a color from an RGB triple of the form "rgb([0-9]+, [0-9]+, [0-9]+)". If the color
+    // cannot be parsed, returns white.
+    private static int parseColorFromGecko(String string) {
+        if (sColorPattern == null) {
+            sColorPattern = Pattern.compile("rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)");
+        }
+
+        Matcher matcher = sColorPattern.matcher(string);
+        if (!matcher.matches()) {
+            return Color.WHITE;
+        }
+
+        int r = Integer.parseInt(matcher.group(1));
+        int g = Integer.parseInt(matcher.group(2));
+        int b = Integer.parseInt(matcher.group(3));
+        return Color.rgb(r, g, b);
+    }
+
+    @Override
+    public void render() {
+        adjustViewportWithThrottling();
+    }
+
+    private void adjustViewportWithThrottling() {
+        if (!getLayerController().getRedrawHint())
+            return;
+
+        if (mPendingViewportAdjust)
+            return;
+
+        long timeDelta = System.currentTimeMillis() - mLastViewportChangeTime;
+        if (timeDelta < MIN_VIEWPORT_CHANGE_DELAY) {
+            getLayerController().getView().postDelayed(
+                new Runnable() {
+                    public void run() {
+                        mPendingViewportAdjust = false;
+                        adjustViewport();
+                    }
+                }, MIN_VIEWPORT_CHANGE_DELAY - timeDelta);
+            mPendingViewportAdjust = true;
+            return;
+        }
+
+        adjustViewport();
+    }
+
+    @Override
+    public void viewportSizeChanged() {
+        mViewportSizeChanged = true;
+    }
+
+    private void adjustViewport() {
+        ViewportMetrics viewportMetrics =
+            new ViewportMetrics(getLayerController().getViewportMetrics());
+
+        PointF viewportOffset = viewportMetrics.getOptimumViewportOffset(mBufferSize);
+        viewportMetrics.setViewportOffset(viewportOffset);
+        viewportMetrics.setViewport(viewportMetrics.getClampedViewport());
+
+        GeckoAppShell.sendEventToGecko(new GeckoEvent(viewportMetrics));
+        if (mViewportSizeChanged) {
+            mViewportSizeChanged = false;
+            GeckoAppShell.viewSizeChanged();
+        }
+
+        mLastViewportChangeTime = System.currentTimeMillis();
+    }
+
+    public void handleMessage(String event, JSONObject message) {
+        if ("Viewport:UpdateAndDraw".equals(event)) {
+            mUpdateViewportOnEndDraw = true;
+
+            // Redraw everything.
+            Rect rect = new Rect(0, 0, mBufferSize.width, mBufferSize.height);
+            GeckoAppShell.sendEventToGecko(new GeckoEvent(GeckoEvent.DRAW, rect));
+        } else if ("Viewport:UpdateLater".equals(event)) {
+            mUpdateViewportOnEndDraw = true;
+        }
+    }
+
+    @Override
+    public void geometryChanged() {
+        /* Let Gecko know if the screensize has changed */
+        sendResizeEventIfNecessary();
+        render();
+    }
+
+    private void sendResizeEventIfNecessary() {
+        sendResizeEventIfNecessary(false);
+    }
+
+    /** Used by robocop for testing purposes. Not for production use! This is called via reflection by robocop. */
+    public void setDrawListener(DrawListener listener) {
+        mDrawListener = listener;
+    }
+
+    /** Used by robocop for testing purposes. Not for production use! This is used via reflection by robocop. */
+    public interface DrawListener {
+        public void drawFinished(int x, int y, int width, int height);
+    }
+}
+
--- a/mobile/android/base/gfx/GeckoSoftwareLayerClient.java
+++ b/mobile/android/base/gfx/GeckoSoftwareLayerClient.java
@@ -41,92 +41,52 @@ package org.mozilla.gecko.gfx;
 import org.mozilla.gecko.gfx.CairoImage;
 import org.mozilla.gecko.gfx.IntSize;
 import org.mozilla.gecko.gfx.LayerClient;
 import org.mozilla.gecko.gfx.LayerController;
 import org.mozilla.gecko.gfx.LayerRenderer;
 import org.mozilla.gecko.gfx.MultiTileLayer;
 import org.mozilla.gecko.gfx.PointUtils;
 import org.mozilla.gecko.gfx.WidgetTileLayer;
-import org.mozilla.gecko.FloatUtils;
-import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoEvent;
-import org.mozilla.gecko.GeckoEventListener;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Point;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.os.SystemClock;
-import android.util.DisplayMetrics;
 import android.util.Log;
-import org.json.JSONException;
-import org.json.JSONObject;
 import java.nio.ByteBuffer;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * Transfers a software-rendered Gecko to an ImageLayer so that it can be rendered by our
  * compositor.
  *
  * TODO: Throttle down Gecko's priority when we pan and zoom.
  */
-public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventListener {
+public class GeckoSoftwareLayerClient extends GeckoLayerClient {
     private static final String LOGTAG = "GeckoSoftwareLayerClient";
 
-    private Context mContext;
     private int mFormat;
-    private IntSize mScreenSize, mViewportSize;
-    private IntSize mBufferSize;
+    private IntSize mViewportSize;
     private ByteBuffer mBuffer;
-    private Layer mTileLayer;
-
-    /* The viewport that Gecko is currently displaying. */
-    private ViewportMetrics mGeckoViewport;
-
-    /* The viewport that Gecko will display when drawing is finished */
-    private ViewportMetrics mNewGeckoViewport;
 
     /* The offset used to make sure tiles are snapped to the pixel grid */
     private Point mRenderOffset;
 
     private CairoImage mCairoImage;
 
     private static final IntSize TILE_SIZE = new IntSize(256, 256);
 
-    private static final long MIN_VIEWPORT_CHANGE_DELAY = 350L;
-    private long mLastViewportChangeTime;
-    private boolean mPendingViewportAdjust;
-    private boolean mViewportSizeChanged;
-
     // Whether or not the last paint we got used direct texturing
     private boolean mHasDirectTexture;
 
-    // mUpdateViewportOnEndDraw is used to indicate that we received a
-    // viewport update notification while drawing. therefore, when the
-    // draw finishes, we need to update the entire viewport rather than
-    // just the page size. this boolean should always be accessed from
-    // inside a transaction, so no synchronization is needed.
-    private boolean mUpdateViewportOnEndDraw;
+    public GeckoSoftwareLayerClient(Context context) {
+        super(context);
 
-    /* Used by robocop for testing purposes */
-    private DrawListener mDrawListener;
-
-    private static Pattern sColorPattern;
-
-    public GeckoSoftwareLayerClient(Context context) {
-        mContext = context;
-
-        mScreenSize = new IntSize(0, 0);
-        mBufferSize = new IntSize(0, 0);
         mFormat = CairoImage.FORMAT_RGB16_565;
         mRenderOffset = new Point(0, 0);
 
         mCairoImage = new CairoImage() {
             @Override
             public ByteBuffer getBuffer() { return mBuffer; }
             @Override
             public IntSize getSize() { return mBufferSize; }
@@ -148,33 +108,18 @@ public class GeckoSoftwareLayerClient ex
             if (mBuffer != null)
                 GeckoAppShell.freeDirectBuffer(mBuffer);
             mBuffer = null;
         } finally {
             super.finalize();
         }
     }
 
-    /** Attaches the root layer to the layer controller so that Gecko appears. */
     @Override
-    public void setLayerController(LayerController layerController) {
-        super.setLayerController(layerController);
-
-        layerController.setRoot(mTileLayer);
-        if (mGeckoViewport != null) {
-            layerController.setViewportMetrics(mGeckoViewport);
-        }
-
-        GeckoAppShell.registerGeckoEventListener("Viewport:UpdateAndDraw", this);
-        GeckoAppShell.registerGeckoEventListener("Viewport:UpdateLater", this);
-
-        sendResizeEventIfNecessary();
-    }
-
-    private boolean setHasDirectTexture(boolean hasDirectTexture) {
+    protected boolean handleDirectTextureChange(boolean hasDirectTexture) {
         if (mTileLayer != null && hasDirectTexture == mHasDirectTexture)
             return false;
 
         mHasDirectTexture = hasDirectTexture;
 
         if (mHasDirectTexture) {
             Log.i(LOGTAG, "Creating WidgetTileLayer");
             mTileLayer = new WidgetTileLayer(mCairoImage);
@@ -188,53 +133,45 @@ public class GeckoSoftwareLayerClient ex
 
         // Force a resize event to be sent because the results of this
         // are different depending on what tile system we're using
         sendResizeEventIfNecessary(true);
 
         return true;
     }
 
-    public boolean beginDrawing(int width, int height, int tileWidth, int tileHeight, String metadata, boolean hasDirectTexture) {
-        // If we've changed surface types, cancel this draw
-        if (setHasDirectTexture(hasDirectTexture)) {
-            return false;
-        }
-
+    @Override
+    protected boolean shouldDrawProceed(int tileWidth, int tileHeight) {
         // Make sure the tile-size matches. If it doesn't, we could crash trying
         // to access invalid memory.
         if (mHasDirectTexture) {
             if (tileWidth != 0 || tileHeight != 0) {
-                Log.e(LOGTAG, "Aborting draw, incorrect tile size of " + tileWidth + "x" + tileHeight);
+                Log.e(LOGTAG, "Aborting draw, incorrect tile size of " + tileWidth + "x" +
+                      tileHeight);
                 return false;
             }
         } else {
             if (tileWidth != TILE_SIZE.width || tileHeight != TILE_SIZE.height) {
-                Log.e(LOGTAG, "Aborting draw, incorrect tile size of " + tileWidth + "x" + tileHeight);
+                Log.e(LOGTAG, "Aborting draw, incorrect tile size of " + tileWidth + "x" +
+                      tileHeight);
                 return false;
             }
         }
 
-        try {
-            JSONObject viewportObject = new JSONObject(metadata);
-            mNewGeckoViewport = new ViewportMetrics(viewportObject);
+        return true;
+    }
 
-            // Update the background color, if it's present.
-            String backgroundColorString = viewportObject.optString("backgroundColor");
-            if (backgroundColorString != null) {
-                LayerController controller = getLayerController();
-                controller.setCheckerboardColor(parseColorFromGecko(backgroundColorString));
-            }
-        } catch (JSONException e) {
-            Log.e(LOGTAG, "Aborting draw, bad viewport description: " + metadata);
+    @Override
+    public boolean beginDrawing(int width, int height, int tileWidth, int tileHeight,
+                                String metadata, boolean hasDirectTexture) {
+        if (!super.beginDrawing(width, height, tileWidth, tileHeight, metadata,
+                                hasDirectTexture)) {
             return false;
         }
 
-        beginTransaction(mTileLayer);
-
         // We only need to set a render offset/allocate buffer memory if
         // we're using MultiTileLayer. Otherwise, just synchronise the
         // buffer size and return.
         if (!(mTileLayer instanceof MultiTileLayer)) {
             if (mBufferSize.width != width || mBufferSize.height != height)
                 mBufferSize = new IntSize(width, height);
             return true;
         }
@@ -276,68 +213,25 @@ public class GeckoSoftwareLayerClient ex
 
                 mBuffer = GeckoAppShell.allocateDirectBuffer(size);
             }
         }
 
         return true;
     }
 
-    private void updateViewport(final boolean onlyUpdatePageSize) {
-        // save and restore the viewport size stored in java; never let the
-        // JS-side viewport dimensions override the java-side ones because
-        // java is the One True Source of this information, and allowing JS
-        // to override can lead to race conditions where this data gets clobbered.
-        FloatSize viewportSize = getLayerController().getViewportSize();
-        mGeckoViewport = mNewGeckoViewport;
-        mGeckoViewport.setSize(viewportSize);
-
-        LayerController controller = getLayerController();
-        PointF displayportOrigin = mGeckoViewport.getDisplayportOrigin();
-        mTileLayer.setOrigin(PointUtils.round(displayportOrigin));
-        mTileLayer.setResolution(mGeckoViewport.getZoomFactor());
-
-        if (onlyUpdatePageSize) {
-            // Don't adjust page size when zooming unless zoom levels are
-            // approximately equal.
-            if (FloatUtils.fuzzyEquals(controller.getZoomFactor(),
-                    mGeckoViewport.getZoomFactor()))
-                controller.setPageSize(mGeckoViewport.getPageSize());
-        } else {
-            controller.setViewportMetrics(mGeckoViewport);
-            controller.abortPanZoomAnimation();
+    @Override
+    protected void updateLayerAfterDraw(Rect updatedRect) {
+        if (!(mTileLayer instanceof MultiTileLayer)) {
+            return;
         }
-    }
 
-    /*
-     * TODO: Would be cleaner if this took an android.graphics.Rect instead, but that would require
-     * a little more JNI magic.
-     */
-    public void endDrawing(int x, int y, int width, int height) {
-        synchronized (getLayerController()) {
-            try {
-                updateViewport(!mUpdateViewportOnEndDraw);
-                mUpdateViewportOnEndDraw = false;
-
-                if (mTileLayer instanceof MultiTileLayer) {
-                    Rect rect = new Rect(x, y, x + width, y + height);
-                    rect.offset(mRenderOffset.x, mRenderOffset.y);
-                    ((MultiTileLayer)mTileLayer).invalidate(rect);
-                    ((MultiTileLayer)mTileLayer).setRenderOffset(mRenderOffset);
-                }
-            } finally {
-                endTransaction(mTileLayer);
-            }
-        }
-        Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - endDrawing");
-
-        /* Used by robocop for testing purposes */
-        if (mDrawListener != null) {
-            mDrawListener.drawFinished(x, y, width, height);
-        }
+        updatedRect.offset(mRenderOffset.x, mRenderOffset.y);
+        ((MultiTileLayer)mTileLayer).invalidate(updatedRect);
+        ((MultiTileLayer)mTileLayer).setRenderOffset(mRenderOffset);
     }
 
     public ViewportMetrics getGeckoViewportMetrics() {
         // Return a copy, as we modify this inside the Gecko thread
         if (mGeckoViewport != null)
             return new ViewportMetrics(mGeckoViewport);
         return null;
     }
@@ -409,156 +303,41 @@ public class GeckoSoftwareLayerClient ex
      * Gecko calls this function to signal that it is done with the back buffer. After this call,
      * it is forbidden for Gecko to touch the buffer.
      */
     public void unlockBuffer() {
         /* no-op */
     }
 
     @Override
-    public void geometryChanged() {
-        /* Let Gecko know if the screensize has changed */
-        sendResizeEventIfNecessary();
-        render();
-    }
-
-    private void sendResizeEventIfNecessary() {
-        sendResizeEventIfNecessary(false);
-    }
-
-    /* Informs Gecko that the screen size has changed. */
-    private void sendResizeEventIfNecessary(boolean force) {
-        DisplayMetrics metrics = new DisplayMetrics();
-        GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
-
-        // Return immediately if the screen size hasn't changed or the viewport
-        // size is zero (which indicates that the rendering surface hasn't been
-        // allocated yet).
-        boolean screenSizeChanged = (metrics.widthPixels != mScreenSize.width ||
-                                     metrics.heightPixels != mScreenSize.height);
-        boolean viewportSizeValid = (getLayerController() != null &&
-                                     getLayerController().getViewportSize().isPositive());
-        if (!(force || (screenSizeChanged && viewportSizeValid))) {
-            return;
-        }
-
-        mScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels);
-        IntSize bufferSize;
-        IntSize tileSize;
-
+    protected IntSize getBufferSize() {
         // Round up depending on layer implementation to remove texture wastage
         if (!mHasDirectTexture) {
             // Round to the next multiple of the tile size
-            bufferSize = new IntSize(((mScreenSize.width + LayerController.MIN_BUFFER.width - 1) / TILE_SIZE.width + 1) * TILE_SIZE.width,
-                                     ((mScreenSize.height + LayerController.MIN_BUFFER.height - 1) / TILE_SIZE.height + 1) * TILE_SIZE.height);
-            tileSize = TILE_SIZE;
-        } else {
-            int maxSize = getLayerController().getView().getMaxTextureSize();
-
-            // XXX Integrate gralloc/tiling work to circumvent this
-            if (mScreenSize.width > maxSize || mScreenSize.height > maxSize)
-                throw new RuntimeException("Screen size of " + mScreenSize + " larger than maximum texture size of " + maxSize);
-
-            // Round to next power of two until we have NPOT texture support, respecting maximum texture size
-            bufferSize = new IntSize(Math.min(maxSize, IntSize.nextPowerOfTwo(mScreenSize.width + LayerController.MIN_BUFFER.width)),
-                                     Math.min(maxSize, IntSize.nextPowerOfTwo(mScreenSize.height + LayerController.MIN_BUFFER.height)));
-            tileSize = new IntSize(0, 0);
+            return new IntSize(((mScreenSize.width + LayerController.MIN_BUFFER.width - 1) /
+                                    TILE_SIZE.width + 1) * TILE_SIZE.width,
+                               ((mScreenSize.height + LayerController.MIN_BUFFER.height - 1) /
+                                    TILE_SIZE.height + 1) * TILE_SIZE.height);
         }
 
-        Log.i(LOGTAG, "Screen-size changed to " + mScreenSize);
-        GeckoEvent event = new GeckoEvent(GeckoEvent.SIZE_CHANGED,
-                                          bufferSize.width, bufferSize.height,
-                                          metrics.widthPixels, metrics.heightPixels,
-                                          tileSize.width, tileSize.height);
-        GeckoAppShell.sendEventToGecko(event);
-    }
+        int maxSize = getLayerController().getView().getMaxTextureSize();
 
-    @Override
-    public void viewportSizeChanged() {
-        mViewportSizeChanged = true;
+        // XXX Integrate gralloc/tiling work to circumvent this
+        if (mScreenSize.width > maxSize || mScreenSize.height > maxSize) {
+            throw new RuntimeException("Screen size of " + mScreenSize +
+                                       " larger than maximum texture size of " + maxSize);
+        }
+
+        // Round to next power of two until we have NPOT texture support, respecting maximum
+        // texture size
+        return new IntSize(Math.min(maxSize, IntSize.nextPowerOfTwo(mScreenSize.width +
+                                             LayerController.MIN_BUFFER.width)),
+                           Math.min(maxSize, IntSize.nextPowerOfTwo(mScreenSize.height +
+                                             LayerController.MIN_BUFFER.height)));
     }
 
     @Override
-    public void render() {
-        adjustViewportWithThrottling();
-    }
-
-    private void adjustViewportWithThrottling() {
-        if (!getLayerController().getRedrawHint())
-            return;
-
-        if (mPendingViewportAdjust)
-            return;
-
-        long timeDelta = System.currentTimeMillis() - mLastViewportChangeTime;
-        if (timeDelta < MIN_VIEWPORT_CHANGE_DELAY) {
-            getLayerController().getView().postDelayed(
-                new Runnable() {
-                    public void run() {
-                        mPendingViewportAdjust = false;
-                        adjustViewport();
-                    }
-                }, MIN_VIEWPORT_CHANGE_DELAY - timeDelta);
-            mPendingViewportAdjust = true;
-            return;
-        }
-
-        adjustViewport();
-    }
-
-    private void adjustViewport() {
-        ViewportMetrics viewportMetrics =
-            new ViewportMetrics(getLayerController().getViewportMetrics());
-
-        PointF viewportOffset = viewportMetrics.getOptimumViewportOffset(mBufferSize);
-        viewportMetrics.setViewportOffset(viewportOffset);
-        viewportMetrics.setViewport(viewportMetrics.getClampedViewport());
-
-        GeckoAppShell.sendEventToGecko(new GeckoEvent(viewportMetrics));
-        if (mViewportSizeChanged) {
-            mViewportSizeChanged = false;
-            GeckoAppShell.viewSizeChanged();
-        }
-
-        mLastViewportChangeTime = System.currentTimeMillis();
-    }
-
-    public void handleMessage(String event, JSONObject message) {
-        if ("Viewport:UpdateAndDraw".equals(event)) {
-            mUpdateViewportOnEndDraw = true;
-
-            // Redraw everything.
-            Rect rect = new Rect(0, 0, mBufferSize.width, mBufferSize.height);
-            GeckoAppShell.sendEventToGecko(new GeckoEvent(GeckoEvent.DRAW, rect));
-        } else if ("Viewport:UpdateLater".equals(event)) {
-            mUpdateViewportOnEndDraw = true;
-        }
-    }
-
-    // Parses a color from an RGB triple of the form "rgb([0-9]+, [0-9]+, [0-9]+)". If the color
-    // cannot be parsed, returns white.
-    private static int parseColorFromGecko(String string) {
-        if (sColorPattern == null) {
-            sColorPattern = Pattern.compile("rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)");
-        }
-
-        Matcher matcher = sColorPattern.matcher(string);
-        if (!matcher.matches()) {
-            return Color.WHITE;
-        }
-
-        int r = Integer.parseInt(matcher.group(1));
-        int g = Integer.parseInt(matcher.group(2));
-        int b = Integer.parseInt(matcher.group(3));
-        return Color.rgb(r, g, b);
-    }
-
-    /** Used by robocop for testing purposes. Not for production use! This is called via reflection by robocop. */
-    public void setDrawListener(DrawListener listener) {
-        mDrawListener = listener;
-    }
-
-    /** Used by robocop for testing purposes. Not for production use! This is used via reflection by robocop. */
-    public interface DrawListener {
-        public void drawFinished(int x, int y, int width, int height);
+    protected IntSize getTileSize() {
+        // Round up depending on layer implementation to remove texture wastage
+        return !mHasDirectTexture ? TILE_SIZE : new IntSize(0, 0);
     }
 }
 
--- a/widget/android/AndroidJavaWrappers.cpp
+++ b/widget/android/AndroidJavaWrappers.cpp
@@ -103,22 +103,25 @@ jmethodID AndroidAddress::jGetFeatureNam
 jmethodID AndroidAddress::jGetLocalityMethod;
 jmethodID AndroidAddress::jGetPostalCodeMethod;
 jmethodID AndroidAddress::jGetPremisesMethod;
 jmethodID AndroidAddress::jGetSubAdminAreaMethod;
 jmethodID AndroidAddress::jGetSubLocalityMethod;
 jmethodID AndroidAddress::jGetSubThoroughfareMethod;
 jmethodID AndroidAddress::jGetThoroughfareMethod;
 
+jclass AndroidGeckoLayerClient::jGeckoLayerClientClass = 0;
+jmethodID AndroidGeckoLayerClient::jBeginDrawingMethod = 0;
+jmethodID AndroidGeckoLayerClient::jEndDrawingMethod = 0;
+
 jclass AndroidGeckoSoftwareLayerClient::jGeckoSoftwareLayerClientClass = 0;
 jmethodID AndroidGeckoSoftwareLayerClient::jLockBufferMethod = 0;
 jmethodID AndroidGeckoSoftwareLayerClient::jUnlockBufferMethod = 0;
 jmethodID AndroidGeckoSoftwareLayerClient::jGetRenderOffsetMethod = 0;
-jmethodID AndroidGeckoSoftwareLayerClient::jBeginDrawingMethod = 0;
-jmethodID AndroidGeckoSoftwareLayerClient::jEndDrawingMethod = 0;
+
 jclass AndroidGeckoSurfaceView::jGeckoSurfaceViewClass = 0;
 jmethodID AndroidGeckoSurfaceView::jBeginDrawingMethod = 0;
 jmethodID AndroidGeckoSurfaceView::jEndDrawingMethod = 0;
 jmethodID AndroidGeckoSurfaceView::jDraw2DBitmapMethod = 0;
 jmethodID AndroidGeckoSurfaceView::jDraw2DBufferMethod = 0;
 jmethodID AndroidGeckoSurfaceView::jGetSoftwareDrawBitmapMethod = 0;
 jmethodID AndroidGeckoSurfaceView::jGetSoftwareDrawBufferMethod = 0;
 jmethodID AndroidGeckoSurfaceView::jGetSurfaceMethod = 0;
@@ -139,16 +142,17 @@ jmethodID AndroidGeckoSurfaceView::jGetH
 void
 mozilla::InitAndroidJavaWrappers(JNIEnv *jEnv)
 {
     AndroidGeckoEvent::InitGeckoEventClass(jEnv);
     AndroidPoint::InitPointClass(jEnv);
     AndroidLocation::InitLocationClass(jEnv);
     AndroidAddress::InitAddressClass(jEnv);
     AndroidRect::InitRectClass(jEnv);
+    AndroidGeckoLayerClient::InitGeckoLayerClientClass(jEnv);
     AndroidGeckoSoftwareLayerClient::InitGeckoSoftwareLayerClientClass(jEnv);
     AndroidGeckoSurfaceView::InitGeckoSurfaceViewClass(jEnv);
 }
 
 void
 AndroidGeckoEvent::InitGeckoEventClass(JNIEnv *jEnv)
 {
     initInit();
@@ -315,29 +319,40 @@ AndroidRect::InitRectClass(JNIEnv *jEnv)
 
     jBottomField = getField("bottom", "I");
     jLeftField = getField("left", "I");
     jTopField = getField("top", "I");
     jRightField = getField("right", "I");
 }
 
 void
+AndroidGeckoLayerClient::InitGeckoLayerClientClass(JNIEnv *jEnv)
+{
+#ifdef MOZ_JAVA_COMPOSITOR
+    initInit();
+
+    jGeckoLayerClientClass = getClassGlobalRef("org/mozilla/gecko/gfx/GeckoLayerClient");
+
+    jBeginDrawingMethod = getMethod("beginDrawing", "(IIIILjava/lang/String;Z)Z");
+    jEndDrawingMethod = getMethod("endDrawing", "(IIII)V");
+#endif
+}
+
+void
 AndroidGeckoSoftwareLayerClient::InitGeckoSoftwareLayerClientClass(JNIEnv *jEnv)
 {
 #ifdef MOZ_JAVA_COMPOSITOR
     initInit();
 
     jGeckoSoftwareLayerClientClass =
         getClassGlobalRef("org/mozilla/gecko/gfx/GeckoSoftwareLayerClient");
 
     jLockBufferMethod = getMethod("lockBuffer", "()Ljava/nio/ByteBuffer;");
     jUnlockBufferMethod = getMethod("unlockBuffer", "()V");
     jGetRenderOffsetMethod = getMethod("getRenderOffset", "()Landroid/graphics/Point;");
-    jBeginDrawingMethod = getMethod("beginDrawing", "(IIIILjava/lang/String;Z)Z");
-    jEndDrawingMethod = getMethod("endDrawing", "(IIII)V");
 #endif
 }
 
 #undef initInit
 #undef initClassGlobalRef
 #undef getField
 #undef getMethod
 
@@ -638,16 +653,44 @@ AndroidGeckoSurfaceView::Draw2D(jobject 
 {
     JNIEnv *env = AndroidBridge::GetJNIEnv();
     if (!env)
         return;
 
     env->CallVoidMethod(wrapped_obj, jDraw2DBufferMethod, buffer, stride);
 }
 
+bool
+AndroidGeckoLayerClient::BeginDrawing(int aWidth, int aHeight, int aTileWidth, int aTileHeight,
+                                      const nsAString &aMetadata, bool aHasDirectTexture)
+{
+    NS_ASSERTION(!isNull(), "BeginDrawing() called on null layer client!");
+    JNIEnv *env = AndroidBridge::GetJNIEnv();
+    if (!env)
+        return false;
+
+    AndroidBridge::AutoLocalJNIFrame(env, 1);
+    jstring jMetadata = env->NewString(nsPromiseFlatString(aMetadata).get(), aMetadata.Length());
+    return env->CallBooleanMethod(wrapped_obj, jBeginDrawingMethod, aWidth, aHeight, aTileWidth,
+                                  aTileHeight, jMetadata, aHasDirectTexture);
+}
+
+void
+AndroidGeckoLayerClient::EndDrawing(const nsIntRect &aRect)
+{
+    NS_ASSERTION(!isNull(), "EndDrawing() called on null layer client!");
+    JNIEnv *env = AndroidBridge::GetJNIEnv();
+    if (!env)
+        return;
+
+    AndroidBridge::AutoLocalJNIFrame(env, 1);
+    return env->CallVoidMethod(wrapped_obj, jEndDrawingMethod, aRect.x, aRect.y, aRect.width,
+                               aRect.height);
+}
+
 jobject
 AndroidGeckoSoftwareLayerClient::LockBuffer()
 {
     NS_ASSERTION(!isNull(), "LockBuffer() called on null software layer client!");
 
     JNIEnv *env = AndroidBridge::GetJNIEnv();
     if (!env)
         return nsnull;
@@ -691,41 +734,16 @@ AndroidGeckoSoftwareLayerClient::GetRend
     if (!env)
         return;
 
     AndroidPoint offset(env, env->CallObjectMethod(wrapped_obj, jGetRenderOffsetMethod));
     aOffset.x = offset.X();
     aOffset.y = offset.Y();
 }
 
-bool
-AndroidGeckoSoftwareLayerClient::BeginDrawing(int aWidth, int aHeight, int aTileWidth, int aTileHeight, const nsAString &aMetadata, bool aHasDirectTexture)
-{
-    NS_ASSERTION(!isNull(), "BeginDrawing() called on null software layer client!");
-    JNIEnv *env = AndroidBridge::GetJNIEnv();
-    if (!env)
-        return false;
-
-    AndroidBridge::AutoLocalJNIFrame(env, 1);
-    jstring jMetadata = env->NewString(nsPromiseFlatString(aMetadata).get(), aMetadata.Length());
-    return env->CallBooleanMethod(wrapped_obj, jBeginDrawingMethod, aWidth, aHeight, aTileWidth, aTileHeight, jMetadata, aHasDirectTexture);
-}
-
-void
-AndroidGeckoSoftwareLayerClient::EndDrawing(const nsIntRect &aRect)
-{
-    NS_ASSERTION(!isNull(), "EndDrawing() called on null software layer client!");
-    JNIEnv *env = AndroidBridge::GetJNIEnv();
-    if (!env)
-        return;
-
-    AndroidBridge::AutoLocalJNIFrame(env, 1);
-    return env->CallVoidMethod(wrapped_obj, jEndDrawingMethod, aRect.x, aRect.y, aRect.width, aRect.height);
-}
-
 jobject
 AndroidGeckoSurfaceView::GetSoftwareDrawBitmap()
 {
     JNIEnv *env = AndroidBridge::GetJNIEnv();
     if (!env)
         return nsnull;
 
     return env->CallObjectMethod(wrapped_obj, jGetSoftwareDrawBitmapMethod);
--- a/widget/android/AndroidJavaWrappers.h
+++ b/widget/android/AndroidJavaWrappers.h
@@ -144,41 +144,58 @@ protected:
 
     static jclass jRectClass;
     static jfieldID jBottomField;
     static jfieldID jLeftField;
     static jfieldID jRightField;
     static jfieldID jTopField;
 };
 
-class AndroidGeckoSoftwareLayerClient : public WrappedJavaObject {
+class AndroidGeckoLayerClient : public WrappedJavaObject {
+public:
+    static void InitGeckoLayerClientClass(JNIEnv *jEnv);
+
+    void Init(jobject jobj);
+
+    bool BeginDrawing(int aWidth, int aHeight, int aTileWidth, int aTileHeight,
+                      const nsAString &aMetadata, bool aHasDirectTexture);
+    void EndDrawing(const nsIntRect &aRect);
+
+protected:
+    AndroidGeckoLayerClient() {
+        // You shouldn't directly instantiate one of these; instead use one of the concrete derived
+        // classes.
+    }
+
+    static jclass jGeckoLayerClientClass;
+    static jmethodID jBeginDrawingMethod;
+    static jmethodID jEndDrawingMethod;
+};
+
+class AndroidGeckoSoftwareLayerClient : public AndroidGeckoLayerClient {
 public:
     static void InitGeckoSoftwareLayerClientClass(JNIEnv *jEnv);
  
-     void Init(jobject jobj);
+    void Init(jobject jobj);
  
     AndroidGeckoSoftwareLayerClient() {}
     AndroidGeckoSoftwareLayerClient(jobject jobj) { Init(jobj); }
 
     jobject LockBuffer();
     unsigned char *LockBufferBits();
     void UnlockBuffer();
     void GetRenderOffset(nsIntPoint &aOffset);
-    bool BeginDrawing(int aWidth, int aHeight, int aTileWidth, int aTileHeight, const nsAString &aMetadata, bool aHasDirectTexture);
-    void EndDrawing(const nsIntRect &aRect);
 
 private:
     static jclass jGeckoSoftwareLayerClientClass;
     static jmethodID jLockBufferMethod;
     static jmethodID jUnlockBufferMethod;
 
 protected:
-     static jmethodID jGetRenderOffsetMethod;
-     static jmethodID jBeginDrawingMethod;
-     static jmethodID jEndDrawingMethod;
+    static jmethodID jGetRenderOffsetMethod;
 };
 
 
 class AndroidGeckoSurfaceView : public WrappedJavaObject
 {
 public:
     static void InitGeckoSurfaceViewClass(JNIEnv *jEnv);