bug 755070 - Scrolling causes after paint notifications which causes screenshotting which causes checkerboarding, only update dirty rects, reuse a single bytebuffer and stop using java bitmaps r=kats
authorBrad Lassey <blassey@mozilla.com>
Mon, 04 Jun 2012 11:56:31 -0400
changeset 95898 e70f09c6d8faeed5c860edff45cf2e89c996309f
parent 95897 6de8aa076a222c5b36e07979351cfc624f8b3311
child 95899 a0022b63efeaf17a4fc3509564645f360f1ce894
push id834
push usertim.taubert@gmx.de
push dateWed, 06 Jun 2012 15:40:30 +0000
treeherderfx-team@7f6e0921d3c8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs755070
milestone16.0a1
bug 755070 - Scrolling causes after paint notifications which causes screenshotting which causes checkerboarding, only update dirty rects, reuse a single bytebuffer and stop using java bitmaps r=kats
mobile/android/base/GeckoApp.java
mobile/android/base/GeckoAppShell.java
mobile/android/base/GeckoEvent.java
mobile/android/base/Tab.java
mobile/android/base/Tabs.java
mobile/android/base/gfx/IntSize.java
mobile/android/base/gfx/LayerRenderer.java
mobile/android/base/gfx/ScreenshotLayer.java
widget/android/AndroidBridge.cpp
widget/android/AndroidBridge.h
widget/android/AndroidJavaWrappers.cpp
widget/android/AndroidJavaWrappers.h
widget/android/nsAppShell.cpp
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -733,17 +733,17 @@ abstract public class GeckoApp
                 byte[] thumbnail = BrowserDB.getThumbnailForUrl(getContentResolver(), tab.getURL());
                 if (thumbnail != null)
                     processThumbnail(tab, null, thumbnail);
                 return;
             }
 
             int dw = tab.getThumbnailWidth();
             int dh = tab.getThumbnailHeight();
