diff --git a/embedding/android/GeckoAppShell.java b/embedding/android/GeckoAppShell.java index be482e47052..e9ade7ef012 100644 --- a/embedding/android/GeckoAppShell.java +++ b/embedding/android/GeckoAppShell.java @@ -603,9 +603,13 @@ public class GeckoAppShell imm, text, start, end, newEnd); } - public static void notifyScreenShot(ByteBuffer data, int tabId, int width, int height) { - // this stub is never called in XUL Fennec, but we need it so that the JNI code - // shared between XUL and Native Fennec doesn't die. + // these 2 stubs are never called in XUL Fennec, but we need them so that + // the JNI code shared between XUL and Native Fennec doesn't die. + 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) { + } + + public static void notifyPaintedRect(float top, float left, float bottom, float right) { } private static CountDownLatch sGeckoPendingAcks = null; diff --git a/mobile/android/app/mobile.js b/mobile/android/app/mobile.js index 8984d204624..90a542169f2 100644 --- a/mobile/android/app/mobile.js +++ b/mobile/android/app/mobile.js @@ -728,7 +728,7 @@ pref("direct-texture.force.enabled", false); pref("direct-texture.force.disabled", false); // show checkerboard pattern on android; we use background colour instead -pref("gfx.show_checkerboard_pattern", false); +pref("gfx.show_checkerboard_pattern", true); pref("remote-debugger.enabled", false); pref("remote-debugger.port", 6000); diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index 5379c601b85..d93a612602a 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -556,7 +556,7 @@ abstract public class GeckoApp int sh = tab.getMinScreenshotHeight(); int dw = tab.getThumbnailWidth(); int dh = tab.getThumbnailHeight(); - GeckoAppShell.sendEventToGecko(GeckoEvent.createScreenshotEvent(tab.getId(), sw, sh, dw, dh)); + GeckoAppShell.sendEventToGecko(GeckoEvent.createScreenshotEvent(tab.getId(), 0, 0, sw, sh, 0, 0, dw, dh, GeckoAppShell.SCREENSHOT_THUMBNAIL)); } } @@ -1193,7 +1193,8 @@ abstract public class GeckoApp tab.updateURL(uri); tab.setState(Tab.STATE_LOADING); tab.updateSecurityMode("unknown"); - + if (Tabs.getInstance().isSelectedTab(tab)) + getLayerController().getView().getRenderer().resetCheckerboard(); mMainHandler.post(new Runnable() { public void run() { if (Tabs.getInstance().isSelectedTab(tab)) { @@ -1223,6 +1224,11 @@ abstract public class GeckoApp GeckoAppShell.getHandler().postDelayed(new Runnable() { public void run() { getAndProcessThumbnailForTab(tab); + if (Tabs.getInstance().isSelectedTab(tab)) { + GeckoAppShell.sendEventToGecko(GeckoEvent.createStartPaintListentingEvent(tab.getId())); + GeckoAppShell.screenshotWholePage(tab); + } + } }, 500); } diff --git a/mobile/android/base/GeckoAppShell.java b/mobile/android/base/GeckoAppShell.java index cde7fd5c7b9..fff02ed8c92 100644 --- a/mobile/android/base/GeckoAppShell.java +++ b/mobile/android/base/GeckoAppShell.java @@ -39,9 +39,13 @@ package org.mozilla.gecko; import org.mozilla.gecko.gfx.BitmapUtils; +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 java.io.*; import java.lang.reflect.*; @@ -110,6 +114,10 @@ public class GeckoAppShell public static final String SHORTCUT_TYPE_WEBAPP = "webapp"; public static final String SHORTCUT_TYPE_BOOKMARK = "bookmark"; + static public final int SCREENSHOT_THUMBNAIL = 0; + static public final int SCREENSHOT_WHOLE_PAGE = 1; + static public final int SCREENSHOT_UPDATE = 2; + static private File sCacheFile = null; static private int sFreeSpace = -1; static File sHomeDir = null; @@ -118,6 +126,9 @@ public class GeckoAppShell private static Boolean sNSSLibsLoaded = false; private static Boolean sLibsSetup = false; private static File sGREDir = null; + private static float sCheckerboardPageWidth, sCheckerboardPageHeight; + private static float sLastCheckerboardWidthRatio, sLastCheckerboardHeightRatio; + private static RepaintRunnable sRepaintRunnable = new RepaintRunnable(); private static Map> mEventListeners = new HashMap>(); @@ -525,8 +536,9 @@ public class GeckoAppShell mInputConnection.notifyIMEChange(text, start, end, newEnd); } - public static void notifyScreenShot(final ByteBuffer data, final int tabId, - final int width, final int height) { + // Called by AndroidBridge using JNI + 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) { getHandler().post(new Runnable() { public void run() { try { @@ -534,9 +546,28 @@ public class GeckoAppShell if (tab == null) return; + if (!Tabs.getInstance().isSelectedTab(tab) && SCREENSHOT_THUMBNAIL != token) + return; + Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); b.copyPixelsFromBuffer(data); - GeckoApp.mAppContext.processThumbnail(tab, b, null); + switch (token) { + case SCREENSHOT_WHOLE_PAGE: + GeckoApp.mAppContext.getLayerController() + .getView().getRenderer().setCheckerboardBitmap(b); + break; + case SCREENSHOT_UPDATE: + GeckoApp.mAppContext.getLayerController().getView().getRenderer(). + updateCheckerboardBitmap( + b, sLastCheckerboardWidthRatio * x, + sLastCheckerboardHeightRatio * y, + sLastCheckerboardWidthRatio * width, + sLastCheckerboardHeightRatio * height); + break; + case SCREENSHOT_THUMBNAIL: + GeckoApp.mAppContext.processThumbnail(tab, b, null); + break; + } } finally { freeDirectBuffer(data); } @@ -2114,4 +2145,75 @@ public class GeckoAppShell if (!GeckoApp.mAppContext.showFilePicker(aMimeType, new AsyncResultHandler(id))) GeckoAppShell.notifyFilePickerResult("", id); } + + 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; + // synchronize so we don't try to accumulate more rects while painting the ones we have + synchronized(this) { + top = mDirtyTop; + left = mDirtyLeft; + right = mDirtyRight; + bottom = mDirtyBottom; + // 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(); + ImmutableViewportMetrics viewport = GeckoApp.mAppContext.getLayerController().getViewportMetrics(); + if (FloatUtils.fuzzyEquals(sCheckerboardPageWidth, viewport.pageSizeWidth) && + FloatUtils.fuzzyEquals(sCheckerboardPageHeight, viewport.pageSizeHeight)) { + float width = right - left; + float height = bottom - top; + GeckoAppShell.sendEventToGecko(GeckoEvent.createScreenshotEvent(tab.getId(), (int)top, (int)left, (int)width, (int)height, 0, 0, (int)(sLastCheckerboardWidthRatio * width), (int)(sLastCheckerboardHeightRatio * height), GeckoAppShell.SCREENSHOT_UPDATE)); + } 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); + if (!mIsRepaintRunnablePosted) { + getHandler().postDelayed(this, 5000); + mIsRepaintRunnablePosted = true; + } + } + } + } + + // Called by AndroidBridge using JNI + public static void notifyPaintedRect(float top, float left, float bottom, float right) { + sRepaintRunnable.addRectToRepaint(top, left, bottom, right); + } + + public static void screenshotWholePage(Tab tab) { + ImmutableViewportMetrics viewport = GeckoApp.mAppContext.getLayerController().getViewportMetrics(); + // source width and height to screenshot + float sw = viewport.pageSizeWidth; + float sh = viewport.pageSizeHeight; + // 2Mb of 16bit image data + // may be bumped by up to 4x for power of 2 alignment + float ratio = (float)Math.sqrt((sw * sh) / (ScreenshotLayer.getMaxNumPixels() / 4)); + // destination width and hight + int dw = IntSize.nextPowerOfTwo(sw / ratio); + int dh = IntSize.nextPowerOfTwo(sh / ratio); + sLastCheckerboardWidthRatio = dw / sw; + sLastCheckerboardHeightRatio = dh / sh; + sCheckerboardPageWidth = viewport.pageSizeWidth; + sCheckerboardPageHeight = viewport.pageSizeHeight; + + GeckoAppShell.sendEventToGecko(GeckoEvent.createScreenshotEvent(tab.getId(), 0, 0, (int)sw, (int)sh, 0, 0, dw, dh, GeckoAppShell.SCREENSHOT_WHOLE_PAGE)); + } } diff --git a/mobile/android/base/GeckoEvent.java b/mobile/android/base/GeckoEvent.java index b213b02018c..737f6f6a414 100644 --- a/mobile/android/base/GeckoEvent.java +++ b/mobile/android/base/GeckoEvent.java @@ -97,7 +97,8 @@ public class GeckoEvent { private static final int UNUSED2_EVENT = 26; private static final int SCREENORIENTATION_CHANGED = 27; private static final int COMPOSITOR_PAUSE = 28; - private static final int COMPOSITOR_RESUME = 29; + private static final int COMPOSITOR_RESUME = 29; + private static final int PAINT_LISTEN_START_EVENT = 30; public static final int IME_COMPOSITION_END = 0; public static final int IME_COMPOSITION_BEGIN = 1; @@ -478,12 +479,15 @@ public class GeckoEvent { return event; } - public static GeckoEvent createScreenshotEvent(int tabId, int sw, int sh, int dw, int dh) { + public static GeckoEvent createScreenshotEvent(int tabId, int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh, int token) { GeckoEvent event = new GeckoEvent(SCREENSHOT); - event.mPoints = new Point[2]; - event.mPoints[0] = new Point(sw, sh); - event.mPoints[1] = new Point(dw, dh); + event.mPoints = new Point[4]; + 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.mMetaState = tabId; + event.mFlags = token; return event; } @@ -492,4 +496,10 @@ public class GeckoEvent { event.mScreenOrientation = aScreenOrientation; return event; } + + public static GeckoEvent createStartPaintListentingEvent(int tabId) { + GeckoEvent event = new GeckoEvent(PAINT_LISTEN_START_EVENT); + event.mMetaState = tabId; + return event; + } } diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index ca15823eb8a..c61b188a12d 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -138,6 +138,7 @@ FENNEC_JAVA_FILES = \ gfx/PanningPerfAPI.java \ gfx/PointUtils.java \ gfx/RectUtils.java \ + gfx/ScreenshotLayer.java \ gfx/ScrollbarLayer.java \ gfx/SingleTileLayer.java \ gfx/SurfaceTextureLayer.java \ diff --git a/mobile/android/base/gfx/GeckoLayerClient.java b/mobile/android/base/gfx/GeckoLayerClient.java index 80bd248acd8..410da0e9fe1 100644 --- a/mobile/android/base/gfx/GeckoLayerClient.java +++ b/mobile/android/base/gfx/GeckoLayerClient.java @@ -43,6 +43,7 @@ import org.mozilla.gecko.GeckoApp; import org.mozilla.gecko.GeckoAppShell; import org.mozilla.gecko.GeckoEvent; import org.mozilla.gecko.GeckoEventResponder; +import org.mozilla.gecko.Tabs; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -341,6 +342,8 @@ public class GeckoLayerClient implements GeckoEventResponder, mLayerController.abortPanZoomAnimation(); mLayerController.getView().setPaintState(LayerView.PAINT_BEFORE_FIRST); } + mLayerController.getView().getRenderer().resetCheckerboard(); + GeckoAppShell.screenshotWholePage(Tabs.getInstance().getSelectedTab()); } /** This function is invoked by Gecko via JNI; be careful when modifying signature. diff --git a/mobile/android/base/gfx/IntSize.java b/mobile/android/base/gfx/IntSize.java index ee43b1bf557..8f5b3f4b989 100644 --- a/mobile/android/base/gfx/IntSize.java +++ b/mobile/android/base/gfx/IntSize.java @@ -96,6 +96,10 @@ public class IntSize { return value + 1; } + public static int nextPowerOfTwo(float value) { + return nextPowerOfTwo((int) value); + } + public IntSize nextPowerOfTwo() { return new IntSize(nextPowerOfTwo(width), nextPowerOfTwo(height)); } diff --git a/mobile/android/base/gfx/LayerRenderer.java b/mobile/android/base/gfx/LayerRenderer.java index d846c7d8e30..d3b84875be7 100644 --- a/mobile/android/base/gfx/LayerRenderer.java +++ b/mobile/android/base/gfx/LayerRenderer.java @@ -52,6 +52,7 @@ import org.mozilla.gecko.gfx.TileLayer; import org.mozilla.gecko.GeckoAppShell; import android.content.Context; import android.content.SharedPreferences; +import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; @@ -90,8 +91,7 @@ public class LayerRenderer implements GLSurfaceView.Renderer { private final LayerView mView; private final SingleTileLayer mBackgroundLayer; - private final CheckerboardImage mCheckerboardImage; - private final SingleTileLayer mCheckerboardLayer; + private final ScreenshotLayer mCheckerboardLayer; private final NinePatchTileLayer mShadowLayer; private TextLayer mFrameRateLayer; private final ScrollbarLayer mHorizScrollLayer; @@ -158,6 +158,36 @@ public class LayerRenderer implements GLSurfaceView.Renderer { " gl_FragColor = texture2D(sTexture, vec2(vTexCoord.x, 1.0 - vTexCoord.y));\n" + "}\n"; + public void setCheckerboardBitmap(Bitmap bitmap) { + mCheckerboardLayer.setBitmap(bitmap); + mCheckerboardLayer.beginTransaction(); + try { + mCheckerboardLayer.invalidate(); + } finally { + mCheckerboardLayer.endTransaction(); + } + } + + public void updateCheckerboardBitmap(Bitmap bitmap, float x, float y, float width, float height) { + mCheckerboardLayer.updateBitmap(bitmap, x, y, width, height); + mCheckerboardLayer.beginTransaction(); + try { + mCheckerboardLayer.invalidate(); + } finally { + mCheckerboardLayer.endTransaction(); + } + } + + public void resetCheckerboard() { + mCheckerboardLayer.reset(); + mCheckerboardLayer.beginTransaction(); + try { + mCheckerboardLayer.invalidate(); + } finally { + mCheckerboardLayer.endTransaction(); + } + } + public LayerRenderer(LayerView view) { mView = view; @@ -166,8 +196,7 @@ public class LayerRenderer implements GLSurfaceView.Renderer { CairoImage backgroundImage = new BufferedCairoImage(controller.getBackgroundPattern()); mBackgroundLayer = new SingleTileLayer(true, backgroundImage); - mCheckerboardImage = new CheckerboardImage(); - mCheckerboardLayer = new SingleTileLayer(true, mCheckerboardImage); + mCheckerboardLayer = ScreenshotLayer.create(); CairoImage shadowImage = new BufferedCairoImage(controller.getShadowPattern()); mShadowLayer = new NinePatchTileLayer(shadowImage); @@ -391,18 +420,15 @@ public class LayerRenderer implements GLSurfaceView.Renderer { private void updateCheckerboardImage() { int checkerboardColor = mView.getController().getCheckerboardColor(); boolean showChecks = mView.getController().checkerboardShouldShowChecks(); - if (checkerboardColor == mCheckerboardImage.getColor() && - showChecks == mCheckerboardImage.getShowChecks()) { - return; - } mCheckerboardLayer.beginTransaction(); // called on compositor thread try { - mCheckerboardImage.update(showChecks, checkerboardColor); - mCheckerboardLayer.invalidate(); + if (mCheckerboardLayer.updateBackground(showChecks, checkerboardColor)) + mCheckerboardLayer.invalidate(); } finally { mCheckerboardLayer.endTransaction(); } + } /* @@ -533,7 +559,7 @@ public class LayerRenderer implements GLSurfaceView.Renderer { mUpdated &= mBackgroundLayer.update(mScreenContext); // called on compositor thread mUpdated &= mShadowLayer.update(mPageContext); // called on compositor thread updateCheckerboardImage(); - mUpdated &= mCheckerboardLayer.update(mScreenContext); // called on compositor thread + mUpdated &= mCheckerboardLayer.update(mPageContext); // called on compositor thread if (mFrameRateLayer != null) mUpdated &= mFrameRateLayer.update(mScreenContext); // called on compositor thread mUpdated &= mVertScrollLayer.update(mPageContext); // called on compositor thread mUpdated &= mHorizScrollLayer.update(mPageContext); // called on compositor thread @@ -569,7 +595,7 @@ public class LayerRenderer implements GLSurfaceView.Renderer { /* Draw the checkerboard. */ setScissorRect(); mCheckerboardLayer.setMask(rootMask); - mCheckerboardLayer.draw(mScreenContext); + mCheckerboardLayer.draw(mPageContext); GLES20.glDisable(GLES20.GL_SCISSOR_TEST); } diff --git a/mobile/android/base/gfx/LayerView.java b/mobile/android/base/gfx/LayerView.java index 2bb45ffbe8b..991acfd21c3 100644 --- a/mobile/android/base/gfx/LayerView.java +++ b/mobile/android/base/gfx/LayerView.java @@ -230,7 +230,7 @@ public class LayerView extends SurfaceView implements SurfaceHolder.Callback { } - public GLSurfaceView.Renderer getRenderer() { + public LayerRenderer getRenderer() { return mRenderer; } diff --git a/mobile/android/base/gfx/NinePatchTileLayer.java b/mobile/android/base/gfx/NinePatchTileLayer.java index 1c9fed8059b..f0bcdc50604 100644 --- a/mobile/android/base/gfx/NinePatchTileLayer.java +++ b/mobile/android/base/gfx/NinePatchTileLayer.java @@ -57,7 +57,7 @@ public class NinePatchTileLayer extends TileLayer { private static final int TEXTURE_SIZE = 64; public NinePatchTileLayer(CairoImage image) { - super(false, image); + super(image, TileLayer.PaintMode.NORMAL); } @Override diff --git a/mobile/android/base/gfx/ScreenshotLayer.java b/mobile/android/base/gfx/ScreenshotLayer.java new file mode 100644 index 00000000000..c8f6453135b --- /dev/null +++ b/mobile/android/base/gfx/ScreenshotLayer.java @@ -0,0 +1,254 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.gfx; + +import org.mozilla.gecko.GeckoAppShell; +import org.mozilla.gecko.gfx.BufferedCairoImage; +import org.mozilla.gecko.gfx.CairoImage; +import org.mozilla.gecko.gfx.IntSize; +import org.mozilla.gecko.gfx.SingleTileLayer; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Region; +import android.graphics.RegionIterator; +import android.opengl.GLES20; +import android.util.Log; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.FloatBuffer; + +public class ScreenshotLayer extends SingleTileLayer { + private static final int SCREENSHOT_SIZE_LIMIT = 1048576; + 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; + // 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; + + public static int getMaxNumPixels() { + return SCREENSHOT_SIZE_LIMIT; + } + + public void reset() { + mIsSingleColor = true; + updateBackground(mForceSingleColor, Color.WHITE); + } + + 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; + } + + public void updateBitmap(Bitmap bitmap, float x, float y, float width, float height) { + mImage.updateBitmap(bitmap, x, y, width, height); + } + + 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)); + } + + public static ScreenshotLayer create(IntSize size) { + Bitmap bitmap = Bitmap.createBitmap(size.width, size.height, Bitmap.Config.RGB_565); + ScreenshotLayer sl = create(bitmap); + sl.reset(); + return sl; + } + + public static ScreenshotLayer create(Bitmap bitmap) { + IntSize size = new IntSize(bitmap.getWidth(), bitmap.getHeight()); + // allocate a buffer that can hold our max screenshot size + ByteBuffer buffer = GeckoAppShell.allocateDirectBuffer(SCREENSHOT_SIZE_LIMIT * 2); + // construct the screenshot layer + ScreenshotLayer sl = new ScreenshotLayer(new ScreenshotImage(buffer, size.width, size.height, CairoImage.FORMAT_RGB16_565), size); + // paint the passed in bitmap into the buffer + sl.setBitmap(bitmap); + return sl; + } + + private ScreenshotLayer(ScreenshotImage image, IntSize size) { + super(image, TileLayer.PaintMode.STRETCH); + 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; + } + + /** A Cairo image that simply saves a buffer of pixel data. */ + static class ScreenshotImage extends CairoImage { + 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; + } + + @Override + protected void finalize() throws Throwable { + try { + if (mBuffer != null) + GeckoAppShell.freeDirectBuffer(mBuffer); + } finally { + super.finalize(); + } + } + + 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()); + } + tmp.copyPixelsToBuffer(mBuffer.asIntBuffer()); + } + + public void updateBitmap(Bitmap bitmap, float x, float y, float width, float height) { + 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; } + @Override + public IntSize getSize() { return mSize; } + @Override + public int getFormat() { return mFormat; } + } +} diff --git a/mobile/android/base/gfx/ScrollbarLayer.java b/mobile/android/base/gfx/ScrollbarLayer.java index 601fa506164..c22878f39ae 100644 --- a/mobile/android/base/gfx/ScrollbarLayer.java +++ b/mobile/android/base/gfx/ScrollbarLayer.java @@ -141,7 +141,7 @@ public class ScrollbarLayer extends TileLayer { }; private ScrollbarLayer(LayerRenderer renderer, CairoImage image, boolean vertical, ByteBuffer buffer) { - super(false, image); + super(image, TileLayer.PaintMode.NORMAL); mVertical = vertical; mBuffer = buffer; mRenderer = renderer; diff --git a/mobile/android/base/gfx/SingleTileLayer.java b/mobile/android/base/gfx/SingleTileLayer.java index 1264768b365..ce11bce779a 100644 --- a/mobile/android/base/gfx/SingleTileLayer.java +++ b/mobile/android/base/gfx/SingleTileLayer.java @@ -65,7 +65,11 @@ public class SingleTileLayer extends TileLayer { public SingleTileLayer(CairoImage image) { this(false, image); } public SingleTileLayer(boolean repeat, CairoImage image) { - super(repeat, image); + super(image, repeat ? TileLayer.PaintMode.REPEAT : TileLayer.PaintMode.NORMAL); + } + + public SingleTileLayer(CairoImage image, TileLayer.PaintMode paintMode) { + super(image, paintMode); } /** @@ -86,7 +90,7 @@ public class SingleTileLayer extends TileLayer { Rect position = getPosition(); RectF viewport = context.viewport; - if (repeats()) { + if (repeats() || stretches()) { bounds = new RectF(0.0f, 0.0f, viewport.width(), viewport.height()); int width = Math.round(viewport.width()); int height = Math.round(viewport.height()); diff --git a/mobile/android/base/gfx/TileLayer.java b/mobile/android/base/gfx/TileLayer.java index a9a6e3cf258..c699e178ebc 100644 --- a/mobile/android/base/gfx/TileLayer.java +++ b/mobile/android/base/gfx/TileLayer.java @@ -58,20 +58,23 @@ public abstract class TileLayer extends Layer { private final Rect mDirtyRect; private final CairoImage mImage; - private final boolean mRepeat; private IntSize mSize; private int[] mTextureIDs; - public TileLayer(boolean repeat, CairoImage image) { + public enum PaintMode { NORMAL, REPEAT, STRETCH }; + private PaintMode mPaintMode; + + public TileLayer(CairoImage image, PaintMode paintMode) { super(image.getSize()); - mRepeat = repeat; + mPaintMode = paintMode; mImage = image; mSize = new IntSize(0, 0); mDirtyRect = new Rect(); } - protected boolean repeats() { return mRepeat; } + protected boolean repeats() { return mPaintMode == PaintMode.REPEAT; } + protected boolean stretches() { return mPaintMode == PaintMode.STRETCH; } protected int getTextureID() { return mTextureIDs[0]; } protected boolean initialized() { return mImage != null && mTextureIDs != null; } @@ -186,7 +189,7 @@ public abstract class TileLayer extends Layer { GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); - int repeatMode = mRepeat ? GLES20.GL_REPEAT : GLES20.GL_CLAMP_TO_EDGE; + int repeatMode = repeats() ? GLES20.GL_REPEAT : GLES20.GL_CLAMP_TO_EDGE; GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, repeatMode); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, repeatMode); } diff --git a/widget/android/AndroidBridge.cpp b/widget/android/AndroidBridge.cpp index 642d6ac9f40..f77d97e95f0 100644 --- a/widget/android/AndroidBridge.cpp +++ b/widget/android/AndroidBridge.cpp @@ -115,7 +115,7 @@ AndroidBridge::Init(JNIEnv *jEnv, jNotifyIME = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyIME", "(II)V"); jNotifyIMEEnabled = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyIMEEnabled", "(ILjava/lang/String;Ljava/lang/String;Z)V"); jNotifyIMEChange = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyIMEChange", "(Ljava/lang/String;III)V"); - jNotifyScreenShot = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyScreenShot", "(Ljava/nio/ByteBuffer;III)V"); + jNotifyScreenShot = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyScreenShot", "(Ljava/nio/ByteBuffer;IIIIII)V"); jAcknowledgeEventSync = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "acknowledgeEventSync", "()V"); jEnableLocation = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "enableLocation", "(Z)V"); @@ -166,6 +166,7 @@ AndroidBridge::Init(JNIEnv *jEnv, jDisableBatteryNotifications = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "disableBatteryNotifications", "()V"); jGetCurrentBatteryInformation = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getCurrentBatteryInformation", "()[D"); jRemovePluginView = jEnv->GetStaticMethodID(jGeckoAppShellClass, "removePluginView", "(Landroid/view/View;)V"); + jNotifyPaintedRect = jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyPaintedRect", "(FFFF)V"); jHandleGeckoMessage = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "handleGeckoMessage", "(Ljava/lang/String;)Ljava/lang/String;"); jCheckUriVisited = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "checkUriVisited", "(Ljava/lang/String;)V"); @@ -2135,7 +2136,7 @@ jobject JNICALL Java_org_mozilla_gecko_GeckoAppShell_allocateDirectBuffer(JNIEnv *jenv, jclass, jlong size); -nsresult AndroidBridge::TakeScreenshot(nsIDOMWindow *window, PRInt32 srcX, PRInt32 srcY, PRInt32 srcW, PRInt32 srcH, PRInt32 dstW, PRInt32 dstH, PRInt32 tabId, float scale) +nsresult AndroidBridge::TakeScreenshot(nsIDOMWindow *window, PRInt32 srcX, PRInt32 srcY, PRInt32 srcW, PRInt32 srcH, PRInt32 dstW, PRInt32 dstH, PRInt32 tabId, float scale, PRInt32 token) { nsCOMPtr win = do_QueryInterface(window); if (!win) @@ -2175,6 +2176,15 @@ nsresult AndroidBridge::TakeScreenshot(nsIDOMWindow *window, PRInt32 srcX, PRInt nsresult rv = presShell->RenderDocument(r, renderDocFlags, bgColor, context); NS_ENSURE_SUCCESS(rv, rv); AndroidBridge::AutoLocalJNIFrame jniFrame(jenv, 1); - jenv->CallStaticVoidMethod(AndroidBridge::Bridge()->mGeckoAppShellClass, AndroidBridge::Bridge()->jNotifyScreenShot, buffer, tabId, dstW, dstH); + jenv->CallStaticVoidMethod(AndroidBridge::Bridge()->mGeckoAppShellClass, AndroidBridge::Bridge()->jNotifyScreenShot, buffer, tabId, srcX * dstW / srcW , srcY * dstH / srcH, dstW, dstH, token); return NS_OK; } + +void +AndroidBridge::NotifyPaintedRect(float top, float left, float bottom, float right) +{ + JNIEnv* jenv = AndroidBridge::GetJNIEnv(); + if (!jenv) + return; + jenv->CallStaticVoidMethod(AndroidBridge::Bridge()->mGeckoAppShellClass, AndroidBridge::Bridge()->jNotifyPaintedRect, top, left, bottom, right); +} diff --git a/widget/android/AndroidBridge.h b/widget/android/AndroidBridge.h index cff7179674f..2827fc0e318 100644 --- a/widget/android/AndroidBridge.h +++ b/widget/android/AndroidBridge.h @@ -183,7 +183,9 @@ public: static void RemovePluginView(void* surface); - nsresult TakeScreenshot(nsIDOMWindow *window, PRInt32 srcX, PRInt32 srcY, PRInt32 srcW, PRInt32 srcH, PRInt32 dstW, PRInt32 dstH, PRInt32 tabId, float scale); + nsresult TakeScreenshot(nsIDOMWindow *window, PRInt32 srcX, PRInt32 srcY, PRInt32 srcW, PRInt32 srcH, PRInt32 dstW, PRInt32 dstH, PRInt32 tabId, float scale, PRInt32 token); + + static void NotifyPaintedRect(float top, float left, float bottom, float right); void AcknowledgeEventSync(); @@ -524,6 +526,7 @@ protected: jmethodID jCheckUriVisited; jmethodID jMarkUriVisited; jmethodID jRemovePluginView; + jmethodID jNotifyPaintedRect; jmethodID jNumberOfMessages; jmethodID jSendMessage; diff --git a/widget/android/AndroidJavaWrappers.cpp b/widget/android/AndroidJavaWrappers.cpp index aeb682f10f0..b1ff4ab18d3 100644 --- a/widget/android/AndroidJavaWrappers.cpp +++ b/widget/android/AndroidJavaWrappers.cpp @@ -520,7 +520,13 @@ AndroidGeckoEvent::Init(JNIEnv *jenv, jobject jobj) case SCREENSHOT: { mMetaState = jenv->GetIntField(jobj, jMetaStateField); - ReadPointArray(mPoints, jenv, jPoints, 2); + mFlags = jenv->GetIntField(jobj, jFlagsField); + ReadPointArray(mPoints, jenv, jPoints, 4); + break; + } + + case PAINT_LISTEN_START_EVENT: { + mMetaState = jenv->GetIntField(jobj, jMetaStateField); break; } diff --git a/widget/android/AndroidJavaWrappers.h b/widget/android/AndroidJavaWrappers.h index 691a0b0543d..43666ebe6d6 100644 --- a/widget/android/AndroidJavaWrappers.h +++ b/widget/android/AndroidJavaWrappers.h @@ -719,6 +719,7 @@ public: SCREENORIENTATION_CHANGED = 27, COMPOSITOR_PAUSE = 28, COMPOSITOR_RESUME = 29, + PAINT_LISTEN_START_EVENT = 30, dummy_java_enum_list_end }; diff --git a/widget/android/nsAppShell.cpp b/widget/android/nsAppShell.cpp index 823aa409f1e..eb7d4950df3 100644 --- a/widget/android/nsAppShell.cpp +++ b/widget/android/nsAppShell.cpp @@ -49,6 +49,10 @@ #include "nsIAppStartup.h" #include "nsIGeolocationProvider.h" #include "nsCacheService.h" +#include "nsIDOMEventListener.h" +#include "nsDOMNotifyPaintEvent.h" +#include "nsIDOMClientRectList.h" +#include "nsIDOMClientRect.h" #include "mozilla/Services.h" #include "mozilla/unused.h" @@ -92,6 +96,64 @@ nsAppShell *nsAppShell::gAppShell = nsnull; NS_IMPL_ISUPPORTS_INHERITED1(nsAppShell, nsBaseAppShell, nsIObserver) +class AfterPaintListener : public nsIDOMEventListener { + public: + NS_DECL_ISUPPORTS + + void Register(nsIDOMWindow* window) { + if (mEventTarget) + Unregister(); + nsCOMPtr win = do_QueryInterface(window); + if (!win) + return; + mEventTarget = win->GetChromeEventHandler(); + if (mEventTarget) + mEventTarget->AddEventListener(NS_LITERAL_STRING("MozAfterPaint"), this, false); + } + + void Unregister() { + if (mEventTarget) + mEventTarget->RemoveEventListener(NS_LITERAL_STRING("MozAfterPaint"), this, false); + mEventTarget = nsnull; + } + + virtual nsresult HandleEvent(nsIDOMEvent* aEvent) { + nsCOMPtr paintEvent = do_QueryInterface(aEvent); + if (!paintEvent) + return NS_OK; + + nsCOMPtr 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 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); + } + return NS_OK; + } + + ~AfterPaintListener() { + if (mEventTarget) + Unregister(); + } + + private: + nsCOMPtr mEventTarget; +}; + +NS_IMPL_ISUPPORTS1(AfterPaintListener, nsIDOMEventListener) +nsCOMPtr sAfterPaintListener = nsnull; + nsAppShell::nsAppShell() : mQueueLock("nsAppShell.mQueueLock"), mCondLock("nsAppShell.mCondLock"), @@ -101,11 +163,13 @@ nsAppShell::nsAppShell() mAllowCoalescingNextDraw(false) { gAppShell = this; + sAfterPaintListener = new AfterPaintListener(); } nsAppShell::~nsAppShell() { gAppShell = nsnull; + delete sAfterPaintListener; } void @@ -369,6 +433,21 @@ nsAppShell::ProcessNextNativeEvent(bool mayWait) break; } + case AndroidGeckoEvent::PAINT_LISTEN_START_EVENT: { + nsCOMPtr domWindow; + nsCOMPtr tab; + mBrowserApp->GetBrowserTab(curEvent->MetaState(), getter_AddRefs(tab)); + if (!tab) + break; + + tab->GetWindow(getter_AddRefs(domWindow)); + if (!domWindow) + break; + + sAfterPaintListener->Register(domWindow); + break; + } + case AndroidGeckoEvent::SCREENSHOT: { if (!mBrowserApp) break; @@ -392,8 +471,8 @@ nsAppShell::ProcessNextNativeEvent(bool mayWait) break; nsTArray points = curEvent->Points(); - NS_ASSERTION(points.Length() == 2, "Screenshot event does not have enough coordinates"); - bridge->TakeScreenshot(domWindow, 0, 0, points[0].x, points[0].y, points[1].x, points[1].y, curEvent->MetaState(), scale); + NS_ASSERTION(points.Length() == 4, "Screenshot event does not have enough coordinates"); + bridge->TakeScreenshot(domWindow, points[0].x, points[0].y, points[1].x, points[1].y, points[3].x, points[3].y, curEvent->MetaState(), scale, curEvent->Flags()); break; }