merge with mozilla-central:
authorDoug Turner <dougt@dougt.org>
Wed, 22 Feb 2012 09:30:09 -0800
changeset 91028 8a378d15767bd1bcd7d43351ae41b46ea86eee4c
parent 88935 bd6567f435bf103b7dd9df623f4759c8a5807c33 (current diff)
parent 91027 ba6b3a24d5c624de30a4bd5deab1d175d00279b4 (diff)
child 91029 149d48043167bdbc87c64190270b924430075004
push idunknown
push userunknown
push dateunknown
milestone13.0a1
merge with mozilla-central:
configure.in
content/base/public/nsIFrameLoader.idl
gfx/gl/GLContext.cpp
layout/base/FrameLayerBuilder.cpp
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsGfxScrollFrame.h
mobile/android/base/GeckoApp.java
mobile/android/base/gfx/GeckoSoftwareLayerClient.java
mobile/android/base/gfx/LayerClient.java
mobile/android/chrome/content/browser.js
modules/libpref/src/init/all.js
mozglue/android/APKOpen.cpp
widget/android/AndroidJNI.cpp
widget/android/Makefile.in
--- a/config/android-common.mk
+++ b/config/android-common.mk
@@ -66,10 +66,11 @@ endif
 
 JAVAC_FLAGS = \
   -target $(JAVA_VERSION) \
   -source $(JAVA_VERSION) \
   -classpath $(JAVA_CLASSPATH) \
   -bootclasspath $(JAVA_BOOTCLASSPATH) \
   -encoding UTF8 \
   -g:source,lines \
-  -Werror \
+  # Still getting warngings against 1.6.0_29/1.7, remove until fixed on m-c
+  #-Werror \
   $(NULL)
--- a/embedding/android/GeckoAppShell.java
+++ b/embedding/android/GeckoAppShell.java
@@ -1554,17 +1554,17 @@ public class GeckoAppShell
             GeckoAppShell.executeNextRunnable();
         }
     }
 
     public static void postToJavaThread(boolean mainThread) {
         Log.i("GeckoShell", "post to " + (mainThread ? "main " : "") + "java thread");
         getMainHandler().post(new GeckoRunnableCallback());
     }
-    
+
     public static android.hardware.Camera sCamera = null;
     
     static native void cameraCallbackBridge(byte[] data);
 
     static int kPreferedFps = 25;
     static byte[] sCameraBuffer = null;
 
     static int[] initCamera(String aContentType, int aCamera, int aWidth, int aHeight) {
--- a/embedding/android/GeckoEvent.java
+++ b/embedding/android/GeckoEvent.java
@@ -329,9 +329,14 @@ public class GeckoEvent {
         mCharacters = uri;
     }
 
     public GeckoEvent(double bandwidth, boolean canBeMetered) {
         mType = NETWORK_CHANGED;
         mBandwidth = bandwidth;
         mCanBeMetered = canBeMetered;
     }
+
+    public void doCallback(String json) {
+        // 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.
+    }
 }
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -49,18 +49,21 @@
 
 #include "gfxPlatform.h"
 #include "GLContext.h"
 #include "GLContextProvider.h"
 
 #include "gfxCrashReporterUtils.h"
 #include "gfxUtils.h"
 
+#include "mozilla/Preferences.h"
 #include "mozilla/Util.h" // for DebugOnly
 
+using namespace mozilla::gfx;
+
 namespace mozilla {
 namespace gl {
 
 #ifdef DEBUG
 // see comment near declaration in GLContext.h. Should be thread-local.
 GLContext* GLContext::sCurrentGLContext = nsnull;
 #endif
 
@@ -382,34 +385,41 @@ GLContext::InitWithPrefix(const char *pr
             }
         }
     }
 
     const char *glVendorString;
     const char *glRendererString;
 
     if (mInitialized) {
+        // The order of these strings must match up with the order of the enum
+        // defined in GLContext.h for vendor IDs
         glVendorString = (const char *)fGetString(LOCAL_GL_VENDOR);
         const char *vendorMatchStrings[VendorOther] = {
                 "Intel",
                 "NVIDIA",
                 "ATI",
-                "Qualcomm"
+                "Qualcomm",
+                "Imagination"
         };
         mVendor = VendorOther;
         for (int i = 0; i < VendorOther; ++i) {
             if (DoesStringMatch(glVendorString, vendorMatchStrings[i])) {
                 mVendor = i;
                 break;
             }
         }
 
+        // The order of these strings must match up with the order of the enum
+        // defined in GLContext.h for renderer IDs
         glRendererString = (const char *)fGetString(LOCAL_GL_RENDERER);
         const char *rendererMatchStrings[RendererOther] = {
-                "Adreno 200"
+                "Adreno 200",
+                "Adreno 205",
+                "PowerVR SGX 540"
         };
         mRenderer = RendererOther;
         for (int i = 0; i < RendererOther; ++i) {
             if (DoesStringMatch(glRendererString, rendererMatchStrings[i])) {
                 mRenderer = i;
                 break;
             }
         }
@@ -504,18 +514,24 @@ GLContext::InitWithPrefix(const char *pr
 
         fGetIntegerv(LOCAL_GL_VIEWPORT, v);
         mViewportStack.AppendElement(nsIntRect(v[0], v[1], v[2], v[3]));
 
         fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
         fGetIntegerv(LOCAL_GL_MAX_RENDERBUFFER_SIZE, &mMaxRenderbufferSize);
         mMaxTextureImageSize = mMaxTextureSize;
 
+        // BGRA ReadPixels support - in particular, what is returned for
+        // GL_IMPLEMENTATION_COLOR_READ_FORMAT et al - is only guaranteed to be
+        // accurate for what's currently bound to the context. It seems that,
+        // after we initialize, we bind something incompatible with the BGRA
+        // pixel format on at least some devices.
+        // For now, just disable this code altogether.
         mSupport_ES_ReadPixels_BGRA_UByte = false;
-        if (mIsGLES2) {
+        if (false) {
             if (IsExtensionSupported(gl::GLContext::EXT_bgra)) {
                 mSupport_ES_ReadPixels_BGRA_UByte = true;
             } else if (IsExtensionSupported(gl::GLContext::EXT_read_format_bgra) ||
                        IsExtensionSupported(gl::GLContext::IMG_read_format)) {
                 GLint auxFormat = 0;
                 GLint auxType = 0;
 
                 fGetIntegerv(LOCAL_GL_IMPLEMENTATION_COLOR_READ_FORMAT, &auxFormat);
@@ -597,29 +613,109 @@ GLContext::InitExtensions()
 
     free(exts);
 
 #ifdef DEBUG
     once = true;
 #endif
 }
 
+// Take texture data in a given buffer and copy it into a larger buffer,
+// padding out the edge pixels for filtering if necessary
+static void
+CopyAndPadTextureData(const GLvoid* srcBuffer,
+                      GLvoid* dstBuffer,
+                      GLsizei srcWidth, GLsizei srcHeight,
+                      GLsizei dstWidth, GLsizei dstHeight,
+                      GLsizei stride, GLint pixelsize)
+{
+    unsigned char *rowDest = static_cast<unsigned char*>(dstBuffer);
+    const unsigned char *source = static_cast<const unsigned char*>(srcBuffer);
+
+    for (GLsizei h = 0; h < srcHeight; ++h) {
+        memcpy(rowDest, source, srcWidth * pixelsize);
+        rowDest += dstWidth * pixelsize;
+        source += stride;
+    }
+
+    GLsizei padHeight = srcHeight;
+
+    // Pad out an extra row of pixels so that edge filtering doesn't use garbage data
+    if (dstHeight > srcHeight) {
+        memcpy(rowDest, source - stride, srcWidth * pixelsize);
+        padHeight++;
+    }
+
+    // Pad out an extra column of pixels
+    if (dstWidth > srcWidth) {
+        rowDest = static_cast<unsigned char*>(dstBuffer) + srcWidth * pixelsize;
+        for (GLsizei h = 0; h < padHeight; ++h) {
+            memcpy(rowDest, rowDest - pixelsize, pixelsize);
+            rowDest += dstWidth * pixelsize;
+        }
+    }
+}
+
 bool
 GLContext::IsExtensionSupported(const char *extension)
 {
     return ListHasExtension(fGetString(LOCAL_GL_EXTENSIONS), extension);
 }
 
+// In both of these cases (for the Adreno at least) it is impossible
+// to determine good or bad driver versions for POT texture uploads,
+// so blacklist them all. Newer drivers use a different rendering
+// string in the form "Adreno (TM) 200" and the drivers we've seen so
+// far work fine with NPOT textures, so don't blacklist those until we
+// have evidence of any problems with them.
 bool
 GLContext::CanUploadSubTextures()
 {
     // There are certain GPUs that we don't want to use glTexSubImage2D on
     // because that function can be very slow and/or buggy
-
-    return !(Renderer() == RendererAdreno200);
+    return (Renderer() != RendererAdreno200 &&
+            Renderer() != RendererAdreno205);
+}
+
+bool
+GLContext::CanUploadNonPowerOfTwo()
+{
+    static bool sPowerOfTwoForced;
+    static bool sPowerOfTwoPrefCached = false;
+
+    if (!sPowerOfTwoPrefCached) {
+        sPowerOfTwoPrefCached = true;
+        mozilla::Preferences::AddBoolVarCache(&sPowerOfTwoForced,
+                                              "gfx.textures.poweroftwo.force-enabled");
+    }
+
+    // Some GPUs driver crash when uploading non power of two 565 textures.
+    return sPowerOfTwoForced ? false : (Renderer() != RendererAdreno200 &&
+                                        Renderer() != RendererAdreno205);
+}
+
+bool
+GLContext::WantsSmallTiles()
+{
+#ifdef MOZ_WIDGET_ANDROID
+    // We must use small tiles for good performance if we can't use
+    // glTexSubImage2D() for some reason.
+    if (!CanUploadSubTextures())
+        return true;
+
+    // We can't use small tiles on the SGX 540, because of races in texture upload.
+    if (Renderer() == RendererSGX540)
+        return false;
+
+    // Don't use small tiles otherwise. (If we implement incremental texture upload,
+    // then we will want to revisit this.)
+    return false;
+#else
+    return false;
+#endif
 }
 
 // Common code for checking for both GL extensions and GLX extensions.
 bool
 GLContext::ListHasExtension(const GLubyte *extensions, const char *extension)
 {
     // fix bug 612572 - we were crashing as we were calling this function with extensions==null
     if (extensions == nsnull || extension == nsnull)
@@ -679,19 +775,16 @@ GLContext::CreateTextureImage(const nsIn
 }
 
 void GLContext::ApplyFilterToBoundTexture(gfxPattern::GraphicsFilter aFilter)
 {
     if (aFilter == gfxPattern::FILTER_NEAREST) {
         fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_NEAREST);
         fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_NEAREST);
     } else {
-        if (aFilter != gfxPattern::FILTER_GOOD) {
-            NS_WARNING("Unsupported filter type!");
-        }
         fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);
        fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR);
     }
 }
 
 BasicTextureImage::~BasicTextureImage()
 {
     GLContext *ctx = mGLContext;
@@ -864,17 +957,17 @@ TiledTextureImage::TiledTextureImage(GLC
                                      bool aUseNearestFilter)
     : TextureImage(aSize, LOCAL_GL_CLAMP_TO_EDGE, aContentType, aUseNearestFilter)
     , mCurrentImage(0)
     , mInUpdate(false)
     , mGL(aGL)
     , mUseNearestFilter(aUseNearestFilter)
     , mTextureState(Created)
 {
-    mTileSize = mGL->GetMaxTextureSize();
+    mTileSize = mGL->WantsSmallTiles() ? 256 : mGL->GetMaxTextureSize();
     if (aSize != nsIntSize(0,0)) {
         Resize(aSize);
     }
 }
 
 TiledTextureImage::~TiledTextureImage()
 {
 }
@@ -1871,26 +1964,33 @@ GLContext::BlitTextureImage(TextureImage
 
             float dx0 = 2.0 * float(srcSubInDstRect.x) / float(dstSize.width) - 1.0;
             float dy0 = 2.0 * float(srcSubInDstRect.y) / float(dstSize.height) - 1.0;
             float dx1 = 2.0 * float(srcSubInDstRect.x + srcSubInDstRect.width) / float(dstSize.width) - 1.0;
             float dy1 = 2.0 * float(srcSubInDstRect.y + srcSubInDstRect.height) / float(dstSize.height) - 1.0;
             PushViewportRect(nsIntRect(0, 0, dstSize.width, dstSize.height));
 
             RectTriangles rects;
+
+            nsIntSize realTexSize = srcSize;
+            if (!CanUploadNonPowerOfTwo()) {
+                realTexSize = nsIntSize(NextPowerOfTwo(srcSize.width),
+                                        NextPowerOfTwo(srcSize.height));
+            }
+
             if (aSrc->GetWrapMode() == LOCAL_GL_REPEAT) {
                 rects.addRect(/* dest rectangle */
                         dx0, dy0, dx1, dy1,
                         /* tex coords */
-                        srcSubRect.x / float(srcSize.width),
-                        srcSubRect.y / float(srcSize.height),
-                        srcSubRect.XMost() / float(srcSize.width),
-                        srcSubRect.YMost() / float(srcSize.height));
+                        srcSubRect.x / float(realTexSize.width),
+                        srcSubRect.y / float(realTexSize.height),
+                        srcSubRect.XMost() / float(realTexSize.width),
+                        srcSubRect.YMost() / float(realTexSize.height));
             } else {
-                DecomposeIntoNoRepeatTriangles(srcSubRect, srcSize, rects);
+                DecomposeIntoNoRepeatTriangles(srcSubRect, realTexSize, rects);
 
                 // now put the coords into the d[xy]0 .. d[xy]1 coordinate space
                 // from the 0..1 that it comes out of decompose
                 RectTriangles::vert_coord* v = (RectTriangles::vert_coord*)rects.vertexPointer();
 
                 for (unsigned int i = 0; i < rects.elements(); ++i) {
                     v[i].x = (v[i].x * (dx1 - dx0)) + dx0;
                     v[i].y = (v[i].y * (dy1 - dy0)) + dy0;
@@ -2125,16 +2225,51 @@ GLContext::TexImage2D(GLenum target, GLi
                       GLint pixelsize, GLint border, GLenum format,
                       GLenum type, const GLvoid *pixels)
 {
 #ifdef USE_GLES2
 
     NS_ASSERTION(format == internalformat,
                  "format and internalformat not the same for glTexImage2D on GLES2");
 
+    if (!CanUploadNonPowerOfTwo()
+        && (stride != width * pixelsize
+        || !IsPowerOfTwo(width)
+        || !IsPowerOfTwo(height))) {
+
+        // Pad out texture width and height to the next power of two
+        // as we don't support/want non power of two texture uploads
+        GLsizei paddedWidth = NextPowerOfTwo(width);
+        GLsizei paddedHeight = NextPowerOfTwo(height);
+
+        GLvoid* paddedPixels = new unsigned char[paddedWidth * paddedHeight * pixelsize];
+
+        // Pad out texture data to be in a POT sized buffer for uploading to
+        // a POT sized texture
+        CopyAndPadTextureData(pixels, paddedPixels, width, height,
+                              paddedWidth, paddedHeight, stride, pixelsize);
+
+        fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
+                 NS_MIN(GetAddressAlignment((ptrdiff_t)paddedPixels),
+                        GetAddressAlignment((ptrdiff_t)paddedWidth * pixelsize)));
+        fTexImage2D(target,
+                    border,
+                    internalformat,
+                    paddedWidth,
+                    paddedHeight,
+                    border,
+                    format,
+                    type,
+                    paddedPixels);
+        fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
+
+        delete[] static_cast<unsigned char*>(paddedPixels);
+        return;
+    }
+
     if (stride == width * pixelsize) {
         fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
                  NS_MIN(GetAddressAlignment((ptrdiff_t)pixels),
                         GetAddressAlignment((ptrdiff_t)stride)));
         fTexImage2D(target,
                     border,
                     internalformat,
                     width,
@@ -2431,18 +2566,20 @@ GLContext::DecomposeIntoNoRepeatTriangle
     GLfloat ylen = (1.0f - tl[1]) + br[1];
 
     NS_ASSERTION(!xwrap || xlen > 0.0f, "xlen isn't > 0, what's going on?");
     NS_ASSERTION(!ywrap || ylen > 0.0f, "ylen isn't > 0, what's going on?");
     NS_ASSERTION(aTexCoordRect.width <= aTexSize.width &&
                  aTexCoordRect.height <= aTexSize.height, "tex coord rect would cause tiling!");
 
     if (!xwrap && !ywrap) {
-        aRects.addRect(0.0f, 0.0f, 1.0f, 1.0f,
-                       tl[0], tl[1], br[0], br[1]);
+        aRects.addRect(0.0f, 0.0f,
+                       1.0f, 1.0f,
+                       tl[0], tl[1],
+                       br[0], br[1]);
     } else if (!xwrap && ywrap) {
         GLfloat ymid = (1.0f - tl[1]) / ylen;
         aRects.addRect(0.0f, 0.0f,
                        1.0f, ymid,
                        tl[0], tl[1],
                        br[0], 1.0f);
         aRects.addRect(0.0f, ymid,
                        1.0f, 1.0f,
--- a/gfx/gl/GLContext.h
+++ b/gfx/gl/GLContext.h
@@ -692,33 +692,38 @@ public:
      */
     virtual bool SupportsRobustness() = 0;
 
     enum {
         VendorIntel,
         VendorNVIDIA,
         VendorATI,
         VendorQualcomm,
+        VendorImagination,
         VendorOther
     };
 
     enum {
         RendererAdreno200,
+        RendererAdreno205,
+        RendererSGX540,
         RendererOther
     };
 
     int Vendor() const {
         return mVendor;
     }
 
     int Renderer() const {
         return mRenderer;
     }
 
     bool CanUploadSubTextures();
+    bool CanUploadNonPowerOfTwo();
+    bool WantsSmallTiles();
 
     /**
      * If this context wraps a double-buffered target, swap the back
      * and front buffers.  It should be assumed that after a swap, the
      * contents of the new back buffer are undefined.
      */
     virtual bool SwapBuffers() { return false; }
 
--- a/gfx/gl/GLContextProviderEGL.cpp
+++ b/gfx/gl/GLContextProviderEGL.cpp
@@ -1128,18 +1128,18 @@ public:
     bool MakeCurrentImpl(bool aForce = false) {
         bool succeeded = true;
 
         // Assume that EGL has the same problem as WGL does,
         // where MakeCurrent with an already-current context is
         // still expensive.
 #ifndef MOZ_WIDGET_QT
         if (!mSurface) {
-            // We need to be able to bind the surface when we don't
-            // have access to a surface. We wont be drawing to the screen
+            // We need to be able to bind NO_SURFACE when we don't
+            // have access to a surface. We won't be drawing to the screen
             // but we will be able to do things like resource releases.
             succeeded = sEGLLibrary.fMakeCurrent(EGL_DISPLAY(),
                                                  EGL_NO_SURFACE, EGL_NO_SURFACE,
                                                  EGL_NO_CONTEXT);
             if (!succeeded && sEGLLibrary.fGetError() == LOCAL_EGL_CONTEXT_LOST) {
                 mContextLost = true;
                 NS_WARNING("EGL context has been lost.");
             }
@@ -2211,31 +2211,42 @@ CreateConfig(EGLConfig* aConfig)
     return false;
 }
 
 static EGLSurface
 CreateSurfaceForWindow(nsIWidget *aWidget, EGLConfig config)
 {
     EGLSurface surface;
 
-
 #ifdef DEBUG
     sEGLLibrary.DumpEGLConfig(config);
 #endif
 
-#ifdef MOZ_WIDGET_ANDROID
+#ifdef MOZ_JAVA_COMPOSITOR
+    printf_stderr("... requesting window surface from bridge\n");
+    surface = mozilla::AndroidBridge::Bridge()->ProvideEGLSurface();
+    printf_stderr("got surface %p\n", surface);
+    return surface;
+#elif defined(MOZ_WIDGET_ANDROID)
+    printf_stderr("... requesting window surface from bridge\n");
+
     // On Android, we have to ask Java to make the eglCreateWindowSurface
     // call for us.  See GLHelpers.java for a description of why.
     //
     // We also only have one true "window", so we just use it directly and ignore
     // what was passed in.
+    AndroidGeckoSurfaceView& sview = mozilla::AndroidBridge::Bridge()->SurfaceView();
+    if (sview.isNull()) {
+        printf_stderr("got null surface\n");
+        return NULL;
+    }
+
     printf_stderr("... requesting window surface from bridge\n");
     surface = mozilla::AndroidBridge::Bridge()->
-        CallEglCreateWindowSurface(EGL_DISPLAY(), config,
-                                   mozilla::AndroidBridge::Bridge()->SurfaceView());
+        CallEglCreateWindowSurface(EGL_DISPLAY(), config, sview);
     printf_stderr("got surface %p\n", surface);
 #else
     surface = sEGLLibrary.fCreateWindowSurface(EGL_DISPLAY(), config, GET_NATIVE_WINDOW(aWidget), 0);
 #endif
 
 #ifdef MOZ_WIDGET_GONK
     gScreenBounds.x = 0;
     gScreenBounds.y = 0;
@@ -2265,17 +2276,22 @@ GLContextProviderEGL::CreateForWindow(ns
         return nsnull;
     }
 
     if (!CreateConfig(&config)) {
         printf_stderr("Failed to create EGL config!\n");
         return nsnull;
     }
 
-    EGLSurface surface = CreateSurfaceForWindow(aWidget, config);
+#ifdef MOZ_JAVA_COMPOSITOR
+    printf_stderr("... registering OGL compositor with bridge\n");
+    mozilla::AndroidBridge::Bridge()->RegisterCompositor();
+#endif
+
+   EGLSurface surface = CreateSurfaceForWindow(aWidget, config);
 
     if (!surface) {
         return nsnull;
     }
 
     if (!sEGLLibrary.fBindAPI(LOCAL_EGL_OPENGL_ES_API)) {
         sEGLLibrary.fDestroySurface(EGL_DISPLAY(), surface);
         return nsnull;
--- a/gfx/gl/Makefile.in
+++ b/gfx/gl/Makefile.in
@@ -117,14 +117,18 @@ else
 CPPSRCS += GLContextProvider$(GL_PROVIDER).cpp
 endif
 
 # Win32 is a special snowflake, for ANGLE
 ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
 CPPSRCS += GLContextProviderEGL.cpp
 endif
 
+ifdef MOZ_JAVA_COMPOSITOR
+DEFINES += -DMOZ_JAVA_COMPOSITOR
+endif
+
 include $(topsrcdir)/config/rules.mk
 
 DEFINES := $(filter-out -DUNICODE,$(DEFINES))
 
 CXXFLAGS += $(MOZ_CAIRO_CFLAGS) $(TK_CFLAGS)
 CFLAGS += $(MOZ_CAIRO_CFLAGS) $(TK_CFLAGS)
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -1340,12 +1340,13 @@ protected:
   void* mCallbackData;
   gfxPattern::GraphicsFilter mFilter;
   /**
    * Set to true in Updated(), cleared during a transaction.
    */
   bool mDirty;
 };
 
+
 }
 }
 
 #endif /* GFX_LAYERS_H */
--- a/gfx/layers/Makefile.in
+++ b/gfx/layers/Makefile.in
@@ -68,16 +68,17 @@ EXPORTS = \
         ReadbackLayer.h \
         LayerSorter.h \
         $(NULL)
 
 CPPSRCS = \
         BasicImages.cpp \
         BasicLayers.cpp \
         Layers.cpp \
+        RenderTrace.cpp \
         ReadbackProcessor.cpp \
         ThebesLayerBuffer.cpp \
         CanvasLayerOGL.cpp \
         ColorLayerOGL.cpp \
         ContainerLayerOGL.cpp \
         ImageLayerOGL.cpp \
         LayerManagerOGL.cpp \
         ThebesLayerOGL.cpp \
new file mode 100644
--- /dev/null
+++ b/gfx/layers/RenderTrace.cpp
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * ***** 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 Corporation code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Benoit Girard <bgirard@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "Layers.h"
+#include "RenderTrace.h"
+
+// If rendertrace is off let's no compile this code
+#ifdef MOZ_RENDERTRACE
+
+
+namespace mozilla {
+namespace layers {
+
+static int colorId = 0;
+
+// This should be done in the printf but android's printf is buggy
+const char* colors[] = {
+    "00", "01", "02", "03", "04", "05", "06", "07", "08", "09",
+    "10", "11", "12", "13", "14", "15", "16", "17", "18", "19"
+    };
+
+const char* layerName[] = {
+    "CANVAS", "COLOR", "CONTAINER", "IMAGE", "READBACK", "SHADOW", "THEBES"
+    };
+
+static gfx3DMatrix GetRootTransform(Layer *aLayer) {
+  if (aLayer->GetParent() != NULL)
+    return GetRootTransform(aLayer->GetParent()) * aLayer->GetTransform();
+  return aLayer->GetTransform();
+}
+
+void RenderTraceLayers(Layer *aLayer, const char *aColor, gfx3DMatrix aRootTransform, bool aReset) {
+  if (!aLayer)
+    return;
+
+  gfx3DMatrix trans = aRootTransform * aLayer->GetTransform();
+  nsIntRect clipRect = aLayer->GetEffectiveVisibleRegion().GetBounds();
+  clipRect.MoveBy((int)trans.ProjectTo2D()[3][0], (int)trans.ProjectTo2D()[3][1]);
+
+  printf_stderr("%s RENDERTRACE %u rect #%s%s %i %i %i %i\n",
+    layerName[aLayer->GetType()], (int)PR_IntervalNow(),
+    colors[colorId%19], aColor,
+    clipRect.x, clipRect.y, clipRect.width, clipRect.height);
+
+  colorId++;
+
+  for (Layer* child = aLayer->GetFirstChild();
+        child; child = child->GetNextSibling()) {
+    RenderTraceLayers(child, aColor, aRootTransform, false);
+  }
+
+  if (aReset) colorId = 0;
+}
+
+void RenderTraceInvalidateStart(Layer *aLayer, const char *aColor, nsIntRect aRect) {
+  gfx3DMatrix trans = GetRootTransform(aLayer);
+  aRect.MoveBy((int)trans.ProjectTo2D()[3][0], (int)trans.ProjectTo2D()[3][1]);
+
+  printf_stderr("%s RENDERTRACE %u fillrect #%s%s %i %i %i %i\n",
+    layerName[aLayer->GetType()], (int)PR_IntervalNow(),
+    "FF", aColor,
+    aRect.x, aRect.y, aRect.width, aRect.height);
+}
+void RenderTraceInvalidateEnd(Layer *aLayer, const char *aColor) {
+  // Clear with an empty rect
+  RenderTraceInvalidateStart(aLayer, aColor, nsIntRect());
+}
+
+}
+}
+
+#endif
+
new file mode 100644
--- /dev/null
+++ b/gfx/layers/RenderTrace.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * ***** 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 Corporation code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Benoit Girard <bgirard@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// This is a general tool that will let you visualize platform operation.
+// Currently used for the layer system, the general syntax allows this
+// tools to be adapted to trace other operations.
+//
+// For the front end see: https://github.com/staktrace/rendertrace
+
+// Uncomment this line to enable RENDERTRACE
+//#define MOZ_RENDERTRACE
+
+#ifndef GFX_RENDERTRACE_H
+#define GFX_RENDERTRACE_H
+
+#include "gfx3DMatrix.h"
+#include "nsRect.h"
+
+namespace mozilla {
+namespace layers {
+
+class Layer;
+
+void RenderTraceLayers(Layer *aLayer, const char *aColor, gfx3DMatrix aRootTransform = gfx3DMatrix(), bool aReset = true);
+
+void RenderTraceInvalidateStart(Layer *aLayer, const char *aColor, nsIntRect aRect);
+void RenderTraceInvalidateEnd(Layer *aLayer, const char *aColor);
+
+#ifndef MOZ_RENDERTRACE
+inline void RenderTraceLayers(Layer *aLayer, const char *aColor, gfx3DMatrix aRootTransform, bool aReset)
+{}
+
+inline void RenderTraceInvalidateStart(Layer *aLayer, const char *aColor, nsIntRect aRect)
+{}
+
+inline void RenderTraceInvalidateEnd(Layer *aLayer, const char *aColor)
+{}
+
+#endif // MOZ_RENDERTRACE
+
+}
+}
+
+#endif //GFX_RENDERTRACE_H
--- a/gfx/layers/basic/BasicLayers.cpp
+++ b/gfx/layers/basic/BasicLayers.cpp
@@ -44,16 +44,17 @@
 #include "mozilla/layers/PLayersChild.h"
 #include "mozilla/layers/PLayersParent.h"
 #include "mozilla/gfx/2D.h"
 
 #include "ipc/ShadowLayerChild.h"
 
 #include "BasicLayers.h"
 #include "ImageLayers.h"
+#include "RenderTrace.h"
 
 #include "prprf.h"
 #include "nsTArray.h"
 #include "nsGUIEvent.h"
 #include "gfxContext.h"
 #include "gfxImageSurface.h"
 #include "gfxPattern.h"
 #include "gfxPlatform.h"
@@ -683,16 +684,19 @@ BasicThebesLayer::PaintThebes(gfxContext
        (mContentFlags & CONTENT_COMPONENT_ALPHA) &&
        !MustRetainContent())) {
     NS_ASSERTION(readbackUpdates.IsEmpty(), "Can't do readback for non-retained layer");
 
     mValidRegion.SetEmpty();
     mBuffer.Clear();
 
     nsIntRegion toDraw = IntersectWithClip(GetEffectiveVisibleRegion(), aContext);
+
+    RenderTraceInvalidateStart(this, "FF00", toDraw.GetBounds());
+
     if (!toDraw.IsEmpty() && !IsHidden()) {
       if (!aCallback) {
         BasicManager()->SetTransactionIncomplete();
         return;
       }
 
       aContext->Save();
 
@@ -718,16 +722,18 @@ BasicThebesLayer::PaintThebes(gfxContext
           gfxUtils::ClipToRegion(aContext, toDraw);
         }
         AutoSetOperator setOperator(aContext, GetOperator());
         aContext->Paint(opacity);
       }
 
       aContext->Restore();
     }
+
+    RenderTraceInvalidateEnd(this, "FF00");
     return;
   }
 
   {
     PRUint32 flags = 0;
 #ifndef MOZ_GFX_OPTIMIZE_MOBILE
     gfxMatrix transform;
     if (!GetEffectiveTransform().CanDraw2D(&transform) ||
@@ -743,21 +749,26 @@ BasicThebesLayer::PaintThebes(gfxContext
       // The area that became invalid and is visible needs to be repainted
       // (this could be the whole visible area if our buffer switched
       // from RGB to RGBA, because we might need to repaint with
       // subpixel AA)
       state.mRegionToInvalidate.And(state.mRegionToInvalidate,
                                     GetEffectiveVisibleRegion());
       nsIntRegion extendedDrawRegion = state.mRegionToDraw;
       SetAntialiasingFlags(this, state.mContext);
+
+      RenderTraceInvalidateStart(this, "FF00", state.mRegionToDraw.GetBounds());
+
       PaintBuffer(state.mContext,
                   state.mRegionToDraw, extendedDrawRegion, state.mRegionToInvalidate,
                   state.mDidSelfCopy,
                   aCallback, aCallbackData);
       Mutated();
+
+      RenderTraceInvalidateEnd(this, "FF00");
     } else {
       // It's possible that state.mRegionToInvalidate is nonempty here,
       // if we are shrinking the valid region to nothing.
       NS_ASSERTION(state.mRegionToDraw.IsEmpty(),
                    "If we need to draw, we should have a context");
     }
   }
 
@@ -1593,16 +1604,19 @@ BasicLayerManager::EndTransactionInterna
   Log();
 #endif
 
   NS_ASSERTION(InConstruction(), "Should be in construction phase");
 #ifdef DEBUG
   mPhase = PHASE_DRAWING;
 #endif
 
+  Layer* aLayer = GetRoot();
+  RenderTraceLayers(aLayer, "FF00");
+
   mTransactionIncomplete = false;
 
   if (mTarget && mRoot && !(aFlags & END_NO_IMMEDIATE_REDRAW)) {
     nsIntRect clipRect;
     if (HasShadowManager()) {
       // If this has a shadow manager, the clip extents of mTarget are meaningless.
       // So instead just use the root layer's visible region bounds.
       const nsIntRect& bounds = mRoot->GetVisibleRegion().GetBounds();
@@ -1813,16 +1827,18 @@ Transform3D(gfxASurface* aSource, gfxCon
   }
 
   // If we haven't actually drawn to aDest then return our temporary image so that
   // the caller can do this.
   aDrawOffset = destRect.TopLeft();
   return destImage.forget(); 
 }
 
+
+
 void
 BasicLayerManager::PaintLayer(gfxContext* aTarget,
                               Layer* aLayer,
                               DrawThebesLayerCallback aCallback,
                               void* aCallbackData,
                               ReadbackProcessor* aReadback)
 {
   const nsIntRect* clipRect = aLayer->GetEffectiveClipRect();
--- a/gfx/layers/ipc/CompositorChild.cpp
+++ b/gfx/layers/ipc/CompositorChild.cpp
@@ -67,16 +67,17 @@ CompositorChild::Destroy()
   NS_ABORT_IF_FALSE(0 == numChildren || 1 == numChildren,
                     "compositor must only have 0 or 1 layer forwarder");
 
   if (numChildren) {
     ShadowLayersChild* layers =
       static_cast<ShadowLayersChild*>(ManagedPLayersChild()[0]);
     layers->Destroy();
   }
+  printf_stderr("Destroy compositor\n");
   SendStop();
 }
 
 PLayersChild*
 CompositorChild::AllocPLayers(const LayersBackend &backend)
 {
   return new ShadowLayersChild();
 }
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -37,22 +37,31 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "CompositorParent.h"
 #include "ShadowLayersParent.h"
 #include "LayerManagerOGL.h"
 #include "nsIWidget.h"
+#include "nsGkAtoms.h"
+#include "RenderTrace.h"
+
+#if defined(MOZ_WIDGET_ANDROID)
+#include "AndroidBridge.h"
+#include <android/log.h>
+#endif
 
 namespace mozilla {
 namespace layers {
 
 CompositorParent::CompositorParent(nsIWidget* aWidget)
-  : mStopped(false), mWidget(aWidget)
+  : mWidget(aWidget)
+  , mCurrentCompositeTask(NULL)
+  , mPaused(false)
 {
   MOZ_COUNT_CTOR(CompositorParent);
 }
 
 CompositorParent::~CompositorParent()
 {
   MOZ_COUNT_DTOR(CompositorParent);
 }
@@ -65,38 +74,166 @@ CompositorParent::Destroy()
 
   // Ensure that the layer manager is destroyed on the compositor thread.
   mLayerManager = NULL;
 }
 
 bool
 CompositorParent::RecvStop()
 {
-  mStopped = true;
+  printf_stderr("Stop composition\n");
+  mPaused = true;
   Destroy();
   return true;
 }
 
 void
+CompositorParent::ScheduleRenderOnCompositorThread(::base::Thread &aCompositorThread)
+{
+  CancelableTask *renderTask = NewRunnableMethod(this, &CompositorParent::AsyncRender);
+  aCompositorThread.message_loop()->PostTask(FROM_HERE, renderTask);
+}
+
+void
+CompositorParent::PauseComposition()
+{
+  printf_stderr("Pause composition\n");
+  if (!mPaused) {
+    mPaused = true;
+
+#ifdef MOZ_WIDGET_ANDROID
+    static_cast<LayerManagerOGL*>(mLayerManager.get())->gl()->ReleaseSurface();
+#endif
+  }
+}
+
+void
+CompositorParent::ResumeComposition()
+{
+  mPaused = false;
+
+#ifdef MOZ_WIDGET_ANDROID
+  static_cast<LayerManagerOGL*>(mLayerManager.get())->gl()->RenewSurface();
+#endif
+}
+
+void
+CompositorParent::SchedulePauseOnCompositorThread(::base::Thread &aCompositorThread)
+{
+  CancelableTask *pauseTask = NewRunnableMethod(this,
+                                                &CompositorParent::PauseComposition);
+  aCompositorThread.message_loop()->PostTask(FROM_HERE, pauseTask);
+}
+
+void
+CompositorParent::ScheduleResumeOnCompositorThread(::base::Thread &aCompositorThread)
+{
+  CancelableTask *resumeTask = NewRunnableMethod(this,
+                                                 &CompositorParent::ResumeComposition);
+  aCompositorThread.message_loop()->PostTask(FROM_HERE, resumeTask);
+}
+
+void
 CompositorParent::ScheduleComposition()
 {
-  CancelableTask *composeTask = NewRunnableMethod(this, &CompositorParent::Composite);
-  MessageLoop::current()->PostTask(FROM_HERE, composeTask);
+  if (mCurrentCompositeTask) {
+    return;
+  }
+
+  bool initialComposition = mLastCompose.IsNull();
+  TimeDuration delta;
+  if (!initialComposition)
+    delta = mozilla::TimeStamp::Now() - mLastCompose;
+
+#ifdef COMPOSITOR_PERFORMANCE_WARNING
+  mExpectedComposeTime = mozilla::TimeStamp::Now() + TimeDuration::FromMilliseconds(15);
+#endif
+
+  printf_stderr("Schedule composition\n");
+  mCurrentCompositeTask = NewRunnableMethod(this, &CompositorParent::Composite);
+  if (!initialComposition && delta.ToMilliseconds() < 15) {
+#ifdef COMPOSITOR_PERFORMANCE_WARNING
+    mExpectedComposeTime = mozilla::TimeStamp::Now() + TimeDuration::FromMilliseconds(15 - delta.ToMilliseconds());
+#endif
+    MessageLoop::current()->PostDelayedTask(FROM_HERE, mCurrentCompositeTask, 15 - delta.ToMilliseconds());
+  } else {
+    MessageLoop::current()->PostTask(FROM_HERE, mCurrentCompositeTask);
+  }
+}
+
+void
+CompositorParent::SetTransformation(float aScale, nsIntPoint aScrollOffset)
+{
+  mXScale = aScale;
+  mYScale = aScale;
+  mScrollOffset = aScrollOffset;
 }
 
 void
 CompositorParent::Composite()
 {
-  if (mStopped || !mLayerManager) {
+  mCurrentCompositeTask = NULL;
+
+  mLastCompose = mozilla::TimeStamp::Now();
+
+  if (mPaused || !mLayerManager) {
     return;
   }
 
+#ifdef MOZ_WIDGET_ANDROID
+  RequestViewTransform();
+  printf_stderr("Correcting for position fixed %i, %i\n", -mScrollOffset.x, -mScrollOffset.y);
+  TransformShadowTree();
+#endif
+
+  Layer* aLayer = mLayerManager->GetRoot();
+  mozilla::layers::RenderTraceLayers(aLayer, "0000");
+
   mLayerManager->EndEmptyTransaction();
+
+#ifdef COMPOSITOR_PERFORMANCE_WARNING
+  if (mExpectedComposeTime + TimeDuration::FromMilliseconds(15) < mozilla::TimeStamp::Now()) {
+    printf_stderr("Compositor: Compose frame took %i time then expected.\n",
+                  (int)(mozilla::TimeStamp::Now() - mExpectedComposeTime).ToMilliseconds());
+  }
+#endif
 }
 
+#ifdef MOZ_WIDGET_ANDROID
+// Do a breadth-first search to find the first layer in the tree that is
+// scrollable.
+Layer*
+CompositorParent::GetPrimaryScrollableLayer()
+{
+  Layer* root = mLayerManager->GetRoot();
+
+  nsTArray<Layer*> queue;
+  queue.AppendElement(root);
+  for (unsigned i = 0; i < queue.Length(); i++) {
+    ContainerLayer* containerLayer = queue[i]->AsContainerLayer();
+    if (!containerLayer) {
+      continue;
+    }
+
+    const FrameMetrics& frameMetrics = containerLayer->GetFrameMetrics();
+    if (frameMetrics.IsScrollable()) {
+      return containerLayer;
+    }
+
+    Layer* child = containerLayer->GetFirstChild();
+    while (child) {
+      queue.AppendElement(child);
+      child = child->GetNextSibling();
+    }
+  }
+
+  return root;
+}
+#endif
+
 // Go down shadow layer tree, setting properties to match their non-shadow
 // counterparts.
 static void
 SetShadowProperties(Layer* aLayer)
 {
   // FIXME: Bug 717688 -- Do these updates in ShadowLayersParent::RecvUpdate.
   ShadowLayer* shadow = aLayer->AsShadowLayer();
   shadow->SetShadowTransform(aLayer->GetTransform());
@@ -104,19 +241,95 @@ SetShadowProperties(Layer* aLayer)
   shadow->SetShadowClipRect(aLayer->GetClipRect());
 
   for (Layer* child = aLayer->GetFirstChild();
       child; child = child->GetNextSibling()) {
     SetShadowProperties(child);
   }
 }
 
+static double GetXScale(const gfx3DMatrix& aTransform)
+{
+  return aTransform._11;
+}
+
+static double GetYScale(const gfx3DMatrix& aTransform)
+{
+  return aTransform._22;
+}
+
+void
+CompositorParent::TransformShadowTree()
+{
+#ifdef MOZ_WIDGET_ANDROID
+  Layer* layer = GetPrimaryScrollableLayer();
+  ShadowLayer* shadow = layer->AsShadowLayer();
+
+  gfx3DMatrix shadowTransform = layer->GetTransform();
+
+  ContainerLayer* container = layer->AsContainerLayer();
+
+  const FrameMetrics* metrics = &container->GetFrameMetrics();
+  const gfx3DMatrix& currentTransform = layer->GetTransform();
+
+  if (metrics && metrics->IsScrollable()) {
+    float tempScaleDiffX = GetXScale(mLayerManager->GetRoot()->GetTransform()) * mXScale;
+    float tempScaleDiffY = GetYScale(mLayerManager->GetRoot()->GetTransform()) * mYScale;
+
+    nsIntPoint metricsScrollOffset = metrics->mViewportScrollOffset;
+
+    nsIntPoint scrollCompensation(
+      (mScrollOffset.x / tempScaleDiffX - metricsScrollOffset.x) * mXScale,
+      (mScrollOffset.y / tempScaleDiffY - metricsScrollOffset.y) * mYScale);
+    ViewTransform treeTransform(-scrollCompensation, mXScale,
+                              mYScale);
+    shadowTransform = gfx3DMatrix(treeTransform) * currentTransform;
+
+    shadow->SetShadowTransform(shadowTransform);
+  } else {
+    ViewTransform treeTransform(nsIntPoint(0,0), mXScale,
+                              mYScale);
+    shadowTransform = gfx3DMatrix(treeTransform) * currentTransform;
+
+    shadow->SetShadowTransform(shadowTransform);
+  }
+#endif
+}
+
+void
+CompositorParent::AsyncRender()
+{
+  if (mPaused || !mLayerManager) {
+    return;
+  }
+
+  Layer* root = mLayerManager->GetRoot();
+  if (!root) {
+    return;
+  }
+
+  ScheduleComposition();
+}
+
+#ifdef MOZ_WIDGET_ANDROID
+void
+CompositorParent::RequestViewTransform()
+{
+  mozilla::AndroidBridge::Bridge()->GetViewTransform(mScrollOffset, mXScale, mYScale);
+
+  __android_log_print(ANDROID_LOG_ERROR, "Gecko", "### mScrollOffset=%g %g "
+                      "mXScale=%g mYScale=%g", (float)mScrollOffset.x, (float)mScrollOffset.y,
+                      (float)mXScale, (float)mYScale);
+}
+#endif
+
 void
 CompositorParent::ShadowLayersUpdated()
 {
+  printf_stderr("ShadowLayersUpdated\n");
   const nsTArray<PLayersParent*>& shadowParents = ManagedPLayersParent();
   NS_ABORT_IF_FALSE(shadowParents.Length() <= 1,
                     "can only support at most 1 ShadowLayersParent");
   if (shadowParents.Length()) {
     Layer* root = static_cast<ShadowLayersParent*>(shadowParents[0])->GetRoot();
     mLayerManager->SetRoot(root);
     SetShadowProperties(root);
   }
--- a/gfx/layers/ipc/CompositorParent.h
+++ b/gfx/layers/ipc/CompositorParent.h
@@ -36,53 +36,118 @@
  * 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 ***** */
 
 #ifndef mozilla_layers_CompositorParent_h
 #define mozilla_layers_CompositorParent_h
 
+// Enable this pref to turn on compositor performance warning.
+// This will print warnings if the compositor isn't meeting
+// it's responsiveness objectives:
+//    1) Compose a frame within 15ms of receiving a ScheduleCompositeCall
+//    2) Unless a frame was composited within the throttle threshold in
+//       which the deadline will be 15ms + throttle threshold
+#define COMPOSITOR_PERFORMANCE_WARNING
+
 #include "mozilla/layers/PCompositorParent.h"
 #include "mozilla/layers/PLayersParent.h"
+#include "base/thread.h"
 #include "ShadowLayersManager.h"
 
 class nsIWidget;
 
 namespace mozilla {
 namespace layers {
 
 class LayerManager;
 
+// Represents (affine) transforms that are calculated from a content view.
+struct ViewTransform {
+  ViewTransform(nsIntPoint aTranslation = nsIntPoint(0, 0), float aXScale = 1, float aYScale = 1)
+    : mTranslation(aTranslation)
+    , mXScale(aXScale)
+    , mYScale(aYScale)
+  {}
+
+  operator gfx3DMatrix() const
+  {
+    return
+      gfx3DMatrix::ScalingMatrix(mXScale, mYScale, 1) *
+      gfx3DMatrix::Translation(mTranslation.x, mTranslation.y, 0);
+  }
+
+  nsIntPoint mTranslation;
+  float mXScale;
+  float mYScale;
+};
+
 class CompositorParent : public PCompositorParent,
                          public ShadowLayersManager
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorParent)
 public:
   CompositorParent(nsIWidget* aWidget);
   virtual ~CompositorParent();
 
   virtual bool RecvStop() MOZ_OVERRIDE;
 
   virtual void ShadowLayersUpdated() MOZ_OVERRIDE;
   void Destroy();
 
   LayerManager* GetLayerManager() { return mLayerManager; }
 
+  void SetTransformation(float aScale, nsIntPoint aScrollOffset);
+  void AsyncRender();
+
+  // Can be called from any thread
+  void ScheduleRenderOnCompositorThread(::base::Thread &aCompositorThread);
+  void SchedulePauseOnCompositorThread(::base::Thread &aCompositorThread);
+  void ScheduleResumeOnCompositorThread(::base::Thread &aCompositorThread);
+
 protected:
   virtual PLayersParent* AllocPLayers(const LayersBackend &backendType);
   virtual bool DeallocPLayers(PLayersParent* aLayers);
 
 private:
+  void PauseComposition();
+  void ResumeComposition();
+
+  void Composite();
   void ScheduleComposition();
-  void Composite();
+  void TransformShadowTree();
+
+  // Platform specific functions
+#ifdef MOZ_WIDGET_ANDROID
+  /**
+   * Asks Java for the viewport position and updates the world transform
+   * accordingly.
+   */
+  void RequestViewTransform();
+
+  /**
+   * Does a breadth-first search to find the first layer in the tree with a
+   * displayport set.
+   */
+  Layer* GetPrimaryScrollableLayer();
+#endif
 
   nsRefPtr<LayerManager> mLayerManager;
-  bool mStopped;
   nsIWidget* mWidget;
+  CancelableTask *mCurrentCompositeTask;
+  TimeStamp mLastCompose;
+#ifdef COMPOSITOR_PERFORMANCE_WARNING
+  TimeStamp mExpectedComposeTime;
+#endif
+
+  bool mPaused;
+  float mXScale;
+  float mYScale;
+  nsIntPoint mScrollOffset;
 
   DISALLOW_EVIL_CONSTRUCTORS(CompositorParent);
 };
 
 } // layers
 } // mozilla
 
 #endif // mozilla_layers_CompositorParent_h
--- a/gfx/layers/ipc/ShadowLayersParent.cpp
+++ b/gfx/layers/ipc/ShadowLayersParent.cpp
@@ -38,16 +38,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include <vector>
 
 #include "ShadowLayersParent.h"
 #include "ShadowLayerParent.h"
 #include "ShadowLayers.h"
+#include "RenderTrace.h"
 
 #include "mozilla/unused.h"
 
 #include "mozilla/layout/RenderFrameParent.h"
 #include "CompositorParent.h"
 
 #include "gfxSharedImageSurface.h"
 
@@ -144,16 +145,20 @@ ShadowLayersParent::Destroy()
     slp->Destroy();
   }
 }
 
 bool
 ShadowLayersParent::RecvUpdate(const InfallibleTArray<Edit>& cset,
                                InfallibleTArray<EditReply>* reply)
 {
+#ifdef COMPOSITOR_PERFORMANCE_WARNING
+  TimeStamp updateStart = TimeStamp::Now();
+#endif
+
   MOZ_LAYERS_LOG(("[ParentSide] received txn with %d edits", cset.Length()));
 
   if (mDestroyed || layer_manager()->IsDestroyed()) {
     return true;
   }
 
   EditReplyVector replyv;
 
@@ -313,45 +318,52 @@ ShadowLayersParent::RecvUpdate(const Inf
       MOZ_LAYERS_LOG(("[ParentSide] Paint ThebesLayer"));
 
       const OpPaintThebesBuffer& op = edit.get_OpPaintThebesBuffer();
       ShadowLayerParent* shadow = AsShadowLayer(op);
       ShadowThebesLayer* thebes =
         static_cast<ShadowThebesLayer*>(shadow->AsLayer());
       const ThebesBuffer& newFront = op.newFrontBuffer();
 
+      RenderTraceInvalidateStart(thebes, "00FF", op.updatedRegion().GetBounds());
+
       OptionalThebesBuffer newBack;
       nsIntRegion newValidRegion;
       OptionalThebesBuffer readonlyFront;
       nsIntRegion frontUpdatedRegion;
       thebes->Swap(newFront, op.updatedRegion(),
                    &newBack, &newValidRegion,
                    &readonlyFront, &frontUpdatedRegion);
       replyv.push_back(
         OpThebesBufferSwap(
           shadow, NULL,
           newBack, newValidRegion,
           readonlyFront, frontUpdatedRegion));
+
+      RenderTraceInvalidateEnd(thebes, "00FF");
       break;
     }
     case Edit::TOpPaintCanvas: {
       MOZ_LAYERS_LOG(("[ParentSide] Paint CanvasLayer"));
 
       const OpPaintCanvas& op = edit.get_OpPaintCanvas();
       ShadowLayerParent* shadow = AsShadowLayer(op);
       ShadowCanvasLayer* canvas =
         static_cast<ShadowCanvasLayer*>(shadow->AsLayer());
 
+      RenderTraceInvalidateStart(canvas, "00FF", canvas->GetVisibleRegion().GetBounds());
+
       canvas->SetAllocator(this);
       CanvasSurface newBack;
       canvas->Swap(op.newFrontBuffer(), op.needYFlip(), &newBack);
       canvas->Updated();
       replyv.push_back(OpBufferSwap(shadow, NULL,
                                     newBack));
 
+      RenderTraceInvalidateEnd(canvas, "00FF");
       break;
     }
     case Edit::TOpPaintImage: {
       MOZ_LAYERS_LOG(("[ParentSide] Paint ImageLayer"));
 
       const OpPaintImage& op = edit.get_OpPaintImage();
       ShadowLayerParent* shadow = AsShadowLayer(op);
       ShadowImageLayer* image =
@@ -380,16 +392,21 @@ ShadowLayersParent::RecvUpdate(const Inf
 
   // Ensure that any pending operations involving back and front
   // buffers have completed, so that neither process stomps on the
   // other's buffer contents.
   ShadowLayerManager::PlatformSyncBeforeReplyUpdate();
 
   mShadowLayersManager->ShadowLayersUpdated();
 
+#ifdef COMPOSITOR_PERFORMANCE_WARNING
+  printf_stderr("Compositor: Layers update took %i ms (blocking gecko).\n",
+                (int)(mozilla::TimeStamp::Now() - updateStart).ToMilliseconds());
+#endif
+
   return true;
 }
 
 PLayerParent*
 ShadowLayersParent::AllocPLayer()
 {
   return new ShadowLayerParent();
 }
--- a/gfx/layers/opengl/CanvasLayerOGL.cpp
+++ b/gfx/layers/opengl/CanvasLayerOGL.cpp
@@ -278,17 +278,21 @@ CanvasLayerOGL::RenderLayer(int aPreviou
 
   program->Activate();
   program->SetLayerQuadRect(drawRect);
   program->SetLayerTransform(GetEffectiveTransform());
   program->SetLayerOpacity(GetEffectiveOpacity());
   program->SetRenderOffset(aOffset);
   program->SetTextureUnit(0);
 
-  mOGLManager->BindAndDrawQuad(program, mNeedsYFlip ? true : false);
+  if (gl()->CanUploadNonPowerOfTwo()) {
+    mOGLManager->BindAndDrawQuad(program, mNeedsYFlip ? true : false);
+  } else {
+    mOGLManager->BindAndDrawQuadWithTextureRect(program, drawRect, drawRect.Size());
+  }
 
 #if defined(MOZ_WIDGET_GTK2) && !defined(MOZ_PLATFORM_MAEMO)
   if (mPixmap && !mDelayedUpdates) {
     sGLXLibrary.ReleaseTexImage(mPixmap);
   }
 #endif
 
   if (useGLContext) {
@@ -410,20 +414,36 @@ ShadowCanvasLayerOGL::RenderLayer(int aP
 
   program->Activate();
   program->SetLayerTransform(effectiveTransform);
   program->SetLayerOpacity(GetEffectiveOpacity());
   program->SetRenderOffset(aOffset);
   program->SetTextureUnit(0);
 
   mTexImage->BeginTileIteration();
-  do {
-    TextureImage::ScopedBindTextureAndApplyFilter texBind(mTexImage, LOCAL_GL_TEXTURE0);
-    program->SetLayerQuadRect(mTexImage->GetTileRect());
-    mOGLManager->BindAndDrawQuad(program, mNeedsYFlip); // FIXME flip order of tiles?
-  } while (mTexImage->NextTile());
+  if (gl()->CanUploadNonPowerOfTwo()) {
+    do {
+      TextureImage::ScopedBindTextureAndApplyFilter texBind(mTexImage, LOCAL_GL_TEXTURE0);
+      program->SetLayerQuadRect(mTexImage->GetTileRect());
+      mOGLManager->BindAndDrawQuad(program, mNeedsYFlip); // FIXME flip order of tiles?
+    } while (mTexImage->NextTile());
+  } else {
+    do {
+      TextureImage::ScopedBindTextureAndApplyFilter texBind(mTexImage, LOCAL_GL_TEXTURE0);
+      program->SetLayerQuadRect(mTexImage->GetTileRect());
+      // We can't use BindAndDrawQuad because that always uploads the whole texture from 0.0f -> 1.0f
+      // in x and y. We use BindAndDrawQuadWithTextureRect to actually draw a subrect of the texture
+      // We need to reset the origin to 0,0 from the tile rect because the tile originates at 0,0 in the
+      // actual texture, even though its origin in the composed (tiled) texture is not 0,0
+      // FIXME: we need to handle mNeedsYFlip, Bug #728625
+      mOGLManager->BindAndDrawQuadWithTextureRect(program,
+                                                  nsIntRect(0, 0, mTexImage->GetTileRect().width,
+                                                                  mTexImage->GetTileRect().height),
+                                                  mTexImage->GetTileRect().Size());
+    } while (mTexImage->NextTile());
+  }
 }
 
 void
 ShadowCanvasLayerOGL::CleanupResources()
 {
   DestroyFrontBuffer();
 }
--- a/gfx/layers/opengl/ImageLayerOGL.cpp
+++ b/gfx/layers/opengl/ImageLayerOGL.cpp
@@ -35,23 +35,25 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "gfxSharedImageSurface.h"
 
 #include "ImageLayerOGL.h"
 #include "gfxImageSurface.h"
+#include "gfxUtils.h"
 #include "yuv_convert.h"
 #include "GLContextProvider.h"
 #if defined(MOZ_WIDGET_GTK2) && !defined(MOZ_PLATFORM_MAEMO)
 # include "GLXLibrary.h"
 # include "mozilla/X11Util.h"
 #endif
 
+using namespace mozilla::gfx;
 using namespace mozilla::gl;
 
 namespace mozilla {
 namespace layers {
 
 /**
  * This is an event used to unref a GLContext on the main thread and
  * optionally delete a texture associated with that context.
@@ -328,18 +330,19 @@ ImageLayerOGL::RenderLayer(int,
     program->SetLayerOpacity(GetEffectiveOpacity());
     program->SetRenderOffset(aOffset);
     program->SetTextureUnit(0);
 
     nsIntRect rect = GetVisibleRegion().GetBounds();
 
     bool tileIsWholeImage = (mTileSourceRect == nsIntRect(0, 0, iwidth, iheight)) 
                             || !mUseTileSourceRect;
-    bool imageIsPowerOfTwo = ((iwidth  & (iwidth - 1)) == 0 &&
-                              (iheight & (iheight - 1)) == 0);
+    bool imageIsPowerOfTwo = IsPowerOfTwo(iwidth) &&
+                             IsPowerOfTwo(iheight);
+
     bool canDoNPOT = (
           gl()->IsExtensionSupported(GLContext::ARB_texture_non_power_of_two) ||
           gl()->IsExtensionSupported(GLContext::OES_texture_npot));
 
     GLContext::RectTriangles triangleBuffer;
     // do GL_REPEAT if we can - should be the fastest option.
     // draw a single rect for the whole region, a little overdraw
     // on the gpu should be faster than tesselating
@@ -771,21 +774,36 @@ ShadowImageLayerOGL::RenderLayer(int aPr
     colorProgram->Activate();
     colorProgram->SetTextureUnit(0);
     colorProgram->SetLayerTransform(GetEffectiveTransform());
     colorProgram->SetLayerOpacity(GetEffectiveOpacity());
     colorProgram->SetRenderOffset(aOffset);
 
     mTexImage->SetFilter(mFilter);
     mTexImage->BeginTileIteration();
-    do {
-      TextureImage::ScopedBindTextureAndApplyFilter texBind(mTexImage, LOCAL_GL_TEXTURE0);
-      colorProgram->SetLayerQuadRect(mTexImage->GetTileRect());
-      mOGLManager->BindAndDrawQuad(colorProgram);
-    } while (mTexImage->NextTile());
+
+    if (gl()->CanUploadNonPowerOfTwo()) {
+      do {
+        TextureImage::ScopedBindTextureAndApplyFilter texBind(mTexImage, LOCAL_GL_TEXTURE0);
+        colorProgram->SetLayerQuadRect(mTexImage->GetTileRect());
+        mOGLManager->BindAndDrawQuad(colorProgram);
+      } while (mTexImage->NextTile());
+    } else {
+      do {
+        TextureImage::ScopedBindTextureAndApplyFilter texBind(mTexImage, LOCAL_GL_TEXTURE0);
+        colorProgram->SetLayerQuadRect(mTexImage->GetTileRect());
+        // We can't use BindAndDrawQuad because that always uploads the whole texture from 0.0f -> 1.0f
+        // in x and y. We use BindAndDrawQuadWithTextureRect to actually draw a subrect of the texture
+        mOGLManager->BindAndDrawQuadWithTextureRect(colorProgram,
+                                                    nsIntRect(0, 0, mTexImage->GetTileRect().width,
+                                                                    mTexImage->GetTileRect().height),
+                                                    mTexImage->GetTileRect().Size());
+      } while (mTexImage->NextTile());
+    }
+
   } else {
     gl()->fActiveTexture(LOCAL_GL_TEXTURE0);
     gl()->fBindTexture(LOCAL_GL_TEXTURE_2D, mYUVTexture[0].GetTextureID());
     gl()->ApplyFilterToBoundTexture(mFilter);
     gl()->fActiveTexture(LOCAL_GL_TEXTURE1);
     gl()->fBindTexture(LOCAL_GL_TEXTURE_2D, mYUVTexture[1].GetTextureID());
     gl()->ApplyFilterToBoundTexture(mFilter);
     gl()->fActiveTexture(LOCAL_GL_TEXTURE2);
--- a/gfx/layers/opengl/LayerManagerOGL.cpp
+++ b/gfx/layers/opengl/LayerManagerOGL.cpp
@@ -49,31 +49,37 @@
 #include "ColorLayerOGL.h"
 #include "CanvasLayerOGL.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Preferences.h"
 
 #include "LayerManagerOGLShaders.h"
 
 #include "gfxContext.h"
+#include "gfxUtils.h"
 #include "nsIWidget.h"
 
 #include "GLContext.h"
 #include "GLContextProvider.h"
 
 #include "nsIServiceManager.h"
 #include "nsIConsoleService.h"
 
 #include "gfxCrashReporterUtils.h"
 
 #include "sampler.h"
 
+#ifdef MOZ_WIDGET_ANDROID
+#include <android/log.h>
+#endif
+
 namespace mozilla {
 namespace layers {
 
+using namespace mozilla::gfx;
 using namespace mozilla::gl;
 
 #ifdef CHECK_CURRENT_PROGRAM
 int LayerManagerOGLProgram::sCurrentProgramKey = 0;
 #endif
 
 /**
  * LayerManagerOGL
@@ -518,30 +524,36 @@ LayerManagerOGL::RootLayer() const
 
 bool LayerManagerOGL::sDrawFPS = false;
 
 /* This function tries to stick to portable C89 as much as possible
  * so that it can be easily copied into other applications */
 void
 LayerManagerOGL::FPSState::DrawFPS(GLContext* context, CopyProgram* copyprog)
 {
+  printf_stderr("draw fps\n");
   fcount++;
 
   int rate = 30;
   if (fcount >= rate) {
     TimeStamp now = TimeStamp::Now();
     TimeDuration duration = now - last;
     last = now;
     fps = rate / duration.ToSeconds() + .5;
     fcount = 0;
   }
 
   GLint viewport[4];
   context->fGetIntegerv(LOCAL_GL_VIEWPORT, viewport);
 
+#ifdef MOZ_WIDGET_ANDROID
+  __android_log_print(ANDROID_LOG_ERROR, "Gecko", "### Viewport: %d %d %d %d",
+                      viewport[0], viewport[1], viewport[2], viewport[3]);
+#endif
+
   static GLuint texture;
   if (!initialized) {
     // Bind the number of textures we need, in this case one.
     context->fGenTextures(1, &texture);
     context->fBindTexture(LOCAL_GL_TEXTURE_2D, texture);
     context->fTexParameteri(LOCAL_GL_TEXTURE_2D,LOCAL_GL_TEXTURE_MIN_FILTER,LOCAL_GL_NEAREST);
     context->fTexParameteri(LOCAL_GL_TEXTURE_2D,LOCAL_GL_TEXTURE_MAG_FILTER,LOCAL_GL_NEAREST);
 
@@ -678,26 +690,32 @@ LayerManagerOGL::BindAndDrawQuadWithText
   // compute fmod(t, 1.0f) to get the same texture coordinate out.  If
   // the texCoordRect dimension is < 0 or > width/height, then we have
   // wraparound that we need to deal with by drawing multiple quads,
   // because we can't rely on full non-power-of-two texture support
   // (which is required for the REPEAT wrap mode).
 
   GLContext::RectTriangles rects;
 
+  nsIntSize realTexSize = aTexSize;
+  if (!mGLContext->CanUploadNonPowerOfTwo()) {
+    realTexSize = nsIntSize(NextPowerOfTwo(aTexSize.width),
+                            NextPowerOfTwo(aTexSize.height));
+  }
+
   if (aWrapMode == LOCAL_GL_REPEAT) {
     rects.addRect(/* dest rectangle */
                   0.0f, 0.0f, 1.0f, 1.0f,
                   /* tex coords */
-                  aTexCoordRect.x / GLfloat(aTexSize.width),
-                  aTexCoordRect.y / GLfloat(aTexSize.height),
-                  aTexCoordRect.XMost() / GLfloat(aTexSize.width),
-                  aTexCoordRect.YMost() / GLfloat(aTexSize.height));
+                  aTexCoordRect.x / GLfloat(realTexSize.width),
+                  aTexCoordRect.y / GLfloat(realTexSize.height),
+                  aTexCoordRect.XMost() / GLfloat(realTexSize.width),
+                  aTexCoordRect.YMost() / GLfloat(realTexSize.height));
   } else {
-    GLContext::DecomposeIntoNoRepeatTriangles(aTexCoordRect, aTexSize, rects);
+    GLContext::DecomposeIntoNoRepeatTriangles(aTexCoordRect, realTexSize, rects);
   }
 
   mGLContext->fVertexAttribPointer(vertAttribIndex, 2,
                                    LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0,
                                    rects.vertexPointer());
 
   mGLContext->fVertexAttribPointer(texCoordAttribIndex, 2,
                                    LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0,
@@ -732,16 +750,18 @@ LayerManagerOGL::Render()
   GLint width = rect.width;
   GLint height = rect.height;
 
   // We can't draw anything to something with no area
   // so just return
   if (width == 0 || height == 0)
     return;
 
+  printf_stderr("render %i, %i\n", width, height);
+
   // If the widget size changed, we have to force a MakeCurrent
   // to make sure that GL sees the updated widget size.
   if (mWidgetSize.width != width ||
       mWidgetSize.height != height)
   {
     MakeCurrent(true);
 
     mWidgetSize.width = width;
@@ -768,20 +788,24 @@ LayerManagerOGL::Render()
     mGLContext->fScissor(0, 0, width, height);
   }
 
   mGLContext->fEnable(LOCAL_GL_SCISSOR_TEST);
 
   mGLContext->fClearColor(0.0, 0.0, 0.0, 0.0);
   mGLContext->fClear(LOCAL_GL_COLOR_BUFFER_BIT | LOCAL_GL_DEPTH_BUFFER_BIT);
 
+  // Allow widget to render a custom background.
+  mWidget->DrawWindowUnderlay(this, rect);
+
   // Render our layers.
   RootLayer()->RenderLayer(mGLContext->IsDoubleBuffered() ? 0 : mBackBufferFBO,
                            nsIntPoint(0, 0));
 
+  // Allow widget to render a custom foreground too.
   mWidget->DrawWindowOverlay(this, rect);
 
   if (mTarget) {
     CopyToTarget();
     mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
     return;
   }
 
--- a/gfx/layers/opengl/LayerManagerOGL.h
+++ b/gfx/layers/opengl/LayerManagerOGL.h
@@ -382,17 +382,17 @@ public:
     DontApplyWorldTransform
   };
 
   /**
    * Setup the viewport and projection matrix for rendering
    * to a window of the given dimensions.
    */
   void SetupPipeline(int aWidth, int aHeight, WorldTransforPolicy aTransformPolicy);
-  
+
   /**
    * Setup World transform matrix.
    * Transform will be ignored if it is not PreservesAxisAlignedRectangles
    * or has non integer scale
    */
   void SetWorldTransform(const gfxMatrix& aMatrix);
   gfxMatrix& GetWorldTransform(void);
   void WorldTransformRect(nsIntRect& aRect);
--- a/gfx/layers/opengl/ThebesLayerOGL.cpp
+++ b/gfx/layers/opengl/ThebesLayerOGL.cpp
@@ -946,16 +946,17 @@ ShadowThebesLayerOGL::~ShadowThebesLayer
 void
 ShadowThebesLayerOGL::Swap(const ThebesBuffer& aNewFront,
                            const nsIntRegion& aUpdatedRegion,
                            OptionalThebesBuffer* aNewBack,
                            nsIntRegion* aNewBackValidRegion,
                            OptionalThebesBuffer* aReadOnlyFront,
                            nsIntRegion* aFrontUpdatedRegion)
 {
+  printf_stderr("Thebes Swap\n");
   if (!mDestroyed) {
     if (!mBuffer) {
       mBuffer = new ShadowBufferOGL(this);
     }
     nsRefPtr<gfxASurface> surf = ShadowLayerForwarder::OpenDescriptor(aNewFront.buffer());
     mBuffer->Upload(surf, aUpdatedRegion, aNewFront.rect(), aNewFront.rotation());
   }
 
--- a/gfx/thebes/gfxUtils.h
+++ b/gfx/thebes/gfxUtils.h
@@ -165,9 +165,76 @@ public:
 
     /**
      * Copy a PNG encoded Data URL to the clipboard.
      */
     static void CopyAsDataURL(mozilla::gfx::DrawTarget* aDT);
 #endif
 };
 
+namespace mozilla {
+namespace gfx {
+
+/*
+ * Copyright 2008 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+// Some helper functions for power-of-two arithmetic
+// from Skia
+#if defined(__arm__)
+    #define CountLeadingZeroes(x) __builtin_clz(x)
+#else
+
+#define sub_shift(zeros, x, n)  \
+    zeros -= n;                 \
+    x >>= n
+
+static inline int CountLeadingZeroes(uint32_t aNumber)
+{
+    if (aNumber == 0) {
+        return 32;
+    }
+    int zeros = 31;
+    if (aNumber & 0xFFFF0000) {
+        sub_shift(zeros, aNumber, 16);
+    }
+    if (aNumber & 0xFF00) {
+        sub_shift(zeros, aNumber, 8);
+    }
+    if (aNumber & 0xF0) {
+        sub_shift(zeros, aNumber, 4);
+    }
+    if (aNumber & 0xC) {
+        sub_shift(zeros, aNumber, 2);
+    }
+    if (aNumber & 0x2) {
+        sub_shift(zeros, aNumber, 1);
+    }
+    return zeros;
+}
 #endif
+
+/**
+ * Returns true if |aNumber| is a power of two
+ */
+static inline bool
+IsPowerOfTwo(int aNumber)
+{
+    return (aNumber & (aNumber - 1)) == 0;
+}
+
+/**
+ * Returns the first integer greater than |aNumber| which is a power of two
+ * Undefined for |aNumber| < 0
+ */
+static inline int
+NextPowerOfTwo(int aNumber)
+{
+    return 1 << (32 - CountLeadingZeroes(aNumber - 1));
+}
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -866,27 +866,30 @@ ContainerState::CreateOrRecycleThebesLay
   gfxPoint scaledOffset(
       NSAppUnitsToDoublePixels(offset.x, appUnitsPerDevPixel)*mParameters.mXScale,
       NSAppUnitsToDoublePixels(offset.y, appUnitsPerDevPixel)*mParameters.mYScale);
   nsIntPoint pixOffset(NSToIntRoundUp(scaledOffset.x), NSToIntRoundUp(scaledOffset.y));
   gfxMatrix matrix;
   matrix.Translate(gfxPoint(pixOffset.x, pixOffset.y));
   layer->SetTransform(gfx3DMatrix::From2D(matrix));
 
+   // FIXME: Temporary workaround for bug 681192 and bug 724786. Uncomment this code before review!
+#ifndef MOZ_JAVA_COMPOSITOR
   // Calculate exact position of the top-left of the active scrolled root.
   // This might not be 0,0 due to the snapping in ScaleToNearestPixels.
   gfxPoint activeScrolledRootTopLeft = scaledOffset - matrix.GetTranslation();
   // If it has changed, then we need to invalidate the entire layer since the
   // pixels in the layer buffer have the content at a (subpixel) offset
   // from what we need.
   if (activeScrolledRootTopLeft != data->mActiveScrolledRootPosition) {
     data->mActiveScrolledRootPosition = activeScrolledRootTopLeft;
     nsIntRect invalidate = layer->GetValidRegion().GetBounds();
     layer->InvalidateRegion(invalidate);
   }
+#endif
 
   return layer.forget();
 }
 
 /**
  * Returns the appunits per dev pixel for the item's frame. The item must
  * have a frame because only nsDisplayClip items don't have a frame,
  * and those items are flattened away by ProcessDisplayItems.
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -2062,26 +2062,28 @@ nsGfxScrollFrameInner::BuildDisplayList(
   bool usingDisplayport =
     nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &dirtyRect);
 
   nsDisplayListCollection set;
   rv = mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, dirtyRect, set);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Since making new layers is expensive, only use nsDisplayScrollLayer
-  // if the area is scrollable.
+  // if the area is scrollable and there's a displayport (or we're the content
+  // process).
   nsRect scrollRange = GetScrollRange();
   ScrollbarStyles styles = GetScrollbarStylesFromFrame();
   mShouldBuildLayer =
-     (XRE_GetProcessType() == GeckoProcessType_Content &&
+     ((XRE_GetProcessType() == GeckoProcessType_Content || usingDisplayport) &&
      (styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN ||
       styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN) &&
      (scrollRange.width > 0 ||
-      scrollRange.height > 0) &&
-     (!mIsRoot || !mOuter->PresContext()->IsRootContentDocument()));
+      scrollRange.height > 0 || usingDisplayport) &&
+     (usingDisplayport || !mIsRoot ||
+      !mOuter->PresContext()->IsRootContentDocument()));
 
   if (ShouldBuildLayer()) {
     // ScrollLayerWrapper must always be created because it initializes the
     // scroll layer count. The display lists depend on this.
     ScrollLayerWrapper wrapper(mOuter, mScrolledFrame);
 
     if (usingDisplayport) {
       // Once a displayport is set, assume that scrolling needs to be fast
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -588,16 +588,19 @@ pref("ui.dragThresholdY", 25);
 #if MOZ_PLATFORM_MAEMO == 6
 pref("layers.acceleration.disabled", false);
 #elifdef ANDROID
 pref("layers.acceleration.disabled", false);
 #else
 pref("layers.acceleration.disabled", true);
 #endif
 
+pref("layers.offmainthreadcomposition.enabled", true);
+pref("layers.acceleration.draw-fps", true);
+
 pref("notification.feature.enabled", true);
 
 // prevent tooltips from showing up
 pref("browser.chrome.toolbar_tips", false);
 pref("indexedDB.feature.enabled", true);
 pref("dom.indexedDB.warningQuota", 5);
 
 // prevent video elements from preloading too much data
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -37,16 +37,19 @@
 
     <uses-feature android:name="android.hardware.location" android:required="false"/>
     <uses-feature android:name="android.hardware.location.gps" android:required="false"/>
     <uses-feature android:name="android.hardware.touchscreen"/>
 
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-feature android:name="android.hardware.camera" android:required="false"/>
     <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
+
+    <!-- App requires OpenGL ES 2.0 -->
+    <uses-feature android:glEsVersion="0x00020000" android:required="true" />
  
     <application android:label="@MOZ_APP_DISPLAYNAME@"
 		 android:icon="@drawable/icon"
 #if MOZILLA_OFFICIAL
 		 android:debuggable="false">
 #else
 		 android:debuggable="true">
 #endif
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -37,17 +37,18 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.gfx.FloatSize;
-import org.mozilla.gecko.gfx.GeckoSoftwareLayerClient;
+import org.mozilla.gecko.gfx.GeckoGLLayerClient;
+import org.mozilla.gecko.gfx.GeckoLayerClient;
 import org.mozilla.gecko.gfx.IntSize;
 import org.mozilla.gecko.gfx.Layer;
 import org.mozilla.gecko.gfx.LayerController;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.PlaceholderLayerClient;
 import org.mozilla.gecko.gfx.RectUtils;
 import org.mozilla.gecko.gfx.SurfaceTextureLayer;
 import org.mozilla.gecko.gfx.ViewportMetrics;
@@ -138,17 +139,17 @@ abstract public class GeckoApp
     public static DoorHangerPopup mDoorHangerPopup;
     public static AutoCompletePopup mAutoCompletePopup;
     public Favicons mFavicons;
 
     private Geocoder mGeocoder;
     private Address  mLastGeoAddress;
     private static LayerController mLayerController;
     private static PlaceholderLayerClient mPlaceholderLayerClient;
-    private static GeckoSoftwareLayerClient mSoftwareLayerClient;
+    private static GeckoLayerClient mLayerClient;
     private AboutHomeContent mAboutHomeContent;
     private static AbsoluteLayout mPluginContainer;
 
     public String mLastTitle;
     public String mLastSnapshotUri;
     public String mLastViewport;
     public byte[] mLastScreen;
     public int mOwnActivityDepth = 0;
@@ -541,20 +542,16 @@ abstract public class GeckoApp
             case R.id.char_encoding:
                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("CharEncoding:Get", null));
                 return true;
             default:
                 return super.onOptionsItemSelected(item);
         }
     }
 
-    public String getLastViewport() {
-        return mLastViewport;
-    }
-
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
         if (mOwnActivityDepth > 0)
             return; // we're showing one of our own activities and likely won't get paged out
 
         if (outState == null)
             outState = new Bundle();
 
@@ -568,31 +565,28 @@ abstract public class GeckoApp
 
     public class SessionSnapshotRunnable implements Runnable {
         Tab mThumbnailTab;
         SessionSnapshotRunnable(Tab thumbnailTab) {
             mThumbnailTab = thumbnailTab;
         }
 
         public void run() {
-            if (mSoftwareLayerClient == null)
+            if (mLayerClient == null) {
                 return;
 
-            synchronized (mSoftwareLayerClient) {
+            synchronized (mLayerClient) {
                 if (!Tabs.getInstance().isSelectedTab(mThumbnailTab))
                     return;
 
-                if (getLayerController().getLayerClient() != mSoftwareLayerClient)
-                    return;
-
                 HistoryEntry lastHistoryEntry = mThumbnailTab.getLastHistoryEntry();
                 if (lastHistoryEntry == null)
                     return;
 
-                ViewportMetrics viewportMetrics = mSoftwareLayerClient.getGeckoViewportMetrics();
+                ViewportMetrics viewportMetrics = mLayerClient.getGeckoViewportMetrics();
                 // If we don't have viewport metrics, the screenshot won't be right so bail
                 if (viewportMetrics == null)
                     return;
                 
                 String viewportJSON = viewportMetrics.toJSON();
                 // If the title, uri and viewport haven't changed, the old screenshot is probably valid
                 if (viewportJSON.equals(mLastViewport) &&
                     mLastTitle.equals(lastHistoryEntry.mTitle) &&
@@ -604,34 +598,33 @@ abstract public class GeckoApp
                 mLastSnapshotUri = lastHistoryEntry.mUri;
                 getAndProcessThumbnailForTab(mThumbnailTab, true);
             }
         }
     }
 
     void getAndProcessThumbnailForTab(final Tab tab, boolean forceBigSceenshot) {
         boolean isSelectedTab = Tabs.getInstance().isSelectedTab(tab);
-        final Bitmap bitmap = isSelectedTab ?
-            mSoftwareLayerClient.getBitmap() : null;
+        final Bitmap bitmap = isSelectedTab ? mLayerClient.getBitmap() : null;
         
         if (bitmap != null) {
             ByteArrayOutputStream bos = new ByteArrayOutputStream();
             bitmap.compress(Bitmap.CompressFormat.PNG, 0, bos);
             processThumbnail(tab, bitmap, bos.toByteArray());
         } else {
             if (!tab.hasLoaded()) {
                 byte[] thumbnail = BrowserDB.getThumbnailForUrl(getContentResolver(), tab.getURL());
                 if (thumbnail != null)
                     processThumbnail(tab, null, thumbnail);
                 return;
             }
 
             mLastScreen = null;
-            int sw = forceBigSceenshot ? mSoftwareLayerClient.getWidth() : tab.getMinScreenshotWidth();
-            int sh = forceBigSceenshot ? mSoftwareLayerClient.getHeight(): tab.getMinScreenshotHeight();
+            int sw = forceBigSceenshot ? mLayerClient.getWidth() : tab.getMinScreenshotWidth();
+            int sh = forceBigSceenshot ? mLayerClient.getHeight(): tab.getMinScreenshotHeight();
             int dw = forceBigSceenshot ? sw : tab.getThumbnailWidth();
             int dh = forceBigSceenshot ? sh : tab.getThumbnailHeight();
             GeckoAppShell.sendEventToGecko(GeckoEvent.createScreenshotEvent(tab.getId(), sw, sh, dw, dh));
         }
     }
     
     void processThumbnail(Tab thumbnailTab, Bitmap bitmap, byte[] compressed) {
         if (Tabs.getInstance().isSelectedTab(thumbnailTab)) {
@@ -1785,31 +1778,34 @@ abstract public class GeckoApp
             cameraView = new SurfaceView(this);
             cameraView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
         }
 
         if (mLayerController == null) {
             /*
              * Create a layer client so that Gecko will have a buffer to draw into, but don't hook
              * it up to the layer controller yet.
+             *
+             * TODO: Switch between software and GL appropriately.
              */
-            mSoftwareLayerClient = new GeckoSoftwareLayerClient(this);
+            Log.e(LOGTAG, "### Creating GeckoGLLayerClient");
+            mLayerClient = new GeckoGLLayerClient(this);
+            Log.e(LOGTAG, "### Done creating GeckoGLLayerClient");
 
             /*
              * Hook a placeholder layer client up to the layer controller so that the user can pan
              * and zoom a cached screenshot of the previous page. This call will return null if
              * there is no cached screenshot; in that case, we have no choice but to display a
              * checkerboard.
              *
              * TODO: Fall back to a built-in screenshot of the Fennec Start page for a nice first-
              * run experience, perhaps?
              */
             mLayerController = new LayerController(this);
-            mPlaceholderLayerClient = PlaceholderLayerClient.createInstance(this);
-            mLayerController.setLayerClient(mPlaceholderLayerClient);
+            mPlaceholderLayerClient = new PlaceholderLayerClient(mLayerController, mLastViewport);
 
             mGeckoLayout.addView(mLayerController.getView(), 0);
         }
 
         mPluginContainer = (AbsoluteLayout) findViewById(R.id.plugin_container);
 
         mDoorHangerPopup = new DoorHangerPopup(this);
         mAutoCompletePopup = (AutoCompletePopup) findViewById(R.id.autocomplete_popup);
@@ -2656,17 +2652,17 @@ abstract public class GeckoApp
             args.put("parentId", Tabs.getInstance().getSelectedTab().getId());
         } catch (Exception e) {
             Log.e(LOGTAG, "error building JSON arguments");
         }
         Log.i(LOGTAG, "Sending message to Gecko: " + SystemClock.uptimeMillis() + " - Tab:Add");
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Add", args.toString()));
     }
 
-    public GeckoSoftwareLayerClient getSoftwareLayerClient() { return mSoftwareLayerClient; }
+    public GeckoLayerClient getLayerClient() { return mLayerClient; }
     public LayerController getLayerController() { return mLayerController; }
 
     // accelerometer
     public void onAccuracyChanged(Sensor sensor, int accuracy)
     {
     }
 
     public void onSensorChanged(SensorEvent event)
@@ -2735,17 +2731,17 @@ abstract public class GeckoApp
     }
 
 
     private void connectGeckoLayerClient() {
         if (mPlaceholderLayerClient != null)
             mPlaceholderLayerClient.destroy();
 
         LayerController layerController = getLayerController();
-        layerController.setLayerClient(mSoftwareLayerClient);
+        layerController.setLayerClient(mLayerClient);
     }
 
     public class GeckoAppHandler extends Handler {
         @Override
         public void handleMessage(Message message) {
             Bundle bundle = message.getData();
             if (bundle == null)
                 return;
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -34,17 +34,17 @@
  * 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;
 
 import org.mozilla.gecko.gfx.BitmapUtils;
-import org.mozilla.gecko.gfx.GeckoSoftwareLayerClient;
+import org.mozilla.gecko.gfx.GeckoLayerClient;
 import org.mozilla.gecko.gfx.LayerController;
 import org.mozilla.gecko.gfx.LayerView;
 
 import java.io.*;
 import java.lang.reflect.*;
 import java.nio.*;
 import java.nio.channels.*;
 import java.text.*;
@@ -127,17 +127,17 @@ public class GeckoAppShell
     /* The Android-side API: API methods that Android calls */
 
     // Initialization methods
     public static native void nativeInit();
     public static native void nativeRun(String args);
 
     // helper methods
     //    public static native void setSurfaceView(GeckoSurfaceView sv);
-    public static native void setSoftwareLayerClient(GeckoSoftwareLayerClient client);
+    public static native void setLayerClient(GeckoLayerClient client);
     public static native void putenv(String map);
     public static native void onResume();
     public static native void onLowMemory();
     public static native void callObserver(String observerKey, String topic, String data);
     public static native void removeObserver(String observerKey);
     public static native void loadGeckoLibsNative(String apkName);
     public static native void loadSQLiteLibsNative(String apkName, boolean shouldExtract);
     public static native void onChangeNetworkLinkStatus(String status);
@@ -173,16 +173,19 @@ public class GeckoAppShell
     public static native void notifyNoMessageInList(int aRequestId, long aProcessId);
     public static native void notifyListCreated(int aListId, int aMessageId, String aReceiver, String aSender, String aBody, long aTimestamp, int aRequestId, long aProcessId);
     public static native void notifyGotNextMessage(int aMessageId, String aReceiver, String aSender, String aBody, long aTimestamp, int aRequestId, long aProcessId);
     public static native void notifyReadingMessageListFailed(int aError, int aRequestId, long aProcessId);
 
     public static native ByteBuffer allocateDirectBuffer(long size);
     public static native void freeDirectBuffer(ByteBuffer buf);
     public static native void bindWidgetTexture();
+    public static native void scheduleComposite();
+    public static native void schedulePauseComposition();
+    public static native void scheduleResumeComposition();
 
     // A looper thread, accessed by GeckoAppShell.getHandler
     private static class LooperThread extends Thread {
         public SynchronousQueue<Handler> mHandlerQueue =
             new SynchronousQueue<Handler>();
         
         public void run() {
             setName("GeckoLooper Thread");
@@ -471,19 +474,19 @@ public class GeckoAppShell
 
     public static void runGecko(String apkPath, String args, String url, boolean restoreSession) {
         // run gecko -- it will spawn its own thread
         GeckoAppShell.nativeInit();
 
         Log.i(LOGTAG, "post native init");
 
         // Tell Gecko where the target byte buffer is for rendering
-        GeckoAppShell.setSoftwareLayerClient(GeckoApp.mAppContext.getSoftwareLayerClient());
+        GeckoAppShell.setLayerClient(GeckoApp.mAppContext.getLayerClient());
 
-        Log.i(LOGTAG, "setSoftwareLayerClient called");
+        Log.i(LOGTAG, "setLayerClient called");
 
         // First argument is the .apk path
         String combinedArgs = apkPath + " -greomni " + apkPath;
         if (args != null)
             combinedArgs += " " + args;
         if (url != null)
             combinedArgs += " -remote " + url;
         if (restoreSession)
@@ -502,18 +505,17 @@ public class GeckoAppShell
         // and go
         GeckoAppShell.nativeRun(combinedArgs);
     }
 
     // Called on the UI thread after Gecko loads.
     private static void geckoLoaded() {
         final LayerController layerController = GeckoApp.mAppContext.getLayerController();
         LayerView v = layerController.getView();
-        mInputConnection = GeckoInputConnection.create(v);
-        v.setInputConnectionHandler(mInputConnection);
+        mInputConnection = v.setInputConnectionHandler();
 
         layerController.setOnTouchListener(new View.OnTouchListener() {
             public boolean onTouch(View view, MotionEvent event) {
                 if (event == null)
                     return true;
                 GeckoAppShell.sendEventToGecko(GeckoEvent.createMotionEvent(event));
                 return true;
             }
@@ -623,18 +625,16 @@ public class GeckoAppShell
             if (gOrientationSensor != null)
                 sm.unregisterListener(GeckoApp.mAppContext, gOrientationSensor);
         }
     }
 
     public static void enableLocation(final boolean enable) {
         getMainHandler().post(new Runnable() { 
                 public void run() {
-                    LayerView v = GeckoApp.mAppContext.getLayerController().getView();
-
                     LocationManager lm = (LocationManager)
                         GeckoApp.mAppContext.getSystemService(Context.LOCATION_SERVICE);
 
                     if (enable) {
                         Criteria criteria = new Criteria();
                         String provider = lm.getBestProvider(criteria, true);
                         if (provider == null)
                             return;
--- a/mobile/android/base/GeckoEvent.java
+++ b/mobile/android/base/GeckoEvent.java
@@ -57,16 +57,20 @@ import java.lang.System;
 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.
  */
 
 public class GeckoEvent {
+    public interface Callback {
+        public void callback(GeckoEvent event, String jsonData);
+    }
+
     private static final String LOGTAG = "GeckoEvent";
 
     private static final int INVALID = -1;
     private static final int NATIVE_POKE = 0;
     private static final int KEY_EVENT = 1;
     private static final int MOTION_EVENT = 2;
     private static final int ORIENTATION_EVENT = 3;
     private static final int ACCELERATION_EVENT = 4;
@@ -132,16 +136,18 @@ public class GeckoEvent {
     public Location mLocation;
     public Address  mAddress;
 
     public double mBandwidth;
     public boolean mCanBeMetered;
 
     public int mNativeWindow;
 
+    Callback mCallback;
+
     private GeckoEvent(int evType) {
         mType = evType;
     }
 
     public static GeckoEvent createPauseEvent(int activityDepth) {
         GeckoEvent event = new GeckoEvent(ACTIVITY_PAUSING);
         event.mFlags = activityDepth > 0 ? 1 : 0;
         return event;
@@ -404,9 +410,15 @@ public class GeckoEvent {
     public static GeckoEvent createScreenshotEvent(int tabId, int sw, int sh, int dw, int dh) {
         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.mMetaState = tabId;
         return event;
     }
+
+    public void doCallback(String jsonData) {
+        if (mCallback != null) {
+            mCallback.callback(this, jsonData);
+        }
+    }
 }
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -103,39 +103,44 @@ FENNEC_JAVA_FILES = \
   Tabs.java \
   TabsTray.java \
   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/GeckoSoftwareLayerClient.java \
+  gfx/GeckoGLLayerClient.java \
+  gfx/GeckoLayerClient.java \
+  gfx/GLController.java \
+  gfx/GLThread.java \
   gfx/InputConnectionHandler.java \
   gfx/IntSize.java \
   gfx/Layer.java \
-  gfx/LayerClient.java \
   gfx/LayerController.java \
   gfx/LayerRenderer.java \
   gfx/LayerView.java \
   gfx/MultiTileLayer.java \
   gfx/NinePatchTileLayer.java \
   gfx/PanningPerfAPI.java \
   gfx/PlaceholderLayerClient.java \
   gfx/PointUtils.java \
   gfx/RectUtils.java \
   gfx/ScrollbarLayer.java \
   gfx/SingleTileLayer.java \
   gfx/SurfaceTextureLayer.java \
   gfx/TextLayer.java \
   gfx/TextureGenerator.java \
   gfx/TextureReaper.java \
   gfx/TileLayer.java \
+  gfx/ViewTransform.java \
   gfx/ViewportMetrics.java \
+  gfx/VirtualLayer.java \
   gfx/WidgetTileLayer.java \
   ui/Axis.java \
   ui/PanZoomController.java \
   ui/SimpleScaleGestureDetector.java \
   ui/SubdocumentScrollHelper.java \
   GeckoNetworkManager.java \
   $(NULL)
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/FlexibleGLSurfaceView.java
@@ -0,0 +1,216 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011-2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.GeckoApp;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.opengl.GLSurfaceView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+public class FlexibleGLSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
+    private static final String LOGTAG = "GeckoFlexibleGLSurfaceView";
+
+    private GLSurfaceView.Renderer mRenderer;
+    private GLThread mGLThread; // Protected by this class's monitor.
+    private GLController mController;
+    private Listener mListener;
+
+    public FlexibleGLSurfaceView(Context context) {
+        super(context);
+        init();
+    }
+
+    public FlexibleGLSurfaceView(Context context, AttributeSet attributeSet) {
+        super(context, attributeSet);
+        init();
+    }
+
+    public void init() {
+        SurfaceHolder holder = getHolder();
+        holder.addCallback(this);
+        holder.setFormat(PixelFormat.RGB_565);
+
+        mController = new GLController(this);
+    }
+
+    public void setRenderer(GLSurfaceView.Renderer renderer) {
+        mRenderer = renderer;
+    }
+
+    public GLSurfaceView.Renderer getRenderer() {
+        return mRenderer;
+    }
+
+    public void setListener(Listener listener) {
+        mListener = listener;
+    }
+
+    public synchronized void requestRender() {
+        if (mGLThread != null) {
+            mGLThread.renderFrame();
+        }
+        if (mListener != null) {
+            mListener.renderRequested();
+        }
+    }
+
+    /**
+     * Creates a Java GL thread. After this is called, the FlexibleGLSurfaceView may be used just
+     * like a GLSurfaceView. It is illegal to access the controller after this has been called.
+     */
+    public synchronized void createGLThread() {
+        if (mGLThread != null) {
+            throw new FlexibleGLSurfaceViewException("createGLThread() called with a GL thread " +
+                                                     "already in place!");
+        }
+
+        Log.e(LOGTAG, "### Creating GL thread!");
+        mGLThread = new GLThread(mController);
+        mGLThread.start();
+        notifyAll();
+    }
+
+    /**
+     * Destroys the Java GL thread. Returns a Thread that completes when the Java GL thread is
+     * fully shut down.
+     */
+    public synchronized Thread destroyGLThread() {
+        // Wait for the GL thread to be started.
+        Log.e(LOGTAG, "### Waiting for GL thread to be created...");
+        while (mGLThread == null) {
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        Log.e(LOGTAG, "### Destroying GL thread!");
+        Thread glThread = mGLThread;
+        mGLThread.shutdown();
+        mGLThread = null;
+        return glThread;
+    }
+
+    public synchronized void recreateSurface() {
+        if (mGLThread == null) {
+            throw new FlexibleGLSurfaceViewException("recreateSurface() called with no GL " +
+                                                     "thread active!");
+        }
+
+        mGLThread.recreateSurface();
+    }
+
+    public synchronized GLController getGLController() {
+        if (mGLThread != null) {
+            throw new FlexibleGLSurfaceViewException("getGLController() called with a GL thread " +
+                                                     "active; shut down the GL thread first!");
+        }
+
+        return mController;
+    }
+
+    public synchronized void surfaceChanged(SurfaceHolder holder, int format, int width,
+                                            int height) {
+        mController.sizeChanged(width, height);
+        if (mGLThread != null) {
+            mGLThread.surfaceChanged(width, height);
+        }
+        
+        if (mListener != null) {
+            mListener.surfaceChanged(width, height);
+        }
+    }
+
+    public synchronized void surfaceCreated(SurfaceHolder holder) {
+        mController.surfaceCreated();
+        if (mGLThread != null) {
+            mGLThread.surfaceCreated();
+        }
+    }
+
+    public synchronized void surfaceDestroyed(SurfaceHolder holder) {
+        mController.surfaceDestroyed();
+        if (mGLThread != null) {
+            mGLThread.surfaceDestroyed();
+        }
+        
+        if (mListener != null) {
+            mListener.compositionPauseRequested();
+        }
+    }
+
+    // Called from the compositor thread
+    public static GLController registerCxxCompositor() {
+        try {
+            Log.e(LOGTAG, "### registerCxxCompositor point A");
+            System.out.println("register layer comp");
+            Log.e(LOGTAG, "### registerCxxCompositor point B");
+            FlexibleGLSurfaceView flexView = (FlexibleGLSurfaceView)GeckoApp.mAppContext.getLayerController().getView();
+            Log.e(LOGTAG, "### registerCxxCompositor point C: " + flexView);
+            try {
+                flexView.destroyGLThread().join();
+            } catch (InterruptedException e) {}
+            Log.e(LOGTAG, "### registerCxxCompositor point D: " + flexView.getGLController());
+            return flexView.getGLController();
+        } catch (Exception e) {
+            Log.e(LOGTAG, "### Exception! " + e);
+            return null;
+        }
+    }
+
+    public interface Listener {
+        void renderRequested();
+        void compositionPauseRequested();
+        void compositionResumeRequested();
+        void surfaceChanged(int width, int height);
+    }
+
+    public static class FlexibleGLSurfaceViewException extends RuntimeException {
+        public static final long serialVersionUID = 1L;
+
+        FlexibleGLSurfaceViewException(String e) {
+            super(e);
+        }
+    }
+}
+
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/GLController.java
@@ -0,0 +1,279 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011-2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGL11;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL;
+import javax.microedition.khronos.opengles.GL10;
+
+public class GLController {
+    private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+    private static final String LOGTAG = "GeckoGLController";
+
+    private FlexibleGLSurfaceView mView;
+    private int mGLVersion;
+    private boolean mSurfaceValid;
+    private int mWidth, mHeight;
+
+    private EGL10 mEGL;
+    private EGLDisplay mEGLDisplay;
+    private EGLConfig mEGLConfig;
+    private EGLContext mEGLContext;
+    private EGLSurface mEGLSurface;
+
+    private GL mGL;
+
+    private static final int LOCAL_EGL_OPENGL_ES2_BIT = 4;
+
+    private static final int[] CONFIG_SPEC = {
+        EGL10.EGL_RED_SIZE, 5,
+        EGL10.EGL_GREEN_SIZE, 6,
+        EGL10.EGL_BLUE_SIZE, 5,
+        EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT,
+        EGL10.EGL_RENDERABLE_TYPE, LOCAL_EGL_OPENGL_ES2_BIT,
+        EGL10.EGL_NONE
+    };
+
+    public GLController(FlexibleGLSurfaceView view) {
+        mView = view;
+        mGLVersion = 2;
+        mSurfaceValid = false;
+    }
+
+    public void setGLVersion(int version) {
+        mGLVersion = version;
+    }
+
+    /** You must call this on the same thread you intend to use OpenGL on. */
+    public void initGLContext() {
+        initEGLContext();
+        createEGLSurface();
+    }
+
+    public void disposeGLContext() {
+        if (!mEGL.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,
+                                 EGL10.EGL_NO_CONTEXT)) {
+            throw new GLControllerException("EGL context could not be released!");
+        }
+
+        if (mEGLSurface != null) {
+            if (!mEGL.eglDestroySurface(mEGLDisplay, mEGLSurface)) {
+                throw new GLControllerException("EGL surface could not be destroyed!");
+            }
+
+            mEGLSurface = null;
+        }
+
+        if (mEGLContext == null) {
+            if (!mEGL.eglDestroyContext(mEGLDisplay, mEGLContext)) {
+                throw new GLControllerException("EGL context could not be destroyed!");
+            }
+
+            mGL = null;
+            mEGLDisplay = null;
+            mEGLConfig = null;
+            mEGLContext = null;
+        }
+    }
+
+    public GL getGL()                       { return mEGLContext.getGL(); }
+    public EGLDisplay getEGLDisplay()       { return mEGLDisplay;         }
+    public EGLConfig getEGLConfig()         { return mEGLConfig;          }
+    public EGLContext getEGLContext()       { return mEGLContext;         }
+    public EGLSurface getEGLSurface()       { return mEGLSurface;         }
+    public FlexibleGLSurfaceView getView()  { return mView;               }
+
+    public boolean hasSurface() {
+        return mEGLSurface != null;
+    }
+
+    public boolean swapBuffers() {
+        return mEGL.eglSwapBuffers(mEGLDisplay, mEGLSurface);
+    }
+
+    public boolean checkForLostContext() {
+        if (mEGL.eglGetError() != EGL11.EGL_CONTEXT_LOST) {
+            return false;
+        }
+
+        mEGLDisplay = null;
+        mEGLConfig = null;
+        mEGLContext = null;
+        mEGLSurface = null;
+        mGL = null;
+        return true;
+    }
+
+    public synchronized void waitForValidSurface() {
+        while (!mSurfaceValid) {
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    public synchronized int getWidth() {
+        return mWidth;
+    }
+
+    public synchronized int getHeight() {
+        return mHeight;
+    }
+
+    synchronized void surfaceCreated() {
+        mSurfaceValid = true;
+        notifyAll();
+    }
+
+    synchronized void surfaceDestroyed() {
+        mSurfaceValid = false;
+        notifyAll();
+    }
+
+    synchronized void sizeChanged(int newWidth, int newHeight) {
+        mWidth = newWidth;
+        mHeight = newHeight;
+    }
+
+    private void initEGL() {
+        mEGL = (EGL10)EGLContext.getEGL();
+
+        mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+        if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) {
+            throw new GLControllerException("eglGetDisplay() failed");
+        }
+
+        int[] version = new int[2];
+        if (!mEGL.eglInitialize(mEGLDisplay, version)) {
+            throw new GLControllerException("eglInitialize() failed");
+        }
+
+        mEGLConfig = chooseConfig();
+    }
+
+    private void initEGLContext() {
+        initEGL();
+
+        int[] attribList = { EGL_CONTEXT_CLIENT_VERSION, mGLVersion, EGL10.EGL_NONE };
+        mEGLContext = mEGL.eglCreateContext(mEGLDisplay, mEGLConfig, EGL10.EGL_NO_CONTEXT,
+                                            attribList);
+        if (mEGLContext == null || mEGLContext == EGL10.EGL_NO_CONTEXT) {
+            throw new GLControllerException("createContext() failed");
+        }
+    }
+
+    private EGLConfig chooseConfig() {
+        int[] numConfigs = new int[1];
+        if (!mEGL.eglChooseConfig(mEGLDisplay, CONFIG_SPEC, null, 0, numConfigs) ||
+                numConfigs[0] <= 0) {
+            throw new GLControllerException("No available EGL configurations");
+        }
+
+        EGLConfig[] configs = new EGLConfig[numConfigs[0]];
+        if (!mEGL.eglChooseConfig(mEGLDisplay, CONFIG_SPEC, configs, numConfigs[0], numConfigs)) {
+            throw new GLControllerException("No EGL configuration for that specification");
+        }
+
+        // Select the first 565 RGB configuration.
+        int[] red = new int[1], green = new int[1], blue = new int[1];
+        for (EGLConfig config : configs) {
+            mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_RED_SIZE, red);
+            mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_GREEN_SIZE, green);
+            mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_BLUE_SIZE, blue);
+            if (red[0] == 5 && green[0] == 6 && blue[0] == 5) {
+                return config;
+            }
+        }
+
+        throw new GLControllerException("No suitable EGL configuration found");
+    }
+
+    private void createEGLSurface() {
+        SurfaceHolder surfaceHolder = mView.getHolder();
+        mEGLSurface = mEGL.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surfaceHolder, null);
+        if (mEGLSurface == null || mEGLSurface == EGL10.EGL_NO_SURFACE) {
+            throw new GLControllerException("EGL window surface could not be created!");
+        }
+
+        if (!mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
+            throw new GLControllerException("EGL surface could not be made into the current " +
+                                            "surface!");
+        }
+
+        mGL = mEGLContext.getGL();
+
+        if (mView.getRenderer() != null) {
+            mView.getRenderer().onSurfaceCreated((GL10)mGL, mEGLConfig);
+            mView.getRenderer().onSurfaceChanged((GL10)mGL, mView.getWidth(), mView.getHeight());
+        }
+    }
+
+    // Provides an EGLSurface without assuming ownership of this surface.
+    private EGLSurface provideEGLSurface() {
+        if (mEGL == null) {
+            initEGL();
+        }
+
+        SurfaceHolder surfaceHolder = mView.getHolder();
+        mEGLSurface = mEGL.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surfaceHolder, null);
+        if (mEGLSurface == null || mEGLSurface == EGL10.EGL_NO_SURFACE) {
+            throw new GLControllerException("EGL window surface could not be created!");
+        }
+
+        return mEGLSurface;
+    }
+
+    public static class GLControllerException extends RuntimeException {
+        public static final long serialVersionUID = 1L;
+
+        GLControllerException(String e) {
+            super(e);
+        }
+    }
+}
+
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/GLThread.java
@@ -0,0 +1,181 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011-2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import android.opengl.GLSurfaceView;
+import android.view.SurfaceHolder;
+import javax.microedition.khronos.opengles.GL10;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+
+// A GL thread managed by Java. It is not necessary to use this class to use the
+// FlexibleGLSurfaceView, but it can be helpful, especially if the GL rendering is to be done
+// entirely in Java.
+class GLThread extends Thread {
+    private LinkedBlockingQueue<Runnable> mQueue;
+    private GLController mController;
+    private boolean mRenderQueued;
+
+    public GLThread(GLController controller) {
+        mQueue = new LinkedBlockingQueue<Runnable>();
+        mController = controller;
+    }
+
+    @Override
+    public void run() {
+        while (true) {
+            Runnable runnable;
+            try {
+                runnable = mQueue.take();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+
+            runnable.run();
+            if (runnable instanceof ShutdownMessage) {
+                break;
+            }
+        }
+    }
+
+    public void recreateSurface() {
+        mQueue.add(new RecreateSurfaceMessage());
+    }
+
+    public void renderFrame() {
+        // Make sure there's only one render event in the queue at a time.
+        synchronized (this) {
+            if (!mRenderQueued) {
+                mQueue.add(new RenderFrameMessage());
+                mRenderQueued = true;
+            }
+        }
+    }
+
+    public void shutdown() {
+        mQueue.add(new ShutdownMessage());
+    }
+
+    public void surfaceChanged(int width, int height) {
+        mQueue.add(new SizeChangedMessage(width, height));
+    }
+
+    public void surfaceCreated() {
+        mQueue.add(new SurfaceCreatedMessage());
+    }
+
+    public void surfaceDestroyed() {
+        mQueue.add(new SurfaceDestroyedMessage());
+    }
+
+    private void doRecreateSurface() {
+        mController.disposeGLContext();
+        mController.initGLContext();
+    }
+
+    private GLSurfaceView.Renderer getRenderer() {
+        return mController.getView().getRenderer();
+    }
+
+    private class RecreateSurfaceMessage implements Runnable {
+        public void run() {
+            doRecreateSurface();
+        }
+    }
+
+    private class RenderFrameMessage implements Runnable {
+        public void run() {
+            synchronized (GLThread.this) {
+                mRenderQueued = false;
+            }
+
+            // Bail out if the surface was lost.
+            if (mController.getEGLSurface() == null) {
+                return;
+            }
+
+            GLSurfaceView.Renderer renderer = getRenderer();
+            if (renderer != null) {
+                renderer.onDrawFrame((GL10)mController.getGL());
+            }
+
+            mController.swapBuffers();
+            //if (!mController.swapBuffers() && mController.checkForLostContext()) {
+            //    doRecreateSurface();
+            //}
+        }
+    }
+
+    private class ShutdownMessage implements Runnable {
+        public void run() {
+            mController.disposeGLContext();
+            mController = null;
+        }
+    }
+
+    private class SizeChangedMessage implements Runnable {
+        private int mWidth, mHeight;
+
+        public SizeChangedMessage(int width, int height) {
+            mWidth = width;
+            mHeight = height;
+        }
+
+        public void run() {
+            GLSurfaceView.Renderer renderer = getRenderer();
+            if (renderer != null) {
+                renderer.onSurfaceChanged((GL10)mController.getGL(), mWidth, mHeight);
+            }
+        }
+    }
+
+    private class SurfaceCreatedMessage implements Runnable {
+        public void run() {
+            if (!mController.hasSurface()) {
+                mController.initGLContext();
+            }
+        }
+    }
+
+    private class SurfaceDestroyedMessage implements Runnable {
+        public void run() {
+            mController.disposeGLContext();
+        }
+    }
+}
+
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/GeckoGLLayerClient.java
@@ -0,0 +1,104 @@
+/* -*- 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>
+ *
+ * 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.GeckoAppShell;
+import org.mozilla.gecko.GeckoEvent;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.Log;
+import android.view.View;
+
+public class GeckoGLLayerClient extends GeckoLayerClient {
+    private static final String LOGTAG = "GeckoGLLayerClient";
+
+    private boolean mLayerRendererInitialized;
+
+    public GeckoGLLayerClient(Context context) {
+        super(context);
+    }
+
+    /** This function is invoked by Gecko via JNI; be careful when modifying signature. */
+    public ViewTransform getViewTransform() {
+        Log.e(LOGTAG, "### getViewTransform()");
+
+        // NB: We don't begin a transaction here because this can be called in a synchronous
+        // manner between beginDrawing() and endDrawing(), and that will cause a deadlock.
+
+        synchronized (mLayerController) {
+            ViewportMetrics viewportMetrics = mLayerController.getViewportMetrics();
+            PointF viewportOrigin = viewportMetrics.getOrigin();
+            Point tileOrigin = mTileLayer.getOrigin();
+            float scrollX = viewportOrigin.x; 
+            float scrollY = viewportOrigin.y;
+            float zoomFactor = viewportMetrics.getZoomFactor();
+            Log.e(LOGTAG, "### Viewport metrics = " + viewportMetrics + " tile reso = " +
+                  mTileLayer.getResolution());
+            return new ViewTransform(scrollX, scrollY, zoomFactor);
+        }
+    }
+
+    /** This function is invoked by Gecko via JNI; be careful when modifying signature. */
+    public LayerRenderer.Frame createFrame() {
+        // Create the shaders and textures if necessary.
+        if (!mLayerRendererInitialized) {
+            mLayerRenderer.createProgram();
+            mLayerRendererInitialized = true;
+        }
+
+        // Build the contexts and create the frame.
+        Layer.RenderContext pageContext = mLayerRenderer.createPageContext();
+        Layer.RenderContext screenContext = mLayerRenderer.createScreenContext();
+        return mLayerRenderer.createFrame(pageContext, screenContext);
+    }
+
+    /** This function is invoked by Gecko via JNI; be careful when modifying signature. */
+    public void activateProgram() {
+        mLayerRenderer.activateProgram();
+    }
+
+    /** This function is invoked by Gecko via JNI; be careful when modifying signature. */
+    public void deactivateProgram() {
+        mLayerRenderer.deactivateProgram();
+    }
+}
+
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/GeckoLayerClient.java
@@ -0,0 +1,480 @@
+/* -*- 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.Bitmap;
+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 android.view.View;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public abstract class GeckoLayerClient implements GeckoEventListener,
+                                                  FlexibleGLSurfaceView.Listener,
+                                                  VirtualLayer.Listener {
+    private static final String LOGTAG = "GeckoLayerClient";
+
+    protected LayerController mLayerController;
+    protected LayerRenderer mLayerRenderer;
+
+    protected IntSize mScreenSize;
+    protected IntSize mWindowSize;
+    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 = 25L;
+    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 String mLastCheckerboardColor;
+
+    private static Pattern sColorPattern;
+
+    /* Used by robocop for testing purposes */
+    private DrawListener mDrawListener;
+
+    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. */
+    public void setLayerController(LayerController layerController) {
+        mLayerController = layerController;
+
+        layerController.setRoot(mTileLayer);
+        if (mGeckoViewport != null) {
+            layerController.setViewportMetrics(mGeckoViewport);
+        }
+
+        GeckoAppShell.registerGeckoEventListener("Viewport:UpdateAndDraw", this);
+        GeckoAppShell.registerGeckoEventListener("Viewport:UpdateLater", this);
+
+        sendResizeEventIfNecessary();
+
+        LayerView view = layerController.getView();
+        view.setListener(this);
+
+        mLayerRenderer = new LayerRenderer(view);
+    }
+
+    /** This function is invoked by Gecko via JNI; be careful when modifying signature. */
+    public Rect beginDrawing(int width, int height, int tileWidth, int tileHeight,
+                             String metadata, boolean hasDirectTexture) {
+        Log.e(LOGTAG, "### beginDrawing " + width + " " + height + " " + tileWidth + " " +
+              tileHeight + " " + hasDirectTexture);
+
+        // If we've changed surface types, cancel this draw
+        if (handleDirectTextureChange(hasDirectTexture)) {
+            Log.e(LOGTAG, "### Cancelling draw due to direct texture change");
+            return null;
+        }
+
+        try {
+            JSONObject viewportObject = new JSONObject(metadata);
+            mNewGeckoViewport = new ViewportMetrics(viewportObject);
+
+            Log.e(LOGTAG, "### beginDrawing new Gecko viewport " + mNewGeckoViewport);
+
+            // Update the background color, if it's present.
+            String backgroundColorString = viewportObject.optString("backgroundColor");
+            if (backgroundColorString != null && !backgroundColorString.equals(mLastCheckerboardColor)) {
+                mLastCheckerboardColor = backgroundColorString;
+                mLayerController.setCheckerboardColor(parseColorFromGecko(backgroundColorString));
+            }
+        } catch (JSONException e) {
+            Log.e(LOGTAG, "Aborting draw, bad viewport description: " + metadata);
+            return null;
+        }
+
+
+        // Make sure we don't spend time painting areas we aren't interested in.
+        // Only do this if the Gecko viewport isn't going to override our viewport.
+        Rect bufferRect = new Rect(0, 0, width, height);
+
+        if (!mUpdateViewportOnEndDraw) {
+            // First, find out our ideal displayport. This would be what we would
+            // send to Gecko if adjustViewport were called now.
+            ViewportMetrics currentMetrics = mLayerController.getViewportMetrics();
+            PointF currentBestOrigin = RectUtils.getOrigin(currentMetrics.getClampedViewport());
+
+            Rect currentRect = RectUtils.round(new RectF(currentBestOrigin.x, currentBestOrigin.y,
+                                                         currentBestOrigin.x + width, currentBestOrigin.y + height));
+
+            // Second, store Gecko's displayport.
+            PointF currentOrigin = mNewGeckoViewport.getDisplayportOrigin();
+            bufferRect = RectUtils.round(new RectF(currentOrigin.x, currentOrigin.y,
+                                                   currentOrigin.x + width, currentOrigin.y + height));
+
+            int area = width * height;
+
+            // Take the intersection of the two as the area we're interested in rendering.
+            if (!bufferRect.intersect(currentRect)) {
+                Log.w(LOGTAG, "Prediction would avoid useless paint of " + area + " pixels (100.0%)");
+                // If there's no intersection, we have no need to render anything,
+                // but make sure to update the viewport size.
+                mTileLayer.beginTransaction();
+                try {
+                    updateViewport(true);
+                } finally {
+                    mTileLayer.endTransaction();
+                }
+                return null;
+            }
+
+            int wasted = area - (bufferRect.width() * bufferRect.height());
+            Log.w(LOGTAG, "Prediction would avoid useless paint of " + wasted + " pixels (" + ((float)wasted * 100.0f / area) + "%)");
+
+            bufferRect.offset(Math.round(-currentOrigin.x), Math.round(-currentOrigin.y));
+        }
+
+        mTileLayer.beginTransaction();
+
+        if (mBufferSize.width != width || mBufferSize.height != height) {
+            mBufferSize = new IntSize(width, height);
+        }
+
+        return bufferRect;
+    }
+
+    /** This function is invoked by Gecko via JNI; be careful when modifying signature.
+     *
+     * 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 (mLayerController) {
+            try {
+                updateViewport(!mUpdateViewportOnEndDraw);
+                mUpdateViewportOnEndDraw = false;
+
+                Rect rect = new Rect(x, y, x + width, y + height);
+                updateLayerAfterDraw(rect);
+            } finally {
+                mTileLayer.endTransaction();
+            }
+        }
+        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 = mLayerController.getViewportSize();
+        mGeckoViewport = mNewGeckoViewport;
+        mGeckoViewport.setSize(viewportSize);
+
+        PointF displayportOrigin = mGeckoViewport.getDisplayportOrigin();
+        mTileLayer.setOrigin(PointUtils.round(displayportOrigin));
+        mTileLayer.setResolution(mGeckoViewport.getZoomFactor());
+
+        this.tileLayerUpdated();
+        Log.e(LOGTAG, "### updateViewport onlyUpdatePageSize=" + onlyUpdatePageSize +
+              " getTileViewport " + mGeckoViewport);
+
+        if (onlyUpdatePageSize) {
+            // Don't adjust page size when zooming unless zoom levels are
+            // approximately equal.
+            if (FloatUtils.fuzzyEquals(mLayerController.getZoomFactor(),
+                    mGeckoViewport.getZoomFactor()))
+                mLayerController.setPageSize(mGeckoViewport.getPageSize());
+        } else {
+            mLayerController.setViewportMetrics(mGeckoViewport);
+            mLayerController.abortPanZoomAnimation();
+        }
+    }
+
+    /* Informs Gecko that the screen size has changed. */
+    protected void sendResizeEventIfNecessary(boolean force) {
+        Log.d(LOGTAG, "### sendResizeEventIfNecessary " + force);
+
+        DisplayMetrics metrics = new DisplayMetrics();
+        GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+
+        IntSize newScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels);
+        IntSize newWindowSize = getBufferSize();
+
+        boolean screenSizeChanged = mScreenSize == null || !mScreenSize.equals(newScreenSize);
+        boolean windowSizeChanged = mWindowSize == null || !mWindowSize.equals(newWindowSize);
+
+        if (!force && !screenSizeChanged && !windowSizeChanged) {
+            return;
+        }
+
+        mScreenSize = newScreenSize;
+        mWindowSize = newWindowSize;
+
+        if (screenSizeChanged) {
+            Log.i(LOGTAG, "### Screen-size changed to " + mScreenSize);
+        }
+
+        if (windowSizeChanged) {
+            Log.i(LOGTAG, "### Window-size changed to " + mWindowSize);
+        }
+
+        IntSize bufferSize = getBufferSize();
+        GeckoEvent event = GeckoEvent.createSizeChangedEvent(mWindowSize.width, mWindowSize.height, // Window (buffer) size
+                                                             mScreenSize.width, mScreenSize.height, // Screen size
+                                                             0, 0);                                 // Tile-size (unused)
+        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);
+    }
+
+    protected boolean handleDirectTextureChange(boolean hasDirectTexture) {
+        if (mTileLayer != null) {
+            return false;
+        }
+
+        Log.e(LOGTAG, "### Creating virtual layer");
+        VirtualLayer virtualLayer = new VirtualLayer();
+        virtualLayer.setListener(this);
+        virtualLayer.setSize(getBufferSize());
+        mLayerController.setRoot(virtualLayer);
+        mTileLayer = virtualLayer;
+
+        sendResizeEventIfNecessary(true);
+        return true;
+    }
+
+    protected void updateLayerAfterDraw(Rect updatedRect) {
+        Log.e(LOGTAG, "### updateLayerAfterDraw");
+        // Nothing to do.
+    }
+
+    protected IntSize getBufferSize() {
+        View view = mLayerController.getView();
+        IntSize size = new IntSize(view.getWidth(), view.getHeight());
+        Log.e(LOGTAG, "### getBufferSize " + size);
+        return size;
+    }
+
+    protected IntSize getTileSize() {
+        Log.e(LOGTAG, "### getTileSize " + getBufferSize());
+        return getBufferSize();
+    }
+
+    protected void tileLayerUpdated() {
+        // Set the new origin and resolution instantly.
+        mTileLayer.performUpdates(null);
+    }
+
+    public Bitmap getBitmap() {
+        return null;
+    }
+
+    public void render() {
+        adjustViewportWithThrottling();
+    }
+
+    private void adjustViewportWithThrottling() {
+        if (!mLayerController.getRedrawHint())
+            return;
+
+        if (mPendingViewportAdjust)
+            return;
+
+        long timeDelta = System.currentTimeMillis() - mLastViewportChangeTime;
+        if (timeDelta < MIN_VIEWPORT_CHANGE_DELAY) {
+            mLayerController.getView().postDelayed(
+                new Runnable() {
+                    public void run() {
+                        mPendingViewportAdjust = false;
+                        adjustViewport();
+                    }
+                }, MIN_VIEWPORT_CHANGE_DELAY - timeDelta);
+            mPendingViewportAdjust = true;
+            return;
+        }
+
+        adjustViewport();
+    }
+
+    public void viewportSizeChanged() {
+        mViewportSizeChanged = true;
+    }
+
+    private void adjustViewport() {
+        ViewportMetrics viewportMetrics =
+            new ViewportMetrics(mLayerController.getViewportMetrics());
+
+        viewportMetrics.setViewport(viewportMetrics.getClampedViewport());
+
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createViewportEvent(viewportMetrics));
+        if (mViewportSizeChanged) {
+            mViewportSizeChanged = false;
+            GeckoAppShell.viewSizeChanged();
+        }
+
+        mLastViewportChangeTime = System.currentTimeMillis();
+    }
+
+    public void handleMessage(String event, JSONObject message) {
+        if ("Viewport:UpdateAndDraw".equals(event)) {
+            Log.e(LOGTAG, "### Java side Viewport:UpdateAndDraw()!");
+            mUpdateViewportOnEndDraw = true;
+
+            // Redraw everything.
+            Rect rect = new Rect(0, 0, mBufferSize.width, mBufferSize.height);
+            GeckoAppShell.sendEventToGecko(GeckoEvent.createDrawEvent(rect));
+        } else if ("Viewport:UpdateLater".equals(event)) {
+            Log.e(LOGTAG, "### Java side Viewport:UpdateLater()!");
+            mUpdateViewportOnEndDraw = true;
+        }
+    }
+
+    public void geometryChanged() {
+        /* Let Gecko know if the screensize has changed */
+        sendResizeEventIfNecessary();
+        render();
+    }
+
+    public int getWidth() {
+        return mBufferSize.width;
+    }
+
+    public int getHeight() {
+        return mBufferSize.height;
+    }
+
+    public ViewportMetrics getGeckoViewportMetrics() {
+        // Return a copy, as we modify this inside the Gecko thread
+        if (mGeckoViewport != null)
+            return new ViewportMetrics(mGeckoViewport);
+        return null;
+    }
+
+    private void sendResizeEventIfNecessary() {
+        sendResizeEventIfNecessary(false);
+    }
+
+    /** Implementation of FlexibleGLSurfaceView.Listener */
+    public void renderRequested() {
+        Log.e(LOGTAG, "### Render requested, scheduling composite");
+        GeckoAppShell.scheduleComposite();
+    }
+
+    /** Implementation of FlexibleGLSurfaceView.Listener */
+    public void compositionPauseRequested() {
+        Log.e(LOGTAG, "### Scheduling PauseComposition");
+        GeckoAppShell.schedulePauseComposition();
+    }
+
+    /** Implementation of FlexibleGLSurfaceView.Listener */
+    public void compositionResumeRequested() {
+        Log.e(LOGTAG, "### Scheduling ResumeComposition");
+        GeckoAppShell.scheduleResumeComposition();
+    }
+
+    /** Implementation of FlexibleGLSurfaceView.Listener */
+    public void surfaceChanged(int width, int height) {
+        compositionPauseRequested();
+        mLayerController.setViewportSize(new FloatSize(width, height));
+        compositionResumeRequested();
+        renderRequested();
+    }
+
+    /** Implementation of VirtualLayer.Listener */
+    public void dimensionsChanged(Point newOrigin, float newResolution) {
+        Log.e(LOGTAG, "### dimensionsChanged " + newOrigin + " " + newResolution);
+    }
+
+    /** 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);
+    }
+}
+
deleted file mode 100644
--- a/mobile/android/base/gfx/GeckoSoftwareLayerClient.java
+++ /dev/null
@@ -1,575 +0,0 @@
-/* -*- 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.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 {
-    private static final String LOGTAG = "GeckoSoftwareLayerClient";
-
-    private Context mContext;
-    private int mFormat;
-    private IntSize mScreenSize, mViewportSize;
-    private IntSize mBufferSize;
-    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;
-
-    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;
-
-    /* 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;
-
-        mCairoImage = new CairoImage() {
-            @Override
-            public ByteBuffer getBuffer() { return mBuffer; }
-            @Override
-            public IntSize getSize() { return mBufferSize; }
-            @Override
-            public int getFormat() { return mFormat; }
-        };
-    }
-
-    public int getWidth() {
-        return mBufferSize.width;
-    }
-
-    public int getHeight() {
-        return mBufferSize.height;
-    }
-
-    protected void finalize() throws Throwable {
-        try {
-            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);
-        GeckoAppShell.registerGeckoEventListener("Checkerboard:Toggle", this);
-
-        sendResizeEventIfNecessary();
-    }
-
-    private boolean setHasDirectTexture(boolean hasDirectTexture) {
-        if (mTileLayer != null && hasDirectTexture == mHasDirectTexture)
-            return false;
-
-        mHasDirectTexture = hasDirectTexture;
-
-        if (mHasDirectTexture) {
-            Log.i(LOGTAG, "Creating WidgetTileLayer");
-            mTileLayer = new WidgetTileLayer(mCairoImage);
-        } else {
-            Log.i(LOGTAG, "Creating MultiTileLayer");
-            mTileLayer = new MultiTileLayer(mCairoImage, TILE_SIZE);
-        }
-
-        getLayerController().setRoot(mTileLayer);
-
-        // 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 Rect beginDrawing(int width, int height, int tileWidth, int tileHeight,
-                             String metadata, boolean hasDirectTexture) {
-        setHasDirectTexture(hasDirectTexture);
-
-        // 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);
-                return null;
-            }
-        } else {
-            if (tileWidth != TILE_SIZE.width || tileHeight != TILE_SIZE.height) {
-                Log.e(LOGTAG, "Aborting draw, incorrect tile size of " + tileWidth + "x" + tileHeight);
-                return null;
-            }
-        }
-
-        LayerController controller = getLayerController();
-
-        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) {
-                controller.setCheckerboardColor(parseColorFromGecko(backgroundColorString));
-            }
-        } catch (JSONException e) {
-            Log.e(LOGTAG, "Aborting draw, bad viewport description: " + metadata);
-            return null;
-        }
-
-        // Make sure we don't spend time painting areas we aren't interested in.
-        // Only do this if the Gecko viewport isn't going to override our viewport.
-        Rect bufferRect = new Rect(0, 0, width, height);
-
-        if (!mUpdateViewportOnEndDraw) {
-            // First, find out our ideal displayport. We do this by taking the
-            // clamped viewport origin and taking away the optimum viewport offset.
-            // This would be what we would send to Gecko if adjustViewport were
-            // called now.
-            ViewportMetrics currentMetrics = controller.getViewportMetrics();
-            PointF currentBestOrigin = RectUtils.getOrigin(currentMetrics.getClampedViewport());
-            PointF viewportOffset = currentMetrics.getOptimumViewportOffset(new IntSize(width, height));
-            currentBestOrigin.offset(-viewportOffset.x, -viewportOffset.y);
-
-            Rect currentRect = RectUtils.round(new RectF(currentBestOrigin.x, currentBestOrigin.y,
-                                                         currentBestOrigin.x + width, currentBestOrigin.y + height));
-
-            // Second, store Gecko's displayport.
-            PointF currentOrigin = mNewGeckoViewport.getDisplayportOrigin();
-            bufferRect = RectUtils.round(new RectF(currentOrigin.x, currentOrigin.y,
-                                                   currentOrigin.x + width, currentOrigin.y + height));
-
-
-            // Take the intersection of the two as the area we're interested in rendering.
-            if (!bufferRect.intersect(currentRect)) {
-                // If there's no intersection, we have no need to render anything,
-                // but make sure to update the viewport size.
-                beginTransaction(mTileLayer);
-                try {
-                    updateViewport(true);
-                } finally {
-                    endTransaction(mTileLayer);
-                }
-                return null;
-            }
-            bufferRect.offset(Math.round(-currentOrigin.x), Math.round(-currentOrigin.y));
-        }
-
-        beginTransaction(mTileLayer);
-
-        // Synchronise the buffer size with Gecko.
-        if (mBufferSize.width != width || mBufferSize.height != height) {
-            mBufferSize = new IntSize(width, height);
-
-            // Reallocate the buffer if necessary
-            if (mTileLayer instanceof MultiTileLayer) {
-                int bpp = CairoUtils.bitsPerPixelForCairoFormat(mFormat) / 8;
-                int size = mBufferSize.getArea() * bpp;
-                if (mBuffer == null || mBuffer.capacity() != size) {
-                    // Free the old buffer
-                    if (mBuffer != null) {
-                        GeckoAppShell.freeDirectBuffer(mBuffer);
-                        mBuffer = null;
-                    }
-
-                    mBuffer = GeckoAppShell.allocateDirectBuffer(size);
-                }
-            }
-        }
-
-        return bufferRect;
-    }
-
-    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();
-        }
-    }
-
-    /*
-     * 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);
-                    ((MultiTileLayer)mTileLayer).invalidate(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);
-        }
-    }
-
-    public ViewportMetrics getGeckoViewportMetrics() {
-        // Return a copy, as we modify this inside the Gecko thread
-        if (mGeckoViewport != null)
-            return new ViewportMetrics(mGeckoViewport);
-        return null;
-    }
-
-    public void copyPixelsFromMultiTileLayer(Bitmap target) {
-        Canvas c = new Canvas(target);
-        ByteBuffer tileBuffer = mBuffer.slice();
-        int bpp = CairoUtils.bitsPerPixelForCairoFormat(mFormat) / 8;
-
-        for (int y = 0; y < mBufferSize.height; y += TILE_SIZE.height) {
-            for (int x = 0; x < mBufferSize.width; x += TILE_SIZE.width) {
-                // Calculate tile size
-                IntSize tileSize = new IntSize(Math.min(mBufferSize.width - x, TILE_SIZE.width),
-                                               Math.min(mBufferSize.height - y, TILE_SIZE.height));
-
-                // Create a Bitmap from this tile
-                Bitmap tile = Bitmap.createBitmap(tileSize.width, tileSize.height,
-                                                  CairoUtils.cairoFormatTobitmapConfig(mFormat));
-                tile.copyPixelsFromBuffer(tileBuffer.asIntBuffer());
-
-                // Copy the tile to the master Bitmap and recycle it
-                c.drawBitmap(tile, x, y, null);
-                tile.recycle();
-
-                // Progress the buffer to the next tile
-                tileBuffer.position(tileSize.getArea() * bpp);
-                tileBuffer = tileBuffer.slice();
-            }
-        }
-    }
-
-    public Bitmap getBitmap() {
-        if (mTileLayer == null)
-            return null;
-
-        // Begin a tile transaction, otherwise the buffer can be destroyed while
-        // we're reading from it.
-        beginTransaction(mTileLayer);
-        try {
-            if (mBuffer == null || mBufferSize.width <= 0 || mBufferSize.height <= 0)
-                return null;
-            try {
-                Bitmap b = null;
-
-                if (mTileLayer instanceof MultiTileLayer) {
-                    b = Bitmap.createBitmap(mBufferSize.width, mBufferSize.height,
-                                            CairoUtils.cairoFormatTobitmapConfig(mFormat));
-                    copyPixelsFromMultiTileLayer(b);
-                } else {
-                    Log.w(LOGTAG, "getBitmap() called on a layer (" + mTileLayer + ") we don't know how to get a bitmap from");
-                }
-
-                return b;
-            } catch (OutOfMemoryError oom) {
-                Log.w(LOGTAG, "Unable to create bitmap", oom);
-                return null;
-            }
-        } finally {
-            endTransaction(mTileLayer);
-        }
-    }
-
-    /** Returns the back buffer. This function is for Gecko to use. */
-    public ByteBuffer lockBuffer() {
-        return mBuffer;
-    }
-
-    /**
-     * 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;
-
-        // 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);
-        }
-
-        Log.i(LOGTAG, "Screen-size changed to " + mScreenSize);
-        GeckoEvent event = GeckoEvent.createSizeChangedEvent(
-            bufferSize.width, bufferSize.height, metrics.widthPixels, 
-            metrics.heightPixels, tileSize.width, tileSize.height);
-        GeckoAppShell.sendEventToGecko(event);
-    }
-
-    @Override
-    public void viewportSizeChanged() {
-        mViewportSizeChanged = true;
-    }
-
-    @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(GeckoEvent.createViewportEvent(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(GeckoEvent.createDrawEvent(rect));
-        } else if ("Viewport:UpdateLater".equals(event)) {
-            mUpdateViewportOnEndDraw = true;
-        } else if ("Checkerboard:Toggle".equals(event)) {
-            try {
-                boolean showChecks = message.getBoolean("value");
-                LayerController controller = getLayerController();
-                controller.setCheckerboardShowChecks(showChecks);
-                Log.i(LOGTAG, "Showing checks: " + showChecks);
-            } catch(JSONException ex) {
-                Log.e(LOGTAG, "Error decoding JSON", ex);
-            }
-        }
-    }
-
-    // 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);
-    }
-}
-
--- a/mobile/android/base/gfx/Layer.java
+++ b/mobile/android/base/gfx/Layer.java
@@ -16,16 +16,17 @@
  *
  * 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>
+ *   Arkady Blyakher <rkadyb@mit.edu>
  *
  * 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
@@ -38,49 +39,48 @@
 
 package org.mozilla.gecko.gfx;
 
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.util.Log;
+import java.nio.FloatBuffer;
 import java.util.concurrent.locks.ReentrantLock;
-import javax.microedition.khronos.opengles.GL10;
 import org.mozilla.gecko.FloatUtils;
 
 public abstract class Layer {
     private final ReentrantLock mTransactionLock;
     private boolean mInTransaction;
     private Point mNewOrigin;
     private float mNewResolution;
-    private LayerView mView;
 
     protected Point mOrigin;
     protected float mResolution;
 
     public Layer() {
         mTransactionLock = new ReentrantLock();
         mOrigin = new Point(0, 0);
         mResolution = 1.0f;
     }
 
     /**
      * Updates the layer. This returns false if there is still work to be done
      * after this update.
      */
-    public final boolean update(GL10 gl, RenderContext context) {
+    public final boolean update(RenderContext context) {
         if (mTransactionLock.isHeldByCurrentThread()) {
             throw new RuntimeException("draw() called while transaction lock held by this " +
                                        "thread?!");
         }
 
         if (mTransactionLock.tryLock()) {
             try {
-                return performUpdates(gl, context);
+                return performUpdates(context);
             } finally {
                 mTransactionLock.unlock();
             }
         }
 
         return false;
     }
 
@@ -109,38 +109,30 @@ public abstract class Layer {
 
     /**
      * Call this before modifying the layer. Note that, for TileLayers, "modifying the layer"
      * includes altering the underlying CairoImage in any way. Thus you must call this function
      * before modifying the byte buffer associated with this layer.
      *
      * This function may block, so you should never call this on the main UI thread.
      */
-    public void beginTransaction(LayerView aView) {
+    public void beginTransaction() {
         if (mTransactionLock.isHeldByCurrentThread())
             throw new RuntimeException("Nested transactions are not supported");
         mTransactionLock.lock();
-        mView = aView;
         mInTransaction = true;
         mNewResolution = mResolution;
     }
 
-    public void beginTransaction() {
-        beginTransaction(null);
-    }
-
     /** Call this when you're done modifying the layer. */
     public void endTransaction() {
         if (!mInTransaction)
             throw new RuntimeException("endTransaction() called outside a transaction");
         mInTransaction = false;
         mTransactionLock.unlock();
-
-        if (mView != null)
-            mView.requestRender();
     }
 
     /** Returns true if the layer is currently in a transaction and false otherwise. */
     protected boolean inTransaction() {
         return mInTransaction;
     }
 
     /** Returns the current layer origin. */
@@ -172,38 +164,49 @@ public abstract class Layer {
     }
 
     /**
      * Subclasses may override this method to perform custom layer updates. This will be called
      * with the transaction lock held. Subclass implementations of this method must call the
      * superclass implementation. Returns false if there is still work to be done after this
      * update is complete.
      */
-    protected boolean performUpdates(GL10 gl, RenderContext context) {
+    protected boolean performUpdates(RenderContext context) {
         if (mNewOrigin != null) {
             mOrigin = mNewOrigin;
             mNewOrigin = null;
         }
         if (mNewResolution != 0.0f) {
             mResolution = mNewResolution;
             mNewResolution = 0.0f;
         }
 
         return true;
     }
 
+    protected boolean dimensionChangesPending() {
+        return (mNewOrigin != null) || (mNewResolution != 0.0f);
+    }
+
     public static class RenderContext {
         public final RectF viewport;
         public final FloatSize pageSize;
         public final float zoomFactor;
+        public final int positionHandle;
+        public final int textureHandle;
+        public final FloatBuffer coordBuffer;
 
-        public RenderContext(RectF aViewport, FloatSize aPageSize, float aZoomFactor) {
+        public RenderContext(RectF aViewport, FloatSize aPageSize, float aZoomFactor,
+                             int aPositionHandle, int aTextureHandle, FloatBuffer aCoordBuffer) {
             viewport = aViewport;
             pageSize = aPageSize;
             zoomFactor = aZoomFactor;
+            positionHandle = aPositionHandle;
+            textureHandle = aTextureHandle;
+            coordBuffer = aCoordBuffer;
         }
 
         public boolean fuzzyEquals(RenderContext other) {
             if (other == null) {
                 return false;
             }
             return RectUtils.fuzzyEquals(viewport, other.viewport)
                 && pageSize.fuzzyEquals(other.pageSize)
deleted file mode 100644
--- a/mobile/android/base/gfx/LayerClient.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/* -*- 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>
- *
- * 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;
-
-/**
- * A layer client provides tiles and manages other information used by the layer controller.
- */
-public abstract class LayerClient {
-    private LayerController mLayerController;
-
-    public abstract void geometryChanged();
-    public abstract void viewportSizeChanged();
-    protected abstract void render();
-
-    public LayerController getLayerController() { return mLayerController; }
-    public void setLayerController(LayerController layerController) {
-        mLayerController = layerController;
-    }
-
-    /**
-     * A utility function for calling Layer.beginTransaction with the
-     * appropriate LayerView.
-     */
-    public void beginTransaction(Layer aLayer) {
-        if (mLayerController != null) {
-            LayerView view = mLayerController.getView();
-            if (view != null) {
-                aLayer.beginTransaction(view);
-                return;
-            }
-        }
-
-        aLayer.beginTransaction();
-    }
-
-    // Included for symmetry.
-    public void endTransaction(Layer aLayer) {
-        aLayer.endTransaction();
-    }
-}
-
--- a/mobile/android/base/gfx/LayerController.java
+++ b/mobile/android/base/gfx/LayerController.java
@@ -35,18 +35,16 @@
  * 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.gfx.IntSize;
 import org.mozilla.gecko.gfx.Layer;
-import org.mozilla.gecko.gfx.LayerClient;
-import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.ui.PanZoomController;
 import org.mozilla.gecko.ui.SimpleScaleGestureDetector;
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoEvent;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
@@ -83,21 +81,21 @@ public class LayerController {
 
     private PanZoomController mPanZoomController;
     /*
      * The panning and zooming controller, which interprets pan and zoom gestures for us and
      * updates our visible rect appropriately.
      */
 
     private OnTouchListener mOnTouchListener;       /* The touch listener. */
-    private LayerClient mLayerClient;               /* The layer client. */
+    private GeckoLayerClient mLayerClient;          /* The layer client. */
 
     /* The new color for the checkerboard. */
     private int mCheckerboardColor;
-    private boolean mCheckerboardShouldShowChecks;
+    private boolean mCheckerboardShouldShowChecks = true;
 
     private boolean mForceRedraw;
 
     /* The extra area on the sides of the page that we want to buffer to help with
      * smooth, asynchronous scrolling. Depending on a device's support for NPOT
      * textures, this may be rounded up to the nearest power of two.
      */
     public static final IntSize MIN_BUFFER = new IntSize(512, 1024);
@@ -122,26 +120,25 @@ public class LayerController {
         mForceRedraw = true;
         mViewportMetrics = new ViewportMetrics();
         mPanZoomController = new PanZoomController(this);
         mView = new LayerView(context, this);
     }
 
     public void setRoot(Layer layer) { mRootLayer = layer; }
 
-    public void setLayerClient(LayerClient layerClient) {
+    public void setLayerClient(GeckoLayerClient layerClient) {
         mLayerClient = layerClient;
         layerClient.setLayerController(this);
     }
 
     public void setForceRedraw() {
         mForceRedraw = true;
     }
 
-    public LayerClient getLayerClient()           { return mLayerClient; }
     public Layer getRoot()                        { return mRootLayer; }
     public LayerView getView()                    { return mView; }
     public Context getContext()                   { return mContext; }
     public ViewportMetrics getViewportMetrics()   { return mViewportMetrics; }
 
     public RectF getViewport() {
         return mViewportMetrics.getViewport();
     }
@@ -211,20 +208,21 @@ public class LayerController {
 
         PointF newFocus = new PointF(size.width / 2.0f, size.height / 2.0f);
         float newZoomFactor = size.width * oldZoomFactor / oldWidth;
         mViewportMetrics.scaleTo(newZoomFactor, newFocus);
 
         Log.d(LOGTAG, "setViewportSize: " + mViewportMetrics);
         setForceRedraw();
 
-        if (mLayerClient != null)
+        if (mLayerClient != null) {
+            notifyLayerClientOfGeometryChange();
             mLayerClient.viewportSizeChanged();
+        }
 
-        notifyLayerClientOfGeometryChange();
         mPanZoomController.abortAnimation();
         mView.requestRender();
     }
 
     /** Scrolls the viewport by the given offset. You must hold the monitor while calling this. */
     public void scrollBy(PointF point) {
         PointF origin = mViewportMetrics.getOrigin();
         origin.offset(point.x, point.y);
@@ -239,17 +237,17 @@ public class LayerController {
     /** Sets the current page size. You must hold the monitor while calling this. */
     public void setPageSize(FloatSize size) {
         if (mViewportMetrics.getPageSize().fuzzyEquals(size))
             return;
 
         mViewportMetrics.setPageSize(size);
         Log.d(LOGTAG, "setPageSize: " + mViewportMetrics);
 
-        // Page size is owned by the LayerClient, so no need to notify it of
+        // Page size is owned by the layer client, so no need to notify it of
         // this change.
 
         mView.post(new Runnable() {
             public void run() {
                 mPanZoomController.pageSizeUpdated();
                 mView.requestRender();
             }
         });
@@ -315,22 +313,25 @@ public class LayerController {
         }
     }
 
     /**
      * Returns true if this controller is fine with performing a redraw operation or false if it
      * would prefer that the action didn't take place.
      */
     public boolean getRedrawHint() {
+        // FIXME: Allow redraw while a finger is down, but only if we're about to checkerboard.
+        // This requires fixing aboutToCheckerboard() to know about the new buffer size.
+
         if (mForceRedraw) {
             mForceRedraw = false;
             return true;
         }
 
-        return aboutToCheckerboard() && mPanZoomController.getRedrawHint();
+        return mPanZoomController.getRedrawHint();
     }
 
     private RectF getTileRect() {
         if (mRootLayer == null)
             return new RectF();
 
         float x = mRootLayer.getOrigin().x, y = mRootLayer.getOrigin().y;
         IntSize layerSize = mRootLayer.getSize();
@@ -361,16 +362,19 @@ public class LayerController {
      */
     public PointF convertViewPointToLayerPoint(PointF viewPoint) {
         if (mRootLayer == null)
             return null;
 
         // Undo the transforms.
         PointF origin = mViewportMetrics.getOrigin();
         PointF newPoint = new PointF(origin.x, origin.y);
+        float zoom = mViewportMetrics.getZoomFactor();
+        viewPoint.x /= zoom;
+        viewPoint.y /= zoom;
         newPoint.offset(viewPoint.x, viewPoint.y);
 
         Point rootOrigin = mRootLayer.getOrigin();
         newPoint.offset(-rootOrigin.x, -rootOrigin.y);
 
         return newPoint;
     }
 
--- a/mobile/android/base/gfx/LayerRenderer.java
+++ b/mobile/android/base/gfx/LayerRenderer.java
@@ -16,16 +16,17 @@
  *
  * 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>
+ *   Arkady Blyakher <rkadyb@mit.edu>
  *
  * 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
@@ -37,38 +38,42 @@
  * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko.gfx;
 
 import org.mozilla.gecko.gfx.BufferedCairoImage;
 import org.mozilla.gecko.gfx.IntSize;
 import org.mozilla.gecko.gfx.Layer.RenderContext;
 import org.mozilla.gecko.gfx.LayerController;
-import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.NinePatchTileLayer;
 import org.mozilla.gecko.gfx.SingleTileLayer;
 import org.mozilla.gecko.gfx.TextureReaper;
 import org.mozilla.gecko.gfx.TextureGenerator;
 import org.mozilla.gecko.gfx.TextLayer;
 import org.mozilla.gecko.gfx.TileLayer;
+import org.mozilla.gecko.GeckoAppShell;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.graphics.RegionIterator;
+import android.opengl.GLES20;
 import android.opengl.GLSurfaceView;
 import android.os.SystemClock;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.WindowManager;
 import javax.microedition.khronos.egl.EGLConfig;
 import javax.microedition.khronos.opengles.GL10;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
 import java.nio.IntBuffer;
 import java.util.ArrayList;
 
 /**
  * The layer renderer implements the rendering logic for a layer view.
  */
 public class LayerRenderer implements GLSurfaceView.Renderer {
     private static final String LOGTAG = "GeckoLayerRenderer";
@@ -87,16 +92,17 @@ public class LayerRenderer implements GL
     private final SingleTileLayer mBackgroundLayer;
     private final CheckerboardImage mCheckerboardImage;
     private final SingleTileLayer mCheckerboardLayer;
     private final NinePatchTileLayer mShadowLayer;
     private final TextLayer mFrameRateLayer;
     private final ScrollbarLayer mHorizScrollLayer;
     private final ScrollbarLayer mVertScrollLayer;
     private final FadeRunnable mFadeRunnable;
+    private final FloatBuffer mCoordBuffer;
     private RenderContext mLastPageContext;
     private int mMaxTextureSize;
 
     private ArrayList<Layer> mExtraLayers = new ArrayList<Layer>();
 
     // Dropped frames display
     private int[] mFrameTimings;
     private int mCurrentFrame, mFrameTimingsSum, mDroppedFrames;
@@ -106,16 +112,58 @@ public class LayerRenderer implements GL
     private int mFramesRendered;
     private float mCompleteFramesRendered;
     private boolean mProfileRender;
     private long mProfileOutputTime;
 
     /* Used by robocop for testing purposes */
     private IntBuffer mPixelBuffer;
 
+    // Used by GLES 2.0
+    private int mProgram;
+    private int mPositionHandle;
+    private int mTextureHandle;
+    private int mSampleHandle;
+    private int mTMatrixHandle;
+
+    // column-major matrix applied to each vertex to shift the viewport from
+    // one ranging from (-1, -1),(1,1) to (0,0),(1,1) and to scale all sizes by
+    // a factor of 2 to fill up the screen
+    private static final float[] TEXTURE_MATRIX = {
+        2.0f, 0.0f, 0.0f, 0.0f,
+        0.0f, 2.0f, 0.0f, 0.0f,
+        0.0f, 0.0f, 2.0f, 0.0f,
+        -1.0f, -1.0f, 0.0f, 1.0f
+    };
+
+    private static final int COORD_BUFFER_SIZE = 20;
+
+    // The shaders run on the GPU directly, the vertex shader is only applying the
+    // matrix transform detailed above
+    private static final String VERTEX_SHADER =
+        "uniform mat4 uTMatrix;\n" +
+        "attribute vec4 vPosition;\n" +
+        "attribute vec2 aTexCoord;\n" +
+        "varying vec2 vTexCoord;\n" +
+        "void main() {\n" +
+        "    gl_Position = uTMatrix * vPosition;\n" +
+        "    vTexCoord = aTexCoord;\n" +
+        "}\n";
+
+    // Note we flip the y-coordinate in the fragment shader from a
+    // coordinate system with (0,0) in the top left to one with (0,0) in
+    // the bottom left.
+    private static final String FRAGMENT_SHADER =
+        "precision mediump float;\n" +
+        "varying vec2 vTexCoord;\n" +
+        "uniform sampler2D sTexture;\n" +
+        "void main() {\n" +
+        "    gl_FragColor = texture2D(sTexture, vec2(vTexCoord.x, 1.0 - vTexCoord.y));\n" +
+        "}\n";
+
     public LayerRenderer(LayerView view) {
         mView = view;
 
         LayerController controller = view.getController();
 
         CairoImage backgroundImage = new BufferedCairoImage(controller.getBackgroundPattern());
         mBackgroundLayer = new SingleTileLayer(true, backgroundImage);
 
@@ -130,30 +178,79 @@ public class LayerRenderer implements GL
 
         mHorizScrollLayer = ScrollbarLayer.create(false);
         mVertScrollLayer = ScrollbarLayer.create(true);
         mFadeRunnable = new FadeRunnable();
 
         mFrameTimings = new int[60];
         mCurrentFrame = mFrameTimingsSum = mDroppedFrames = 0;
         mShowFrameRate = false;
+
+        // Initialize the FloatBuffer that will be used to store all vertices and texture
+        // coordinates in draw() commands.
+        ByteBuffer byteBuffer = GeckoAppShell.allocateDirectBuffer(COORD_BUFFER_SIZE * 4);
+        byteBuffer.order(ByteOrder.nativeOrder());
+        mCoordBuffer = byteBuffer.asFloatBuffer();
     }
 
     public void onSurfaceCreated(GL10 gl, EGLConfig config) {
         checkMonitoringEnabled();
+        createProgram();
+        activateProgram();
+    }
 
-        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
-        gl.glDisable(GL10.GL_DITHER);
-        gl.glEnable(GL10.GL_TEXTURE_2D);
+    public void createProgram() {
+        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER);
+        int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER);
+
+        mProgram = GLES20.glCreateProgram();
+        GLES20.glAttachShader(mProgram, vertexShader);   // add the vertex shader to program
+        GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
+        GLES20.glLinkProgram(mProgram);                  // creates OpenGL program executables
+
+        // Get handles to the vertex shader's vPosition, aTexCoord, sTexture, and uTMatrix members.
+        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
+        mTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoord");
+        mSampleHandle = GLES20.glGetUniformLocation(mProgram, "sTexture");
+        mTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uTMatrix");
 
         int maxTextureSizeResult[] = new int[1];
-        gl.glGetIntegerv(GL10.GL_MAX_TEXTURE_SIZE, maxTextureSizeResult, 0);
+        GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSizeResult, 0);
         mMaxTextureSize = maxTextureSizeResult[0];
+    }
+
+    // Activates the shader program.
+    public void activateProgram() {
+        // Add the program to the OpenGL environment
+        GLES20.glUseProgram(mProgram);
+
+        // Set the transformation matrix
+        GLES20.glUniformMatrix4fv(mTMatrixHandle, 1, false, TEXTURE_MATRIX, 0);
+
+        Log.e(LOGTAG, "### Position handle is " + mPositionHandle + ", texture handle is " +
+              mTextureHandle + ", last error is " + GLES20.glGetError());
+
+        // Enable the arrays from which we get the vertex and texture coordinates
+        GLES20.glEnableVertexAttribArray(mPositionHandle);
+        GLES20.glEnableVertexAttribArray(mTextureHandle);
+
+        GLES20.glUniform1i(mSampleHandle, 0);
 
         TextureGenerator.get().fill();
+
+        // TODO: Move these calls into a separate deactivate() call that is called after the
+        // underlay and overlay are rendered.
+    }
+
+    // Deactivates the shader program. This must be done to avoid crashes after returning to the
+    // Gecko C++ compositor from Java.
+    public void deactivateProgram() {
+        GLES20.glDisableVertexAttribArray(mTextureHandle);
+        GLES20.glDisableVertexAttribArray(mPositionHandle);
+        GLES20.glUseProgram(0);
     }
 
     public int getMaxTextureSize() {
         return mMaxTextureSize;
     }
 
     public void addLayer(Layer layer) {
         LayerController controller = mView.getController();
@@ -174,159 +271,24 @@ public class LayerRenderer implements GL
             mExtraLayers.remove(layer);
         }
     }
 
     /**
      * Called whenever a new frame is about to be drawn.
      */
     public void onDrawFrame(GL10 gl) {
-        long frameStartTime = SystemClock.uptimeMillis();
-
-        TextureReaper.get().reap(gl);
-        TextureGenerator.get().fill();
-
-        LayerController controller = mView.getController();
-        RenderContext screenContext = createScreenContext();
-
-        boolean updated = true;
-
-        synchronized (controller) {
-            Layer rootLayer = controller.getRoot();
-            RenderContext pageContext = createPageContext();
-
-            if (!pageContext.fuzzyEquals(mLastPageContext)) {
-                // the viewport or page changed, so show the scrollbars again
-                // as per UX decision
-                mVertScrollLayer.unfade();
-                mHorizScrollLayer.unfade();
-                mFadeRunnable.scheduleStartFade(ScrollbarLayer.FADE_DELAY);
-            } else if (mFadeRunnable.timeToFade()) {
-                boolean stillFading = mVertScrollLayer.fade() | mHorizScrollLayer.fade();
-                if (stillFading) {
-                    mFadeRunnable.scheduleNextFadeFrame();
-                }
-            }
-            mLastPageContext = pageContext;
-
-            /* Update layers. */
-            if (rootLayer != null) updated &= rootLayer.update(gl, pageContext);
-            updated &= mBackgroundLayer.update(gl, screenContext);
-            updated &= mShadowLayer.update(gl, pageContext);
-            updateCheckerboardLayer(gl, screenContext);
-            updated &= mFrameRateLayer.update(gl, screenContext);
-            updated &= mVertScrollLayer.update(gl, pageContext);
-            updated &= mHorizScrollLayer.update(gl, pageContext);
-
-            for (Layer layer : mExtraLayers)
-                updated &= layer.update(gl, pageContext);
-
-            /* Draw the background. */
-            mBackgroundLayer.draw(screenContext);
-
-            /* Draw the drop shadow, if we need to. */
-            Rect pageRect = getPageRect();
-            RectF untransformedPageRect = new RectF(0.0f, 0.0f, pageRect.width(),
-                                                    pageRect.height());
-            if (!untransformedPageRect.contains(controller.getViewport()))
-                mShadowLayer.draw(pageContext);
-
-            /* Draw the checkerboard. */
-            Rect scissorRect = transformToScissorRect(pageRect);
-            gl.glEnable(GL10.GL_SCISSOR_TEST);
-            gl.glScissor(scissorRect.left, scissorRect.top,
-                         scissorRect.width(), scissorRect.height());
-
-            mCheckerboardLayer.draw(screenContext);
-
-            /* Draw the layer the client added to us. */
-            if (rootLayer != null)
-                rootLayer.draw(pageContext);
-
-            gl.glDisable(GL10.GL_SCISSOR_TEST);
-
-            /* Draw any extra layers that were added (likely plugins) */
-            for (Layer layer : mExtraLayers)
-                layer.draw(pageContext);
-
-            /* Draw the vertical scrollbar. */
-            IntSize screenSize = new IntSize(controller.getViewportSize());
-            if (pageRect.height() > screenSize.height)
-                mVertScrollLayer.draw(pageContext);
-
-            /* Draw the horizontal scrollbar. */
-            if (pageRect.width() > screenSize.width)
-                mHorizScrollLayer.draw(pageContext);
-
-            /* Measure how much of the screen is checkerboarding */
-            if ((rootLayer != null) &&
-                (mProfileRender || PanningPerfAPI.isRecordingCheckerboard())) {
-                // Find out how much of the viewport area is valid
-                Rect viewport = RectUtils.round(pageContext.viewport);
-                Region validRegion = rootLayer.getValidRegion(pageContext);
-                validRegion.op(viewport, Region.Op.INTERSECT);
-
-                float checkerboard = 0.0f;
-                if (!(validRegion.isRect() && validRegion.getBounds().equals(viewport))) {
-                    int screenArea = viewport.width() * viewport.height();
-                    validRegion.op(viewport, Region.Op.REVERSE_DIFFERENCE);
-
-                    // XXX The assumption here is that a Region never has overlapping
-                    //     rects. This is true, as evidenced by reading the SkRegion
-                    //     source, but is not mentioned in the Android documentation,
-                    //     and so is liable to change.
-                    //     If it does change, this code will need to be reevaluated.
-                    Rect r = new Rect();
-                    int checkerboardArea = 0;
-                    for (RegionIterator i = new RegionIterator(validRegion); i.next(r);) {
-                        checkerboardArea += r.width() * r.height();
-                    }
-
-                    checkerboard = checkerboardArea / (float)screenArea;
-                }
-
-                PanningPerfAPI.recordCheckerboard(checkerboard);
-
-                mCompleteFramesRendered += 1.0f - checkerboard;
-                mFramesRendered ++;
-
-                if (frameStartTime - mProfileOutputTime > 1000) {
-                    mProfileOutputTime = frameStartTime;
-                    printCheckerboardStats();
-                }
-            }
-        }
-
-        /* Draw the FPS. */
-        if (mShowFrameRate) {
-            updateDroppedFrames(frameStartTime);
-
-            try {
-                gl.glEnable(GL10.GL_BLEND);
-                gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
-                mFrameRateLayer.draw(screenContext);
-            } finally {
-                gl.glDisable(GL10.GL_BLEND);
-            }
-        }
-
-        // If a layer update requires further work, schedule another redraw
-        if (!updated)
-            mView.requestRender();
-
-        PanningPerfAPI.recordFrameTime();
-
-        /* Used by robocop for testing purposes */
-        IntBuffer pixelBuffer = mPixelBuffer;
-        if (updated && pixelBuffer != null) {
-            synchronized (pixelBuffer) {
-                pixelBuffer.position(0);
-                gl.glReadPixels(0, 0, (int)screenContext.viewport.width(), (int)screenContext.viewport.height(), GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, pixelBuffer);
-                pixelBuffer.notify();
-            }
+        RenderContext pageContext = createPageContext(), screenContext = createScreenContext();
+        Frame frame = createFrame(pageContext, screenContext);
+        synchronized (mView.getController()) {
+            frame.beginDrawing();
+            frame.drawBackground();
+            frame.drawRootLayer();
+            frame.drawForeground();
+            frame.endDrawing();
         }
     }
 
     private void printCheckerboardStats() {
         Log.d(PROFTAG, "Frames rendered over last 1000ms: " + mCompleteFramesRendered + "/" + mFramesRendered);
         mFramesRendered = 0;
         mCompleteFramesRendered = 0;
     }
@@ -341,33 +303,38 @@ public class LayerRenderer implements GL
                 pixelBuffer.wait();
             } catch (InterruptedException ie) {
             }
             mPixelBuffer = null;
         }
         return pixelBuffer;
     }
 
-    private RenderContext createScreenContext() {
+    public RenderContext createScreenContext() {
         LayerController layerController = mView.getController();
         IntSize viewportSize = new IntSize(layerController.getViewportSize());
         RectF viewport = new RectF(0.0f, 0.0f, viewportSize.width, viewportSize.height);
         FloatSize pageSize = new FloatSize(layerController.getPageSize());
-        return new RenderContext(viewport, pageSize, 1.0f);
+        return createContext(viewport, pageSize, 1.0f);
     }
 
-    private RenderContext createPageContext() {
+    public RenderContext createPageContext() {
         LayerController layerController = mView.getController();
 
         Rect viewport = new Rect();
         layerController.getViewport().round(viewport);
 
         FloatSize pageSize = new FloatSize(layerController.getPageSize());
         float zoomFactor = layerController.getZoomFactor();
-        return new RenderContext(new RectF(viewport), pageSize, zoomFactor);
+        return createContext(new RectF(viewport), pageSize, zoomFactor);
+    }
+
+    private RenderContext createContext(RectF viewport, FloatSize pageSize, float zoomFactor) {
+        return new RenderContext(viewport, pageSize, zoomFactor, mPositionHandle, mTextureHandle,
+                                 mCoordBuffer);
     }
 
     private Rect getPageRect() {
         LayerController controller = mView.getController();
 
         Point origin = PointUtils.round(controller.getOrigin());
         IntSize pageSize = new IntSize(controller.getPageSize());
 
@@ -386,17 +353,17 @@ public class LayerRenderer implements GL
         int right = Math.min(screenSize.width, rect.right);
         int bottom = Math.min(screenSize.height, rect.bottom);
 
         return new Rect(left, screenSize.height - bottom, right,
                         (screenSize.height - bottom) + (bottom - top));
     }
 
     public void onSurfaceChanged(GL10 gl, final int width, final int height) {
-        gl.glViewport(0, 0, width, height);
+        GLES20.glViewport(0, 0, width, height);
 
         // updating the state in the view/controller/client should be
         // done on the main UI thread, not the GL renderer thread
         mView.post(new Runnable() {
             public void run() {
                 mView.setViewportSize(new IntSize(width, height));
                 moveFrameRateLayer(width, height);
             }
@@ -446,33 +413,48 @@ public class LayerRenderer implements GL
                 Context context = mView.getContext();
                 SharedPreferences preferences = context.getSharedPreferences("GeckoApp", 0);
                 mShowFrameRate = preferences.getBoolean("showFrameRate", false);
                 mProfileRender = Log.isLoggable(PROFTAG, Log.DEBUG);
             }
         }).start();
     }
 
-    private void updateCheckerboardLayer(GL10 gl, RenderContext renderContext) {
+    private void updateCheckerboardLayer(RenderContext renderContext) {
         int checkerboardColor = mView.getController().getCheckerboardColor();
         boolean showChecks = mView.getController().checkerboardShouldShowChecks();
         if (checkerboardColor == mCheckerboardImage.getColor() &&
             showChecks == mCheckerboardImage.getShowChecks()) {
             return;
         }
 
         mCheckerboardLayer.beginTransaction();
         try {
             mCheckerboardImage.update(showChecks, checkerboardColor);
             mCheckerboardLayer.invalidate();
         } finally {
             mCheckerboardLayer.endTransaction();
         }
 
-        mCheckerboardLayer.update(gl, renderContext);
+        mCheckerboardLayer.update(renderContext);
+    }
+
+    /*
+     * create a vertex shader type (GLES20.GL_VERTEX_SHADER)
+     * or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
+     */
+    private int loadShader(int type, String shaderCode) {
+        int shader = GLES20.glCreateShader(type);
+        GLES20.glShaderSource(shader, shaderCode);
+        GLES20.glCompileShader(shader);
+        return shader;
+    }
+
+    public Frame createFrame(RenderContext pageContext, RenderContext screenContext) {
+        return new Frame(pageContext, screenContext);
     }
 
     class FadeRunnable implements Runnable {
         private boolean mStarted;
         private long mRunAt;
 
         void scheduleStartFade(long delay) {
             mRunAt = SystemClock.elapsedRealtime() + delay;
@@ -500,9 +482,203 @@ public class LayerRenderer implements GL
                 mView.postDelayed(this, timeDelta);
             } else {
                 // reached the run-at time, execute
                 mStarted = false;
                 mView.requestRender();
             }
         }
     }
+
+    public class Frame {
+        // The timestamp recording the start of this frame.
+        private long mFrameStartTime;
+        // A rendering context for page-positioned layers, and one for screen-positioned layers.
+        private RenderContext mPageContext, mScreenContext;
+        // Whether a layer was updated.
+        private boolean mUpdated;
+
+        public Frame(RenderContext pageContext, RenderContext screenContext) {
+            mPageContext = pageContext;
+            mScreenContext = screenContext;
+        }
+
+        private void setScissorRect() {
+            Rect scissorRect = transformToScissorRect(getPageRect());
+            GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
+            GLES20.glScissor(scissorRect.left, scissorRect.top,
+                             scissorRect.width(), scissorRect.height());
+        }
+
+        public void beginDrawing() {
+            mFrameStartTime = SystemClock.uptimeMillis();
+
+            TextureReaper.get().reap();
+            TextureGenerator.get().fill();
+
+            mUpdated = true;
+
+            LayerController controller = mView.getController();
+            Layer rootLayer = controller.getRoot();
+
+            if (!mPageContext.fuzzyEquals(mLastPageContext)) {
+                // the viewport or page changed, so show the scrollbars again
+                // as per UX decision
+                mVertScrollLayer.unfade();
+                mHorizScrollLayer.unfade();
+                mFadeRunnable.scheduleStartFade(ScrollbarLayer.FADE_DELAY);
+            } else if (mFadeRunnable.timeToFade()) {
+                boolean stillFading = mVertScrollLayer.fade() | mHorizScrollLayer.fade();
+                if (stillFading) {
+                    mFadeRunnable.scheduleNextFadeFrame();
+                }
+            }
+            mLastPageContext = mPageContext;
+
+            /* Update layers. */
+            if (rootLayer != null) mUpdated &= rootLayer.update(mPageContext);
+            mUpdated &= mBackgroundLayer.update(mScreenContext);
+            mUpdated &= mShadowLayer.update(mPageContext);
+            updateCheckerboardLayer(mScreenContext);
+            mUpdated &= mFrameRateLayer.update(mScreenContext);
+            mUpdated &= mVertScrollLayer.update(mPageContext);
+            mUpdated &= mHorizScrollLayer.update(mPageContext);
+
+            for (Layer layer : mExtraLayers)
+                mUpdated &= layer.update(mPageContext);
+
+            GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
+
+            // If a layer update requires further work, schedule another redraw
+            if (!mUpdated)
+                mView.requestRender();
+
+            PanningPerfAPI.recordFrameTime();
+
+            /* Used by robocop for testing purposes */
+            IntBuffer pixelBuffer = mPixelBuffer;
+            if (mUpdated && pixelBuffer != null) {
+                synchronized (pixelBuffer) {
+                    pixelBuffer.position(0);
+                    GLES20.glReadPixels(0, 0, (int)mScreenContext.viewport.width(),
+                                        (int)mScreenContext.viewport.height(), GLES20.GL_RGBA,
+                                        GLES20.GL_UNSIGNED_BYTE, pixelBuffer);
+                    pixelBuffer.notify();
+                }
+            }
+        }
+
+        public void drawBackground() {
+            /* Draw the background. */
+            mBackgroundLayer.draw(mScreenContext);
+
+            /* Draw the drop shadow, if we need to. */
+            Rect pageRect = getPageRect();
+            RectF untransformedPageRect = new RectF(0.0f, 0.0f, pageRect.width(),
+                                                    pageRect.height());
+            if (!untransformedPageRect.contains(mView.getController().getViewport()))
+                mShadowLayer.draw(mPageContext);
+
+            /* Draw the checkerboard. */
+            setScissorRect();
+            mCheckerboardLayer.draw(mScreenContext);
+            GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
+        }
+
+        // Draws the layer the client added to us.
+        void drawRootLayer() {
+            Layer rootLayer = mView.getController().getRoot();
+            if (rootLayer == null) {
+                return;
+            }
+
+            setScissorRect();
+            rootLayer.draw(mPageContext);
+            GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
+        }
+
+        public void drawForeground() {
+            Rect pageRect = getPageRect();
+            LayerController controller = mView.getController();
+
+            /* Draw any extra layers that were added (likely plugins) */
+            for (Layer layer : mExtraLayers)
+                layer.draw(mPageContext);
+
+            /* Draw the vertical scrollbar. */
+            IntSize screenSize = new IntSize(controller.getViewportSize());
+            if (pageRect.height() > screenSize.height)
+                mVertScrollLayer.draw(mPageContext);
+
+            /* Draw the horizontal scrollbar. */
+            if (pageRect.width() > screenSize.width)
+                mHorizScrollLayer.draw(mPageContext);
+
+            /* Measure how much of the screen is checkerboarding */
+            Layer rootLayer = controller.getRoot();
+            if ((rootLayer != null) &&
+                (mProfileRender || PanningPerfAPI.isRecordingCheckerboard())) {
+                // Find out how much of the viewport area is valid
+                Rect viewport = RectUtils.round(mPageContext.viewport);
+                Region validRegion = rootLayer.getValidRegion(mPageContext);
+                validRegion.op(viewport, Region.Op.INTERSECT);
+
+                float checkerboard = 0.0f;
+                if (!(validRegion.isRect() && validRegion.getBounds().equals(viewport))) {
+                    int screenArea = viewport.width() * viewport.height();
+                    validRegion.op(viewport, Region.Op.REVERSE_DIFFERENCE);
+
+                    // XXX The assumption here is that a Region never has overlapping
+                    //     rects. This is true, as evidenced by reading the SkRegion
+                    //     source, but is not mentioned in the Android documentation,
+                    //     and so is liable to change.
+                    //     If it does change, this code will need to be reevaluated.
+                    Rect r = new Rect();
+                    int checkerboardArea = 0;
+                    for (RegionIterator i = new RegionIterator(validRegion); i.next(r);) {
+                        checkerboardArea += r.width() * r.height();
+                    }
+
+                    checkerboard = checkerboardArea / (float)screenArea;
+                }
+
+                PanningPerfAPI.recordCheckerboard(checkerboard);
+
+                mCompleteFramesRendered += 1.0f - checkerboard;
+                mFramesRendered ++;
+
+                if (mFrameStartTime - mProfileOutputTime > 1000) {
+                    mProfileOutputTime = mFrameStartTime;
+                    printCheckerboardStats();
+                }
+            }
+
+            /* Draw the FPS. */
+            if (mShowFrameRate) {
+                updateDroppedFrames(mFrameStartTime);
+
+		GLES20.glEnable(GLES20.GL_BLEND);
+		GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+		mFrameRateLayer.draw(mScreenContext);
+            }
+        }
+
+        public void endDrawing() {
+            // If a layer update requires further work, schedule another redraw
+            if (!mUpdated)
+                mView.requestRender();
+
+            PanningPerfAPI.recordFrameTime();
+
+            /* Used by robocop for testing purposes */
+            IntBuffer pixelBuffer = mPixelBuffer;
+            if (mUpdated && pixelBuffer != null) {
+                synchronized (pixelBuffer) {
+                    pixelBuffer.position(0);
+                    GLES20.glReadPixels(0, 0, (int)mScreenContext.viewport.width(),
+                                        (int)mScreenContext.viewport.height(), GLES20.GL_RGBA,
+                                        GLES20.GL_UNSIGNED_BYTE, pixelBuffer);
+                    pixelBuffer.notify();
+                }
+            }
+        }
+    }
 }
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -15,16 +15,17 @@
  * 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>
+ *   Arkady Blyakher <rkadyb@mit.edu>
  *
  * 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
@@ -32,38 +33,42 @@
  * 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.GeckoInputConnection;
 import org.mozilla.gecko.gfx.FloatSize;
 import org.mozilla.gecko.gfx.InputConnectionHandler;
 import org.mozilla.gecko.gfx.LayerController;
 import org.mozilla.gecko.ui.SimpleScaleGestureDetector;
 import android.content.Context;
 import android.opengl.GLSurfaceView;
+import android.view.View;
 import android.view.GestureDetector;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
+import android.view.ScaleGestureDetector;
+import android.widget.RelativeLayout;
 import android.util.Log;
 import java.nio.IntBuffer;
 import java.util.LinkedList;
 
 /**
  * A view rendered by the layer compositor.
  *
  * This view delegates to LayerRenderer to actually do the drawing. Its role is largely that of a
  * mediator between the LayerRenderer and the LayerController.
  */
-public class LayerView extends GLSurfaceView {
+public class LayerView extends FlexibleGLSurfaceView {
     private Context mContext;
     private LayerController mController;
     private InputConnectionHandler mInputConnectionHandler;
     private LayerRenderer mRenderer;
     private GestureDetector mGestureDetector;
     private SimpleScaleGestureDetector mScaleGestureDetector;
     private long mRenderTime;
     private boolean mRenderTimeReset;
@@ -74,25 +79,26 @@ public class LayerView extends GLSurface
 
     public LayerView(Context context, LayerController controller) {
         super(context);
 
         mContext = context;
         mController = controller;
         mRenderer = new LayerRenderer(this);
         setRenderer(mRenderer);
-        setRenderMode(RENDERMODE_WHEN_DIRTY);
         mGestureDetector = new GestureDetector(context, controller.getGestureListener());
         mScaleGestureDetector =
             new SimpleScaleGestureDetector(controller.getScaleGestureListener());
         mGestureDetector.setOnDoubleTapListener(controller.getDoubleTapListener());
         mInputConnectionHandler = null;
 
         setFocusable(true);
         setFocusableInTouchMode(true);
+
+        createGLThread();
     }
 
     private void addToEventQueue(MotionEvent event) {
         MotionEvent copy = MotionEvent.obtain(event);
         mEventQueue.add(copy);
     }
 
     public void processEventQueue() {
@@ -128,18 +134,20 @@ public class LayerView extends GLSurface
 
     public LayerController getController() { return mController; }
 
     /** The LayerRenderer calls this to indicate that the window has changed size. */
     public void setViewportSize(IntSize size) {
         mController.setViewportSize(new FloatSize(size));
     }
 
-    public void setInputConnectionHandler(InputConnectionHandler handler) {
-        mInputConnectionHandler = handler;
+    public GeckoInputConnection setInputConnectionHandler() {
+        GeckoInputConnection geckoInputConnection = GeckoInputConnection.create(this);
+        mInputConnectionHandler = geckoInputConnection;
+        return geckoInputConnection;
     }
 
     @Override
     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
         if (mInputConnectionHandler != null)
             return mInputConnectionHandler.onCreateInputConnection(outAttrs);
         return null;
     }
--- a/mobile/android/base/gfx/MultiTileLayer.java
+++ b/mobile/android/base/gfx/MultiTileLayer.java
@@ -1,9 +1,9 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ /* -*- 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/
  *
@@ -15,16 +15,17 @@
  * The Original Code is Mozilla Android code.
  *
  * The Initial Developer of the Original Code is Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2011-2012
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Chris Lord <chrislord.net@gmail.com>
+ *   Arkady Blyakher <rkadyb@mit.edu>
  *
  * 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
@@ -41,18 +42,18 @@ import org.mozilla.gecko.gfx.CairoImage;
 import org.mozilla.gecko.gfx.IntSize;
 import org.mozilla.gecko.gfx.SingleTileLayer;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.util.Log;
 import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
 import java.util.ArrayList;
-import javax.microedition.khronos.opengles.GL10;
 
 /**
  * Encapsulates the logic needed to draw a layer made of multiple tiles.
  *
  * TODO: Support repeating.
  */
 public class MultiTileLayer extends Layer {
     private static final String LOGTAG = "GeckoMultiTileLayer";
@@ -152,47 +153,47 @@ public class MultiTileLayer extends Laye
 
         // Set tile origins and resolution
         refreshTileMetrics(getOrigin(), getResolution(), false);
 
         mBufferSize = size;
     }
 
     @Override
-    protected boolean performUpdates(GL10 gl, RenderContext context) {
-        super.performUpdates(gl, context);
+    protected boolean performUpdates(RenderContext context) {
+        super.performUpdates(context);
 
         validateTiles();
 
         // Iterate over the tiles and decide which ones we'll be drawing
         int dirtyTiles = 0;
         boolean screenUpdateDone = false;
         SubTile firstDirtyTile = null;
         for (SubTile layer : mTiles) {
             // First do a non-texture update to make sure coordinates are
             // up-to-date.
             boolean invalid = layer.getSkipTextureUpdate();
             layer.setSkipTextureUpdate(true);
-            layer.performUpdates(gl, context);
+            layer.performUpdates(context);
 
             RectF layerBounds = layer.getBounds(context, new FloatSize(layer.getSize()));
             boolean isDirty = layer.isDirty();
 
             if (isDirty) {
                 if (!RectF.intersects(layerBounds, context.viewport)) {
                     if (firstDirtyTile == null)
                         firstDirtyTile = layer;
                     dirtyTiles ++;
                     invalid = true;
                 } else {
                     // This tile intersects with the screen and is dirty,
                     // update it immediately.
                     layer.setSkipTextureUpdate(false);
                     screenUpdateDone = true;
-                    layer.performUpdates(gl, context);
+                    layer.performUpdates(context);
                     invalid = false;
                 }
             }
 
             // We use the SkipTextureUpdate flag as a marker of a tile's
             // validity. This is required, as sometimes layers are drawn
             // without updating first, and we mustn't draw tiles that have
             // been marked as invalid that we haven't updated.
@@ -200,28 +201,28 @@ public class MultiTileLayer extends Laye
         }
 
         // Now if no tiles that intersect with the screen were updated, update
         // a single tile that doesn't (if there are any). This has the effect
         // of spreading out non-critical texture upload over time, and smoothing
         // upload-related hitches.
         if (!screenUpdateDone && firstDirtyTile != null) {
             firstDirtyTile.setSkipTextureUpdate(false);
-            firstDirtyTile.performUpdates(gl, context);
+            firstDirtyTile.performUpdates(context);
             dirtyTiles --;
         }
 
         return (dirtyTiles == 0);
     }
 
     private void refreshTileMetrics(Point origin, float resolution, boolean inTransaction) {
         IntSize size = getSize();
         for (SubTile layer : mTiles) {
             if (!inTransaction) {
-                layer.beginTransaction(null);
+                layer.beginTransaction();
             }
 
             if (origin != null) {
                 layer.setOrigin(new Point(origin.x + layer.x, origin.y + layer.y));
             }
             if (resolution >= 0.0f) {
                 layer.setResolution(resolution);
             }
@@ -240,21 +241,21 @@ public class MultiTileLayer extends Laye
 
     @Override
     public void setResolution(float newResolution) {
         super.setResolution(newResolution);
         refreshTileMetrics(null, newResolution, true);
     }
 
     @Override
-    public void beginTransaction(LayerView aView) {
-        super.beginTransaction(aView);
+    public void beginTransaction() {
+        super.beginTransaction();
 
         for (SubTile layer : mTiles) {
-            layer.beginTransaction(aView);
+            layer.beginTransaction();
         }
     }
 
     @Override
     public void endTransaction() {
         for (SubTile layer : mTiles) {
             layer.endTransaction();
         }
--- a/mobile/android/base/gfx/NinePatchTileLayer.java
+++ b/mobile/android/base/gfx/NinePatchTileLayer.java
@@ -15,16 +15,17 @@
  * 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>
+ *   Arkady Blyakher <rkadyb@mit.edu>
  *
  * 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
@@ -35,49 +36,45 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko.gfx;
 
 import org.mozilla.gecko.gfx.FloatSize;
 import android.graphics.PointF;
 import android.graphics.RectF;
-import android.opengl.GLES11;
-import android.opengl.GLES11Ext;
 import android.util.Log;
 import javax.microedition.khronos.opengles.GL10;
 import java.nio.FloatBuffer;
+import android.opengl.GLES20;
 
 /**
  * Encapsulates the logic needed to draw a nine-patch bitmap using OpenGL ES.
  *
  * For more information on nine-patch bitmaps, see the following document:
  *   http://developer.android.com/guide/topics/graphics/2d-graphics.html#nine-patch
  */
 public class NinePatchTileLayer extends TileLayer {
     private static final int PATCH_SIZE = 16;
-    private static final int TEXTURE_SIZE = 48;
+    private static final int TEXTURE_SIZE = 64;
 
     public NinePatchTileLayer(CairoImage image) {
         super(false, image);
     }
 
     @Override
     public void draw(RenderContext context) {
         if (!initialized())
             return;
 
-        GLES11.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
-        GLES11.glEnable(GL10.GL_BLEND);
-        try {
-            GLES11.glBindTexture(GL10.GL_TEXTURE_2D, getTextureID());
-            drawPatches(context);
-        } finally {
-            GLES11.glDisable(GL10.GL_BLEND);
-        }
+        GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+        GLES20.glEnable(GLES20.GL_BLEND);
+
+	GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTextureID());
+	drawPatches(context);
     }
 
     private void drawPatches(RenderContext context) {
         /*
          * We divide the nine-patch bitmap up as follows:
          *
          *    +---+---+---+
          *    | 0 | 1 | 2 |
@@ -86,39 +83,82 @@ public class NinePatchTileLayer extends 
          *    +---+---+---+
          *    | 5 | 6 | 7 |
          *    +---+---+---+
          */
 
         FloatSize size = context.pageSize;
         float width = size.width, height = size.height;
 
-        drawPatch(context, 0, 0,                                                    /* 0 */
+        drawPatch(context, 0, PATCH_SIZE * 3,                                              /* 0 */
                   0.0f, 0.0f, PATCH_SIZE, PATCH_SIZE);
-        drawPatch(context, PATCH_SIZE, 0,                                           /* 1 */
+        drawPatch(context, PATCH_SIZE, PATCH_SIZE*3,                                       /* 1 */
                   PATCH_SIZE, 0.0f, width, PATCH_SIZE);
-        drawPatch(context, PATCH_SIZE * 2, 0,                                       /* 2 */
+        drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE*3,                                   /* 2 */
                   PATCH_SIZE + width, 0.0f, PATCH_SIZE, PATCH_SIZE);
-        drawPatch(context, 0, PATCH_SIZE,                                           /* 3 */
+        drawPatch(context, 0, PATCH_SIZE * 2,                                              /* 3 */
                   0.0f, PATCH_SIZE, PATCH_SIZE, height);
-        drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE,                              /* 4 */
+        drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE * 2,                                 /* 4 */
                   PATCH_SIZE + width, PATCH_SIZE, PATCH_SIZE, height);
-        drawPatch(context, 0, PATCH_SIZE * 2,                                       /* 5 */
+        drawPatch(context, 0, PATCH_SIZE,                                                  /* 5 */
                   0.0f, PATCH_SIZE + height, PATCH_SIZE, PATCH_SIZE);
-        drawPatch(context, PATCH_SIZE, PATCH_SIZE * 2,                              /* 6 */
+        drawPatch(context, PATCH_SIZE, PATCH_SIZE,                                         /* 6 */
                   PATCH_SIZE, PATCH_SIZE + height, width, PATCH_SIZE);
-        drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE * 2,                          /* 7 */
+        drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE,                                     /* 7 */
                   PATCH_SIZE + width, PATCH_SIZE + height, PATCH_SIZE, PATCH_SIZE);
     }
 
-    private void drawPatch(RenderContext context, int textureX, int textureY, float tileX,
-                           float tileY, float tileWidth, float tileHeight) {
-        int[] cropRect = { textureX, textureY + PATCH_SIZE, PATCH_SIZE, -PATCH_SIZE };
-        GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, cropRect,
-                                0);
-
+    private void drawPatch(RenderContext context, int textureX, int textureY,
+                           float tileX, float tileY, float tileWidth, float tileHeight) {
         RectF viewport = context.viewport;
         float viewportHeight = viewport.height();
         float drawX = tileX - viewport.left - PATCH_SIZE;
         float drawY = viewportHeight - (tileY + tileHeight - viewport.top - PATCH_SIZE);
-        GLES11Ext.glDrawTexfOES(drawX, drawY, 0.0f, tileWidth, tileHeight);
+
+        float[] coords = {
+            //x, y, z, texture_x, texture_y
+            drawX/viewport.width(), drawY/viewport.height(), 0,
+            textureX/(float)TEXTURE_SIZE, textureY/(float)TEXTURE_SIZE,
+
+            drawX/viewport.width(), (drawY+tileHeight)/viewport.height(), 0,
+            textureX/(float)TEXTURE_SIZE, (textureY+PATCH_SIZE)/(float)TEXTURE_SIZE,
+
+            (drawX+tileWidth)/viewport.width(), drawY/viewport.height(), 0,
+            (textureX+PATCH_SIZE)/(float)TEXTURE_SIZE, textureY/(float)TEXTURE_SIZE,
+
+            (drawX+tileWidth)/viewport.width(), (drawY+tileHeight)/viewport.height(), 0,
+            (textureX+PATCH_SIZE)/(float)TEXTURE_SIZE, (textureY+PATCH_SIZE)/(float)TEXTURE_SIZE
+
+        };
+
+        // Get the buffer and handles from the context
+        FloatBuffer coordBuffer = context.coordBuffer;
+        int positionHandle = context.positionHandle;
+        int textureHandle = context.textureHandle;
+
+        // Make sure we are at position zero in the buffer in case other draw methods did not clean
+        // up after themselves
+        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.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
+                               GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
+                               GLES20.GL_CLAMP_TO_EDGE);
+
+        // Use bilinear filtering for both magnification and minimization of the texture. This
+        // applies only to the shadow layer so we do not incur a high overhead.
+        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
+                               GLES20.GL_LINEAR);
+        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
+                               GLES20.GL_LINEAR);
+
+        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
     }
 }
--- a/mobile/android/base/gfx/PlaceholderLayerClient.java
+++ b/mobile/android/base/gfx/PlaceholderLayerClient.java
@@ -32,83 +32,87 @@
  * 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.gfx.BufferedCairoImage;
-import org.mozilla.gecko.gfx.CairoUtils;
-import org.mozilla.gecko.gfx.FloatSize;
-import org.mozilla.gecko.gfx.LayerClient;
-import org.mozilla.gecko.gfx.PointUtils;
-import org.mozilla.gecko.gfx.SingleTileLayer;
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
-import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
-import android.graphics.PointF;
-import android.graphics.RectF;
-import android.os.Environment;
 import android.util.Log;
 import org.json.JSONException;
 import org.json.JSONObject;
-import java.io.File;
 import java.io.ByteArrayInputStream;
 import java.nio.ByteBuffer;
 
 /**
  * A stand-in for Gecko that renders cached content of the previous page. We use this until Gecko
  * is up, then we hand off control to it.
  */
-public class PlaceholderLayerClient extends LayerClient {
+public class PlaceholderLayerClient {
     private static final String LOGTAG = "PlaceholderLayerClient";
 
-    private Context mContext;
+    private final LayerController mLayerController;
+
     private ViewportMetrics mViewport;
     private boolean mViewportUnknown;
     private int mWidth, mHeight, mFormat;
     private ByteBuffer mBuffer;
 
-    private PlaceholderLayerClient(Context context) {
-        mContext = context;
-        String viewport = GeckoApp.mAppContext.getLastViewport();
+    public PlaceholderLayerClient(LayerController controller, String lastViewport) {
+        mLayerController = controller;
+
         mViewportUnknown = true;
-        if (viewport != null) {
+        if (lastViewport != null) {
             try {
-                JSONObject viewportObject = new JSONObject(viewport);
+                JSONObject viewportObject = new JSONObject(lastViewport);
                 mViewport = new ViewportMetrics(viewportObject);
                 mViewportUnknown = false;
             } catch (JSONException e) {
                 Log.e(LOGTAG, "Error parsing saved viewport!");
                 mViewport = new ViewportMetrics();
             }
         } else {
             mViewport = new ViewportMetrics();
         }
         loadScreenshot();
-    }
+
+
+        if (mViewportUnknown)
+            mViewport.setViewport(mLayerController.getViewport());
+        mLayerController.setViewportMetrics(mViewport);
+
+        BufferedCairoImage image = new BufferedCairoImage(mBuffer, mWidth, mHeight, mFormat);
+        SingleTileLayer tileLayer = new SingleTileLayer(image);
 
-    public static PlaceholderLayerClient createInstance(Context context) {
-        return new PlaceholderLayerClient(context);
+        tileLayer.beginTransaction();
+        try {
+            tileLayer.setOrigin(PointUtils.round(mViewport.getDisplayportOrigin()));
+        } finally {
+            tileLayer.endTransaction();
+        }
+
+        mLayerController.setRoot(tileLayer);
     }
 
     public void destroy() {
         if (mBuffer != null) {
             GeckoAppShell.freeDirectBuffer(mBuffer);
             mBuffer = null;
         }
     }
 
     boolean loadScreenshot() {
         if (GeckoApp.mAppContext.mLastScreen == null)
             return false;
+
         Bitmap bitmap = BitmapFactory.decodeStream(new ByteArrayInputStream(GeckoApp.mAppContext.mLastScreen));
         if (bitmap == null)
             return false;
 
         Bitmap.Config config = bitmap.getConfig();
 
         mWidth = bitmap.getWidth();
         mHeight = bitmap.getHeight();
@@ -116,40 +120,14 @@ public class PlaceholderLayerClient exte
 
         int bpp = CairoUtils.bitsPerPixelForCairoFormat(mFormat) / 8;
         mBuffer = GeckoAppShell.allocateDirectBuffer(mWidth * mHeight * bpp);
 
         bitmap.copyPixelsToBuffer(mBuffer.asIntBuffer());
 
         if (mViewportUnknown) {
             mViewport.setPageSize(new FloatSize(mWidth, mHeight));
-            if (getLayerController() != null)
-                getLayerController().setPageSize(mViewport.getPageSize());
+            mLayerController.setPageSize(mViewport.getPageSize());
         }
 
         return true;
     }
-
-    @Override
-    public void geometryChanged() { /* no-op */ }
-    @Override
-    public void viewportSizeChanged() { /* no-op */ }
-    @Override
-    public void render() { /* no-op */ }
-
-    @Override
-    public void setLayerController(LayerController layerController) {
-        super.setLayerController(layerController);
-
-        if (mViewportUnknown)
-            mViewport.setViewport(layerController.getViewport());
-        layerController.setViewportMetrics(mViewport);
-
-        BufferedCairoImage image = new BufferedCairoImage(mBuffer, mWidth, mHeight, mFormat);
-        SingleTileLayer tileLayer = new SingleTileLayer(image);
-
-        beginTransaction(tileLayer);
-        tileLayer.setOrigin(PointUtils.round(mViewport.getDisplayportOrigin()));
-        endTransaction(tileLayer);
-
-        layerController.setRoot(tileLayer);
-    }
 }
--- a/mobile/android/base/gfx/ScrollbarLayer.java
+++ b/mobile/android/base/gfx/ScrollbarLayer.java
@@ -15,16 +15,17 @@
  * The Original Code is Mozilla Android code.
  *
  * The Initial Developer of the Original Code is Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2011
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Kartikaya Gupta <kgupta@mozilla.com>
+ *   Arkady Blyakher <rkadyb@mit.edu>
  *
  * 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
@@ -40,48 +41,91 @@ package org.mozilla.gecko.gfx;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.PointF;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.opengl.GLES11;
-import android.opengl.GLES11Ext;
+import android.opengl.GLES20;
 import android.util.Log;
 import java.nio.ByteBuffer;
-import javax.microedition.khronos.opengles.GL10;
+import java.nio.FloatBuffer;
 import org.mozilla.gecko.FloatUtils;
 import org.mozilla.gecko.GeckoAppShell;
 
 /**
  * Draws a small rect. This is scaled to become a scrollbar.
  */
 public class ScrollbarLayer extends TileLayer {
     public static final long FADE_DELAY = 500; // milliseconds before fade-out starts
     private static final float FADE_AMOUNT = 0.03f; // how much (as a percent) the scrollbar should fade per frame
 
     private static final int PADDING = 1;   // gap between scrollbar and edge of viewport
     private static final int BAR_SIZE = 6;
     private static final int CAP_RADIUS = (BAR_SIZE / 2);
 
-    private static final int[] CROPRECT_MIDPIXEL = new int[] { CAP_RADIUS, CAP_RADIUS, 1, 1 };
-    private static final int[] CROPRECT_TOPCAP = new int[] { 0, CAP_RADIUS, BAR_SIZE, -CAP_RADIUS };
-    private static final int[] CROPRECT_BOTTOMCAP = new int[] { 0, BAR_SIZE, BAR_SIZE, -CAP_RADIUS };
-    private static final int[] CROPRECT_LEFTCAP = new int[] { 0, BAR_SIZE, CAP_RADIUS, -BAR_SIZE };
-    private static final int[] CROPRECT_RIGHTCAP = new int[] { CAP_RADIUS, BAR_SIZE, CAP_RADIUS, -BAR_SIZE };
-
     private final boolean mVertical;
     private final ByteBuffer mBuffer;
     private final Bitmap mBitmap;
     private final Canvas mCanvas;
     private float mOpacity;
     private boolean mFinalized = false;
 
+    // Dimensions of the texture image
+    private static final float TEX_HEIGHT = 8.0f;
+    private static final float TEX_WIDTH = 8.0f;
+
+    // Texture coordinates for the scrollbar's body
+    // We take a 1x1 pixel from the center of the image and scale it to become the bar
+    private static final float[] BODY_TEX_COORDS = {
+        // x, y
+        CAP_RADIUS/TEX_WIDTH, CAP_RADIUS/TEX_HEIGHT,
+        CAP_RADIUS/TEX_WIDTH, (CAP_RADIUS+1)/TEX_HEIGHT,
+        (CAP_RADIUS+1)/TEX_WIDTH, CAP_RADIUS/TEX_HEIGHT,
+        (CAP_RADIUS+1)/TEX_WIDTH, (CAP_RADIUS+1)/TEX_HEIGHT
+    };
+
+    // Texture coordinates for the top cap of the scrollbar
+    private static final float[] TOP_CAP_TEX_COORDS = {
+        // x, y
+        0                 , 1.0f - CAP_RADIUS/TEX_HEIGHT,
+        0                 , 1.0f,
+        BAR_SIZE/TEX_WIDTH, 1.0f - CAP_RADIUS/TEX_HEIGHT,
+        BAR_SIZE/TEX_WIDTH, 1.0f
+    };
+
+    // Texture coordinates for the bottom cap of the scrollbar
+    private static final float[] BOT_CAP_TEX_COORDS = {
+        // x, y
+        0                 , 1.0f - BAR_SIZE/TEX_HEIGHT,
+        0                 , 1.0f - CAP_RADIUS/TEX_HEIGHT,
+        BAR_SIZE/TEX_WIDTH, 1.0f - BAR_SIZE/TEX_HEIGHT,
+        BAR_SIZE/TEX_WIDTH, 1.0f - CAP_RADIUS/TEX_HEIGHT
+    };
+
+    // Texture coordinates for the left cap of the scrollbar
+    private static final float[] LEFT_CAP_TEX_COORDS = {
+        // x, y
+        0                   , 1.0f - BAR_SIZE/TEX_HEIGHT,
+        0                   , 1.0f,
+        CAP_RADIUS/TEX_WIDTH, 1.0f - BAR_SIZE/TEX_HEIGHT,
+        CAP_RADIUS/TEX_WIDTH, 1.0f
+    };
+
+    // Texture coordinates for the right cap of the scrollbar
+    private static final float[] RIGHT_CAP_TEX_COORDS = {
+        // x, y
+        CAP_RADIUS/TEX_WIDTH, 1.0f - BAR_SIZE/TEX_HEIGHT,
+        CAP_RADIUS/TEX_WIDTH, 1.0f,
+        BAR_SIZE/TEX_WIDTH  , 1.0f - BAR_SIZE/TEX_HEIGHT,
+        BAR_SIZE/TEX_WIDTH  , 1.0f
+    };
+
     private ScrollbarLayer(CairoImage image, boolean vertical, ByteBuffer buffer) {
         super(false, image);
         mVertical = vertical;
         mBuffer = buffer;
 
         IntSize size = image.getSize();
         mBitmap = Bitmap.createBitmap(size.width, size.height, Bitmap.Config.ARGB_8888);
         mCanvas = new Canvas(mBitmap);
@@ -92,23 +136,23 @@ public class ScrollbarLayer extends Tile
             if (!mFinalized && mBuffer != null)
                 GeckoAppShell.freeDirectBuffer(mBuffer);
             mFinalized = true;
         } finally {
             super.finalize();
         }
     }
 
-
     public static ScrollbarLayer create(boolean vertical) {
         // just create an empty image for now, it will get drawn
         // on demand anyway
         int imageSize = IntSize.nextPowerOfTwo(BAR_SIZE);
         ByteBuffer buffer = GeckoAppShell.allocateDirectBuffer(imageSize * imageSize * 4);
-        CairoImage image = new BufferedCairoImage(buffer, imageSize, imageSize, CairoImage.FORMAT_ARGB32);
+        CairoImage image = new BufferedCairoImage(buffer, imageSize, imageSize,
+                                                  CairoImage.FORMAT_ARGB32);
         return new ScrollbarLayer(image, vertical, buffer);
     }
 
     /**
      * Decrease the opacity of the scrollbar by one frame's worth.
      * Return true if the opacity was decreased, or false if the scrollbars
      * are already fully faded out.
      */
@@ -160,47 +204,204 @@ public class ScrollbarLayer extends Tile
         mBitmap.copyPixelsToBuffer(mBuffer.asIntBuffer());
     }
 
     @Override
     public void draw(RenderContext context) {
         if (!initialized())
             return;
 
-        try {
-            GLES11.glEnable(GL10.GL_BLEND);
-            GLES11.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
+        GLES20.glEnable(GLES20.GL_BLEND);
+        GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+
+        Rect rect = RectUtils.round(mVertical
+                ? getVerticalRect(context)
+                : getHorizontalRect(context));
+        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTextureID());
+
+        float viewWidth = context.viewport.width();
+        float viewHeight = context.viewport.height();
+
+        float top = viewHeight - rect.top;
+        float bot = viewHeight - rect.bottom;
+
+        // Coordinates for the scrollbar's body combined with the texture coordinates
+        float[] bodyCoords = {
+            // x, y, z, texture_x, texture_y
+            rect.left/viewWidth, bot/viewHeight, 0,
+            BODY_TEX_COORDS[0], BODY_TEX_COORDS[1],
 
-            Rect rect = RectUtils.round(mVertical ? getVerticalRect(context) : getHorizontalRect(context));
-            GLES11.glBindTexture(GL10.GL_TEXTURE_2D, getTextureID());
+            rect.left/viewWidth, (bot+rect.height())/viewHeight, 0,
+            BODY_TEX_COORDS[2], BODY_TEX_COORDS[3],
+
+            (rect.left+rect.width())/viewWidth, bot/viewHeight, 0,
+            BODY_TEX_COORDS[4], BODY_TEX_COORDS[5],
+
+            (rect.left+rect.width())/viewWidth, (bot+rect.height())/viewHeight, 0,
+            BODY_TEX_COORDS[6], BODY_TEX_COORDS[7]
+        };
+
+        // Get the buffer and handles from the context
+        FloatBuffer coordBuffer = context.coordBuffer;
+        int positionHandle = context.positionHandle;
+        int textureHandle = context.textureHandle;
+
+        // Make sure we are at position zero in the buffer in case other draw methods did not
+        // clean up after themselves
+        coordBuffer.position(0);
+        coordBuffer.put(bodyCoords);
+
+        // 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);
 
-            float viewHeight = context.viewport.height();
+        // 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);
+
+        // Reset the position in the buffer for the next set of vertex and texture coordinates.
+        coordBuffer.position(0);
+
+        if (mVertical) {
+            // top endcap
+            float[] topCap = {
+                // x, y, z, texture_x, texture_y
+                rect.left/viewWidth, top/viewHeight, 0,
+                TOP_CAP_TEX_COORDS[0], TOP_CAP_TEX_COORDS[1],
+
+                rect.left/viewWidth, (top+CAP_RADIUS)/viewHeight, 0,
+                TOP_CAP_TEX_COORDS[2], TOP_CAP_TEX_COORDS[3],
+
+                (rect.left+BAR_SIZE)/viewWidth, top/viewHeight, 0,
+                TOP_CAP_TEX_COORDS[4], TOP_CAP_TEX_COORDS[5],
 
-            // for the body of the scrollbar, we take a 1x1 pixel from the center of the image
-            // and scale it to become the bar
-            GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, CROPRECT_MIDPIXEL, 0);
-            GLES11Ext.glDrawTexfOES(rect.left, viewHeight - rect.bottom, 0.0f, rect.width(), rect.height());
+                (rect.left+BAR_SIZE)/viewWidth, (top+CAP_RADIUS)/viewHeight, 0,
+                TOP_CAP_TEX_COORDS[6], TOP_CAP_TEX_COORDS[7]
+            };
+
+            coordBuffer.put(topCap);
+
+            // 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);
+
+            // Reset the position in the buffer for the next set of vertex and texture
+            // coordinates.
+            coordBuffer.position(0);
 
-            if (mVertical) {
-                // top endcap
-                GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, CROPRECT_TOPCAP, 0);
-                GLES11Ext.glDrawTexfOES(rect.left, viewHeight - rect.top, 0.0f, BAR_SIZE, CAP_RADIUS);
-                // bottom endcap
-                GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, CROPRECT_BOTTOMCAP, 0);
-                GLES11Ext.glDrawTexfOES(rect.left, viewHeight - (rect.bottom + CAP_RADIUS), 0.0f, BAR_SIZE, CAP_RADIUS);
-            } else {
-                // left endcap
-                GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, CROPRECT_LEFTCAP, 0);
-                GLES11Ext.glDrawTexfOES(rect.left - CAP_RADIUS, viewHeight - rect.bottom, 0.0f, CAP_RADIUS, BAR_SIZE);
-                // right endcap
-                GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, CROPRECT_RIGHTCAP, 0);
-                GLES11Ext.glDrawTexfOES(rect.right, viewHeight - rect.bottom, 0.0f, CAP_RADIUS, BAR_SIZE);
-            }
-        } finally {
-            GLES11.glDisable(GL10.GL_BLEND);
+            // bottom endcap
+            float[] botCap = {
+                // x, y, z, texture_x, texture_y
+                rect.left/viewWidth, (bot-CAP_RADIUS)/viewHeight, 0,
+                BOT_CAP_TEX_COORDS[0], BOT_CAP_TEX_COORDS[1],
+
+                rect.left/viewWidth, (bot)/viewHeight, 0,
+                BOT_CAP_TEX_COORDS[2], BOT_CAP_TEX_COORDS[3],
+
+                (rect.left+BAR_SIZE)/viewWidth, (bot-CAP_RADIUS)/viewHeight, 0,
+                BOT_CAP_TEX_COORDS[4], BOT_CAP_TEX_COORDS[5],
+
+                (rect.left+BAR_SIZE)/viewWidth, (bot)/viewHeight, 0,
+                BOT_CAP_TEX_COORDS[6], BOT_CAP_TEX_COORDS[7]
+            };
+
+            coordBuffer.put(botCap);
+
+            // 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);
+
+            // Reset the position in the buffer for the next set of vertex and texture
+            // coordinates.
+            coordBuffer.position(0);
+        } else {
+            // left endcap
+            float[] leftCap = {
+                // x, y, z, texture_x, texture_y
+                (rect.left-CAP_RADIUS)/viewWidth, bot/viewHeight, 0,
+                LEFT_CAP_TEX_COORDS[0], LEFT_CAP_TEX_COORDS[1],
+                (rect.left-CAP_RADIUS)/viewWidth, (bot+BAR_SIZE)/viewHeight, 0,
+                LEFT_CAP_TEX_COORDS[2], LEFT_CAP_TEX_COORDS[3],
+                (rect.left)/viewWidth, bot/viewHeight, 0, LEFT_CAP_TEX_COORDS[4],
+                LEFT_CAP_TEX_COORDS[5],
+                (rect.left)/viewWidth, (bot+BAR_SIZE)/viewHeight, 0,
+                LEFT_CAP_TEX_COORDS[6], LEFT_CAP_TEX_COORDS[7]
+            };
+
+            coordBuffer.put(leftCap);
+
+            // 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);
+
+            // Reset the position in the buffer for the next set of vertex and texture
+            // coordinates.
+            coordBuffer.position(0);
+
+            // right endcap
+            float[] rightCap = {
+                // x, y, z, texture_x, texture_y
+                rect.right/viewWidth, (bot)/viewHeight, 0,
+                RIGHT_CAP_TEX_COORDS[0], RIGHT_CAP_TEX_COORDS[1],
+
+                rect.right/viewWidth, (bot+BAR_SIZE)/viewHeight, 0,
+                RIGHT_CAP_TEX_COORDS[2], RIGHT_CAP_TEX_COORDS[3],
+
+                (rect.right+CAP_RADIUS)/viewWidth, (bot)/viewHeight, 0,
+                RIGHT_CAP_TEX_COORDS[4], RIGHT_CAP_TEX_COORDS[5],
+
+                (rect.right+CAP_RADIUS)/viewWidth, (bot+BAR_SIZE)/viewHeight, 0,
+                RIGHT_CAP_TEX_COORDS[6], RIGHT_CAP_TEX_COORDS[7]
+            };
+
+            coordBuffer.put(rightCap);
+
+            // 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);
         }
     }
 
     private RectF getVerticalRect(RenderContext context) {
         RectF viewport = context.viewport;
         FloatSize pageSize = context.pageSize;
         float barStart = (viewport.height() * viewport.top / pageSize.height) + CAP_RADIUS;
         float barEnd = (viewport.height() * viewport.bottom / pageSize.height) - CAP_RADIUS;
--- a/mobile/android/base/gfx/SingleTileLayer.java
+++ b/mobile/android/base/gfx/SingleTileLayer.java
@@ -15,16 +15,17 @@
  * 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>
+ *   Arkady Blyakher <rkadyb@mit.edu>
  *
  * 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
@@ -39,21 +40,18 @@ package org.mozilla.gecko.gfx;
 
 import org.mozilla.gecko.gfx.CairoImage;
 import org.mozilla.gecko.gfx.CairoUtils;
 import org.mozilla.gecko.gfx.IntSize;
 import org.mozilla.gecko.gfx.LayerController;
 import org.mozilla.gecko.gfx.TileLayer;
 import android.graphics.PointF;
 import android.graphics.RectF;
-import android.opengl.GLES11;
-import android.opengl.GLES11Ext;
+import android.opengl.GLES20;
 import android.util.Log;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
 import java.nio.FloatBuffer;
 import javax.microedition.khronos.opengles.GL10;
 
 /**
  * Encapsulates the logic needed to draw a single textured tile.
  *
  * TODO: Repeating textures really should be their own type of layer.
  */
@@ -66,36 +64,64 @@ public class SingleTileLayer extends Til
 
     @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;
 
-        GLES11.glBindTexture(GL10.GL_TEXTURE_2D, getTextureID());
+        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTextureID());
 
         RectF bounds;
         int[] cropRect;
         IntSize size = getSize();
         RectF viewport = context.viewport;
 
         if (repeats()) {
             bounds = new RectF(0.0f, 0.0f, viewport.width(), viewport.height());
             int width = Math.round(viewport.width());
-            int height = Math.round(-viewport.height());
-            cropRect = new int[] { 0, size.height, width, height };
+            int height = Math.round(viewport.height());
+            cropRect = new int[] { 0, 0, width, height };
         } else {
             bounds = getBounds(context, new FloatSize(size));
-            cropRect = new int[] { 0, size.height, size.width, -size.height };
+            cropRect = new int[] { 0, 0, size.width, size.height };
         }
 
-        GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, cropRect,
-                                0);
-
         float height = bounds.height();
         float left = bounds.left - viewport.left;
         float top = viewport.height() - (bounds.top + height - viewport.top);
 
-        GLES11Ext.glDrawTexfOES(left, top, 0.0f, bounds.width(), height);
+        float[] coords = {
+            //x, y, z, texture_x, texture_y
+            left/viewport.width(), top/viewport.height(), 0,
+            cropRect[0]/(float)size.width, cropRect[1]/(float)size.height,
+
+            left/viewport.width(), (top+height)/viewport.height(), 0,
+            cropRect[0]/(float)size.width, cropRect[3]/(float)size.height,
+
+            (left+bounds.width())/viewport.width(), top/viewport.height(), 0,
+            cropRect[2]/(float)size.width, cropRect[1]/(float)size.height,
+
+            (left+bounds.width())/viewport.width(), (top+height)/viewport.height(), 0,
+            cropRect[2]/(float)size.width, cropRect[3]/(float)size.height
+        };
+
+        FloatBuffer coordBuffer = context.coordBuffer;
+        int positionHandle = context.positionHandle;
+        int textureHandle = context.textureHandle;
+
+        // Make sure we are at position zero in the buffer in case other draw methods did not clean
+        // up after themselves
+        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);
     }
 }
 
--- a/mobile/android/base/gfx/SurfaceTextureLayer.java
+++ b/mobile/android/base/gfx/SurfaceTextureLayer.java
@@ -51,16 +51,17 @@ import android.view.Surface;
 import javax.microedition.khronos.opengles.GL10;
 import javax.microedition.khronos.opengles.GL11Ext;
 import java.nio.Buffer;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.FloatBuffer;
 import android.hardware.Camera;
 
+// TODO: Port this to GLES 2.0.
 public class SurfaceTextureLayer extends Layer implements SurfaceTexture.OnFrameAvailableListener {
     private static final String LOGTAG = "SurfaceTextureLayer";
     private static final int LOCAL_GL_TEXTURE_EXTERNAL_OES = 0x00008d65; // This is only defined in API level 15 for some reason (Android 4.0.3)
 
     private final SurfaceTexture mSurfaceTexture;
     private final Surface mSurface;
     private int mTextureId;
     private boolean mHaveFrame;
@@ -135,17 +136,17 @@ public class SurfaceTextureLayer extends
         FloatBuffer floatBuffer = byteBuffer.asFloatBuffer();
         floatBuffer.put(input);
 		floatBuffer.position(0);
 
         return floatBuffer;
     }
 
     public void update(Point origin, IntSize size, float resolution, boolean inverted, boolean blend) {
-        beginTransaction(null);
+        beginTransaction();
 
         setOrigin(origin);
         setResolution(resolution);
 
         mNewSize = size;
         mNewInverted = inverted;
         mNewBlend = blend;
 
@@ -168,31 +169,31 @@ public class SurfaceTextureLayer extends
                 Log.e(LOGTAG, "some other exception while invoking release method on mSurfaceTexture", e);
             }
         }
         if (mTextureId > 0)
             TextureReaper.get().add(mTextureId);
     }
 
     @Override
-    protected boolean performUpdates(GL10 gl, RenderContext context) {
-        super.performUpdates(gl, context);
+    protected boolean performUpdates(RenderContext context) {
+        super.performUpdates(context);
 
         if (mNewSize != null) {
             mSize = mNewSize;
             mNewSize = null;
         }
 
         mInverted = mNewInverted;
         mBlend = mNewBlend;
 
-        gl.glEnable(LOCAL_GL_TEXTURE_EXTERNAL_OES);
-        gl.glBindTexture(LOCAL_GL_TEXTURE_EXTERNAL_OES, mTextureId);
+        GLES11.glEnable(LOCAL_GL_TEXTURE_EXTERNAL_OES);
+        GLES11.glBindTexture(LOCAL_GL_TEXTURE_EXTERNAL_OES, mTextureId);
         mSurfaceTexture.updateTexImage();
-        gl.glDisable(LOCAL_GL_TEXTURE_EXTERNAL_OES);
+        GLES11.glDisable(LOCAL_GL_TEXTURE_EXTERNAL_OES);
 
         // FIXME: we should return true and rely on onFrameAvailable, but
         // that isn't working for some reason
         return false;
     }
 
     private float mapToGLCoords(float input, float viewport, boolean flip) {
         if (flip) input = viewport - input;
@@ -252,20 +253,16 @@ public class SurfaceTextureLayer extends
         // Draw the vertices as triangle strip
         GLES11.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, vertices.length / 2);
 
         // Clean up
         GLES11.glDisableClientState(GL10.GL_VERTEX_ARRAY);
         GLES11.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
         GLES11.glDisable(LOCAL_GL_TEXTURE_EXTERNAL_OES);
         GLES11.glLoadIdentity();
-
-        if (mBlend) {
-            GLES11.glDisable(GL10.GL_BLEND);
-        }
     }
 
     public SurfaceTexture getSurfaceTexture() {
         return mSurfaceTexture;
     }
 
     public Surface getSurface() {
         return mSurface;
--- a/mobile/android/base/gfx/TextureGenerator.java
+++ b/mobile/android/base/gfx/TextureGenerator.java
@@ -32,17 +32,17 @@
  * 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 android.opengl.GLES10;
+import android.opengl.GLES20;
 import java.util.Stack;
 
 public class TextureGenerator {
     private static final int MIN_TEXTURES = 5;
 
     private static TextureGenerator sSharedInstance;
     private Stack<Integer> mTextureIds;
 
@@ -59,15 +59,15 @@ public class TextureGenerator {
             return 0;
 
         return (int)mTextureIds.pop();
     }
 
     public synchronized void fill() {
         int[] textures = new int[1];
         while (mTextureIds.size() < MIN_TEXTURES) {
-            GLES10.glGenTextures(1, textures, 0);
+            GLES20.glGenTextures(1, textures, 0);
             mTextureIds.push(textures[0]);
         }
     }
 }
 
 
--- a/mobile/android/base/gfx/TextureReaper.java
+++ b/mobile/android/base/gfx/TextureReaper.java
@@ -15,16 +15,17 @@
  * 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>
+ *   Arkady Blyakher <rkadyb@mit.edu>
  *
  * 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
@@ -32,17 +33,17 @@
  * 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 javax.microedition.khronos.opengles.GL10;
+import android.opengl.GLES20;
 import java.util.ArrayList;
 
 /** Manages a list of dead tiles, so we don't leak resources. */
 public class TextureReaper {
     private static TextureReaper sSharedInstance;
     private ArrayList<Integer> mDeadTextureIDs;
 
     private TextureReaper() { mDeadTextureIDs = new ArrayList<Integer>(); }
@@ -57,19 +58,19 @@ public class TextureReaper {
         for (int textureID : textureIDs)
             add(textureID);
     }
 
     public void add(int textureID) {
         mDeadTextureIDs.add(textureID);
     }
 
-    public void reap(GL10 gl) {
+    public void reap() {
         int[] deadTextureIDs = new int[mDeadTextureIDs.size()];
         for (int i = 0; i < deadTextureIDs.length; i++)
             deadTextureIDs[i] = mDeadTextureIDs.get(i);
         mDeadTextureIDs.clear();
 
-        gl.glDeleteTextures(deadTextureIDs.length, deadTextureIDs, 0);
+        GLES20.glDeleteTextures(deadTextureIDs.length, deadTextureIDs, 0);
     }
 }
 
 
--- a/mobile/android/base/gfx/TileLayer.java
+++ b/mobile/android/base/gfx/TileLayer.java
@@ -15,16 +15,17 @@
  * 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>
+ *   Arkady Blyakher <rkadyb@mit.edu>
  *
  * 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
@@ -38,18 +39,16 @@
 package org.mozilla.gecko.gfx;
 
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.opengl.GLES20;
 import android.util.Log;
-import javax.microedition.khronos.opengles.GL10;
-import javax.microedition.khronos.opengles.GL11Ext;
 import java.nio.Buffer;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.FloatBuffer;
 
 /**
  * Base class for tile layers, which encapsulate the logic needed to draw textured tiles in OpenGL
  * ES.
@@ -101,17 +100,17 @@ public abstract class TileLayer extends 
         IntSize bufferSize = mImage.getSize();
         invalidate(new Rect(0, 0, bufferSize.width, bufferSize.height));
     }
 
     public boolean isDirty() {
         return mImage.getSize().isPositive() && (mTextureIDs == null || !mDirtyRect.isEmpty());
     }
 
-    private void validateTexture(GL10 gl) {
+    private void validateTexture() {
         /* Calculate the ideal texture size. This must be a power of two if
          * the texture is repeated or OpenGL ES 2.0 isn't supported, as
          * OpenGL ES 2.0 is required for NPOT texture support (without
          * extensions), but doesn't support repeating NPOT textures.
          *
          * XXX Currently, we don't pick a GLES 2.0 context, so always round.
          */
         IntSize bufferSize = mImage.getSize();
@@ -124,100 +123,92 @@ public abstract class TileLayer extends 
 
             // Delete the old texture
             if (mTextureIDs != null) {
                 TextureReaper.get().add(mTextureIDs);
                 mTextureIDs = null;
 
                 // Free the texture immediately, so we don't incur a
                 // temporarily increased memory usage.
-                TextureReaper.get().reap(gl);
+                TextureReaper.get().reap();
             }
         }
     }
 
     /** Tells the tile not to update the texture on the next update. */
     public void setSkipTextureUpdate(boolean skip) {
         mSkipTextureUpdate = skip;
     }
 
     public boolean getSkipTextureUpdate() {
         return mSkipTextureUpdate;
     }
 
     @Override
-    protected boolean performUpdates(GL10 gl, RenderContext context) {
-        super.performUpdates(gl, context);
+    protected boolean performUpdates(RenderContext context) {
+        super.performUpdates(context);
 
         if (mSkipTextureUpdate) {
             return false;
         }
 
         // Reallocate the texture if the size has changed
-        validateTexture(gl);
+        validateTexture();
 
         // Don't do any work if the image has an invalid size.
         if (!mImage.getSize().isPositive())
             return true;
 
         // If we haven't allocated a texture, assume the whole region is dirty
         if (mTextureIDs == null) {
-            uploadFullTexture(gl);
+            uploadFullTexture();
         } else {
-            uploadDirtyRect(gl, mDirtyRect);
+            uploadDirtyRect(mDirtyRect);
         }
 
         mDirtyRect.setEmpty();
 
         return true;
     }
 
-    private void uploadFullTexture(GL10 gl) {
+    private void uploadFullTexture() {
         IntSize bufferSize = mImage.getSize();
-        uploadDirtyRect(gl, new Rect(0, 0, bufferSize.width, bufferSize.height));
+        uploadDirtyRect(new Rect(0, 0, bufferSize.width, bufferSize.height));
     }
 
-    private void uploadDirtyRect(GL10 gl, Rect dirtyRect) {
+    private void uploadDirtyRect(Rect dirtyRect) {
         // If we have nothing to upload, just return for now
         if (dirtyRect.isEmpty())
             return;
 
         // It's possible that the buffer will be null, check for that and return
         ByteBuffer imageBuffer = mImage.getBuffer();
         if (imageBuffer == null)
             return;
 
         boolean newlyCreated = false;
 
         if (mTextureIDs == null) {
             mTextureIDs = new int[1];
-            gl.glGenTextures(mTextureIDs.length, mTextureIDs, 0);
+            GLES20.glGenTextures(mTextureIDs.length, mTextureIDs, 0);
             newlyCreated = true;
         }
 
         IntSize bufferSize = mImage.getSize();
         Rect bufferRect = new Rect(0, 0, bufferSize.width, bufferSize.height);
 
         int cairoFormat = mImage.getFormat();
         CairoGLInfo glInfo = new CairoGLInfo(cairoFormat);
 
-        bindAndSetGLParameters(gl);
+        bindAndSetGLParameters();
 
         if (newlyCreated || dirtyRect.contains(bufferRect)) {
-            if (mSize.equals(bufferSize)) {
-                gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width, mSize.height,
-                                0, glInfo.format, glInfo.type, imageBuffer);
-                return;
-            } else {
-                gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width, mSize.height,
-                                0, glInfo.format, glInfo.type, null);
-                gl.glTexSubImage2D(GL10.GL_TEXTURE_2D, 0, 0, 0, bufferSize.width, bufferSize.height,
-                                   glInfo.format, glInfo.type, imageBuffer);
-                return;
-            }
+            GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width,
+                                mSize.height, 0, glInfo.format, glInfo.type, imageBuffer);
+            return;
         }
 
         // Make sure that the dirty region intersects with the buffer rect,
         // otherwise we'll end up with an invalid buffer pointer.
         if (!Rect.intersects(dirtyRect, bufferRect)) {
             return;
         }
 
@@ -232,24 +223,26 @@ public abstract class TileLayer extends 
         int bpp = CairoUtils.bitsPerPixelForCairoFormat(cairoFormat) / 8;
         int position = dirtyRect.top * bufferSize.width * bpp;
         if (position > viewBuffer.limit()) {
             Log.e(LOGTAG, "### Position outside tile! " + dirtyRect.top);
             return;
         }
 
         viewBuffer.position(position);
-        gl.glTexSubImage2D(GL10.GL_TEXTURE_2D, 0, 0, dirtyRect.top, bufferSize.width,
-                           Math.min(bufferSize.height - dirtyRect.top, dirtyRect.height()),
-                           glInfo.format, glInfo.type, viewBuffer);
+        GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, dirtyRect.top, bufferSize.width,
+                               Math.min(bufferSize.height - dirtyRect.top, dirtyRect.height()),
+                               glInfo.format, glInfo.type, viewBuffer);
     }
 
-    private void bindAndSetGLParameters(GL10 gl) {
-        gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIDs[0]);
-        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
-        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
+    private void bindAndSetGLParameters() {
+        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIDs[0]);
+        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
+                               GLES20.GL_NEAREST);
+        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
+                               GLES20.GL_LINEAR);
 
-        int repeatMode = mRepeat ? GL10.GL_REPEAT : GL10.GL_CLAMP_TO_EDGE;
-        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, repeatMode);
-        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, repeatMode);
+        int repeatMode = mRepeat ? 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);
     }
 }
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/ViewTransform.java
@@ -0,0 +1,51 @@
+/* -*- 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>
+ *
+ * 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;
+
+public class ViewTransform {
+    public float x;
+    public float y;
+    public float scale;
+
+    public ViewTransform(float inX, float inY, float inScale) {
+        x = inX;
+        y = inY;
+        scale = inScale;
+    }
+}
+
--- a/mobile/android/base/gfx/ViewportMetrics.java
+++ b/mobile/android/base/gfx/ViewportMetrics.java
@@ -57,98 +57,54 @@ import android.util.Log;
  * ViewportMetrics manages state and contains some utility functions related to
  * the page viewport for the Gecko layer client to use.
  */
 public class ViewportMetrics {
     private static final String LOGTAG = "GeckoViewportMetrics";
 
     private FloatSize mPageSize;
     private RectF mViewportRect;
-    private PointF mViewportOffset;
     private float mZoomFactor;
 
-    // A scale from -1,-1 to 1,1 that represents what edge of the displayport
-    // we want the viewport to be biased towards.
-    private PointF mViewportBias;
-    private static final float MAX_BIAS = 0.8f;
-
     public ViewportMetrics() {
         DisplayMetrics metrics = new DisplayMetrics();
         GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
 
         mPageSize = new FloatSize(metrics.widthPixels, metrics.heightPixels);
         mViewportRect = new RectF(0, 0, metrics.widthPixels, metrics.heightPixels);
-        mViewportOffset = new PointF(0, 0);
         mZoomFactor = 1.0f;
-        mViewportBias = new PointF(0.0f, 0.0f);
     }
 
     public ViewportMetrics(ViewportMetrics viewport) {
         mPageSize = new FloatSize(viewport.getPageSize());
         mViewportRect = new RectF(viewport.getViewport());
-        PointF offset = viewport.getViewportOffset();
-        mViewportOffset = new PointF(offset.x, offset.y);
         mZoomFactor = viewport.getZoomFactor();
-        mViewportBias = viewport.mViewportBias;
     }
 
     public ViewportMetrics(JSONObject json) throws JSONException {
         float x = (float)json.getDouble("x");
         float y = (float)json.getDouble("y");
         float width = (float)json.getDouble("width");
         float height = (float)json.getDouble("height");
         float pageWidth = (float)json.getDouble("pageWidth");
         float pageHeight = (float)json.getDouble("pageHeight");
-        float offsetX = (float)json.getDouble("offsetX");
-        float offsetY = (float)json.getDouble("offsetY");
         float zoom = (float)json.getDouble("zoom");
 
         mPageSize = new FloatSize(pageWidth, pageHeight);
         mViewportRect = new RectF(x, y, x + width, y + height);
-        mViewportOffset = new PointF(offsetX, offsetY);
         mZoomFactor = zoom;
-        mViewportBias = new PointF(0.0f, 0.0f);
-    }
-
-    public PointF getOptimumViewportOffset(IntSize displayportSize) {
-        /* XXX Until bug #524925 is fixed, changing the viewport origin will
-         *     cause unnecessary relayouts. This may cause rendering time to
-         *     increase and should be considered.
-         */
-        RectF viewport = getClampedViewport();
-
-        FloatSize bufferSpace = new FloatSize(displayportSize.width - viewport.width(),
-                                            displayportSize.height - viewport.height());
-        PointF optimumOffset =
-            new PointF(bufferSpace.width * ((mViewportBias.x + 1.0f) / 2.0f),
-                       bufferSpace.height * ((mViewportBias.y + 1.0f) / 2.0f));
-
-        // Make sure this offset won't cause wasted pixels in the displayport
-        // (i.e. make sure the resultant displayport intersects with the page
-        //  as much as possible)
-        if (viewport.left - optimumOffset.x < 0)
-          optimumOffset.x = viewport.left;
-        else if ((bufferSpace.width - optimumOffset.x) + viewport.right > mPageSize.width)
-          optimumOffset.x = bufferSpace.width - (mPageSize.width - viewport.right);
-
-        if (viewport.top - optimumOffset.y < 0)
-          optimumOffset.y = viewport.top;
-        else if ((bufferSpace.height - optimumOffset.y) + viewport.bottom > mPageSize.height)
-          optimumOffset.y = bufferSpace.height - (mPageSize.height - viewport.bottom);
-
-        return new PointF(Math.round(optimumOffset.x), Math.round(optimumOffset.y));
     }
 
     public PointF getOrigin() {
         return new PointF(mViewportRect.left, mViewportRect.top);
     }
 
     public PointF getDisplayportOrigin() {
-        return new PointF(mViewportRect.left - mViewportOffset.x,
-                          mViewportRect.top - mViewportOffset.y);
+        return new PointF(mViewportRect.left,
+                          mViewportRect.top);
     }
 
     public FloatSize getSize() {
         return new FloatSize(mViewportRect.width(), mViewportRect.height());
     }
 
     public RectF getViewport() {
         return mViewportRect;
@@ -169,20 +125,16 @@ public class ViewportMetrics {
         if (clampedViewport.bottom > mPageSize.height)
             clampedViewport.offset(0, mPageSize.height - clampedViewport.bottom);
         if (clampedViewport.top < 0)
             clampedViewport.offset(0, -clampedViewport.top);
 
         return clampedViewport;
     }
 
-    public PointF getViewportOffset() {
-        return mViewportOffset;
-    }
-
     public FloatSize getPageSize() {
         return mPageSize;
     }
 
     public float getZoomFactor() {
         return mZoomFactor;
     }
 
@@ -190,47 +142,26 @@ public class ViewportMetrics {
         mPageSize = pageSize;
     }
 
     public void setViewport(RectF viewport) {
         mViewportRect = viewport;
     }
 
     public void setOrigin(PointF origin) {
-        // When the origin is set, we compare it with the last value set and
-        // change the viewport bias accordingly, so that any viewport based
-        // on these metrics will have a larger buffer in the direction of
-        // movement.
-
-        // XXX Note the comment about bug #524925 in getOptimumViewportOffset.
-        //     Ideally, the viewport bias would be a sliding scale, but we
-        //     don't want to change it too often at the moment.
-        if (FloatUtils.fuzzyEquals(origin.x, mViewportRect.left))
-            mViewportBias.x = 0;
-        else
-            mViewportBias.x = ((mViewportRect.left - origin.x) > 0) ? MAX_BIAS : -MAX_BIAS;
-        if (FloatUtils.fuzzyEquals(origin.y, mViewportRect.top))
-            mViewportBias.y = 0;
-        else
-            mViewportBias.y = ((mViewportRect.top - origin.y) > 0) ? MAX_BIAS : -MAX_BIAS;
-
         mViewportRect.set(origin.x, origin.y,
                           origin.x + mViewportRect.width(),
                           origin.y + mViewportRect.height());
     }
 
     public void setSize(FloatSize size) {
         mViewportRect.right = mViewportRect.left + size.width;
         mViewportRect.bottom = mViewportRect.top + size.height;
     }
 
-    public void setViewportOffset(PointF offset) {
-        mViewportOffset = offset;
-    }
-
     public void setZoomFactor(float zoomFactor) {
         mZoomFactor = zoomFactor;
     }
 
     /* This will set the zoom factor and re-scale page-size and viewport offset
      * accordingly. The given focus will remain at the same point on the screen
      * after scaling.
      */
@@ -241,72 +172,57 @@ public class ViewportMetrics {
 
         PointF origin = getOrigin();
         origin.offset(focus.x, focus.y);
         origin = PointUtils.scale(origin, scaleFactor);
         origin.offset(-focus.x, -focus.y);
         setOrigin(origin);
 
         mZoomFactor = newZoomFactor;
-
-        // Similar to setOrigin, set the viewport bias based on the focal point
-        // of the zoom so that a viewport based on these metrics will have a
-        // larger buffer based on the direction of movement when scaling.
-        //
-        // This is biased towards scaling outwards, as zooming in doesn't
-        // really require a viewport bias.
-        mViewportBias.set(((focus.x / mViewportRect.width()) * (2.0f * MAX_BIAS)) - MAX_BIAS,
-                          ((focus.y / mViewportRect.height()) * (2.0f * MAX_BIAS)) - MAX_BIAS);
     }
 
     /*
      * Returns the viewport metrics that represent a linear transition between `from` and `to` at
      * time `t`, which is on the scale [0, 1). This function interpolates the viewport rect, the
      * page size, the offset, and the zoom factor.
      */
     public ViewportMetrics interpolate(ViewportMetrics to, float t) {
         ViewportMetrics result = new ViewportMetrics();
         result.mPageSize = mPageSize.interpolate(to.mPageSize, t);
         result.mZoomFactor = FloatUtils.interpolate(mZoomFactor, to.mZoomFactor, t);
         result.mViewportRect = RectUtils.interpolate(mViewportRect, to.mViewportRect, t);
-        result.mViewportOffset = PointUtils.interpolate(mViewportOffset, to.mViewportOffset, t);
         return result;
     }
 
     public boolean fuzzyEquals(ViewportMetrics other) {
         return mPageSize.fuzzyEquals(other.mPageSize)
             && RectUtils.fuzzyEquals(mViewportRect, other.mViewportRect)
-            && FloatUtils.fuzzyEquals(mViewportOffset, other.mViewportOffset)
             && FloatUtils.fuzzyEquals(mZoomFactor, other.mZoomFactor);
     }
 
     public String toJSON() {
         // Round off height and width. Since the height and width are the size of the screen, it
         // makes no sense to send non-integer coordinates to Gecko.
         int height = Math.round(mViewportRect.height());
         int width = Math.round(mViewportRect.width());
 
         StringBuffer sb = new StringBuffer(256);
         sb.append("{ \"x\" : ").append(mViewportRect.left)
           .append(", \"y\" : ").append(mViewportRect.top)
           .append(", \"width\" : ").append(width)
           .append(", \"height\" : ").append(height)
           .append(", \"pageWidth\" : ").append(mPageSize.width)
           .append(", \"pageHeight\" : ").append(mPageSize.height)
-          .append(", \"offsetX\" : ").append(mViewportOffset.x)
-          .append(", \"offsetY\" : ").append(mViewportOffset.y)
           .append(", \"zoom\" : ").append(mZoomFactor)
           .append(" }");
         return sb.toString();
     }
 
     @Override
     public String toString() {
         StringBuffer buff = new StringBuffer(128);
         buff.append("v=").append(mViewportRect.toString())
             .append(" p=").append(mPageSize.toString())
-            .append(" z=").append(mZoomFactor)
-            .append(" o=").append(mViewportOffset.x)
-            .append(',').append(mViewportOffset.y);
+            .append(" z=").append(mZoomFactor);
         return buff.toString();
     }
 }
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/VirtualLayer.java
@@ -0,0 +1,80 @@
+/* -*- 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 android.graphics.Point;
+
+public class VirtualLayer extends Layer {
+    private Listener mListener;
+    private IntSize mSize;
+
+    public void setListener(Listener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public void draw(RenderContext context) {
+        // No-op.
+    }
+
+    @Override
+    public IntSize getSize() {
+        return mSize;
+    }
+
+    public void setSize(IntSize size) {
+        mSize = size;
+    }
+
+    @Override
+    protected boolean performUpdates(RenderContext context) {
+        boolean dimensionsChanged = dimensionChangesPending();
+        boolean result = super.performUpdates(context);
+        if (dimensionsChanged && mListener != null) {
+            mListener.dimensionsChanged(getOrigin(), getResolution());
+        }
+
+        return result;
+    }
+
+    public interface Listener {
+        void dimensionsChanged(Point newOrigin, float newResolution);
+    }
+}
+
--- a/mobile/android/base/gfx/WidgetTileLayer.java
+++ b/mobile/android/base/gfx/WidgetTileLayer.java
@@ -15,16 +15,17 @@
  * 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):
  *   James Willcox <jwillcox@mozilla.com>
+ *   Arkady Blyakher <rkadyb@mit.edu>
  *
  * 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
@@ -35,21 +36,20 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko.gfx;
 
 import org.mozilla.gecko.gfx.LayerController;
 import org.mozilla.gecko.gfx.SingleTileLayer;
 import org.mozilla.gecko.GeckoAppShell;
-import android.opengl.GLES11;
-import android.opengl.GLES11Ext;
 import android.graphics.RectF;
 import android.util.Log;
-import javax.microedition.khronos.opengles.GL10;
+import android.opengl.GLES20;
+import java.nio.FloatBuffer;
 
 /**
  * Encapsulates the logic needed to draw the single-tiled Gecko texture
  */
 public class WidgetTileLayer extends Layer {
     private static final String LOGTAG = "WidgetTileLayer";
 
     private int[] mTextureIDs;
@@ -60,69 +60,100 @@ public class WidgetTileLayer extends Lay
     }
 
     protected boolean initialized() { return mTextureIDs != null; }
 
     @Override
     public IntSize getSize() { return mImage.getSize(); }
 
     protected void bindAndSetGLParameters() {
-        GLES11.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIDs[0]);
-        GLES11.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
-        GLES11.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
+        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIDs[0]);
+        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
+        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
     }
 
     @Override
     protected void finalize() throws Throwable {
         if (mTextureIDs != null)
             TextureReaper.get().add(mTextureIDs);
     }
 
     @Override
-    protected boolean performUpdates(GL10 gl, RenderContext context) {
-        super.performUpdates(gl, context);
+    protected boolean performUpdates(RenderContext context) {
+        super.performUpdates(context);
 
         if (mTextureIDs == null) {
             mTextureIDs = new int[1];
-            GLES11.glGenTextures(1, mTextureIDs, 0);
+            GLES20.glGenTextures(1, mTextureIDs, 0);
         }
 
         bindAndSetGLParameters();
         GeckoAppShell.bindWidgetTexture();
 
         return true;
     }
 
     @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;
 
-        GLES11.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIDs[0]);
+        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIDs[0]);
 
         RectF bounds;
         int[] cropRect;
         IntSize size = getSize();
         RectF viewport = context.viewport;
 
         bounds = getBounds(context, new FloatSize(size));
-        cropRect = new int[] { 0, size.height, size.width, -size.height };
+        cropRect = new int[] { 0, 0, size.width, size.height };
         bounds.offset(-viewport.left, -viewport.top);
 
-        GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, cropRect,
-                                0);
-
         float top = viewport.height() - (bounds.top + bounds.height());
 
         // There may be errors from a previous GL call, so clear them first because
         // we want to check for one below
-        while (GLES11.glGetError() != GLES11.GL_NO_ERROR);
+        while (GLES20.glGetError() != GLES20.GL_NO_ERROR);
+
+        float[] coords = {
+            //x, y, z, texture_x, texture_y
+            bounds.left/viewport.width(), top/viewport.height(), 0,
+            cropRect[0]/size.width, cropRect[1]/size.height,
+
+            bounds.left/viewport.width(), (top+bounds.height())/viewport.height(), 0,
+            cropRect[0]/size.width, cropRect[3]/size.height,
+
+            (bounds.left+bounds.width())/viewport.width(), top/viewport.height(), 0,
+            cropRect[2]/size.width, cropRect[1]/size.height,
+
+            (bounds.left+bounds.width())/viewport.width(), (top+bounds.height())/viewport.height(),
+                0,
+            cropRect[2]/size.width, cropRect[3]/size.height
+        };
 
-        GLES11Ext.glDrawTexfOES(bounds.left, top, 0.0f, bounds.width(), bounds.height());
-        int error = GLES11.glGetError();
-        if (error != GLES11.GL_NO_ERROR) {
+        // Get the buffer and handles from the context
+        FloatBuffer coordBuffer = context.coordBuffer;
+        int positionHandle = context.positionHandle;
+        int textureHandle = context.textureHandle;
+
+        // Make sure we are at position zero in the buffer in case other draw methods did not clean
+        // up after themselves
+        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);
+
+        int error = GLES20.glGetError();
+        if (error != GLES20.GL_NO_ERROR) {
             Log.i(LOGTAG, "Failed to draw texture: " + error);
         }
     }
 }
 
--- a/mobile/android/base/ui/PanZoomController.java
+++ b/mobile/android/base/ui/PanZoomController.java
@@ -825,17 +825,17 @@ public class PanZoomController
 
         // Force a viewport synchronisation
         GeckoApp.mAppContext.showPlugins();
         mController.setForceRedraw();
         mController.notifyLayerClientOfGeometryChange();
     }
 
     public boolean getRedrawHint() {
-        return (mState == PanZoomState.NOTHING || mState == PanZoomState.FLING);
+        return (mState != PanZoomState.PINCHING && mState != PanZoomState.ANIMATED_ZOOM);
     }
 
     private void sendPointToGecko(String event, MotionEvent motionEvent) {
         String json;
         try {
             PointF point = new PointF(motionEvent.getX(), motionEvent.getY());
             point = mController.convertViewPointToLayerPoint(point);
             if (point == null) {
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -97,16 +97,22 @@ const kElementsReceivingInput = {
     embed: true,
     input: true,
     map: true,
     select: true,
     textarea: true,
     video: true
 };
 
+// How many pixels on each side to buffer.
+const kBufferAmount = 300;
+
+// Whether we're using GL layers.
+const kUsingGLLayers = true;
+
 function dump(a) {
   Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(a);
 }
 
 function getBridge() {
   return Cc["@mozilla.org/android/bridge;1"].getService(Ci.nsIAndroidBridge);
 }
 
@@ -422,17 +428,17 @@ var BrowserApp = {
       this._selectedTab.setActive(false);
 
     this._selectedTab = aTab;
     if (!aTab)
       return;
 
     aTab.setActive(true);
     aTab.updateViewport(false);
-    this.deck.selectedPanel = aTab.vbox;
+    this.deck.selectedPanel = aTab.browser;
   },
 
   get selectedBrowser() {
     if (this._selectedTab)
       return this._selectedTab.browser;
     return null;
   },
 
@@ -817,66 +823,53 @@ var BrowserApp = {
     if ((focused instanceof HTMLInputElement && focused.mozIsTextField(false)) || (focused instanceof HTMLTextAreaElement)) {
       let tab = BrowserApp.getTabForBrowser(aBrowser);
       let win = aBrowser.contentWindow;
 
       // tell gecko to scroll the field into view. this will scroll any nested scrollable elements
       // as well as the browser's content window, and modify the scrollX and scrollY on the content window.
       focused.scrollIntoView(false);
 
+      // As Gecko isn't aware of the zoom level we're drawing with, the element may not entirely be in view
+      // yet. Check for that, and scroll some extra to compensate, if necessary.
+      let focusedRect = focused.getBoundingClientRect();
+      let visibleContentWidth = gScreenWidth / tab._zoom;
+      let visibleContentHeight = gScreenHeight / tab._zoom;
+
+      let positionChanged = false;
+      let scrollX = win.scrollX;
+      let scrollY = win.scrollY;
+
+      if (focusedRect.right >= visibleContentWidth && focusedRect.left > 0) {
+        // the element is too far off the right side, so we need to scroll to the right more
+        scrollX += Math.min(focusedRect.left, focusedRect.right - visibleContentWidth);
+        positionChanged = true;
+      } else if (focusedRect.left < 0) {
+        // the element is too far off the left side, so we need to scroll to the left more
+        scrollX += focusedRect.left;
+        positionChanged = true;
+      }
+      if (focusedRect.bottom >= visibleContentHeight && focusedRect.top > 0) {
+        // the element is too far down, so we need to scroll down more
+        scrollY += Math.min(focusedRect.top, focusedRect.bottom - visibleContentHeight);
+        positionChanged = true;
+      } else if (focusedRect.top < 0) {
+        // the element is too far up, so we need to scroll up more
+        scrollY += focusedRect.top;
+        positionChanged = true;
+      }
+
+      if (positionChanged)
+        win.scrollTo(scrollX, scrollY);
+
       // update userScrollPos so that we don't send a duplicate viewport update by triggering
       // our scroll listener
       tab.userScrollPos.x = win.scrollX;
       tab.userScrollPos.y = win.scrollY;
 
-      // note that:
-      // 1. because of the way we do zooming using a CSS transform, gecko does not take into
-      // account the effect of the zoom on the viewport size.
-      // 2. if the input element is near the bottom/right of the page (less than one viewport
-      // height/width away from the bottom/right), the scrollIntoView call will make gecko scroll to the
-      // bottom/right of the page in an attempt to align the input field with the top of the viewport.
-      // however, since gecko doesn't know about the zoom, what it thinks is the "bottom/right of
-      // the page" isn't actually the bottom/right of the page at the current zoom level, and we 
-      // need to adjust this further.
-      // 3. we can't actually adjust this by changing the window scroll position, as gecko already thinks
-      // we're at the bottom/right, so instead we do it by changing the viewportExcess on the tab and
-      // moving the browser element.
-
-      let visibleContentWidth = tab._viewport.width / tab._viewport.zoom;
-      let visibleContentHeight = tab._viewport.height / tab._viewport.zoom;
-      // get the rect that the focused element occupies relative to what gecko thinks the viewport is,
-      // and adjust it by viewportExcess to so that it is relative to what the user sees as the viewport.
-      let focusedRect = focused.getBoundingClientRect();
-      focusedRect = {
-        left: focusedRect.left - tab.viewportExcess.x,
-        right: focusedRect.right - tab.viewportExcess.x,
-        top: focusedRect.top - tab.viewportExcess.y,
-        bottom: focusedRect.bottom - tab.viewportExcess.y
-      };
-      let transformChanged = false;
-      if (focusedRect.right >= visibleContentWidth && focusedRect.left > 0) {
-        // the element is too far off the right side, so we need to scroll to the right more
-        tab.viewportExcess.x += Math.min(focusedRect.left, focusedRect.right - visibleContentWidth);
-        transformChanged = true;
-      } else if (focusedRect.left < 0) {
-        // the element is too far off the left side, so we need to scroll to the left more
-        tab.viewportExcess.x += focusedRect.left;
-        transformChanged = true;
-      }
-      if (focusedRect.bottom >= visibleContentHeight && focusedRect.top > 0) {
-        // the element is too far down, so we need to scroll down more
-        tab.viewportExcess.y += Math.min(focusedRect.top, focusedRect.bottom - visibleContentHeight);
-        transformChanged = true;
-      } else if (focusedRect.top < 0) {
-        // the element is too far up, so we need to scroll up more
-        tab.viewportExcess.y += focusedRect.top;
-        transformChanged = true;
-      }
-      if (transformChanged)
-        tab.updateTransform();
       // finally, let java know where we ended up
       tab.sendViewportUpdate();
     }
   },
 
   getDrawMetadata: function getDrawMetadata() {
     let viewport = this.selectedTab.viewport;
 
@@ -1459,51 +1452,48 @@ let gTabIDFactory = 0;
 
 // track the last known screen size so that new tabs
 // get created with the right size rather than being 1x1
 let gScreenWidth = 1;
 let gScreenHeight = 1;
 
 function Tab(aURL, aParams) {
   this.browser = null;
-  this.vbox = null;
   this.id = 0;
   this.showProgress = true;
   this.create(aURL, aParams);
-  this._viewport = { x: 0, y: 0, width: gScreenWidth, height: gScreenHeight, offsetX: 0, offsetY: 0,
-                     pageWidth: gScreenWidth, pageHeight: gScreenHeight, zoom: 1.0 };
-  this.viewportExcess = { x: 0, y: 0 };
+  this._zoom = 1.0;
   this.documentIdForCurrentViewport = null;
   this.userScrollPos = { x: 0, y: 0 };
   this._pluginCount = 0;
   this._pluginOverlayShowing = false;
 }
 
 Tab.prototype = {
   create: function(aURL, aParams) {
     if (this.browser)
       return;
 
     aParams = aParams || {};
 
-    this.vbox = document.createElement("vbox");
-    this.vbox.align = "start";
-    BrowserApp.deck.appendChild(this.vbox);
-
     this.browser = document.createElement("browser");
     this.browser.setAttribute("type", "content-targetable");
     this.setBrowserSize(980, 480);
-    this.browser.style.MozTransformOrigin = "0 0";
-    this.vbox.appendChild(this.browser);
+    BrowserApp.deck.appendChild(this.browser);
 
     this.browser.stop();
 
-    // Turn off clipping so we can buffer areas outside of the browser element.
     let frameLoader = this.browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
-    frameLoader.clipSubdocument = false;
+    if (kUsingGLLayers) {
+        frameLoader.renderMode = Ci.nsIFrameLoader.RENDER_MODE_ASYNC_SCROLL;
+        frameLoader.clampScrollPosition = false;
+    } else {
+        // Turn off clipping so we can buffer areas outside of the browser element.
+        frameLoader.clipSubdocument = false;
+    }
 
     this.id = ++gTabIDFactory;
 
     let message = {
       gecko: {
         type: "Tab:Added",
         tabID: this.id,
         uri: aURL,
@@ -1573,21 +1563,20 @@ Tab.prototype = {
     this.browser.removeEventListener("pagehide", this, true);
     this.browser.removeEventListener("pageshow", this, true);
 
     Services.obs.removeObserver(this, "document-shown");
 
     // Make sure the previously selected panel remains selected. The selected panel of a deck is
     // not stable when panels are removed.
     let selectedPanel = BrowserApp.deck.selectedPanel;
-    BrowserApp.deck.removeChild(this.vbox);
+    BrowserApp.deck.removeChild(this.browser);
     BrowserApp.deck.selectedPanel = selectedPanel;
 
     this.browser = null;
-    this.vbox = null;
     this.documentIdForCurrentViewport = null;
   },
 
   // This should be called to update the browser when the tab gets selected/unselected
   setActive: function setActive(aActive) {
     if (!this.browser)
       return;
 
@@ -1607,123 +1596,97 @@ Tab.prototype = {
     aViewport.y /= aViewport.zoom;
 
     // Set scroll position
     let win = this.browser.contentWindow;
     win.scrollTo(aViewport.x, aViewport.y);
     this.userScrollPos.x = win.scrollX;
     this.userScrollPos.y = win.scrollY;
 
-    // If we've been asked to over-scroll, do it via the transformation
-    // and store it separately to the viewport.
-    let excessX = aViewport.x - win.scrollX;
-    let excessY = aViewport.y - win.scrollY;
-
-    this._viewport.width = gScreenWidth = aViewport.width;
-    this._viewport.height = gScreenHeight = aViewport.height;
-
-    let transformChanged = false;
-
-    if ((aViewport.offsetX != this._viewport.offsetX) ||
-        (excessX != this.viewportExcess.x)) {
-      this._viewport.offsetX = aViewport.offsetX;
-      this.viewportExcess.x = excessX;
-      transformChanged = true;
-    }
-    if ((aViewport.offsetY != this._viewport.offsetY) ||
-        (excessY != this.viewportExcess.y)) {
-      this._viewport.offsetY = aViewport.offsetY;
-      this.viewportExcess.y = excessY;
-      transformChanged = true;
+    gScreenWidth = aViewport.width;
+    gScreenHeight = aViewport.height;
+    dump("### gScreenWidth = " + gScreenWidth + "\n");
+
+    let zoom = aViewport.zoom;
+    let cwu = window.top.QueryInterface(Ci.nsIInterfaceRequestor)
+                         .getInterface(Ci.nsIDOMWindowUtils);
+
+    if (Math.abs(zoom - this._zoom) >= 1e-6) {
+      this._zoom = zoom;
+      cwu.setResolution(zoom, zoom);
     }
-    if (Math.abs(aViewport.zoom - this._viewport.zoom) >= 1e-6) {
-      this._viewport.zoom = aViewport.zoom;
-      transformChanged = true;
-    }
-
-    if (transformChanged)
-      this.updateTransform();
-  },
-
-  updateTransform: function() {
-    let hasZoom = (Math.abs(this._viewport.zoom - 1.0) >= 1e-6);
-    let x = this._viewport.offsetX + Math.round(-this.viewportExcess.x * this._viewport.zoom);
-    let y = this._viewport.offsetY + Math.round(-this.viewportExcess.y * this._viewport.zoom);
-
-    let transform =
-      "translate(" + x + "px, " +
-                     y + "px)";
-    if (hasZoom)
-      transform += " scale(" + this._viewport.zoom + ")";
-
-    this.browser.style.MozTransform = transform;
+
+    cwu.setDisplayPortForElement(-kBufferAmount / zoom, -kBufferAmount / zoom,
+                                 (gScreenWidth + kBufferAmount * 2) / zoom,
+                                 (gScreenHeight + kBufferAmount * 2) / zoom,
+                                 this.browser.contentDocument.documentElement);
   },
 
   get viewport() {
+    let viewport = {
+      width: gScreenWidth,
+      height: gScreenHeight,
+      pageWidth: gScreenWidth,
+      pageHeight: gScreenHeight,
+      zoom: this._zoom
+    };
+
     // Update the viewport to current dimensions
-    this._viewport.x = (this.browser.contentWindow.scrollX +
-                        this.viewportExcess.x) || 0;
-    this._viewport.y = (this.browser.contentWindow.scrollY +
-                        this.viewportExcess.y) || 0;
+    viewport.x = this.browser.contentWindow.scrollX || 0;
+    viewport.y = this.browser.contentWindow.scrollY || 0;
 
     // Transform coordinates based on zoom
-    this._viewport.x = Math.round(this._viewport.x * this._viewport.zoom);
-    this._viewport.y = Math.round(this._viewport.y * this._viewport.zoom);
+    viewport.x = Math.round(viewport.x * viewport.zoom);
+    viewport.y = Math.round(viewport.y * viewport.zoom);
 
     let doc = this.browser.contentDocument;
     if (doc != null) {
-      let pageWidth = this._viewport.width, pageHeight = this._viewport.height;
+      let pageWidth = viewport.width, pageHeight = viewport.height;
       if (doc instanceof SVGDocument) {
         let rect = doc.rootElement.getBoundingClientRect();
         // we need to add rect.left and rect.top twice so that the SVG is drawn
         // centered on the page; if we add it only once then the SVG will be
         // on the bottom-right of the page and if we don't add it at all then
         // we end up with a cropped SVG (see bug 712065)
         pageWidth = Math.ceil(rect.left + rect.width + rect.left);
         pageHeight = Math.ceil(rect.top + rect.height + rect.top);
       } else {
         let body = doc.body || { scrollWidth: pageWidth, scrollHeight: pageHeight };
         let html = doc.documentElement || { scrollWidth: pageWidth, scrollHeight: pageHeight };
         pageWidth = Math.max(body.scrollWidth, html.scrollWidth);
         pageHeight = Math.max(body.scrollHeight, html.scrollHeight);
       }
 
       /* Transform the page width and height based on the zoom factor. */
-      pageWidth *= this._viewport.zoom;
-      pageHeight *= this._viewport.zoom;
+      pageWidth *= viewport.zoom;
+      pageHeight *= viewport.zoom;
 
       /*
        * Avoid sending page sizes of less than screen size before we hit DOMContentLoaded, because
        * this causes the page size to jump around wildly during page load. After the page is loaded,
        * send updates regardless of page size; we'll zoom to fit the content as needed.
        */
       if (doc.readyState === 'complete' || (pageWidth >= gScreenWidth && pageHeight >= gScreenHeight)) {
-        this._viewport.pageWidth = pageWidth;
-        this._viewport.pageHeight = pageHeight;
+        viewport.pageWidth = pageWidth;
+        viewport.pageHeight = pageHeight;
       }
     }
 
-    return this._viewport;
+    return viewport;
   },
 
   updateViewport: function(aReset, aZoomLevel) {
-    if (!aZoomLevel)
-      aZoomLevel = this.getDefaultZoomLevel();
-
-    let win = this.browser.contentWindow;
-    let zoom = (aReset ? aZoomLevel : this._viewport.zoom);
-    let xpos = ((aReset && win) ? win.scrollX * zoom : this._viewport.x);
-    let ypos = ((aReset && win) ? win.scrollY * zoom : this._viewport.y);
-
-    this.viewportExcess = { x: 0, y: 0 };
-    this.viewport = { x: xpos, y: ypos,
-                      offsetX: 0, offsetY: 0,
-                      width: this._viewport.width, height: this._viewport.height,
-                      pageWidth: gScreenWidth, pageHeight: gScreenHeight,
-                      zoom: zoom };
+    dump("### JS side updateViewport " + aReset + " zoom " + aZoomLevel + "\n");
+
+    if (aReset) {
+      if (!aZoomLevel)
+        aZoomLevel = this.getDefaultZoomLevel();
+      this._zoom = aZoomLevel;
+    }
+
     this.sendViewportUpdate();
   },
 
   sendViewportUpdate: function() {
     if (BrowserApp.selectedTab != this)
       return;
     sendMessageToJava({
       gecko: {
@@ -2049,27 +2012,26 @@ Tab.prototype = {
         aMetadata.defaultZoom *= scaleRatio;
       if ("minZoom" in aMetadata && aMetadata.minZoom > 0)
         aMetadata.minZoom *= scaleRatio;
       if ("maxZoom" in aMetadata && aMetadata.maxZoom > 0)
         aMetadata.maxZoom *= scaleRatio;
     }
     ViewportHandler.setMetadataForDocument(this.browser.contentDocument, aMetadata);
     this.updateViewportSize();
-    this.updateViewport(true);
   },
 
   /** Update viewport when the metadata or the window size changes. */
   updateViewportSize: function updateViewportSize() {
     let browser = this.browser;
     if (!browser)
       return;
 
-    let screenW = this._viewport.width;
-    let screenH = this._viewport.height;
+    let screenW = gScreenWidth;
+    let screenH = gScreenHeight;
     let viewportW, viewportH;
 
     let metadata = this.metadata;
     if (metadata.autoSize) {
       if ("scaleRatio" in metadata) {
         viewportW = screenW / metadata.scaleRatio;
         viewportH = screenH / metadata.scaleRatio;
       } else {
@@ -2091,61 +2053,62 @@ Tab.prototype = {
       if (!validW)
         viewportW = validH ? (viewportH * (screenW / screenH)) : BrowserApp.defaultBrowserWidth;
       if (!validH)
         viewportH = viewportW * (screenH / screenW);
     }
 
     // Make sure the viewport height is not shorter than the window when
     // the page is zoomed out to show its full width.
-    let minScale = this.getPageZoomLevel(screenW);
+    let minScale = this.getPageZoomLevel();
     viewportH = Math.max(viewportH, screenH / minScale);
 
-    let oldBrowserWidth = parseInt(this.browser.style.minWidth);
+    let oldBrowserWidth = this.browserWidth;
     this.setBrowserSize(viewportW, viewportH);
 
     // Avoid having the scroll position jump around after device rotation.
     let win = this.browser.contentWindow;
     this.userScrollPos.x = win.scrollX;
     this.userScrollPos.y = win.scrollY;
 
     // If the browser width changes, we change the zoom proportionally. This ensures sensible
     // behavior when rotating the device on pages with automatically-resizing viewports.
 
     if (viewportW == oldBrowserWidth)
       return;
 
-    let viewport = this.viewport;
-    let newZoom = oldBrowserWidth * viewport.zoom / viewportW;
+    let newZoom = oldBrowserWidth * this._zoom / viewportW;
     this.updateViewport(true, newZoom);
   },
 
   getDefaultZoomLevel: function getDefaultZoomLevel() {
     let md = this.metadata;
     if ("defaultZoom" in md && md.defaultZoom)
       return md.defaultZoom;
 
-    let browserWidth = parseInt(this.browser.style.minWidth);
-    return gScreenWidth / browserWidth;
+    dump("### getDefaultZoomLevel gScreenWidth=" + gScreenWidth);
+    return gScreenWidth / this.browserWidth;
   },
 
   getPageZoomLevel: function getPageZoomLevel() {
     // This may get called during a Viewport:Change message while the document
     // has not loaded yet.
     if (!this.browser.contentDocument || !this.browser.contentDocument.body)
       return 1.0;
 
-    return this._viewport.width / this.browser.contentDocument.body.clientWidth;
+    return gScreenWidth / this.browser.contentDocument.body.clientWidth;
   },
 
   setBrowserSize: function(aWidth, aHeight) {
-    // Using min width/height so as not to conflict with the fullscreen style rule.
-    // See Bug #709813.
-    this.browser.style.minWidth = aWidth + "px";
-    this.browser.style.minHeight = aHeight + "px";
+    this.browserWidth = aWidth;
+
+    if (!this.browser.contentWindow)
+      return;
+    let cwu = this.browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+    cwu.setCSSViewport(aWidth, aHeight);
   },
 
   getRequestLoadContext: function(aRequest) {
     if (aRequest && aRequest.notificationCallbacks) {
       try {
         return aRequest.notificationCallbacks.getInterface(Ci.nsILoadContext);
       } catch (ex) { }
     }
@@ -2169,16 +2132,24 @@ Tab.prototype = {
   observe: function(aSubject, aTopic, aData) {
     switch (aTopic) {
       case "document-shown":
         // Is it on the top level?
         let contentDocument = aSubject;
         if (contentDocument == this.browser.contentDocument) {
           ViewportHandler.updateMetadata(this);
           this.documentIdForCurrentViewport = ViewportHandler.getIdForDocument(contentDocument);
+          // FIXME: This is a workaround for the fact that we suppress draw events.
+          // This means we need to retrigger a draw event here since we might
+          // have suppressed a draw event before documentIdForCurrentViewport
+          // got updated. The real fix is to get rid of suppressing draw events
+          // based on the value of documentIdForCurrentViewport, which we
+          // can do once the docshell and the browser element are aware 
+          // of the existence of <meta viewport>. 
+          sendMessageToJava({ gecko: { type: "Viewport:UpdateAndDraw" } });
         }
         break;
     }
   },
 
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIWebProgressListener,
     Ci.nsISHistoryListener,
@@ -2253,17 +2224,16 @@ var BrowserEventHandler = {
             sendMessageToJava({ gecko: { type: "Panning:Override" } });
         }
       }
     } else if (aTopic == "Gesture:SingleTap") {
       let element = this._highlightElement;
       if (element && !FormAssistant.handleClick(element)) {
         try {
           let data = JSON.parse(aData);
-          [data.x, data.y] = ElementTouchHelper.toScreenCoords(element.ownerDocument.defaultView, data.x, data.y);
   
           this._sendMouseEvent("mousemove", element, data.x, data.y);
           this._sendMouseEvent("mousedown", element, data.x, data.y);
           this._sendMouseEvent("mouseup",   element, data.x, data.y);
   
           if (ElementTouchHelper.isElementClickable(element))
             Haptic.performSimpleAction(Haptic.LongPress);
         } catch(e) {
@@ -2301,34 +2271,34 @@ var BrowserEventHandler = {
   },
 
   onDoubleTap: function(aData) {
     let data = JSON.parse(aData);
 
     let rect = {};
     let win = BrowserApp.selectedBrowser.contentWindow;
     
-    let zoom = BrowserApp.selectedTab._viewport.zoom;
+    let zoom = BrowserApp.selectedTab._zoom;
     let element = ElementTouchHelper.anyElementFromPoint(win, data.x, data.y);
     if (!element) {
       this._zoomOut();
       return;
     }
 
     win = element.ownerDocument.defaultView;
     while (element && win.getComputedStyle(element,null).display == "inline")
       element = element.parentNode;
     if (!element || element == this._zoomedToElement) {
       this._zoomOut();
     } else if (element) {
       const margin = 15;
       this._zoomedToElement = element;
       rect = ElementTouchHelper.getBoundingContentRect(element);
 
-      let zoom = BrowserApp.selectedTab.viewport.zoom;
+      let zoom = BrowserApp.selectedTab._zoom;
       rect.x *= zoom;
       rect.y *= zoom;
       rect.w *= zoom;
       rect.h *= zoom;
 
       setTimeout(function() {
         rect.type = "Browser:ZoomToRect";
         rect.x -= margin;
@@ -2389,17 +2359,16 @@ var BrowserEventHandler = {
         let point = { x: rect.x + rect.w/2, y: rect.y + rect.h/2 };
         aX = point.x;
         aY = point.y;
       }
     }
 
     let window = aElement.ownerDocument.defaultView;
     try {
-      [aX, aY] = ElementTouchHelper.toBrowserCoords(window, aX, aY);
       let cwu = window.top.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
       aButton = aButton || 0;
       cwu.sendMouseEventToWindow(aName, Math.round(aX), Math.round(aY), aButton, 1, 0, true);
     } catch(e) {
       Cu.reportError(e);
     }
   },
 
@@ -2496,72 +2465,32 @@ var BrowserEventHandler = {
 
     return scrollX || scrollY;
   }
 };
 
 const kReferenceDpi = 240; // standard "pixel" size used in some preferences
 
 const ElementTouchHelper = {
-  toBrowserCoords: function(aWindow, aX, aY) {
-    if (!aWindow)
-      throw "Must provide a window";
-  
-    let browser = BrowserApp.getBrowserForWindow(aWindow.top);
-    if (!browser)
-      throw "Unable to find a browser";
-
-    let tab = BrowserApp.getTabForBrowser(browser);
-    if (!tab)
-      throw "Unable to find a tab";
-
-    let viewport = tab.viewport;
-    return [
-        ((aX - tab.viewportExcess.x) * viewport.zoom + viewport.offsetX),
-        ((aY - tab.viewportExcess.y) * viewport.zoom + viewport.offsetY)
-    ];
-  },
-
-  toScreenCoords: function(aWindow, aX, aY) {
-    if (!aWindow)
-      throw "Must provide a window";
-  
-    let browser = BrowserApp.getBrowserForWindow(aWindow.top);
-    if (!browser)
-      throw "Unable to find a browser";
-
-    let tab = BrowserApp.getTabForBrowser(browser);
-    if (!tab)
-      throw "Unable to find a tab";
-
-    let viewport = tab.viewport;
-    return [
-        (aX - viewport.offsetX)/viewport.zoom + tab.viewportExcess.x,
-        (aY - viewport.offsetY)/viewport.zoom + tab.viewportExcess.y
-    ];
-  },
-
   anyElementFromPoint: function(aWindow, aX, aY) {
-    [aX, aY] = this.toScreenCoords(aWindow, aX, aY);
     let cwu = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
     let elem = cwu.elementFromPoint(aX, aY, false, true);
 
     while (elem && (elem instanceof HTMLIFrameElement || elem instanceof HTMLFrameElement)) {
       let rect = elem.getBoundingClientRect();
       aX -= rect.left;
       aY -= rect.top;
       cwu = elem.contentDocument.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
       elem = cwu.elementFromPoint(aX, aY, false, true);
     }
 
     return elem;
   },
 
   elementFromPoint: function(aWindow, aX, aY) {
-    [aX, aY] = this.toScreenCoords(aWindow, aX, aY);
     // browser's elementFromPoint expect browser-relative client coordinates.
     // subtract browser's scroll values to adjust
     let cwu = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
     let elem = this.getClosest(cwu, aX, aY);
 
     // step through layers of IFRAMEs and FRAMES to find innermost element
     while (elem && (elem instanceof HTMLIFrameElement || elem instanceof HTMLFrameElement)) {
       // adjust client coordinates' origin to be top left of iframe viewport
@@ -2823,24 +2752,25 @@ var FormAssistant = {
           break;
 
         // Keep track of input element so we can fill it in if the user
         // selects an autocomplete suggestion
         this._currentInputElement = currentElement;
         let suggestions = this._getAutocompleteSuggestions(currentElement.value, currentElement);
 
         let rect = ElementTouchHelper.getBoundingContentRect(currentElement);
-        let viewport = BrowserApp.selectedTab.viewport;
+        let zoom = BrowserApp.selectedTab._zoom;
+        let win  = BrowserApp.selectedTab.browser.contentWindow;
 
         sendMessageToJava({
           gecko: {
             type:  "FormAssist:AutoComplete",
             suggestions: suggestions,
-            rect: [rect.x - (viewport.x / viewport.zoom), rect.y - (viewport.y / viewport.zoom), rect.w, rect.h],
-            zoom: viewport.zoom
+            rect: [rect.x - (win.scrollX / zoom), rect.y - (win.scrollY / zoom), rect.w, rect.h],
+            zoom: zoom
           }
         });
     }
   },
 
   _isAutocomplete: function (aElement) {
     if (!(aElement instanceof HTMLInputElement) ||
         (aElement.getAttribute("type") == "password") ||
@@ -3238,18 +3168,17 @@ var ViewportHandler = {
       height: height,
       autoSize: autoSize,
       allowZoom: allowZoom,
       autoScale: true
     };
   },
 
   onResize: function onResize() {
-    for (let i = 0; i < BrowserApp.tabs.length; i++)
-      BrowserApp.tabs[i].updateViewportSize();
+    BrowserApp.selectedTab.updateViewportSize();
   },
 
   clamp: function(num, min, max) {
     return Math.max(min, Math.min(max, num));
   },
 
   // The device-pixel-to-CSS-px ratio used to adjust meta viewport values.
   // This is higher on higher-dpi displays, so pages stay about the same physical size.
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -228,16 +228,20 @@ pref("gfx.font_rendering.directwrite.use
 #ifdef XP_WIN
 pref("gfx.canvas.azure.enabled", true);
 #else
 #ifdef XP_MACOSX
 pref("gfx.canvas.azure.enabled", true);
 #endif
 #endif
 
+#ifdef ANDROID
+pref("gfx.textures.poweroftwo.force-enabled", false);
+#endif
+
 pref("accessibility.browsewithcaret", false);
 pref("accessibility.warn_on_browsewithcaret", true);
 
 pref("accessibility.browsewithcaret_shortcut.enabled", true);
 
 #ifndef XP_MACOSX
 // Tab focus model bit field:
 // 1 focuses text controls, 2 focuses other form elements, 4 adds links.
@@ -3343,17 +3347,17 @@ pref("network.tcp.sendbuffer", 131072);
 pref("layers.acceleration.disabled", true);
 #else
 pref("layers.acceleration.disabled", false);
 #endif
 
 // Whether to force acceleration on, ignoring blacklists.
 pref("layers.acceleration.force-enabled", false);
 
-pref("layers.acceleration.draw-fps", false);
+pref("layers.acceleration.draw-fps", true);
 
 pref("layers.offmainthreadcomposition.enabled", false);
 
 #ifdef XP_WIN
 // Whether to disable the automatic detection and use of direct2d.
 #ifdef MOZ_E10S_COMPAT
 pref("gfx.direct2d.disabled", true);
 #else
--- a/mozglue/android/APKOpen.cpp
+++ b/mozglue/android/APKOpen.cpp
@@ -281,28 +281,31 @@ Java_org_mozilla_gecko_GeckoAppShell_ ##
 { \
   return f_ ## name(jenv, jc, one, two, three, four, five, six, seven, eight); \
 }
 
 SHELL_WRAPPER0(nativeInit)
 SHELL_WRAPPER1(notifyGeckoOfEvent, jobject)
 SHELL_WRAPPER0(processNextNativeEvent)
 SHELL_WRAPPER1(setSurfaceView, jobject)
-SHELL_WRAPPER1(setSoftwareLayerClient, jobject)
+SHELL_WRAPPER2(setLayerClient, jobject, jint)
 SHELL_WRAPPER0(onResume)
 SHELL_WRAPPER0(onLowMemory)
 SHELL_WRAPPER3(callObserver, jstring, jstring, jstring)
 SHELL_WRAPPER1(removeObserver, jstring)
 SHELL_WRAPPER1(onChangeNetworkLinkStatus, jstring)
 SHELL_WRAPPER1(reportJavaCrash, jstring)
 SHELL_WRAPPER0(executeNextRunnable)
 SHELL_WRAPPER1(cameraCallbackBridge, jbyteArray)
 SHELL_WRAPPER3(notifyBatteryChange, jdouble, jboolean, jdouble)
 SHELL_WRAPPER3(notifySmsReceived, jstring, jstring, jlong)
 SHELL_WRAPPER0(bindWidgetTexture)
+SHELL_WRAPPER0(scheduleComposite)
+SHELL_WRAPPER0(schedulePauseComposition)
+SHELL_WRAPPER0(scheduleResumeComposition)
 SHELL_WRAPPER3_WITH_RETURN(saveMessageInSentbox, jint, jstring, jstring, jlong)
 SHELL_WRAPPER6(notifySmsSent, jint, jstring, jstring, jlong, jint, jlong)
 SHELL_WRAPPER4(notifySmsDelivered, jint, jstring, jstring, jlong)
 SHELL_WRAPPER3(notifySmsSendFailed, jint, jint, jlong)
 SHELL_WRAPPER7(notifyGetSms, jint, jstring, jstring, jstring, jlong, jint, jlong)
 SHELL_WRAPPER3(notifyGetSmsFailed, jint, jint, jlong)
 SHELL_WRAPPER3(notifySmsDeleted, jboolean, jint, jlong)
 SHELL_WRAPPER3(notifySmsDeleteFailed, jint, jint, jlong)
@@ -692,28 +695,31 @@ loadGeckoLibs(const char *apkName)
   if (!xul_handle)
     __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Couldn't get a handle to libxul!");
 
 #define GETFUNC(name) f_ ## name = (name ## _t) __wrap_dlsym(xul_handle, "Java_org_mozilla_gecko_GeckoAppShell_" #name)
   GETFUNC(nativeInit);
   GETFUNC(notifyGeckoOfEvent);
   GETFUNC(processNextNativeEvent);
   GETFUNC(setSurfaceView);
-  GETFUNC(setSoftwareLayerClient);
+  GETFUNC(setLayerClient);
   GETFUNC(onResume);
   GETFUNC(onLowMemory);
   GETFUNC(callObserver);
   GETFUNC(removeObserver);
   GETFUNC(onChangeNetworkLinkStatus);
   GETFUNC(reportJavaCrash);
   GETFUNC(executeNextRunnable);
   GETFUNC(cameraCallbackBridge);
   GETFUNC(notifyBatteryChange);
   GETFUNC(notifySmsReceived);
   GETFUNC(bindWidgetTexture);
+  GETFUNC(scheduleComposite);
+  GETFUNC(schedulePauseComposition);
+  GETFUNC(scheduleResumeComposition);
   GETFUNC(saveMessageInSentbox);
   GETFUNC(notifySmsSent);
   GETFUNC(notifySmsDelivered);
   GETFUNC(notifySmsSendFailed);
   GETFUNC(notifyGetSms);
   GETFUNC(notifyGetSmsFailed);
   GETFUNC(notifySmsDeleted);
   GETFUNC(notifySmsDeleteFailed);
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -30,16 +30,20 @@
  * 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 ***** */
 
+#include "mozilla/Util.h"
+#include "mozilla/layers/CompositorChild.h"
+#include "mozilla/layers/CompositorParent.h"
+
 #include <android/log.h>
 #include <dlfcn.h>
 
 #include "mozilla/Hal.h"
 #include "nsXULAppAPI.h"
 #include <prthread.h>
 #include "nsXPCOMStrings.h"
 
@@ -181,16 +185,22 @@ AndroidBridge::Init(JNIEnv *jEnv,
 
     jEGLContextClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("javax/microedition/khronos/egl/EGLContext"));
     jEGL10Class = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("javax/microedition/khronos/egl/EGL10"));
     jEGLSurfaceImplClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("com/google/android/gles_jni/EGLSurfaceImpl"));
     jEGLContextImplClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("com/google/android/gles_jni/EGLContextImpl"));
     jEGLConfigImplClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("com/google/android/gles_jni/EGLConfigImpl"));
     jEGLDisplayImplClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("com/google/android/gles_jni/EGLDisplayImpl"));
 
+#ifdef MOZ_JAVA_COMPOSITOR
+    jFlexSurfaceView = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("org/mozilla/gecko/gfx/FlexibleGLSurfaceView"));
+
+    AndroidGLController::Init(jEnv);
+    AndroidEGLObject::Init(jEnv);
+#endif
     InitAndroidJavaWrappers(jEnv);
 
     // jEnv should NOT be cached here by anything -- the jEnv here
     // is not valid for the real gecko main thread, which is set
     // at SetMainThread time.
 
     return true;
 }
@@ -973,19 +983,21 @@ AndroidBridge::GetShowPasswordSetting()
 
 void
 AndroidBridge::SetSurfaceView(jobject obj)
 {
     mSurfaceView.Init(obj);
 }
 
 void
-AndroidBridge::SetSoftwareLayerClient(jobject obj)
+AndroidBridge::SetLayerClient(jobject obj)
 {
-    mSoftwareLayerClient.Init(obj);
+    AndroidGeckoGLLayerClient *client = new AndroidGeckoGLLayerClient();
+    client->Init(obj);
+    mLayerClient = client;
 }
 
 void
 AndroidBridge::ShowInputMethodPicker()
 {
     ALOG_BRIDGE("AndroidBridge::ShowInputMethodPicker");
 
     JNIEnv *env = GetJNIEnv();
@@ -995,22 +1007,21 @@ AndroidBridge::ShowInputMethodPicker()
     env->CallStaticVoidMethod(mGeckoAppShellClass, jShowInputMethodPicker);
 }
 
 void *
 AndroidBridge::CallEglCreateWindowSurface(void *dpy, void *config, AndroidGeckoSurfaceView &sview)
 {
     ALOG_BRIDGE("AndroidBridge::CallEglCreateWindowSurface");
 
-    JNIEnv *env = GetJNIEnv();
+    // Called off the main thread by the compositor
+    JNIEnv *env = GetJNIForThread();
     if (!env)
         return NULL;
 
-    AutoLocalJNIFrame jniFrame(env); 
-
     /*
      * This is basically:
      *
      *    s = EGLContext.getEGL().eglCreateWindowSurface(new EGLDisplayImpl(dpy),
      *                                                   new EGLConfigImpl(config),
      *                                                   view.getHolder(), null);
      *    return s.mEGLSurface;
      *
@@ -1040,16 +1051,46 @@ AndroidBridge::CallEglCreateWindowSurfac
 
     jfieldID sfield = env->GetFieldID(jEGLSurfaceImplClass, "mEGLSurface", "I");
 
     jint realSurface = env->GetIntField(surf, sfield);
 
     return (void*) realSurface;
 }
 
+static AndroidGLController sController;
+
+void
+AndroidBridge::RegisterCompositor()
+{
+    ALOG_BRIDGE("AndroidBridge::RegisterCompositor");
+    JNIEnv *env = GetJNIForThread();
+    if (!env)
+        return;
+
+    AutoLocalJNIFrame jniFrame(env, 3);
+
+    jmethodID registerCompositor = env->GetStaticMethodID(jFlexSurfaceView, "registerCxxCompositor", "()Lorg/mozilla/gecko/gfx/GLController;");
+
+    __android_log_print(ANDROID_LOG_ERROR, "Gecko", "### registerCxxCompositor()");
+    jobject glController = env->CallStaticObjectMethod(jFlexSurfaceView, registerCompositor);
+
+    __android_log_print(ANDROID_LOG_ERROR, "Gecko", "### Acquire()");
+    sController.Acquire(env, glController);
+    sController.SetGLVersion(2);
+    __android_log_print(ANDROID_LOG_ERROR, "Gecko", "Registered Compositor");
+}
+
+EGLSurface
+AndroidBridge::ProvideEGLSurface()
+{
+    sController.WaitForValidSurface();
+    return sController.ProvideEGLSurface();
+}
+
 bool
 AndroidBridge::GetStaticIntField(const char *className, const char *fieldName, PRInt32* aInt)
 {
     ALOG_BRIDGE("AndroidBridge::GetStaticIntField %s", fieldName);
     JNIEnv *env = GetJNIEnv();
     if (!env)
         return false;
 
@@ -1801,16 +1842,74 @@ AndroidBridge::IsTablet()
 {
     JNIEnv *env = GetJNIEnv();
     if (!env)
         return false;
 
     return env->CallStaticBooleanMethod(mGeckoAppShellClass, jIsTablet);
 }
 
+void
+AndroidBridge::SetCompositorParent(mozilla::layers::CompositorParent* aCompositorParent,
+                                   ::base::Thread* aCompositorThread)
+{
+    mCompositorParent = aCompositorParent;
+    mCompositorThread = aCompositorThread;
+}
+
+void
+AndroidBridge::ScheduleComposite()
+{
+    if (mCompositorParent) {
+        mCompositorParent->ScheduleRenderOnCompositorThread(*mCompositorThread);
+    }
+}
+
+void
+AndroidBridge::SchedulePauseComposition()
+{
+    if (mCompositorParent) {
+        mCompositorParent->SchedulePauseOnCompositorThread(*mCompositorThread);
+    }
+}
+
+void
+AndroidBridge::ScheduleResumeComposition()
+{
+    if (mCompositorParent) {
+        mCompositorParent->ScheduleResumeOnCompositorThread(*mCompositorThread);
+    }
+}
+
+void
+AndroidBridge::SetViewTransformGetter(AndroidViewTransformGetter& aViewTransformGetter)
+{
+    __android_log_print(ANDROID_LOG_ERROR, "Gecko", "### SetViewTransformGetter()");
+    mViewTransformGetter = &aViewTransformGetter;
+}
+
+void
+AndroidBridge::GetViewTransform(nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY)
+{
+    __android_log_print(ANDROID_LOG_ERROR, "Gecko", "### GetViewTransform()");
+    if (mViewTransformGetter) {
+        (*mViewTransformGetter)(aScrollOffset, aScaleX, aScaleY);
+    }
+}
+
+AndroidBridge::AndroidBridge()
+: mLayerClient(NULL)
+, mViewTransformGetter(NULL)
+{
+}
+
+AndroidBridge::~AndroidBridge()
+{
+}
+
 /* Implementation file */
 NS_IMPL_ISUPPORTS1(nsAndroidBridge, nsIAndroidBridge)
 
 nsAndroidBridge::nsAndroidBridge()
 {
 }
 
 nsAndroidBridge::~nsAndroidBridge()
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -44,21 +44,23 @@
 #include <pthread.h>
 
 #include "nsCOMPtr.h"
 #include "nsCOMArray.h"
 #include "nsIRunnable.h"
 #include "nsIObserver.h"
 #include "nsThreadUtils.h"
 
+#include "AndroidFlexViewWrapper.h"
 #include "AndroidJavaWrappers.h"
 
 #include "nsIMutableArray.h"
 #include "nsIMIMEInfo.h"
 #include "nsColor.h"
+#include "BasicLayers.h"
 #include "gfxRect.h"
 
 #include "nsIAndroidBridge.h"
 
 // Some debug #defines
 // #define DEBUG_ANDROID_EVENTS
 // #define DEBUG_ANDROID_WIDGET
 
@@ -66,29 +68,37 @@ class nsWindow;
 class nsIDOMMozSmsMessage;
 
 /* See the comment in AndroidBridge about this function before using it */
 extern "C" JNIEnv * GetJNIForThread();
 
 extern bool mozilla_AndroidBridge_SetMainThread(void *);
 extern jclass GetGeckoAppShellClass();
 
+namespace base {
+class Thread;
+} // end namespace base
+
 namespace mozilla {
 
 namespace hal {
 class BatteryInformation;
 class NetworkInformation;
 } // namespace hal
 
 namespace dom {
 namespace sms {
 struct SmsFilterData;
 } // namespace sms
 } // namespace dom
 
+namespace layers {
+class CompositorParent;
+} // namespace layers
+
 // The order and number of the members in this structure must correspond
 // to the attrsAppearance array in GeckoAppShell.getSystemColors()
 typedef struct AndroidSystemColors {
     nscolor textColorPrimary;
     nscolor textColorPrimaryInverse;
     nscolor textColorSecondary;
     nscolor textColorSecondaryInverse;
     nscolor textColorTertiary;
@@ -105,16 +115,21 @@ class AndroidBridge
 public:
     enum {
         NOTIFY_IME_RESETINPUTSTATE = 0,
         NOTIFY_IME_SETOPENSTATE = 1,
         NOTIFY_IME_CANCELCOMPOSITION = 2,
         NOTIFY_IME_FOCUSCHANGE = 3
     };
 
+    enum {
+        LAYER_CLIENT_TYPE_NONE = 0,
+        LAYER_CLIENT_TYPE_GL = 2            // AndroidGeckoGLLayerClient
+    };
+
     static AndroidBridge *ConstructBridge(JNIEnv *jEnv,
                                           jclass jGeckoAppShellClass);
 
     static AndroidBridge *Bridge() {
         return sBridge;
     }
 
     static JavaVM *GetVM() {
@@ -169,18 +184,18 @@ public:
     void DisableSensor(int aSensorType);
 
     void ReturnIMEQueryResult(const PRUnichar *aResult, PRUint32 aLen, int aSelStart, int aSelLen);
 
     void NotifyXreExit();
 
     void ScheduleRestart();
 
-    void SetSoftwareLayerClient(jobject jobj);
-    AndroidGeckoSoftwareLayerClient &GetSoftwareLayerClient() { return mSoftwareLayerClient; }
+    void SetLayerClient(jobject jobj);
+    AndroidGeckoLayerClient &GetLayerClient() { return *mLayerClient; }
 
     void SetSurfaceView(jobject jobj);
     AndroidGeckoSurfaceView& SurfaceView() { return mSurfaceView; }
 
     bool GetHandlersForURL(const char *aURL, 
                              nsIMutableArray* handlersArray = nsnull,
                              nsIHandlerApp **aDefaultApp = nsnull,
                              const nsAString& aAction = EmptyString());
@@ -309,16 +324,20 @@ public:
 
         int mEntries;
         JNIEnv* mJNIEnv;
     };
 
     /* See GLHelpers.java as to why this is needed */
     void *CallEglCreateWindowSurface(void *dpy, void *config, AndroidGeckoSurfaceView& surfaceView);
 
+    // Switch Java to composite with the Gecko Compositor thread
+    void RegisterCompositor();
+    EGLSurface ProvideEGLSurface();
+
     bool GetStaticStringField(const char *classID, const char *field, nsAString &result);
 
     bool GetStaticIntField(const char *className, const char *fieldName, PRInt32* aInt);
 
     void SetKeepScreenOn(bool on);
 
     void ScanMedia(const nsAString& aFile, const nsACString& aMimeType);
 
@@ -380,16 +399,24 @@ public:
     void ClearMessageList(PRInt32 aListId);
 
     bool IsTablet();
 
     void GetCurrentNetworkInformation(hal::NetworkInformation* aNetworkInfo);
     void EnableNetworkNotifications();
     void DisableNetworkNotifications();
 
+    void SetCompositorParent(mozilla::layers::CompositorParent* aCompositorParent,
+                             base::Thread* aCompositorThread);
+    void ScheduleComposite();
+    void SchedulePauseComposition();
+    void ScheduleResumeComposition();
+    void SetViewTransformGetter(AndroidViewTransformGetter& aViewTransformGetter);
+    void GetViewTransform(nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY);
+
     jobject CreateSurface();
     void DestroySurface(jobject surface);
     void ShowSurface(jobject surface, const gfxRect& aRect, bool aInverted, bool aBlend);
     void HideSurface(jobject surface);
 
 protected:
     static AndroidBridge *sBridge;
 
@@ -397,22 +424,29 @@ protected:
     JavaVM *mJavaVM;
 
     // the JNIEnv for the main thread
     JNIEnv *mJNIEnv;
     void *mThread;
 
     // the GeckoSurfaceView
     AndroidGeckoSurfaceView mSurfaceView;
-    AndroidGeckoSoftwareLayerClient mSoftwareLayerClient;
+
+    AndroidGeckoLayerClient *mLayerClient;
+
+    nsRefPtr<mozilla::layers::CompositorParent> mCompositorParent;
+    base::Thread *mCompositorThread;
+    AndroidViewTransformGetter *mViewTransformGetter;
 
     // the GeckoAppShell java class
     jclass mGeckoAppShellClass;
 
-    AndroidBridge() { }
+    AndroidBridge();
+    ~AndroidBridge();
+
     bool Init(JNIEnv *jEnv, jclass jGeckoApp);
 
     bool mOpenedGraphicsLibraries;
     void OpenGraphicsLibraries();
 
     bool mHasNativeBitmapAccess;
     bool mHasNativeWindowAccess;
 
@@ -493,16 +527,20 @@ protected:
     // stuff we need for CallEglCreateWindowSurface
     jclass jEGLSurfaceImplClass;
     jclass jEGLContextImplClass;
     jclass jEGLConfigImplClass;
     jclass jEGLDisplayImplClass;
     jclass jEGLContextClass;
     jclass jEGL10Class;
 
+    jclass jFlexSurfaceView;
+
+    jmethodID jRegisterCompositorMethod;
+
     // calls we've dlopened from libjnigraphics.so
     int (* AndroidBitmap_getInfo)(JNIEnv *env, jobject bitmap, void *info);
     int (* AndroidBitmap_lockPixels)(JNIEnv *env, jobject bitmap, void **buffer);
     int (* AndroidBitmap_unlockPixels)(JNIEnv *env, jobject bitmap);
 
     void* (*ANativeWindow_fromSurface)(JNIEnv *env, jobject surface);
     void (*ANativeWindow_release)(void *window);
     int (*ANativeWindow_setBuffersGeometry)(void *window, int width, int height, int format);
new file mode 100644
--- /dev/null
+++ b/widget/android/AndroidFlexViewWrapper.cpp
@@ -0,0 +1,230 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+* ***** BEGIN LICENSE BLOCK *****
+* Version: MPL 1.1/GPL 2.0/LGPL 2.1
+*
+* The contents of this file are subject to the Mozilla Public License Version
+* 1.1 (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+* http://www.mozilla.org/MPL/
+*
+* Software distributed under the License is distributed on an "AS IS" basis,
+* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+* for the specific language governing rights and limitations under the
+* License.
+*
+* The Original Code is Mozilla Android code.
+*
+* The Initial Developer of the Original Code is Mozilla Foundation.
+* Portions created by the Initial Developer are Copyright (C) 2011-2012
+* the Initial Developer. All Rights Reserved.
+*
+* Contributor(s):
+* Patrick Walton <pcwalton@mozilla.com>
+*
+* Alternatively, the contents of this file may be used under the terms of
+* either the GNU General Public License Version 2 or later (the "GPL"), or
+* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+* in which case the provisions of the GPL or the LGPL are applicable instead
+* of those above. If you wish to allow use of your version of this file only
+* under the terms of either the GPL or the LGPL, and not to allow others to
+* use your version of this file under the terms of the MPL, indicate your
+* decision by deleting the provisions above and replace them with the notice
+* and other provisions required by the GPL or the LGPL. If you do not delete
+* the provisions above, a recipient may use your version of this file under
+* the terms of any one of the MPL, the GPL or the LGPL.
+*
+* ***** END LICENSE BLOCK ***** */
+
+#include "AndroidFlexViewWrapper.h"
+
+
+static AndroidGLController sController;
+
+static const char *sEGLDisplayClassName = "com/google/android/gles_jni/EGLDisplayImpl";
+static const char *sEGLDisplayPointerFieldName = "mEGLDisplay";
+static jfieldID jEGLDisplayPointerField = 0;
+
+static const char *sEGLConfigClassName = "com/google/android/gles_jni/EGLConfigImpl";
+static const char *sEGLConfigPointerFieldName = "mEGLConfig";
+static jfieldID jEGLConfigPointerField = 0;
+
+static const char *sEGLContextClassName = "com/google/android/gles_jni/EGLContextImpl";
+static const char *sEGLContextPointerFieldName = "mEGLContext";
+static jfieldID jEGLContextPointerField = 0;
+
+static const char *sEGLSurfaceClassName = "com/google/android/gles_jni/EGLSurfaceImpl";
+static const char *sEGLSurfacePointerFieldName = "mEGLSurface";
+static jfieldID jEGLSurfacePointerField = 0;
+
+void AndroidEGLObject::Init(JNIEnv* aJEnv) {
+    jclass jClass;
+    jClass = reinterpret_cast<jclass>
+        (aJEnv->NewGlobalRef(aJEnv->FindClass(sEGLDisplayClassName)));
+    jEGLDisplayPointerField = aJEnv->GetFieldID(jClass, sEGLDisplayPointerFieldName, "I");
+    jClass = reinterpret_cast<jclass>
+        (aJEnv->NewGlobalRef(aJEnv->FindClass(sEGLConfigClassName)));
+    jEGLConfigPointerField = aJEnv->GetFieldID(jClass, sEGLConfigPointerFieldName, "I");
+    jClass = reinterpret_cast<jclass>
+        (aJEnv->NewGlobalRef(aJEnv->FindClass(sEGLContextClassName)));
+    jEGLContextPointerField = aJEnv->GetFieldID(jClass, sEGLContextPointerFieldName, "I");
+    jClass = reinterpret_cast<jclass>
+        (aJEnv->NewGlobalRef(aJEnv->FindClass(sEGLSurfaceClassName)));
+    jEGLSurfacePointerField = aJEnv->GetFieldID(jClass, sEGLSurfacePointerFieldName, "I");
+}
+
+jmethodID AndroidGLController::jSetGLVersionMethod = 0;
+jmethodID AndroidGLController::jInitGLContextMethod = 0;
+jmethodID AndroidGLController::jDisposeGLContextMethod = 0;
+jmethodID AndroidGLController::jGetEGLDisplayMethod = 0;
+jmethodID AndroidGLController::jGetEGLConfigMethod = 0;
+jmethodID AndroidGLController::jGetEGLContextMethod = 0;
+jmethodID AndroidGLController::jGetEGLSurfaceMethod = 0;
+jmethodID AndroidGLController::jHasSurfaceMethod = 0;
+jmethodID AndroidGLController::jSwapBuffersMethod = 0;
+jmethodID AndroidGLController::jCheckForLostContextMethod = 0;
+jmethodID AndroidGLController::jWaitForValidSurfaceMethod = 0;
+jmethodID AndroidGLController::jGetWidthMethod = 0;
+jmethodID AndroidGLController::jGetHeightMethod = 0;
+jmethodID AndroidGLController::jProvideEGLSurfaceMethod = 0;
+
+void
+AndroidGLController::Init(JNIEnv *aJEnv)
+{
+    const char *className = "org/mozilla/gecko/gfx/GLController";
+    jclass jClass = reinterpret_cast<jclass>(aJEnv->NewGlobalRef(aJEnv->FindClass(className)));
+
+    jSetGLVersionMethod = aJEnv->GetMethodID(jClass, "setGLVersion", "(I)V");
+    jInitGLContextMethod = aJEnv->GetMethodID(jClass, "initGLContext", "()V");
+    jDisposeGLContextMethod = aJEnv->GetMethodID(jClass, "disposeGLContext", "()V");
+    jGetEGLDisplayMethod = aJEnv->GetMethodID(jClass, "getEGLDisplay",
+                                              "()Ljavax/microedition/khronos/egl/EGLDisplay;");
+    jGetEGLConfigMethod = aJEnv->GetMethodID(jClass, "getEGLConfig",
+                                             "()Ljavax/microedition/khronos/egl/EGLConfig;");
+    jGetEGLContextMethod = aJEnv->GetMethodID(jClass, "getEGLContext",
+                                              "()Ljavax/microedition/khronos/egl/EGLContext;");
+    jGetEGLSurfaceMethod = aJEnv->GetMethodID(jClass, "getEGLSurface",
+                                              "()Ljavax/microedition/khronos/egl/EGLSurface;");
+    jProvideEGLSurfaceMethod = aJEnv->GetMethodID(jClass, "provideEGLSurface",
+                                                  "()Ljavax/microedition/khronos/egl/EGLSurface;");
+    jHasSurfaceMethod = aJEnv->GetMethodID(jClass, "hasSurface", "()Z");
+    jSwapBuffersMethod = aJEnv->GetMethodID(jClass, "swapBuffers", "()Z");
+    jCheckForLostContextMethod = aJEnv->GetMethodID(jClass, "checkForLostContext", "()Z");
+    jWaitForValidSurfaceMethod = aJEnv->GetMethodID(jClass, "waitForValidSurface", "()V");
+    jGetWidthMethod = aJEnv->GetMethodID(jClass, "getWidth", "()I");
+    jGetHeightMethod = aJEnv->GetMethodID(jClass, "getHeight", "()I");
+}
+
+void
+AndroidGLController::Acquire(JNIEnv* aJEnv, jobject aJObj)
+{
+    mJEnv = aJEnv;
+    mJObj = aJEnv->NewGlobalRef(aJObj);
+}
+
+void
+AndroidGLController::Acquire(JNIEnv* aJEnv)
+{
+    mJEnv = aJEnv;
+}
+
+void
+AndroidGLController::Release()
+{
+    if (mJObj) {
+        mJEnv->DeleteGlobalRef(mJObj);
+        mJObj = NULL;
+    }
+
+    mJEnv = NULL;
+}
+
+void
+AndroidGLController::SetGLVersion(int aVersion)
+{
+    mJEnv->CallVoidMethod(mJObj, jSetGLVersionMethod, aVersion);
+}
+
+void
+AndroidGLController::InitGLContext()
+{
+    mJEnv->CallVoidMethod(mJObj, jInitGLContextMethod);
+}
+
+void
+AndroidGLController::DisposeGLContext()
+{
+    mJEnv->CallVoidMethod(mJObj, jDisposeGLContextMethod);
+}
+
+EGLDisplay
+AndroidGLController::GetEGLDisplay()
+{
+    jobject jObj = mJEnv->CallObjectMethod(mJObj, jGetEGLDisplayMethod);
+    return reinterpret_cast<EGLDisplay>(mJEnv->GetIntField(jObj, jEGLDisplayPointerField));
+}
+
+EGLConfig
+AndroidGLController::GetEGLConfig()
+{
+    jobject jObj = mJEnv->CallObjectMethod(mJObj, jGetEGLConfigMethod);
+    return reinterpret_cast<EGLConfig>(mJEnv->GetIntField(jObj, jEGLConfigPointerField));
+}
+
+EGLContext
+AndroidGLController::GetEGLContext()
+{
+    jobject jObj = mJEnv->CallObjectMethod(mJObj, jGetEGLContextMethod);
+    return reinterpret_cast<EGLContext>(mJEnv->GetIntField(jObj, jEGLContextPointerField));
+}
+
+EGLSurface
+AndroidGLController::GetEGLSurface()
+{
+    jobject jObj = mJEnv->CallObjectMethod(mJObj, jGetEGLSurfaceMethod);
+    return reinterpret_cast<EGLSurface>(mJEnv->GetIntField(jObj, jEGLSurfacePointerField));
+}
+
+EGLSurface
+AndroidGLController::ProvideEGLSurface()
+{
+    jobject jObj = mJEnv->CallObjectMethod(mJObj, jProvideEGLSurfaceMethod);
+    return reinterpret_cast<EGLSurface>(mJEnv->GetIntField(jObj, jEGLSurfacePointerField));
+}
+
+bool
+AndroidGLController::HasSurface()
+{
+    return mJEnv->CallBooleanMethod(mJObj, jHasSurfaceMethod);
+}
+
+bool
+AndroidGLController::SwapBuffers()
+{
+    return mJEnv->CallBooleanMethod(mJObj, jSwapBuffersMethod);
+}
+
+bool
+AndroidGLController::CheckForLostContext()
+{
+    return mJEnv->CallBooleanMethod(mJObj, jCheckForLostContextMethod);
+}
+
+void
+AndroidGLController::WaitForValidSurface()
+{
+    mJEnv->CallVoidMethod(mJObj, jWaitForValidSurfaceMethod);
+}
+
+int
+AndroidGLController::GetWidth()
+{
+    return mJEnv->CallIntMethod(mJObj, jGetWidthMethod);
+}
+
+int
+AndroidGLController::GetHeight()
+{
+    return mJEnv->CallIntMethod(mJObj, jGetHeightMethod);
+}
+
+
new file mode 100644
--- /dev/null
+++ b/widget/android/AndroidFlexViewWrapper.h
@@ -0,0 +1,119 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+* ***** BEGIN LICENSE BLOCK *****
+* Version: MPL 1.1/GPL 2.0/LGPL 2.1
+*
+* The contents of this file are subject to the Mozilla Public License Version
+* 1.1 (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+* http://www.mozilla.org/MPL/
+*
+* Software distributed under the License is distributed on an "AS IS" basis,
+* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+* for the specific language governing rights and limitations under the
+* License.
+*
+* The Original Code is Mozilla Android code.
+*
+* The Initial Developer of the Original Code is Mozilla Foundation.
+* Portions created by the Initial Developer are Copyright (C) 2011-2012
+* the Initial Developer. All Rights Reserved.
+*
+* Contributor(s):
+* Patrick Walton <pcwalton@mozilla.com>
+*
+* Alternatively, the contents of this file may be used under the terms of
+* either the GNU General Public License Version 2 or later (the "GPL"), or
+* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+* in which case the provisions of the GPL or the LGPL are applicable instead
+* of those above. If you wish to allow use of your version of this file only
+* under the terms of either the GPL or the LGPL, and not to allow others to
+* use your version of this file under the terms of the MPL, indicate your
+* decision by deleting the provisions above and replace them with the notice
+* and other provisions required by the GPL or the LGPL. If you do not delete
+* the provisions above, a recipient may use your version of this file under
+* the terms of any one of the MPL, the GPL or the LGPL.
+*
+* ***** END LICENSE BLOCK ***** */
+
+#ifndef AndroidFlexViewWrapper_h__
+#define AndroidFlexViewWrapper_h__
+
+#include <jni.h>
+//#include <GLES/gl.h>
+//#include <GLES/glext.h>
+#include <cassert>
+#include <cstdlib>
+#include <pthread.h>
+#include <android/log.h>
+
+typedef void *NativeType;
+
+class AndroidEGLObject {
+public:
+    AndroidEGLObject(JNIEnv* aJEnv, jobject aJObj)
+    : mPtr(reinterpret_cast<NativeType>(aJEnv->GetIntField(aJObj, jPointerField))) {}
+
+    static void Init(JNIEnv* aJEnv);
+
+    NativeType const& operator*() const {
+        return mPtr;
+    }
+
+private:
+    static jfieldID jPointerField;
+    static const char* sClassName;
+    static const char* sPointerFieldName;
+
+    const NativeType mPtr;
+};
+
+typedef void *EGLConfig;
+typedef void *EGLContext;
+typedef void *EGLDisplay;
+typedef void *EGLSurface;
+
+class AndroidGLController {
+public:
+    static void Init(JNIEnv* aJEnv);
+
+    void Acquire(JNIEnv *aJEnv, jobject aJObj);
+    void Acquire(JNIEnv *aJEnv);
+    void Release();
+
+    void SetGLVersion(int aVersion);
+    void InitGLContext();
+    void DisposeGLContext();
+    EGLDisplay GetEGLDisplay();
+    EGLConfig GetEGLConfig();
+    EGLContext GetEGLContext();
+    EGLSurface GetEGLSurface();
+    EGLSurface ProvideEGLSurface();
+    bool HasSurface();
+    bool SwapBuffers();
+    bool CheckForLostContext();
+    void WaitForValidSurface();
+    int GetWidth();
+    int GetHeight();
+
+private:
+    static jmethodID jSetGLVersionMethod;
+    static jmethodID jInitGLContextMethod;
+    static jmethodID jDisposeGLContextMethod;
+    static jmethodID jGetEGLDisplayMethod;
+    static jmethodID jGetEGLConfigMethod;
+    static jmethodID jGetEGLContextMethod;
+    static jmethodID jGetEGLSurfaceMethod;
+    static jmethodID jHasSurfaceMethod;
+    static jmethodID jSwapBuffersMethod;
+    static jmethodID jCheckForLostContextMethod;
+    static jmethodID jWaitForValidSurfaceMethod;
+    static jmethodID jGetWidthMethod;
+    static jmethodID jGetHeightMethod;
+    static jmethodID jProvideEGLSurfaceMethod;
+
+    JNIEnv *mJEnv;
+    jobject mJObj;
+};
+
+#endif
+
--- a/widget/android/AndroidGraphicBuffer.cpp
+++ b/widget/android/AndroidGraphicBuffer.cpp
@@ -42,21 +42,16 @@
 #include "AndroidBridge.h"
 #include "mozilla/Preferences.h"
 
 #define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "AndroidGraphicBuffer" , ## args)
 
 #define EGL_NATIVE_BUFFER_ANDROID 0x3140
 #define EGL_IMAGE_PRESERVED_KHR   0x30D2
 
-typedef void* EGLContext;
-typedef void* EGLImageKHR;
-typedef void* EGLClientBuffer;
-typedef void* EGLDisplay;
-
 typedef PRUint32 EGLenum;
 typedef PRInt32 EGLint;
 typedef PRUint32 EGLBoolean;
 
 typedef gfxASurface::gfxImageFormat gfxImageFormat;
 
 #define EGL_TRUE 1
 #define EGL_FALSE 0
--- a/widget/android/AndroidGraphicBuffer.h
+++ b/widget/android/AndroidGraphicBuffer.h
@@ -36,16 +36,19 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef AndroidGraphicBuffer_h_
 #define AndroidGraphicBuffer_h_
 
 #include "gfxASurface.h"
 #include "nsRect.h"
 
+typedef void* EGLImageKHR;
+typedef void* EGLClientBuffer;
+
 namespace mozilla {
 
 /**
  * This class allows access to Android's direct texturing mechanism. Locking
  * the buffer gives you a pointer you can read/write to directly. It is fully
  * threadsafe, but you probably really want to use the AndroidDirectTexture
  * class which will handle double buffering.
  *
--- a/widget/android/AndroidJNI.cpp
+++ b/widget/android/AndroidJNI.cpp
@@ -73,17 +73,17 @@ using namespace mozilla;
 using namespace mozilla::dom::sms;
 
 /* Forward declare all the JNI methods as extern "C" */
 
 extern "C" {
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_nativeInit(JNIEnv *, jclass);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyGeckoOfEvent(JNIEnv *, jclass, jobject event);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_processNextNativeEvent(JNIEnv *, jclass);
-    NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_setSoftwareLayerClient(JNIEnv *jenv, jclass, jobject sv);
+    NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_setLayerClient(JNIEnv *jenv, jclass, jobject sv);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_setSurfaceView(JNIEnv *jenv, jclass, jobject sv);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_onResume(JNIEnv *, jclass);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_onLowMemory(JNIEnv *, jclass);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_callObserver(JNIEnv *, jclass, jstring observerKey, jstring topic, jstring data);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_removeObserver(JNIEnv *jenv, jclass, jstring jObserverKey);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_onChangeNetworkLinkStatus(JNIEnv *, jclass, jstring status);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_reportJavaCrash(JNIEnv *, jclass, jstring stack);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_executeNextRunnable(JNIEnv *, jclass);
@@ -100,17 +100,21 @@ extern "C" {
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifySmsDeleteFailed(JNIEnv* jenv, jclass, jint, jint, jlong);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyNoMessageInList(JNIEnv* jenv, jclass, jint, jlong);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyListCreated(JNIEnv* jenv, jclass, jint, jint, jstring, jstring, jstring, jlong, jint, jlong);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyGotNextMessage(JNIEnv* jenv, jclass, jint, jstring, jstring, jstring, jlong, jint, jlong);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyReadingMessageListFailed(JNIEnv* jenv, jclass, jint, jint, jlong);
 
 #ifdef MOZ_JAVA_COMPOSITOR
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_bindWidgetTexture(JNIEnv* jenv, jclass);
+    NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_scheduleComposite(JNIEnv* jenv, jclass);
+    NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_schedulePauseComposition(JNIEnv* jenv, jclass);
+    NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_scheduleResumeComposition(JNIEnv* jenv, jclass);
 #endif
+
 }
 
 
 /*
  * Incoming JNI methods
  */
 
 NS_EXPORT void JNICALL
@@ -137,19 +141,19 @@ Java_org_mozilla_gecko_GeckoAppShell_pro
 
 NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_setSurfaceView(JNIEnv *jenv, jclass, jobject obj)
 {
     AndroidBridge::Bridge()->SetSurfaceView(jenv->NewGlobalRef(obj));
 }
 
 NS_EXPORT void JNICALL
-Java_org_mozilla_gecko_GeckoAppShell_setSoftwareLayerClient(JNIEnv *jenv, jclass, jobject obj)
+Java_org_mozilla_gecko_GeckoAppShell_setLayerClient(JNIEnv *jenv, jclass, jobject obj)
 {
-    AndroidBridge::Bridge()->SetSoftwareLayerClient(jenv->NewGlobalRef(obj));
+    AndroidBridge::Bridge()->SetLayerClient(jenv->NewGlobalRef(obj));
 }
 
 NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_onLowMemory(JNIEnv *jenv, jclass jc)
 {
     if (nsAppShell::gAppShell) {
         nsAppShell::gAppShell->NotifyObservers(nsnull,
                                                "memory-pressure",
@@ -889,9 +893,30 @@ Java_org_mozilla_gecko_GeckoAppShell_not
 #ifdef MOZ_JAVA_COMPOSITOR
 
 NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_bindWidgetTexture(JNIEnv* jenv, jclass)
 {
     nsWindow::BindToTexture();
 }
 
+NS_EXPORT void JNICALL
+Java_org_mozilla_gecko_GeckoAppShell_scheduleComposite(JNIEnv*, jclass)
+{
+    __android_log_print(ANDROID_LOG_ERROR, "Gecko", "### scheduleComposite()");
+    AndroidBridge::Bridge()->ScheduleComposite();
+}
+
+NS_EXPORT void JNICALL
+Java_org_mozilla_gecko_GeckoAppShell_schedulePauseComposition(JNIEnv*, jclass)
+{
+    __android_log_print(ANDROID_LOG_ERROR, "Gecko", "### schedulePauseComposition()");
+    AndroidBridge::Bridge()->SchedulePauseComposition();
+}
+
+NS_EXPORT void JNICALL
+Java_org_mozilla_gecko_GeckoAppShell_scheduleResumeComposition(JNIEnv*, jclass)
+{
+    __android_log_print(ANDROID_LOG_ERROR, "Gecko", "### scheduleResumeComposition()");
+    AndroidBridge::Bridge()->ScheduleResumeComposition();
+}
+
 #endif
--- a/widget/android/AndroidJavaWrappers.cpp
+++ b/widget/android/AndroidJavaWrappers.cpp
@@ -71,16 +71,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::jAddressField = 0;
 jfieldID AndroidGeckoEvent::jBandwidthField = 0;
 jfieldID AndroidGeckoEvent::jCanBeMeteredField = 0;
+jmethodID AndroidGeckoEvent::jDoCallbackMethod = 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;
@@ -104,21 +105,37 @@ 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 AndroidGeckoSoftwareLayerClient::jGeckoSoftwareLayerClientClass = 0;
-jmethodID AndroidGeckoSoftwareLayerClient::jLockBufferMethod = 0;
-jmethodID AndroidGeckoSoftwareLayerClient::jUnlockBufferMethod = 0;
-jmethodID AndroidGeckoSoftwareLayerClient::jBeginDrawingMethod = 0;
-jmethodID AndroidGeckoSoftwareLayerClient::jEndDrawingMethod = 0;
+jclass AndroidGeckoLayerClient::jGeckoLayerClientClass = 0;
+jmethodID AndroidGeckoLayerClient::jBeginDrawingMethod = 0;
+jmethodID AndroidGeckoLayerClient::jEndDrawingMethod = 0;
+
+jclass AndroidGeckoGLLayerClient::jGeckoGLLayerClientClass = 0;
+jmethodID AndroidGeckoGLLayerClient::jGetViewTransformMethod = 0;
+jmethodID AndroidGeckoGLLayerClient::jCreateFrameMethod = 0;
+jmethodID AndroidGeckoGLLayerClient::jActivateProgramMethod = 0;
+jmethodID AndroidGeckoGLLayerClient::jDeactivateProgramMethod = 0;
+
+jclass AndroidLayerRendererFrame::jLayerRendererFrameClass = 0;
+jmethodID AndroidLayerRendererFrame::jBeginDrawingMethod = 0;
+jmethodID AndroidLayerRendererFrame::jDrawBackgroundMethod = 0;
+jmethodID AndroidLayerRendererFrame::jDrawForegroundMethod = 0;
+jmethodID AndroidLayerRendererFrame::jEndDrawingMethod = 0;
+
+jclass AndroidViewTransform::jViewTransformClass = 0;
+jfieldID AndroidViewTransform::jXField = 0;
+jfieldID AndroidViewTransform::jYField = 0;
+jfieldID AndroidViewTransform::jScaleField = 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,17 +156,20 @@ jmethodID AndroidGeckoSurfaceView::jGetH
 void
 mozilla::InitAndroidJavaWrappers(JNIEnv *jEnv)
 {
     AndroidGeckoEvent::InitGeckoEventClass(jEnv);
     AndroidPoint::InitPointClass(jEnv);
     AndroidLocation::InitLocationClass(jEnv);
     AndroidAddress::InitAddressClass(jEnv);
     AndroidRect::InitRectClass(jEnv);
-    AndroidGeckoSoftwareLayerClient::InitGeckoSoftwareLayerClientClass(jEnv);
+    AndroidGeckoLayerClient::InitGeckoLayerClientClass(jEnv);
+    AndroidGeckoGLLayerClient::InitGeckoGLLayerClientClass(jEnv);
+    AndroidLayerRendererFrame::InitLayerRendererFrameClass(jEnv);
+    AndroidViewTransform::InitViewTransformClass(jEnv);
     AndroidGeckoSurfaceView::InitGeckoSurfaceViewClass(jEnv);
 }
 
 void
 AndroidGeckoEvent::InitGeckoEventClass(JNIEnv *jEnv)
 {
     initInit();
 
@@ -184,16 +204,18 @@ 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;");
     jAddressField = getField("mAddress", "Landroid/location/Address;");
     jBandwidthField = getField("mBandwidth", "D");
     jCanBeMeteredField = getField("mCanBeMetered", "Z");
+
+    jDoCallbackMethod = getMethod("doCallback", "(Ljava/lang/String;)V");
 }
 
 void
 AndroidGeckoSurfaceView::InitGeckoSurfaceViewClass(JNIEnv *jEnv)
 {
 #ifndef MOZ_JAVA_COMPOSITOR
     initInit();
 
@@ -316,28 +338,70 @@ AndroidRect::InitRectClass(JNIEnv *jEnv)
 
     jBottomField = getField("bottom", "I");
     jLeftField = getField("left", "I");
     jTopField = getField("top", "I");
     jRightField = getField("right", "I");
 }
 
 void
-AndroidGeckoSoftwareLayerClient::InitGeckoSoftwareLayerClientClass(JNIEnv *jEnv)
+AndroidGeckoLayerClient::InitGeckoLayerClientClass(JNIEnv *jEnv)
+{
+#ifdef MOZ_JAVA_COMPOSITOR
+    initInit();
+
+    jGeckoLayerClientClass = getClassGlobalRef("org/mozilla/gecko/gfx/GeckoLayerClient");
+
+    jBeginDrawingMethod = getMethod("beginDrawing", "(IIIILjava/lang/String;Z)Landroid/graphics/Rect;");
+    jEndDrawingMethod = getMethod("endDrawing", "(IIII)V");
+#endif
+}
+
+void
+AndroidGeckoGLLayerClient::InitGeckoGLLayerClientClass(JNIEnv *jEnv)
 {
 #ifdef MOZ_JAVA_COMPOSITOR
     initInit();
 
-    jGeckoSoftwareLayerClientClass =
-        getClassGlobalRef("org/mozilla/gecko/gfx/GeckoSoftwareLayerClient");
+    jGeckoGLLayerClientClass = getClassGlobalRef("org/mozilla/gecko/gfx/GeckoGLLayerClient");
+
+    jGetViewTransformMethod = getMethod("getViewTransform",
+                                        "()Lorg/mozilla/gecko/gfx/ViewTransform;");
+    jCreateFrameMethod = getMethod("createFrame", "()Lorg/mozilla/gecko/gfx/LayerRenderer$Frame;");
+    jActivateProgramMethod = getMethod("activateProgram", "()V");
+    jDeactivateProgramMethod = getMethod("deactivateProgram", "()V");
+#endif
+}
+
+void
+AndroidLayerRendererFrame::InitLayerRendererFrameClass(JNIEnv *jEnv)
+{
+#ifdef MOZ_JAVA_COMPOSITOR
+    initInit();
+
+    jLayerRendererFrameClass = getClassGlobalRef("org/mozilla/gecko/gfx/LayerRenderer$Frame");
 
-    jLockBufferMethod = getMethod("lockBuffer", "()Ljava/nio/ByteBuffer;");
-    jUnlockBufferMethod = getMethod("unlockBuffer", "()V");
-    jBeginDrawingMethod = getMethod("beginDrawing", "(IIIILjava/lang/String;Z)Landroid/graphics/Rect;");
-    jEndDrawingMethod = getMethod("endDrawing", "(IIII)V");
+    jBeginDrawingMethod = getMethod("beginDrawing", "()V");
+    jDrawBackgroundMethod = getMethod("drawBackground", "()V");
+    jDrawForegroundMethod = getMethod("drawForeground", "()V");
+    jEndDrawingMethod = getMethod("endDrawing", "()V");
+#endif
+}
+
+void
+AndroidViewTransform::InitViewTransformClass(JNIEnv *jEnv)
+{
+#ifdef MOZ_JAVA_COMPOSITOR
+    initInit();
+
+    jViewTransformClass = getClassGlobalRef("org/mozilla/gecko/gfx/ViewTransform");
+
+    jXField = getField("x", "F");
+    jYField = getField("y", "F");
+    jScaleField = getField("scale", "F");
 #endif
 }
 
 #undef initInit
 #undef initClassGlobalRef
 #undef getField
 #undef getMethod
 
@@ -429,26 +493,38 @@ AndroidGeckoEvent::ReadCharactersExtraFi
 
 void
 AndroidGeckoEvent::Init(int aType, nsIntRect const& aRect)
 {
     mType = aType;
     mRect = aRect;
 }
 
+AndroidGeckoEvent::~AndroidGeckoEvent() {
+    if (!wrapped_obj)
+        return;
+
+    JNIEnv *jenv = GetJNIForThread();
+    if (!jenv)
+        return;
+    jenv->DeleteGlobalRef(wrapped_obj);
+}
+
 void
 AndroidGeckoEvent::Init(JNIEnv *jenv, jobject jobj)
 {
     NS_ASSERTION(!wrapped_obj, "Init called on non-null wrapped_obj!");
 
     wrapped_obj = jobj;
 
     if (!jobj)
         return;
 
+    jenv->NewGlobalRef(jobj);
+
     mAction = jenv->GetIntField(jobj, jActionField);
     mType = jenv->GetIntField(jobj, jTypeField);
 
     switch (mType) {
         case SIZE_CHANGED:
             ReadPointArray(mPoints, jenv, jPoints, 3);
             break;
 
@@ -600,20 +676,60 @@ AndroidPoint::Init(JNIEnv *jenv, jobject
         mY = jenv->GetIntField(jobj, jYField);
     } else {
         mX = 0;
         mY = 0;
     }
 }
 
 void
-AndroidGeckoSoftwareLayerClient::Init(jobject jobj)
+AndroidGeckoEvent::DoCallback(const nsAString& data) {
+    JNIEnv* env = AndroidBridge::GetJNIEnv();
+    if (!env)
+        return;
+    jstring jData = env->NewString(nsPromiseFlatString(data).get(), data.Length());
+    env->CallVoidMethod(wrapped_obj, jDoCallbackMethod, jData);
+}
+
+void
+AndroidGeckoGLLayerClient::Init(jobject jobj)
 {
     NS_ASSERTION(wrapped_obj == nsnull, "Init called on non-null wrapped_obj!");
     wrapped_obj = jobj;
+
+    // Register the view transform getter.
+    AndroidBridge::Bridge()->SetViewTransformGetter(mViewTransformGetter);
+}
+
+void
+AndroidLayerRendererFrame::Init(jobject jobj)
+{
+    if (!isNull()) {
+        Dispose();
+    }
+
+    wrapped_obj = GetJNIForThread()->NewGlobalRef(jobj);
+}
+
+void
+AndroidLayerRendererFrame::Dispose()
+{
+    if (isNull()) {
+        return;
+    }
+
+    GetJNIForThread()->DeleteGlobalRef(wrapped_obj);
+    wrapped_obj = 0;
+}
+
+void
+AndroidViewTransform::Init(jobject jobj)
+{
+    NS_ABORT_IF_FALSE(wrapped_obj == nsnull, "Init called on non-null wrapped_obj!");
+    wrapped_obj = jobj;
 }
 
 void
 AndroidGeckoSurfaceView::Init(jobject jobj)
 {
     NS_ASSERTION(wrapped_obj == nsnull, "Init called on non-null wrapped_obj!");
 
     wrapped_obj = jobj;
@@ -656,61 +772,21 @@ AndroidGeckoSurfaceView::Draw2D(jobject 
 {
     JNIEnv *env = AndroidBridge::GetJNIEnv();
     if (!env)
         return;
 
     env->CallVoidMethod(wrapped_obj, jDraw2DBufferMethod, buffer, stride);
 }
 
-jobject
-AndroidGeckoSoftwareLayerClient::LockBuffer()
-{
-    NS_ASSERTION(!isNull(), "LockBuffer() called on null software layer client!");
-
-    JNIEnv *env = AndroidBridge::GetJNIEnv();
-    if (!env)
-        return nsnull;
-
-    AndroidBridge::AutoLocalJNIFrame(env, 1);
-    return env->CallObjectMethod(wrapped_obj, jLockBufferMethod);
-}
-
-unsigned char *
-AndroidGeckoSoftwareLayerClient::LockBufferBits()
+bool
+AndroidGeckoLayerClient::BeginDrawing(int aWidth, int aHeight, int aTileWidth, int aTileHeight,
+                                      nsIntRect &aDirtyRect, const nsAString &aMetadata, bool aHasDirectTexture)
 {
-    JNIEnv *env = AndroidBridge::GetJNIEnv();
-    if (!env)
-        return nsnull;
-
-    AndroidBridge::AutoLocalJNIFrame(env, 1);
-    jobject bufferObject = LockBuffer();
-
-    if (bufferObject != nsnull)
-        return reinterpret_cast<unsigned char *>(env->GetDirectBufferAddress(bufferObject));
-
-    return nsnull;
-}
-
-void
-AndroidGeckoSoftwareLayerClient::UnlockBuffer()
-{
-    NS_ASSERTION(!isNull(), "UnlockBuffer() called on null software layer client!");
-    JNIEnv *env = AndroidBridge::GetJNIEnv();
-    if (!env)
-        return;
-
-    AndroidBridge::AutoLocalJNIFrame(env, 1);
-    env->CallVoidMethod(wrapped_obj, jUnlockBufferMethod);
-}
-
-bool
-AndroidGeckoSoftwareLayerClient::BeginDrawing(int aWidth, int aHeight, int aTileWidth, int aTileHeight, nsIntRect &aDirtyRect, const nsAString &aMetadata, bool aHasDirectTexture)
-{
-    NS_ASSERTION(!isNull(), "BeginDrawing() called on null software layer client!");
+    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());
 
     jobject rectObject = env->CallObjectMethod(wrapped_obj, jBeginDrawingMethod,
@@ -724,25 +800,26 @@ AndroidGeckoSoftwareLayerClient::BeginDr
     nsIntRect newDirtyRect = aDirtyRect.Intersect(nsIntRect(rect.Top(), rect.Left(),
                                                             rect.Width(), rect.Height()));
     aDirtyRect.SetRect(newDirtyRect.x, newDirtyRect.y, newDirtyRect.width, newDirtyRect.height);
 
     return true;
 }
 
 void
-AndroidGeckoSoftwareLayerClient::EndDrawing(const nsIntRect &aRect)
+AndroidGeckoLayerClient::EndDrawing(const nsIntRect &aRect)
 {
-    NS_ASSERTION(!isNull(), "EndDrawing() called on null software layer client!");
+    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);
+    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;
@@ -768,21 +845,157 @@ AndroidGeckoSurfaceView::GetSurface()
         return nsnull;
 
     return env->CallObjectMethod(wrapped_obj, jGetSurfaceMethod);
 }
 
 jobject
 AndroidGeckoSurfaceView::GetSurfaceHolder()
 {
-    JNIEnv *env = AndroidBridge::GetJNIEnv();
+    return GetJNIForThread()->CallObjectMethod(wrapped_obj, jGetHolderMethod);
+}
+
+void
+AndroidGeckoGLLayerClient::GetViewTransform(AndroidViewTransform& aViewTransform)
+{
+    JNIEnv *env = GetJNIForThread();
+    NS_ABORT_IF_FALSE(env, "No JNI environment at GetViewTransform()!");
+    if (!env) {
+        return;
+    }
+
+    jobject viewTransformJObj = env->CallObjectMethod(wrapped_obj, jGetViewTransformMethod);
+    NS_ABORT_IF_FALSE(viewTransformJObj, "No view transform object!");
+    aViewTransform.Init(viewTransformJObj);
+}
+
+void
+AndroidGeckoGLLayerClient::CreateFrame(AndroidLayerRendererFrame& aFrame)
+{
+    JNIEnv *env = GetJNIForThread();
+    NS_ABORT_IF_FALSE(env, "No JNI environment at CreateFrame()!");
+    if (!env) {
+        return;
+    }
+
+    jobject frameJObj = env->CallObjectMethod(wrapped_obj, jCreateFrameMethod);
+    NS_ABORT_IF_FALSE(frameJObj, "No frame object!");
+    aFrame.Init(frameJObj);
+}
+
+void
+AndroidGeckoGLLayerClient::ActivateProgram()
+{
+    JNIEnv *env = GetJNIForThread();
+    NS_ABORT_IF_FALSE(env, "No JNI environment at ActivateProgram()!");
+    if (!env) {
+        return;
+    }
+
+    env->CallVoidMethod(wrapped_obj, jActivateProgramMethod);
+}
+
+void
+AndroidGeckoGLLayerClient::DeactivateProgram()
+{
+    JNIEnv *env = GetJNIForThread();
+    NS_ABORT_IF_FALSE(env, "No JNI environment at DeactivateProgram()!");
+    if (!env) {
+        return;
+    }
+
+    env->CallVoidMethod(wrapped_obj, jDeactivateProgramMethod);
+}
+
+void
+AndroidLayerRendererFrame::BeginDrawing()
+{
+    JNIEnv *env = GetJNIForThread();
+    NS_ABORT_IF_FALSE(env, "No JNI environment at BeginDrawing()!");
+    if (!env) {
+        return;
+    }
+
+    env->CallVoidMethod(wrapped_obj, jBeginDrawingMethod);
+}
+
+void
+AndroidLayerRendererFrame::DrawBackground()
+{
+    JNIEnv *env = GetJNIForThread();
+    NS_ABORT_IF_FALSE(env, "No JNI environment at DrawBackground()!");
+    if (!env) {
+        return;
+    }
+
+    env->CallVoidMethod(wrapped_obj, jDrawBackgroundMethod);
+}
+
+void
+AndroidLayerRendererFrame::DrawForeground()
+{
+    JNIEnv *env = GetJNIForThread();
+    NS_ABORT_IF_FALSE(env, "No JNI environment at DrawForeground()!");
+    if (!env) {
+        return;
+    }
+
+    env->CallVoidMethod(wrapped_obj, jDrawForegroundMethod);
+}
+
+void
+AndroidLayerRendererFrame::EndDrawing()
+{
+    JNIEnv *env = GetJNIForThread();
+    NS_ABORT_IF_FALSE(env, "No JNI environment at EndDrawing()!");
+    if (!env) {
+        return;
+    }
+
+    env->CallVoidMethod(wrapped_obj, jEndDrawingMethod);
+}
+
+float
+AndroidViewTransform::GetX()
+{
+    JNIEnv *env = GetJNIForThread();
     if (!env)
-        return nsnull;
+        return 0.0f;
+    return env->GetFloatField(wrapped_obj, jXField);
+}
+
+float
+AndroidViewTransform::GetY()
+{
+    JNIEnv *env = GetJNIForThread();
+    if (!env)
+        return 0.0f;
+    return env->GetFloatField(wrapped_obj, jYField);
+}
 
-    return env->CallObjectMethod(wrapped_obj, jGetHolderMethod);
+float
+AndroidViewTransform::GetScale()
+{
+    JNIEnv *env = GetJNIForThread();
+    if (!env)
+        return 0.0f;
+    return env->GetFloatField(wrapped_obj, jScaleField);
+}
+
+void
+AndroidGeckoGLLayerClientViewTransformGetter::operator()(nsIntPoint& aScrollOffset, float& aScaleX,
+                                                         float& aScaleY)
+{
+    AndroidViewTransform viewTransform;
+
+    AndroidBridge::AutoLocalJNIFrame jniFrame(GetJNIForThread());
+    mLayerClient.GetViewTransform(viewTransform);
+
+    aScrollOffset = nsIntPoint(viewTransform.GetX(), viewTransform.GetY());
+    aScaleX = aScaleY = viewTransform.GetScale();
 }
 
 void
 AndroidRect::Init(JNIEnv *jenv, jobject jobj)
 {
     NS_ASSERTION(wrapped_obj == nsnull, "Init called on non-null wrapped_obj!");
 
     wrapped_obj = jobj;
--- a/widget/android/AndroidJavaWrappers.h
+++ b/widget/android/AndroidJavaWrappers.h
@@ -53,16 +53,18 @@
 #define ALOG(args...)  __android_log_print(ANDROID_LOG_INFO, "Gecko" , ## args)
 #else
 #define ALOG(args...)
 #endif
 #endif
 
 namespace mozilla {
 
+class AndroidGeckoGLLayerClient;
+
 void InitAndroidJavaWrappers(JNIEnv *jEnv);
 
 /*
  * 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
@@ -146,41 +148,121 @@ 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 InitGeckoSoftwareLayerClientClass(JNIEnv *jEnv);
- 
-     void Init(jobject jobj);
- 
-    AndroidGeckoSoftwareLayerClient() {}
-    AndroidGeckoSoftwareLayerClient(jobject jobj) { Init(jobj); }
+    static void InitGeckoLayerClientClass(JNIEnv *jEnv);
 
-    jobject LockBuffer();
-    unsigned char *LockBufferBits();
-    void UnlockBuffer();
-    bool BeginDrawing(int aWidth, int aHeight, int aTileWidth, int aTileHeight, nsIntRect &aDirtyRect, const nsAString &aMetadata, bool aHasDirectTexture);
+    void Init(jobject jobj);
+
+    bool BeginDrawing(int aWidth, int aHeight, int aTileWidth, int aTileHeight,
+                      nsIntRect &aDirtyRect, 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;
+};
+
+/** A callback that retrieves the view transform. */
+class AndroidViewTransformGetter
+{
+public:
+    virtual void operator()(nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY) = 0;
+};
+
+class AndroidGeckoGLLayerClientViewTransformGetter : public AndroidViewTransformGetter {
+public:
+    AndroidGeckoGLLayerClientViewTransformGetter(AndroidGeckoGLLayerClient& aLayerClient)
+    : mLayerClient(aLayerClient) {}
+
+    virtual void operator()(nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY);
+
 private:
-    static jclass jGeckoSoftwareLayerClientClass;
-    static jmethodID jLockBufferMethod;
-    static jmethodID jUnlockBufferMethod;
-
-protected:
-     static jmethodID jBeginDrawingMethod;
-     static jmethodID jEndDrawingMethod;
+    AndroidGeckoGLLayerClient& mLayerClient;
 };
 
+class AndroidViewTransform : public WrappedJavaObject {
+public:
+    static void InitViewTransformClass(JNIEnv *jEnv);
+
+    void Init(jobject jobj);
+
+    AndroidViewTransform() {}
+    AndroidViewTransform(jobject jobj) { Init(jobj); }
+
+    float GetX();
+    float GetY();
+    float GetScale();
+
+private:
+    static jclass jViewTransformClass;
+    static jfieldID jXField;
+    static jfieldID jYField;
+    static jfieldID jScaleField;
+};
+
+class AndroidLayerRendererFrame : public WrappedJavaObject {
+public:
+    static void InitLayerRendererFrameClass(JNIEnv *jEnv);
+
+    void Init(jobject jobj);
+    void Dispose();
+
+    void BeginDrawing();
+    void DrawBackground();
+    void DrawForeground();
+    void EndDrawing();
+
+private:
+    static jclass jLayerRendererFrameClass;
+    static jmethodID jBeginDrawingMethod;
+    static jmethodID jDrawBackgroundMethod;
+    static jmethodID jDrawForegroundMethod;
+    static jmethodID jEndDrawingMethod;
+};
+
+class AndroidGeckoGLLayerClient : public AndroidGeckoLayerClient {
+public:
+    static void InitGeckoGLLayerClientClass(JNIEnv *jEnv);
+
+    void Init(jobject jobj);
+
+    AndroidGeckoGLLayerClient()
+    : mViewTransformGetter(*this) {}
+
+    AndroidGeckoGLLayerClient(jobject jobj)
+    : mViewTransformGetter(*this) { Init(jobj); }
+
+    void GetViewTransform(AndroidViewTransform& aViewTransform);
+    void CreateFrame(AndroidLayerRendererFrame& aFrame);
+    void ActivateProgram();
+    void DeactivateProgram();
+
+private:
+    static jclass jGeckoGLLayerClientClass;
+    static jmethodID jGetViewTransformMethod;
+    static jmethodID jCreateFrameMethod;
+    static jmethodID jActivateProgramMethod;
+    static jmethodID jDeactivateProgramMethod;
+
+    AndroidGeckoGLLayerClientViewTransformGetter mViewTransformGetter;
+};
 
 class AndroidGeckoSurfaceView : public WrappedJavaObject
 {
 public:
     static void InitGeckoSurfaceViewClass(JNIEnv *jEnv);
 
     AndroidGeckoSurfaceView() { }
     AndroidGeckoSurfaceView(jobject jobj) {
@@ -420,16 +502,18 @@ public:
     }
     AndroidGeckoEvent(JNIEnv *jenv, jobject jobj) {
         Init(jenv, jobj);
     }
     AndroidGeckoEvent(AndroidGeckoEvent *aResizeEvent) {
         Init(aResizeEvent);
     }
 
+    ~AndroidGeckoEvent();
+
     void Init(JNIEnv *jenv, jobject jobj);
     void Init(int aType);
     void Init(int x1, int y1, int x2, int y2);
     void Init(int aType, const nsIntRect &aRect);
     void Init(AndroidGeckoEvent *aResizeEvent);
 
     int Action() { return mAction; }
     int Type() { return mType; }
@@ -459,16 +543,17 @@ public:
     int RangeType() { return mRangeType; }
     int RangeStyles() { return mRangeStyles; }
     int RangeForeColor() { return mRangeForeColor; }
     int RangeBackColor() { return mRangeBackColor; }
     nsGeoPosition* GeoPosition() { return mGeoPosition; }
     nsGeoPositionAddress* GeoAddress() { return mGeoAddress; }
     double Bandwidth() { return mBandwidth; }
     bool CanBeMetered() { return mCanBeMetered; }
+    void DoCallback(const nsAString& data);
 
 protected:
     int mAction;
     int mType;
     int64_t mTime;
     nsTArray<nsIntPoint> mPoints;
     nsTArray<nsIntPoint> mPointRadii;
     nsTArray<int> mPointIndicies;
@@ -538,16 +623,18 @@ protected:
     static jfieldID jRangeStylesField;
     static jfieldID jRangeForeColorField;
     static jfieldID jRangeBackColorField;
     static jfieldID jLocationField;
     static jfieldID jAddressField;
 
     static jfieldID jBandwidthField;
     static jfieldID jCanBeMeteredField;
+    
+    static jmethodID jDoCallbackMethod;
 
 public:
     enum {
         NATIVE_POKE = 0,
         KEY_EVENT = 1,
         MOTION_EVENT = 2,
         ORIENTATION_EVENT = 3,
         ACCELERATION_EVENT = 4,
--- a/widget/android/Makefile.in
+++ b/widget/android/Makefile.in
@@ -57,16 +57,17 @@ endif
 
 CPPSRCS	= \
 	GfxInfo.cpp \
 	nsWidgetFactory.cpp \
 	nsAppShell.cpp \
 	AndroidJavaWrappers.cpp \
 	AndroidBridge.cpp \
 	AndroidDirectTexture.cpp \
+	AndroidFlexViewWrapper.cpp \
 	AndroidGraphicBuffer.cpp \
 	AndroidJNI.cpp \
 	AndroidMediaLayer.cpp \
 	nsWindow.cpp \
 	nsLookAndFeel.cpp \
 	nsScreenManagerAndroid.cpp \
 	nsIdleServiceAndroid.cpp \
 	nsClipboard.cpp \
@@ -86,17 +87,17 @@ NOT_THERE_YET_CPPSRCS = \
 	$(NULL)
 
 XPIDLSRCS	= \
 	nsIAndroidBridge.idl \
 	$(NULL)
 
 SHARED_LIBRARY_LIBS = ../xpwidgets/libxpwidgets_s.a
 
-EXPORTS = AndroidBridge.h AndroidJavaWrappers.h
+EXPORTS = AndroidBridge.h AndroidJavaWrappers.h AndroidFlexViewWrapper.h
 
 include $(topsrcdir)/config/rules.mk
 
 DEFINES += -D_IMPL_NS_WIDGET
 #DEFINES += -DDEBUG_WIDGETS
 
 LOCAL_INCLUDES += \
 	-I$(topsrcdir)/widget/xpwidgets \
--- a/widget/android/nsAppShell.cpp
+++ b/widget/android/nsAppShell.cpp
@@ -442,22 +442,23 @@ nsAppShell::ProcessNextNativeEvent(bool 
 
         AndroidBridge* bridge = AndroidBridge::Bridge();
         if (!bridge)
             break;
 
         nsCOMPtr<nsIDOMWindow> domWindow;
         mBrowserApp->GetWindowForTab(curEvent->MetaState(), getter_AddRefs(domWindow));
         nsTArray<nsIntPoint> points = curEvent->Points();
-        NS_ASSERTION(points.Length() != 2, "Screenshot event does not have enough coordinates");
+        NS_ASSERTION(points.Length() == 2, "Screenshot event does not have enough coordinates");
         if (domWindow)
             bridge->TakeScreenshot(domWindow, 0, 0, points[0].x, points[0].y, points[1].x, points[1].y, curEvent->MetaState());
         break;
     }
 
+
     case AndroidGeckoEvent::VIEWPORT:
     case AndroidGeckoEvent::BROADCAST: {
 
         if (curEvent->Characters().Length() == 0)
             break;
 
         nsCOMPtr<nsIObserverService> obsServ =
             mozilla::services::GetObserverService();
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -213,16 +213,19 @@ nsWindow::~nsWindow()
     nsWindow *top = FindTopLevel();
     if (top->mFocus == this)
         top->mFocus = nsnull;
 #ifdef ACCESSIBILITY
     if (mRootAccessible)
         mRootAccessible = nsnull;
 #endif
     ALOG("nsWindow %p destructor", (void*)this);
+
+    AndroidBridge::Bridge()->SetCompositorParent(NULL, NULL);
+
 }
 
 bool
 nsWindow::IsTopLevel()
 {
     return mWindowType == eWindowType_toplevel ||
         mWindowType == eWindowType_dialog ||
         mWindowType == eWindowType_invisible;
@@ -761,55 +764,72 @@ nsWindow::GetLayerManager(PLayersChild*,
 {
     if (aAllowRetaining) {
         *aAllowRetaining = true;
     }
     if (mLayerManager) {
         return mLayerManager;
     }
 
-    printf_stderr("nsWindow::GetLayerManager\n");
+    nsWindow *topWindow = TopWindow();
 
-    nsWindow *topWindow = TopWindow();
+    __android_log_print(ANDROID_LOG_ERROR, "Gecko", "### nsWindow::GetLayerManager this=%p "
+                        "topWindow=%p", this, topWindow);
 
     if (!topWindow) {
         printf_stderr(" -- no topwindow\n");
         mLayerManager = CreateBasicLayerManager();
         return mLayerManager;
     }
 
+    bool useCompositor =
+        Preferences::GetBool("layers.offmainthreadcomposition.enabled", false);
+
+    if (useCompositor) {
+        CreateCompositor();
+        if (mLayerManager) {
+            AndroidBridge::Bridge()->SetCompositorParent(mCompositorParent, mCompositorThread);
+            return mLayerManager;
+        }
+
+        // If we get here, then off main thread compositing failed to initialize.
+        sFailedToCreateGLContext = true;
+    }
+
     mUseAcceleratedRendering = GetShouldAccelerate();
 
     if (!mUseAcceleratedRendering ||
         sFailedToCreateGLContext)
     {
         printf_stderr(" -- creating basic, not accelerated\n");
         mLayerManager = CreateBasicLayerManager();
         return mLayerManager;
     }
 
-    if (!sGLContext) {
-        // the window we give doesn't matter here
-        sGLContext = mozilla::gl::GLContextProvider::CreateForWindow(this);
-    }
+    if (!mLayerManager) {
+        if (!sGLContext) {
+            // the window we give doesn't matter here
+            sGLContext = mozilla::gl::GLContextProvider::CreateForWindow(this);
+        }
 
-    if (sGLContext) {
-        nsRefPtr<mozilla::layers::LayerManagerOGL> layerManager =
-            new mozilla::layers::LayerManagerOGL(this);
+        if (sGLContext) {
+                nsRefPtr<mozilla::layers::LayerManagerOGL> layerManager =
+                        new mozilla::layers::LayerManagerOGL(this);
 
-        if (layerManager && layerManager->Initialize(sGLContext))
-            mLayerManager = layerManager;
-        sValidSurface = true;
-    }
+                if (layerManager && layerManager->Initialize(sGLContext))
+                        mLayerManager = layerManager;
+                sValidSurface = true;
+        }
 
-    if (!sGLContext || !mLayerManager) {
-        sGLContext = nsnull;
-        sFailedToCreateGLContext = true;
+        if (!sGLContext || !mLayerManager) {
+                sGLContext = nsnull;
+                sFailedToCreateGLContext = true;
 
-        mLayerManager = CreateBasicLayerManager();
+                mLayerManager = CreateBasicLayerManager();
+        }
     }
 
     return mLayerManager;
 }
 
 gfxASurface*
 nsWindow::GetThebesSurface()
 {
@@ -828,16 +848,21 @@ void
 nsWindow::BindToTexture()
 {
     sDirectTexture->Bind();
 }
 
 bool
 nsWindow::HasDirectTexture()
 {
+  // XXX: Checking fix me
+  // This is currently causes some crashes so disable it for now
+  if (true)
+    return false;
+
   static bool sTestedDirectTexture = false;
   static bool sHasDirectTexture = false;
 
   // If we already tested, return early
   if (sTestedDirectTexture)
     return sHasDirectTexture;
 
   sTestedDirectTexture = true;
@@ -898,17 +923,17 @@ nsWindow::OnGlobalAndroidEvent(AndroidGe
             win->mBounds.height = 0;
             // also resize the children
             for (PRUint32 i = 0; i < win->mChildren.Length(); i++) {
                 win->mChildren[i]->mBounds.width = 0;
                 win->mChildren[i]->mBounds.height = 0;
             }
         case AndroidGeckoEvent::SIZE_CHANGED: {
             nsTArray<nsIntPoint> points = ae->Points();
-            NS_ASSERTION(points.Length() != 3, "Size changed does not have enough coordinates");
+            NS_ASSERTION(points.Length() == 3, "Size changed does not have enough coordinates");
 
             int nw = points[0].x;
             int nh = points[0].y;
 
             if (ae->Type() == AndroidGeckoEvent::FORCED_RESIZE || nw != gAndroidBounds.width ||
                 nh != gAndroidBounds.height) {
 
                 gAndroidBounds.width = nw;
@@ -1101,16 +1126,18 @@ nsWindow::DrawTo(gfxASurface *targetSurf
     if (coveringChildIndex == -1) {
         nsPaintEvent event(true, NS_PAINT, this);
 
         nsIntRect tileRect(0, 0, gAndroidBounds.width, gAndroidBounds.height);
         event.region = boundsRect.Intersect(invalidRect).Intersect(tileRect);
 
         switch (GetLayerManager(nsnull)->GetBackendType()) {
             case LayerManager::LAYERS_BASIC: {
+                __android_log_print(ANDROID_LOG_ERROR, "Gecko", "### Basic layers drawing");
+
                 nsRefPtr<gfxContext> ctx = new gfxContext(targetSurface);
 
                 {
                     AutoLayerManagerSetup
                       setupLayerManager(this, ctx, BasicLayerManager::BUFFER_NONE);
 
                     status = DispatchEvent(&event);
                 }
@@ -1123,16 +1150,18 @@ nsWindow::DrawTo(gfxASurface *targetSurf
 #endif
 
                 // XXX if we got an ignore for the parent, do we still want to draw the children?
                 // We don't really have a good way not to...
                 break;
             }
 
             case LayerManager::LAYERS_OPENGL: {
+                __android_log_print(ANDROID_LOG_ERROR, "Gecko", "### OGL layers drawing");
+
                 static_cast<mozilla::layers::LayerManagerOGL*>(GetLayerManager(nsnull))->
                     SetClippingRegion(nsIntRegion(boundsRect));
 
                 status = DispatchEvent(&event);
                 break;
             }
 
             default:
@@ -1182,103 +1211,116 @@ nsWindow::OnDraw(AndroidGeckoEvent *ae)
     }
 
     if (!mIsVisible) {
         ALOG("##### redraw for window %p, which is not visible -- ignoring!", (void*) this);
         DumpWindows();
         return;
     }
 
+    __android_log_print(ANDROID_LOG_ERROR, "Gecko", "### OnDraw()");
+
+    nsRefPtr<nsWindow> kungFuDeathGrip(this);
+
     AndroidBridge::AutoLocalJNIFrame jniFrame;
 #ifdef MOZ_JAVA_COMPOSITOR
     // We haven't been given a window-size yet, so do nothing
-    if (gAndroidBounds.width <= 0 || gAndroidBounds.height <= 0)
+    if (gAndroidBounds.width <= 0 || gAndroidBounds.height <= 0) {
+        __android_log_print(ANDROID_LOG_ERROR, "Gecko",
+                            "### No window size yet -- skipping draw!");
         return;
+    }
 
     /*
      * Check to see whether the presentation shell corresponding to the document on the screen
      * is suppressing painting. If it is, we bail out, as continuing would result in a mismatch
      * between the content on the screen and the current viewport metrics.
      */
     nsCOMPtr<nsIAndroidDrawMetadataProvider> metadataProvider =
         AndroidBridge::Bridge()->GetDrawMetadataProvider();
 
     bool paintingSuppressed = false;
     if (metadataProvider) {
         metadataProvider->PaintingSuppressed(&paintingSuppressed);
     }
     if (paintingSuppressed) {
+        __android_log_print(ANDROID_LOG_ERROR, "Gecko", "### Painting suppressed!");
         return;
     }
 
     nsAutoString metadata;
     if (metadataProvider) {
         metadataProvider->GetDrawMetadata(metadata);
     }
 
+#if 0
+    // BEGIN HACK: gl layers
+    nsPaintEvent event(true, NS_PAINT, this);
+    nsIntRect tileRect(0, 0, gAndroidBounds.width, gAndroidBounds.height);
+    event.region = tileRect;
+#endif
+
+    static unsigned char *bits2 = NULL;
+    static gfxIntSize bitsSize(0, 0);
+    if (bitsSize.width != gAndroidBounds.width || bitsSize.height != gAndroidBounds.height) {
+        if (bits2) {
+            delete[] bits2;
+        }
+        bits2 = new unsigned char[gAndroidBounds.width * gAndroidBounds.height * 2];
+        bitsSize = gAndroidBounds;
+    }
+
+    nsRefPtr<gfxImageSurface> targetSurface =
+        new gfxImageSurface(bits2, gfxIntSize(32, 32), 32 * 2,
+                            gfxASurface::ImageFormatRGB16_565);
+
+#if 0
+    nsRefPtr<gfxContext> ctx = new gfxContext(targetSurface);
+    AutoLayerManagerSetup setupLayerManager(this, ctx, BasicLayerManager::BUFFER_NONE);
+
+    nsEventStatus status;
+    status = DispatchEvent(&event);
+
+    return;
+    // END HACK: gl layers
+#endif
+
     nsIntRect dirtyRect = ae->Rect().Intersect(nsIntRect(0, 0, gAndroidBounds.width, gAndroidBounds.height));
 
-    AndroidGeckoSoftwareLayerClient &client =
-        AndroidBridge::Bridge()->GetSoftwareLayerClient();
+    AndroidGeckoLayerClient &client = AndroidBridge::Bridge()->GetLayerClient();
     if (!client.BeginDrawing(gAndroidBounds.width, gAndroidBounds.height,
                              gAndroidTileSize.width, gAndroidTileSize.height,
                              dirtyRect, metadata, HasDirectTexture())) {
+        __android_log_print(ANDROID_LOG_ERROR, "Gecko", "### BeginDrawing returned false!");
         return;
     }
 
     unsigned char *bits = NULL;
     if (HasDirectTexture()) {
+      __android_log_print(ANDROID_LOG_ERROR, "Gecko", "### Have direct texture!");
       if (sDirectTexture->Width() != gAndroidBounds.width ||
           sDirectTexture->Height() != gAndroidBounds.height) {
         sDirectTexture->Reallocate(gAndroidBounds.width, gAndroidBounds.height);
       }
 
       sDirectTexture->Lock(AndroidGraphicBuffer::UsageSoftwareWrite, dirtyRect, &bits);
-    } else {
-      bits = client.LockBufferBits();
     }
-    if (!bits) {
-        ALOG("### Failed to lock buffer");
-    } else {
-        // If tile size is 0,0, we assume we only have a single tile
-        int tileWidth = (gAndroidTileSize.width > 0) ? gAndroidTileSize.width : gAndroidBounds.width;
-        int tileHeight = (gAndroidTileSize.height > 0) ? gAndroidTileSize.height : gAndroidBounds.height;
-
-        int offset = 0;
-
-        for (int y = 0; y < gAndroidBounds.height; y += tileHeight) {
-            for (int x = 0; x < gAndroidBounds.width; x += tileWidth) {
-                int width = NS_MIN(tileWidth, gAndroidBounds.width - x);
-                int height = NS_MIN(tileHeight, gAndroidBounds.height - y);
 
-                nsRefPtr<gfxImageSurface> targetSurface =
-                    new gfxImageSurface(bits + offset,
-                                        gfxIntSize(width, height),
-                                        width *