-            GeckoAppShell.sendEventToGecko(GeckoEvent.createScreenshotEvent(tab.getId(), 0, 0, 0, 0, 0, 0, dw, dh, GeckoAppShell.SCREENSHOT_THUMBNAIL));
+            GeckoAppShell.sendEventToGecko(GeckoEvent.createScreenshotEvent(tab.getId(), 0, 0, 0, 0, 0, 0, dw, dh, dw, dh, GeckoAppShell.SCREENSHOT_THUMBNAIL, tab.getThumbnailBuffer()));
         }
     }
     
     void processThumbnail(Tab thumbnailTab, Bitmap bitmap, byte[] compressed) {
         if (Tabs.getInstance().isSelectedTab(thumbnailTab)) {
             if (compressed == null) {
                 ByteArrayOutputStream bos = new ByteArrayOutputStream();
                 bitmap.compress(Bitmap.CompressFormat.PNG, 0, bos);
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -10,16 +10,17 @@ import org.mozilla.gecko.gfx.IntSize;
 import org.mozilla.gecko.gfx.GeckoLayerClient;
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.gfx.LayerController;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.ScreenshotLayer;
 import org.mozilla.gecko.FloatUtils;
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.gfx.ViewportMetrics;
+import org.mozilla.gecko.gfx.RectUtils;
 
 import java.io.*;
 import java.lang.reflect.*;
 import java.nio.*;
 import java.text.*;
 import java.util.*;
 import java.util.zip.*;
 import java.util.concurrent.*;
@@ -2104,19 +2105,21 @@ public class GeckoAppShell
     }
 }
 
 class ScreenshotHandler {
     static Set<PendingScreenshot> sPendingScreenshots = new HashSet<PendingScreenshot>();
     private static RectF sCheckerboardPageRect;
     private static float sLastCheckerboardWidthRatio, sLastCheckerboardHeightRatio;
     private static RepaintRunnable sRepaintRunnable = new RepaintRunnable();
-    static private int sMaxTextureSize = 0;
-    static final String LOGTAG = "GeckoScreenshot";
+    private static int sMaxTextureSize = 0;
+    private static final String LOGTAG = "GeckoScreenshot";
     private static boolean sDisableScreenshot = false;
+    private static ByteBuffer sWholePageScreenshotBuffer;
+    private static int sCheckerboardBufferWidth, sCheckerboardBufferHeight;
 
     static class RepaintRunnable implements Runnable {
         private boolean mIsRepaintRunnablePosted = false;
         private float mDirtyTop = Float.POSITIVE_INFINITY, mDirtyLeft = Float.POSITIVE_INFINITY;
         private float mDirtyBottom = Float.NEGATIVE_INFINITY, mDirtyRight = Float.NEGATIVE_INFINITY;
 
         public void run() {
             float top, left, bottom, right;
@@ -2129,26 +2132,46 @@ class ScreenshotHandler {
                 // reset these to infinity to start accumulating again
                 mDirtyTop = Float.POSITIVE_INFINITY;
                 mDirtyLeft = Float.POSITIVE_INFINITY;
                 mDirtyBottom = Float.NEGATIVE_INFINITY;
                 mDirtyRight = Float.NEGATIVE_INFINITY;
                 mIsRepaintRunnablePosted = false;
             }
 
+
             Tab tab = Tabs.getInstance().getSelectedTab();
-            GeckoAppShell.screenshotWholePage(tab);
+            ImmutableViewportMetrics viewport = GeckoApp.mAppContext.getLayerController().getViewportMetrics();
+            
+            if (RectUtils.fuzzyEquals(sCheckerboardPageRect, viewport.getCssPageRect())) {
+                float width = right - left;
+                float height = bottom - top;
+                GeckoEvent event = GeckoEvent.
+                    createScreenshotEvent(tab.getId(), 
+                                          (int)left, (int)top, (int)width, (int)height, 
+                                          (int)(sLastCheckerboardWidthRatio * left), 
+                                          (int)(sLastCheckerboardHeightRatio * top),
+                                          (int)(sLastCheckerboardWidthRatio * width),
+                                          (int)(sLastCheckerboardHeightRatio * height),
+                                          sCheckerboardBufferWidth, sCheckerboardBufferHeight,
+                                          GeckoAppShell.SCREENSHOT_UPDATE,
+                                          sWholePageScreenshotBuffer);
+                    GeckoAppShell.sendEventToGecko(event);
+            } else {
+                GeckoAppShell.screenshotWholePage(tab);
+            }
         }
 
         void addRectToRepaint(float top, float left, float bottom, float right) {
             synchronized(this) {
-                mDirtyTop = Math.min(top, mDirtyTop);
-                mDirtyLeft = Math.min(left, mDirtyLeft);
-                mDirtyBottom = Math.max(bottom, mDirtyBottom);
-                mDirtyRight = Math.max(right, mDirtyRight);
+                ImmutableViewportMetrics viewport = GeckoApp.mAppContext.getLayerController().getViewportMetrics();
+                mDirtyTop = Math.max(sCheckerboardPageRect.top, Math.min(top, mDirtyTop));
+                mDirtyLeft = Math.max(sCheckerboardPageRect.left, Math.min(left, mDirtyLeft));
+                mDirtyBottom = Math.min(sCheckerboardPageRect.bottom, Math.max(bottom, mDirtyBottom));
+                mDirtyRight = Math.min(sCheckerboardPageRect.right, Math.max(right, mDirtyRight));
                 if (!mIsRepaintRunnablePosted) {
                     GeckoAppShell.getHandler().postDelayed(this, 5000);
                     mIsRepaintRunnablePosted = true;
                 }
             }
         }
     }
 
@@ -2173,16 +2196,17 @@ class ScreenshotHandler {
             return;
 
         if (sMaxTextureSize == 0) {
             int[] maxTextureSize = new int[1];
             GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSize, 0);
             sMaxTextureSize = maxTextureSize[0];
             if (sMaxTextureSize == 0)
                 return;
+            sWholePageScreenshotBuffer = GeckoAppShell.allocateDirectBuffer(ScreenshotLayer.getMaxNumPixels() * 2 /* 16 bpp */);
         }
 
         ImmutableViewportMetrics viewport = GeckoApp.mAppContext.getLayerController().getViewportMetrics();
         Log.i(LOGTAG, "Taking whole-screen screenshot, viewport: " + viewport);
         // source width and height to screenshot
         float sx = viewport.cssPageRectLeft;
         float sy = viewport.cssPageRectTop;
         float sw = viewport.cssPageRectRight - viewport.cssPageRectLeft;
@@ -2195,31 +2219,33 @@ class ScreenshotHandler {
         // calc destination width and hight
         int idealDstWidth = IntSize.nextPowerOfTwo(sw / idealZoomFactor);
         // min texture size such that the other dimention doesn't excede the max
         int minTextureSize = maxPixels / sMaxTextureSize;
         int dx = 0;
         int dy = 0;
         int dw = clamp(minTextureSize, idealDstWidth, sMaxTextureSize);
         int dh = maxPixels / dw;
+        sCheckerboardBufferWidth = dw;
+        sCheckerboardBufferHeight = dh;
 
         sLastCheckerboardWidthRatio = dw / sw;
         sLastCheckerboardHeightRatio = dh / sh;
         sCheckerboardPageRect = viewport.getCssPageRect();
 
         synchronized(sPendingScreenshots) {
             PendingScreenshot ps = new PendingScreenshot(tab.getId(), GeckoAppShell.SCREENSHOT_WHOLE_PAGE);
             if (sPendingScreenshots.contains(ps))
                 return;
             sPendingScreenshots.add(ps);
         }
         GeckoAppShell.sendEventToGecko(GeckoEvent.createScreenshotEvent(tab.getId(),
                (int)FloatMath.ceil(sx), (int)FloatMath.ceil(sy),
                (int)FloatMath.floor(sw), (int)FloatMath.floor(sh),
-               dx, dy, dw, dh, GeckoAppShell.SCREENSHOT_WHOLE_PAGE));
+               dx, dy, dw, dh, dw, dh, GeckoAppShell.SCREENSHOT_WHOLE_PAGE, sWholePageScreenshotBuffer));
     }
     
     static class PendingScreenshot {
         PendingScreenshot(int tabId, int type) {
             mTabId = tabId;
             mType = type;
         }
 
@@ -2241,44 +2267,36 @@ class ScreenshotHandler {
     public static void notifyScreenShot(final ByteBuffer data, final int tabId, final int x, final int y,
                                         final int width, final int height, final int token) {
         synchronized(sPendingScreenshots) {
             sPendingScreenshots.remove(new PendingScreenshot(tabId, token));
         }
 
         GeckoAppShell.getHandler().post(new Runnable() {
             public void run() {
-                try {
-                    final Tab tab = Tabs.getInstance().getTab(tabId);
-                    if (tab == null)
-                        return;
+                final Tab tab = Tabs.getInstance().getTab(tabId);
+                if (tab == null)
+                    return;
 
-                    if (!Tabs.getInstance().isSelectedTab(tab) && GeckoAppShell.SCREENSHOT_THUMBNAIL != token)
-                        return;
+                if (!Tabs.getInstance().isSelectedTab(tab) && GeckoAppShell.SCREENSHOT_THUMBNAIL != token)
+                    return;
 
-                    Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
-                    b.copyPixelsFromBuffer(data);
-                    switch (token) {
+                switch (token) {
+                    case GeckoAppShell.SCREENSHOT_UPDATE:
                     case GeckoAppShell.SCREENSHOT_WHOLE_PAGE:
+                    {
                         GeckoApp.mAppContext.getLayerController()
                             .getView().getRenderer()
-                            .setCheckerboardBitmap(b, sCheckerboardPageRect);
+                            .setCheckerboardBitmap(data, width, height, sCheckerboardPageRect);
                         break;
-                    case GeckoAppShell.SCREENSHOT_UPDATE:
-                        GeckoApp.mAppContext.getLayerController().getView().getRenderer().
-                            updateCheckerboardBitmap(
-                                b, sLastCheckerboardWidthRatio * x,
-                                sLastCheckerboardHeightRatio * y,
-                                sLastCheckerboardWidthRatio * width,
-                                sLastCheckerboardHeightRatio * height,
-                                sCheckerboardPageRect);
-                        break;
+                    }
                     case GeckoAppShell.SCREENSHOT_THUMBNAIL:
+                    {
+                        Bitmap b = tab.getThumbnailBitmap();
+                        b.copyPixelsFromBuffer(data);
                         GeckoApp.mAppContext.processThumbnail(tab, b, null);
                         break;
                     }
-                } finally {
-                    GeckoAppShell.freeDirectBuffer(data);
                 }
             }
         });
     }
 }
--- a/mobile/android/base/GeckoEvent.java
+++ b/mobile/android/base/GeckoEvent.java
@@ -18,16 +18,17 @@ import android.hardware.*;
 import android.location.*;
 import android.util.FloatMath;
 import android.util.DisplayMetrics;
 import android.graphics.PointF;
 import android.text.format.Time;
 import android.os.SystemClock;
 import java.lang.Math;
 import java.lang.System;
+import java.nio.ByteBuffer;
 
 import android.util.Log;
 
 /* We're not allowed to hold on to most events given to us
  * so we save the parts of the events we want to use in GeckoEvent.
  * Fields have different meanings depending on the event type.
  */
 
@@ -111,16 +112,18 @@ public class GeckoEvent {
 
     public double mBandwidth;
     public boolean mCanBeMetered;
 
     public int mNativeWindow;
 
     public short mScreenOrientation;
 
+    public ByteBuffer mBuffer;
+
     private GeckoEvent(int evType) {
         mType = evType;
     }
 
     public static GeckoEvent createPauseEvent(boolean isApplicationInBackground) {
         GeckoEvent event = new GeckoEvent(ACTIVITY_PAUSING);
         event.mFlags = isApplicationInBackground ? 0 : 1;
         return event;
@@ -451,25 +454,27 @@ public class GeckoEvent {
 
     public static GeckoEvent createNetworkEvent(double bandwidth, boolean canBeMetered) {
         GeckoEvent event = new GeckoEvent(NETWORK_CHANGED);
         event.mBandwidth = bandwidth;
         event.mCanBeMetered = canBeMetered;
         return event;
     }
 
-    public static GeckoEvent createScreenshotEvent(int tabId, int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh, int token) {
+    public static GeckoEvent createScreenshotEvent(int tabId, int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh, int bw, int bh, int token, ByteBuffer buffer) {
         GeckoEvent event = new GeckoEvent(SCREENSHOT);
-        event.mPoints = new Point[4];
+        event.mPoints = new Point[5];
         event.mPoints[0] = new Point(sx, sy);
         event.mPoints[1] = new Point(sw, sh);
         event.mPoints[2] = new Point(dx, dy);
         event.mPoints[3] = new Point(dw, dh);
+        event.mPoints[4] = new Point(bw, bh);
         event.mMetaState = tabId;
         event.mFlags = token;
+        event.mBuffer = buffer;
         return event;
     }
 
     public static GeckoEvent createScreenOrientationEvent(short aScreenOrientation) {
         GeckoEvent event = new GeckoEvent(SCREENORIENTATION_CHANGED);
         event.mScreenOrientation = aScreenOrientation;
         return event;
     }
--- a/mobile/android/base/Tab.java
+++ b/mobile/android/base/Tab.java
@@ -16,16 +16,17 @@ import android.util.Log;
 import android.view.Surface;
 import android.view.View;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.gfx.Layer;
 
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.regex.Pattern;
 import java.util.regex.Matcher;
 
@@ -60,16 +61,18 @@ public final class Tab {
     private float mMinZoom;
     private float mMaxZoom;
     private ArrayList<View> mPluginViews;
     private HashMap<Object, Layer> mPluginLayers;
     private ContentResolver mContentResolver;
     private ContentObserver mContentObserver;
     private int mCheckerboardColor = Color.WHITE;
     private int mState;
+    private ByteBuffer mThumbnailBuffer;
+    private Bitmap mThumbnailBitmap;
 
     public static final int STATE_DELAYED = 0;
     public static final int STATE_LOADING = 1;
     public static final int STATE_SUCCESS = 2;
     public static final int STATE_ERROR = 3;
 
     public Tab(int id, String url, boolean external, int parentId, String title) {
         mId = id;
@@ -135,16 +138,41 @@ public final class Tab {
     public Drawable getFavicon() {
         return mFavicon;
     }
 
     public Drawable getThumbnail() {
         return mThumbnail;
     }
 
+    synchronized public ByteBuffer getThumbnailBuffer() {
+        int capacity = getThumbnailWidth() * getThumbnailHeight() * 2 /* 16 bpp */;
+        if (mThumbnailBuffer != null && mThumbnailBuffer.capacity() == capacity)
+            return mThumbnailBuffer;
+        if (mThumbnailBuffer != null)
+            GeckoAppShell.freeDirectBuffer(mThumbnailBuffer); // not calling freeBuffer() because it would deadlock
+        return mThumbnailBuffer = GeckoAppShell.allocateDirectBuffer(capacity);
+    }
+
+    public Bitmap getThumbnailBitmap() {
+        if (mThumbnailBitmap != null)
+            return mThumbnailBitmap;
+        return mThumbnailBitmap = Bitmap.createBitmap(getThumbnailWidth(), getThumbnailHeight(), Bitmap.Config.RGB_565);
+    }
+
+    public void finalize() {
+        freeBuffer();
+    }
+
+    synchronized void freeBuffer() {
+        if (mThumbnailBuffer != null)
+            GeckoAppShell.freeDirectBuffer(mThumbnailBuffer);
+        mThumbnailBuffer = null;
+    }
+
     float getDensity() {
         if (sDensity == 0.0f) {
             sDensity = GeckoApp.mAppContext.getDisplayMetrics().density;
         }
         return sDensity;
     }
 
     int getThumbnailWidth() {
@@ -156,23 +184,20 @@ public final class Tab {
     }
 
     public void updateThumbnail(final Bitmap b) {
         final Tab tab = this;
         GeckoAppShell.getHandler().post(new Runnable() {
             public void run() {
                 if (b != null) {
                     try {
-                        Bitmap bitmap = Bitmap.createScaledBitmap(b, getThumbnailWidth(), getThumbnailHeight(), false);
+                        if (mState == Tab.STATE_SUCCESS)
+                            saveThumbnailToDB(new BitmapDrawable(b));
 
-                        if (mState == Tab.STATE_SUCCESS)
-                            saveThumbnailToDB(new BitmapDrawable(bitmap));
-
-                        mThumbnail = new BitmapDrawable(bitmap);
-                        b.recycle();
+                        mThumbnail = new BitmapDrawable(b);
                     } catch (OutOfMemoryError oom) {
                         Log.e(LOGTAG, "Unable to create/scale bitmap", oom);
                         mThumbnail = null;
                     }
                 } else {
                     mThumbnail = null;
                 }
                 GeckoApp.mAppContext.mMainHandler.post(new Runnable() {
--- a/mobile/android/base/Tabs.java
+++ b/mobile/android/base/Tabs.java
@@ -71,18 +71,20 @@ public class Tabs implements GeckoEventL
         }
 
         Log.i(LOGTAG, "Added a tab with id: " + id);
         return tab;
     }
 
     public void removeTab(int id) {
         if (tabs.containsKey(id)) {
-            order.remove(getTab(id));
+            Tab tab = getTab(id);
+            order.remove(tab);
             tabs.remove(id);
+            tab.freeBuffer();
             Log.i(LOGTAG, "Removed a tab with id: " + id);
         }
     }
 
     public Tab selectTab(int id) {
         if (!tabs.containsKey(id))
             return null;
 
--- a/mobile/android/base/gfx/IntSize.java
+++ b/mobile/android/base/gfx/IntSize.java
@@ -66,10 +66,16 @@ public class IntSize {
 
     public static int nextPowerOfTwo(float value) {
         return nextPowerOfTwo((int) value);
     }
 
     public IntSize nextPowerOfTwo() {
         return new IntSize(nextPowerOfTwo(width), nextPowerOfTwo(height));
     }
+
+    public static boolean isPowerOfTwo(int value) {
+        if (value == 0)
+            return false;
+        return (value & (value - 1)) == 0;
+    }
 }
 
--- a/mobile/android/base/gfx/LayerRenderer.java
+++ b/mobile/android/base/gfx/LayerRenderer.java
@@ -129,18 +129,18 @@ public class LayerRenderer {
     public static final String DEFAULT_FRAGMENT_SHADER =
         "precision highp float;\n" +
         "varying vec2 vTexCoord;\n" +
         "uniform sampler2D sTexture;\n" +
         "void main() {\n" +
         "    gl_FragColor = texture2D(sTexture, vTexCoord);\n" +
         "}\n";
 
-    public void setCheckerboardBitmap(Bitmap bitmap, RectF pageRect) {
-        mCheckerboardLayer.setBitmap(bitmap);
+    public void setCheckerboardBitmap(ByteBuffer data, int width, int height, RectF pageRect) {
+        mCheckerboardLayer.setBitmap(data, width, height);
         mCheckerboardLayer.beginTransaction();
         try {
             mCheckerboardLayer.setPosition(RectUtils.round(pageRect));
             mCheckerboardLayer.invalidate();
         } finally {
             mCheckerboardLayer.endTransaction();
         }
     }
--- a/mobile/android/base/gfx/ScreenshotLayer.java
+++ b/mobile/android/base/gfx/ScreenshotLayer.java
@@ -30,25 +30,39 @@ public class ScreenshotLayer extends Sin
     private ScreenshotImage mImage;
     // Size of the image buffer
     private IntSize mBufferSize;
     // The size of the bitmap painted in the buffer
     // (may be smaller than mBufferSize due to power of 2 padding)
     private IntSize mImageSize;
     // Whether we have an up-to-date image to draw
     private boolean mHasImage;
+    private static String LOGTAG = "GeckoScreenshot";
 
     public static int getMaxNumPixels() {
         return SCREENSHOT_SIZE_LIMIT;
     }
 
     public void reset() {
         mHasImage = false;
     }
 
+    void setBitmap(ByteBuffer data, int width, int height) {
+        mImageSize = new IntSize(width, height);
+        if (IntSize.isPowerOfTwo(width) && IntSize.isPowerOfTwo(height)) {
+            mBufferSize = mImageSize;
+            mHasImage = true;
+            mImage.setBitmap(data, width, height, CairoImage.FORMAT_RGB16_565);
+        } else {
+            Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
+            b.copyPixelsFromBuffer(data);
+            setBitmap(b);
+        }
+    }
+    
     void setBitmap(Bitmap bitmap) {
         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);
         mHasImage = true;
     }
@@ -111,17 +125,27 @@ public class ScreenshotLayer extends Sin
                     GeckoAppShell.freeDirectBuffer(mBuffer);
                     mBuffer = null;
                 }
             } finally {
                 super.finalize();
             }
         }
 
-        void setBitmap(Bitmap bitmap, int width, int height, int format) {
+        void copyBuffer(ByteBuffer src, ByteBuffer dst, int size) {
+            dst.asIntBuffer().put(src.asIntBuffer());
+        }
+
+        synchronized void setBitmap(ByteBuffer data, int width, int height, int format) {
+            mSize = new IntSize(width, height);
+            mFormat = format;
+            copyBuffer(data, mBuffer, width * height * 2);
+        }
+
+        synchronized void setBitmap(Bitmap bitmap, int width, int height, int format) {
             Bitmap tmp;
             mSize = new IntSize(width, height);
             mFormat = format;
             if (width == bitmap.getWidth() && height == bitmap.getHeight()) {
                 tmp = bitmap;
             } else {
                 tmp = Bitmap.createBitmap(width, height, CairoUtils.cairoFormatTobitmapConfig(mFormat));
                 new Canvas(tmp).drawBitmap(bitmap, 0.0f, 0.0f, new Paint());
@@ -133,15 +157,15 @@ public class ScreenshotLayer extends Sin
             Bitmap tmp = Bitmap.createBitmap(mSize.width, mSize.height, CairoUtils.cairoFormatTobitmapConfig(mFormat));
             tmp.copyPixelsFromBuffer(mBuffer.asIntBuffer());
             Canvas c = new Canvas(tmp);
             c.drawBitmap(bitmap, x, y, new Paint());
             tmp.copyPixelsToBuffer(mBuffer.asIntBuffer());
         }
 
         @Override
-        public ByteBuffer getBuffer() { return mBuffer; }
+        synchronized public ByteBuffer getBuffer() { return mBuffer; }
         @Override
-        public IntSize getSize() { return mSize; }
+        synchronized public IntSize getSize() { return mSize; }
         @Override
-        public int getFormat() { return mFormat; }
+        synchronized public int getFormat() { return mFormat; }
     }
 }
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -2345,19 +2345,23 @@ AndroidBridge::RemovePluginView(jobject 
 }
 
 extern "C"
 __attribute__ ((visibility("default")))
 jobject JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_allocateDirectBuffer(JNIEnv *env, jclass, jlong size);
 
 
-nsresult AndroidBridge::TakeScreenshot(nsIDOMWindow *window, PRInt32 srcX, PRInt32 srcY, PRInt32 srcW, PRInt32 srcH, PRInt32 dstW, PRInt32 dstH, PRInt32 tabId, float scale, PRInt32 token)
+nsresult AndroidBridge::TakeScreenshot(nsIDOMWindow *window, PRInt32 srcX, PRInt32 srcY, PRInt32 srcW, PRInt32 srcH, PRInt32 dstX, PRInt32 dstY, PRInt32 dstW, PRInt32 dstH, PRInt32 bufW, PRInt32 bufH, PRInt32 tabId, PRInt32 token, jobject buffer)
 {
     nsresult rv;
+    float scale = 1.0;
+ 
+    if (!buffer)
+        return NS_OK;
 
     // take a screenshot, as wide as possible, proportional to the destination size
     if (!srcW && !srcH) {
         nsCOMPtr<nsIDOMWindowUtils> utils = do_GetInterface(window);
         if (!utils)
             return NS_ERROR_FAILURE;
 
         nsCOMPtr<nsIDOMClientRect> rect;
@@ -2408,31 +2412,27 @@ nsresult AndroidBridge::TakeScreenshot(n
     nsIPresShell* presShell = presContext->PresShell();
     PRUint32 renderDocFlags = (nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING |
                                nsIPresShell::RENDER_DOCUMENT_RELATIVE);
     nsRect r(nsPresContext::CSSPixelsToAppUnits(srcX / scale),
              nsPresContext::CSSPixelsToAppUnits(srcY / scale),
              nsPresContext::CSSPixelsToAppUnits(srcW / scale),
              nsPresContext::CSSPixelsToAppUnits(srcH / scale));
 
-    PRUint32 stride = dstW * 2;
-    PRUint32 bufferSize = dstH * stride;
-
-    jobject buffer = Java_org_mozilla_gecko_GeckoAppShell_allocateDirectBuffer(env, NULL, bufferSize);
-    if (!buffer)
-        return NS_OK;
+    PRUint32 stride = bufW * 2 /* 16 bpp */;
 
     void* data = env->GetDirectBufferAddress(buffer);
-    memset(data, 0, bufferSize);
-    nsRefPtr<gfxImageSurface> surf = new gfxImageSurface(static_cast<unsigned char*>(data), nsIntSize(dstW, dstH), stride, gfxASurface::ImageFormatRGB16_565);
+    nsRefPtr<gfxImageSurface> surf = new gfxImageSurface(static_cast<unsigned char*>(data), nsIntSize(bufW, bufH), stride, gfxASurface::ImageFormatRGB16_565);
     nsRefPtr<gfxContext> context = new gfxContext(surf);
+    gfxPoint pt(dstX, dstY);
+    context->Translate(pt);
     context->Scale(scale * dstW / srcW, scale * dstH / srcH);
     rv = presShell->RenderDocument(r, renderDocFlags, bgColor, context);
     NS_ENSURE_SUCCESS(rv, rv);
-    env->CallStaticVoidMethod(AndroidBridge::Bridge()->mGeckoAppShellClass, AndroidBridge::Bridge()->jNotifyScreenShot, buffer, tabId, srcX * dstW / srcW , srcY * dstH / srcH, dstW, dstH, token);
+    env->CallStaticVoidMethod(AndroidBridge::Bridge()->mGeckoAppShellClass, AndroidBridge::Bridge()->jNotifyScreenShot, buffer, tabId, srcX * dstW / srcW , srcY * dstH / srcH, bufW, bufH, token);
     return NS_OK;
 }
 
 void
 AndroidBridge::NotifyPaintedRect(float top, float left, float bottom, float right)
 {
     JNIEnv* env = GetJNIEnv();
     if (!env)
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -150,17 +150,17 @@ public:
 
     /* These are defined in mobile/android/base/GeckoAppShell.java */
     enum {
         SCREENSHOT_THUMBNAIL = 0,
         SCREENSHOT_WHOLE_PAGE = 1,
         SCREENSHOT_UPDATE = 2
     };
 
-    nsresult TakeScreenshot(nsIDOMWindow *window, PRInt32 srcX, PRInt32 srcY, PRInt32 srcW, PRInt32 srcH, PRInt32 dstW, PRInt32 dstH, PRInt32 tabId, float scale, PRInt32 token);
+    nsresult TakeScreenshot(nsIDOMWindow *window, PRInt32 srcX, PRInt32 srcY, PRInt32 srcW, PRInt32 srcH, PRInt32 dstY, PRInt32 dstX, PRInt32 dstW, PRInt32 dstH, PRInt32 bufW, PRInt32 bufH, PRInt32 tabId, PRInt32 token, jobject buffer);
 
     static void NotifyPaintedRect(float top, float left, float bottom, float right);
 
     void AcknowledgeEventSync();
 
     void EnableLocation(bool aEnable);
     void EnableLocationHighAccuracy(bool aEnable);
 
--- a/widget/android/AndroidJavaWrappers.cpp
+++ b/widget/android/AndroidJavaWrappers.cpp
@@ -37,16 +37,17 @@ jfieldID AndroidGeckoEvent::jPointerInde
 jfieldID AndroidGeckoEvent::jRangeTypeField = 0;
 jfieldID AndroidGeckoEvent::jRangeStylesField = 0;
 jfieldID AndroidGeckoEvent::jRangeForeColorField = 0;
 jfieldID AndroidGeckoEvent::jRangeBackColorField = 0;
 jfieldID AndroidGeckoEvent::jLocationField = 0;
 jfieldID AndroidGeckoEvent::jBandwidthField = 0;
 jfieldID AndroidGeckoEvent::jCanBeMeteredField = 0;
 jfieldID AndroidGeckoEvent::jScreenOrientationField = 0;
+jfieldID AndroidGeckoEvent::jByteBufferField = 0;
 
 jclass AndroidPoint::jPointClass = 0;
 jfieldID AndroidPoint::jXField = 0;
 jfieldID AndroidPoint::jYField = 0;
 
 jclass AndroidRect::jRectClass = 0;
 jfieldID AndroidRect::jBottomField = 0;
 jfieldID AndroidRect::jLeftField = 0;
@@ -98,16 +99,22 @@ jmethodID AndroidGeckoSurfaceView::jGetH
     (jClass = jclass(jEnv->NewGlobalRef(jEnv->FindClass(cname))))
 
 #define getField(fname, ftype) \
     ((jfieldID) jEnv->GetFieldID(jClass, fname, ftype))
 
 #define getMethod(fname, ftype) \
     ((jmethodID) jEnv->GetMethodID(jClass, fname, ftype))
 
+RefCountedJavaObject::~RefCountedJavaObject() {
+    if (mObject)
+        GetJNIForThread()->DeleteGlobalRef(mObject);
+    mObject = NULL;
+}
+
 void
 mozilla::InitAndroidJavaWrappers(JNIEnv *jEnv)
 {
     AndroidGeckoEvent::InitGeckoEventClass(jEnv);
     AndroidPoint::InitPointClass(jEnv);
     AndroidLocation::InitLocationClass(jEnv);
     AndroidRect::InitRectClass(jEnv);
     AndroidGeckoLayerClient::InitGeckoLayerClientClass(jEnv);
@@ -149,16 +156,17 @@ AndroidGeckoEvent::InitGeckoEventClass(J
     jRangeTypeField = getField("mRangeType", "I");
     jRangeStylesField = getField("mRangeStyles", "I");
     jRangeForeColorField = getField("mRangeForeColor", "I");
     jRangeBackColorField = getField("mRangeBackColor", "I");
     jLocationField = getField("mLocation", "Landroid/location/Location;");
     jBandwidthField = getField("mBandwidth", "D");
     jCanBeMeteredField = getField("mCanBeMetered", "Z");
     jScreenOrientationField = getField("mScreenOrientation", "S");
+    jByteBufferField = getField("mBuffer", "Ljava/nio/ByteBuffer;");
 }
 
 void
 AndroidGeckoSurfaceView::InitGeckoSurfaceViewClass(JNIEnv *jEnv)
 {
 #ifndef MOZ_JAVA_COMPOSITOR
     initInit();
 
@@ -493,17 +501,18 @@ AndroidGeckoEvent::Init(JNIEnv *jenv, jo
         case ACTIVITY_RESUMING: {
             mFlags = jenv->GetIntField(jobj, jFlagsField);
             break;
         }
 
         case SCREENSHOT: {
             mMetaState = jenv->GetIntField(jobj, jMetaStateField);
             mFlags = jenv->GetIntField(jobj, jFlagsField);
-            ReadPointArray(mPoints, jenv, jPoints, 4);
+            ReadPointArray(mPoints, jenv, jPoints, 5);
+            mByteBuffer = new RefCountedJavaObject(jenv, jenv->GetObjectField(jobj, jByteBufferField));
             break;
         }
 
         case PAINT_LISTEN_START_EVENT: {
             mMetaState = jenv->GetIntField(jobj, jMetaStateField);
             break;
         }
 
--- a/widget/android/AndroidJavaWrappers.h
+++ b/widget/android/AndroidJavaWrappers.h
@@ -36,16 +36,37 @@ void InitAndroidJavaWrappers(JNIEnv *jEn
  * Note: do not store global refs to any WrappedJavaObject;
  * these are live only during a particular JNI method, as
  * NewGlobalRef is -not- called on the jobject.
  *
  * If this is needed, WrappedJavaObject can be extended to
  * handle it.
  */
 
+class RefCountedJavaObject {
+public:
+    RefCountedJavaObject(JNIEnv* env, jobject obj) : mObject(env->NewGlobalRef(obj)) {}
+
+    ~RefCountedJavaObject();
+
+    PRInt32 AddRef() { return ++mRefCnt; }
+
+    PRInt32 Release() {
+        PRInt32 refcnt = --mRefCnt;
+        if (refcnt == 0)
+            delete this;
+        return refcnt;
+    }
+
+    jobject GetObject() { return mObject; }
+private:
+    PRInt32 mRefCnt;
+    jobject mObject;
+};
+
 class WrappedJavaObject {
 public:
     WrappedJavaObject() :
         wrapped_obj(0)
     { }
 
     WrappedJavaObject(jobject jobj) {
         Init(jobj);
@@ -571,16 +592,17 @@ public:
     int RangeType() { return mRangeType; }
     int RangeStyles() { return mRangeStyles; }
     int RangeForeColor() { return mRangeForeColor; }
     int RangeBackColor() { return mRangeBackColor; }
     nsGeoPosition* GeoPosition() { return mGeoPosition; }
     double Bandwidth() { return mBandwidth; }
     bool CanBeMetered() { return mCanBeMetered; }
     short ScreenOrientation() { return mScreenOrientation; }
+    RefCountedJavaObject* ByteBuffer() { return mByteBuffer; }
 
 protected:
     int mAction;
     int mType;
     int64_t mTime;
     nsTArray<nsIntPoint> mPoints;
     nsTArray<nsIntPoint> mPointRadii;
     nsTArray<int> mPointIndicies;
@@ -595,16 +617,17 @@ protected:
     int mRangeForeColor, mRangeBackColor;
     double mX, mY, mZ;
     int mPointerIndex;
     nsString mCharacters, mCharactersExtra;
     nsRefPtr<nsGeoPosition> mGeoPosition;
     double mBandwidth;
     bool mCanBeMetered;
     short mScreenOrientation;
+    nsRefPtr<RefCountedJavaObject> mByteBuffer;
 
     void ReadIntArray(nsTArray<int> &aVals,
                       JNIEnv *jenv,
                       jfieldID field,
                       PRUint32 count);
     void ReadFloatArray(nsTArray<float> &aVals,
                         JNIEnv *jenv,
                         jfieldID field,
@@ -648,16 +671,17 @@ protected:
     static jfieldID jRangeForeColorField;
     static jfieldID jRangeBackColorField;
     static jfieldID jLocationField;
 
     static jfieldID jBandwidthField;
     static jfieldID jCanBeMeteredField;
 
     static jfieldID jScreenOrientationField;
+    static jfieldID jByteBufferField;
 
 public:
     enum {
         NATIVE_POKE = 0,
         KEY_EVENT = 1,
         MOTION_EVENT = 2,
         SENSOR_EVENT = 3,
         UNUSED1_EVENT = 4,
--- a/widget/android/nsAppShell.cpp
+++ b/widget/android/nsAppShell.cpp
@@ -62,40 +62,40 @@ nsIGeolocationUpdate *gLocationCallback 
 nsAutoPtr<mozilla::AndroidGeckoEvent> gLastSizeChange;
 
 nsAppShell *nsAppShell::gAppShell = nsnull;
 
 NS_IMPL_ISUPPORTS_INHERITED1(nsAppShell, nsBaseAppShell, nsIObserver)
 
 class ScreenshotRunnable : public nsRunnable {
 public:
-    ScreenshotRunnable(nsIAndroidBrowserApp* aBrowserApp, int aTabId, nsTArray<nsIntPoint>& aPoints, int aToken):
-        mBrowserApp(aBrowserApp), mTabId(aTabId), mPoints(aPoints), mToken(aToken) {}
+    ScreenshotRunnable(nsIAndroidBrowserApp* aBrowserApp, int aTabId, nsTArray<nsIntPoint>& aPoints, int aToken, RefCountedJavaObject* aBuffer):
+        mBrowserApp(aBrowserApp), mTabId(aTabId), mPoints(aPoints), mToken(aToken), mBuffer(aBuffer) {}
 
     virtual nsresult Run() {
         nsCOMPtr<nsIDOMWindow> domWindow;
         nsCOMPtr<nsIBrowserTab> tab;
         mBrowserApp->GetBrowserTab(mTabId, getter_AddRefs(tab));
         if (!tab)
             return NS_OK;
 
         tab->GetWindow(getter_AddRefs(domWindow));
         if (!domWindow)
             return NS_OK;
 
-        float scale = 1.0;
-        NS_ASSERTION(mPoints.Length() == 4, "Screenshot event does not have enough coordinates");
+        NS_ASSERTION(mPoints.Length() == 5, "Screenshot event does not have enough coordinates");
 
-        AndroidBridge::Bridge()->TakeScreenshot(domWindow, mPoints[0].x, mPoints[0].y, mPoints[1].x, mPoints[1].y, mPoints[3].x, mPoints[3].y, mTabId, scale, mToken);
+        AndroidBridge::Bridge()->TakeScreenshot(domWindow, mPoints[0].x, mPoints[0].y, mPoints[1].x, mPoints[1].y, mPoints[2].x, mPoints[2].y, mPoints[3].x, mPoints[3].y, mPoints[4].x, mPoints[4].y, mTabId, mToken, mBuffer->GetObject());
         return NS_OK;
     }
 private:
     nsCOMPtr<nsIAndroidBrowserApp> mBrowserApp;
     nsTArray<nsIntPoint> mPoints;
     int mTabId, mToken;
+    nsRefPtr<RefCountedJavaObject> mBuffer;
 };
 
 class AfterPaintListener : public nsIDOMEventListener {
   public:
     NS_DECL_ISUPPORTS
 
     void Register(nsIDOMWindow* window) {
         if (mEventTarget)
@@ -114,33 +114,25 @@ class AfterPaintListener : public nsIDOM
         mEventTarget = nsnull;
     }
 
     virtual nsresult HandleEvent(nsIDOMEvent* aEvent) {
         nsCOMPtr<nsIDOMNotifyPaintEvent> paintEvent = do_QueryInterface(aEvent);
         if (!paintEvent)
             return NS_OK;
 
-        nsCOMPtr<nsIDOMClientRectList> rects;
-        paintEvent->GetClientRects(getter_AddRefs(rects));
-        if (!rects)
-            return NS_OK;
-        PRUint32 length;
-        rects->GetLength(&length);
-        for (PRUint32 i = 0; i < length; ++i) {
-            float top, left, bottom, right;
-            nsCOMPtr<nsIDOMClientRect> rect = rects->GetItemAt(i);
-            if (!rect)
-                continue;
-            rect->GetTop(&top);
-            rect->GetLeft(&left);
-            rect->GetRight(&right);
-            rect->GetBottom(&bottom);
-            AndroidBridge::NotifyPaintedRect(top, left, bottom, right);
-        }
+        nsCOMPtr<nsIDOMClientRect> rect;
+        paintEvent->GetBoundingClientRect(getter_AddRefs(rect));
+        float top, left, bottom, right;
+        rect->GetTop(&top);
+        rect->GetLeft(&left);
+        rect->GetRight(&right);
+        rect->GetBottom(&bottom);
+        __android_log_print(ANDROID_LOG_INFO, "GeckoScreenshot", "rect: %f, %f, %f, %f", top, left, right, bottom);
+        AndroidBridge::NotifyPaintedRect(top, left, bottom, right);
         return NS_OK;
     }
 
     ~AfterPaintListener() {
         if (mEventTarget)
             Unregister();
     }
 
@@ -452,18 +444,19 @@ nsAppShell::ProcessNextNativeEvent(bool 
 
         AndroidBridge* bridge = AndroidBridge::Bridge();
         if (!bridge)
             break;
 
         PRInt32 token = curEvent->Flags();
         PRInt32 tabId = curEvent->MetaState();
         nsTArray<nsIntPoint> points = curEvent->Points();
+        RefCountedJavaObject* buffer = curEvent->ByteBuffer();
         nsCOMPtr<ScreenshotRunnable> sr = 
-            new ScreenshotRunnable(mBrowserApp, tabId, points, token);
+            new ScreenshotRunnable(mBrowserApp, tabId, points, token, buffer);
         MessageLoop::current()->PostIdleTask(
             FROM_HERE, NewRunnableMethod(sr.get(), &ScreenshotRunnable::Run));
         break;
     }
 
     case AndroidGeckoEvent::VIEWPORT:
     case AndroidGeckoEvent::BROADCAST: {