Merge mozilla-central into mozilla-inbound
authorEhsan Akhgari <ehsan@mozilla.com>
Wed, 14 Mar 2012 13:40:34 -0400
changeset 91201 10d7baa4aff02bc6ed52f62879814f989dfc469d
parent 91200 825e0c77d712f0910406c5537ca4b928b2d74a89 (current diff)
parent 91168 936ef50fa498d4185a15d7b75324daf1d4fa35c9 (diff)
child 91202 1d4ce5c2cb978ea8713ebcb36491e6f905e5680f
push idunknown
push userunknown
push dateunknown
milestone14.0a1
Merge mozilla-central into mozilla-inbound
browser/devtools/styleinspector/test/browser_ruleview_editor_changedvalues.js
browser/themes/gnomestripe/devtools/htmlpanel.css
browser/themes/pinstripe/devtools/htmlpanel.css
browser/themes/winstripe/devtools/htmlpanel.css
configure.in
content/base/src/nsGenericElement.cpp
content/base/src/nsGenericElement.h
content/base/src/nsLineBreaker.cpp
dom/base/nsGlobalWindow.h
dom/plugins/base/nsPluginInstanceOwner.cpp
js/src/jit-test/tests/collections/Map-constructor-1.js
js/src/jit-test/tests/collections/Map-size.js
js/src/jit-test/tests/collections/Set-constructor-1.js
js/src/jit-test/tests/collections/Set-size.js
layout/style/nsCSSRuleProcessor.cpp
mobile/android/base/gfx/GeckoSoftwareLayerClient.java
mobile/android/base/gfx/LayerClient.java
mobile/android/base/gfx/MultiTileLayer.java
mobile/android/base/gfx/WidgetTileLayer.java
mobile/android/chrome/content/SelectHelper.js
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -384,16 +384,46 @@ nsDOMWindowUtils::SetResolution(float aX
   }
 
   nsIPresShell* presShell = GetPresShell();
   return presShell ? presShell->SetResolution(aXResolution, aYResolution)
                    : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
+nsDOMWindowUtils::SetIsFirstPaint(bool aIsFirstPaint)
+{
+  if (!IsUniversalXPConnectCapable()) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  nsIPresShell* presShell = GetPresShell();
+  if (presShell) {
+    presShell->SetIsFirstPaint(aIsFirstPaint);
+    return NS_OK;
+  }
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetIsFirstPaint(bool *aIsFirstPaint)
+{
+  if (!IsUniversalXPConnectCapable()) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  nsIPresShell* presShell = GetPresShell();
+  if (presShell) {
+    *aIsFirstPaint = presShell->GetIsFirstPaint();
+    return NS_OK;
+  }
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
 nsDOMWindowUtils::SendMouseEvent(const nsAString& aType,
                                  float aX,
                                  float aY,
                                  PRInt32 aButton,
                                  PRInt32 aClickCount,
                                  PRInt32 aModifiers,
                                  bool aIgnoreRootScrollFrame)
 {
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -65,17 +65,17 @@ interface nsIDOMEvent;
 interface nsITransferable;
 interface nsIQueryContentEventResult;
 interface nsIDOMWindow;
 interface nsIDOMBlob;
 interface nsIDOMFile;
 interface nsIFile;
 interface nsIDOMTouch;
 
-[scriptable, uuid(73b48170-55d5-11e1-b86c-0800200c9a66)]
+[scriptable, uuid(5740d0fe-9f4e-431f-b8b3-b82f9b7ff4cf)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -184,16 +184,25 @@ interface nsIDOMWindowUtils : nsISupport
    *   // elsewhere
    *   browser.setViewportScale(2.0, 2.0);
    *
    * The caller of this method must have UniversalXPConnect
    * privileges.
    */
   void setResolution(in float aXResolution, in float aYResolution);
 
+  /**
+   * Whether the next paint should be flagged as the first paint for a document.
+   * This gives a way to track the next paint that occurs after the flag is
+   * set. The flag gets cleared after the next paint.
+   *
+   * Can only be accessed with UniversalXPConnect privileges.
+   */
+  attribute boolean isFirstPaint;
+
   /** Synthesize a mouse event. The event types supported are:
    *    mousedown, mouseup, mousemove, mouseover, mouseout, contextmenu
    *
    * Events are sent in coordinates offset by aX and aY from the window.
    *
    * Note that additional events may be fired as a result of this call. For
    * instance, typically a click event will be fired as a result of a
    * mousedown and mouseup in sequence.
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -2912,32 +2912,36 @@ void nsPluginInstanceOwner::Paint(gfxCon
                                   const gfxRect& aFrameRect,
                                   const gfxRect& aDirtyRect)
 {
   if (!mInstance || !mObjectFrame || !mPluginDocumentActiveState)
     return;
 
   PRInt32 model = mInstance->GetANPDrawingModel();
 
+  float xResolution = mObjectFrame->PresContext()->GetRootPresContext()->PresShell()->GetXResolution();
+  float yResolution = mObjectFrame->PresContext()->GetRootPresContext()->PresShell()->GetYResolution();
+
+  gfxRect scaledFrameRect = aFrameRect;
+  scaledFrameRect.Scale(xResolution, yResolution);
+
   if (model == kSurface_ANPDrawingModel) {
-    if (!AddPluginView(aFrameRect)) {
+    if (!AddPluginView(scaledFrameRect)) {
       Invalidate();
     }
     return;
   }
 
   if (model == kOpenGL_ANPDrawingModel) {
     if (!mLayer)
       mLayer = new AndroidMediaLayer();
 
-    // FIXME: this is gross
-    float zoomLevel = aFrameRect.width / (float)mPluginWindow->width;
-    mLayer->UpdatePosition(aFrameRect, zoomLevel);
-
-    SendSize((int)aFrameRect.width, (int)aFrameRect.height);
+    mLayer->UpdatePosition(scaledFrameRect, xResolution);
+
+    SendSize((int)scaledFrameRect.width, (int)scaledFrameRect.height);
     return;
   }
 
   if (model != kBitmap_ANPDrawingModel)
     return;
 
 #ifdef ANP_BITMAP_DRAWING_MODEL
   static nsRefPtr<gfxImageSurface> pluginSurface;
--- a/dom/telephony/Telephony.cpp
+++ b/dom/telephony/Telephony.cpp
@@ -159,18 +159,18 @@ Telephony::Create(nsPIDOMWindow* aOwner,
   nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aOwner);
   NS_ENSURE_TRUE(sgo, nsnull);
 
   nsCOMPtr<nsIScriptContext> scriptContext = sgo->GetContext();
   NS_ENSURE_TRUE(scriptContext, nsnull);
 
   nsRefPtr<Telephony> telephony = new Telephony();
 
-  telephony->mOwner = aOwner;
-  telephony->mScriptContext.swap(scriptContext);
+  telephony->BindToOwner(aOwner);
+
   telephony->mRIL = aRIL;
   telephony->mRILTelephonyCallback = new RILTelephonyCallback(telephony);
 
   nsresult rv = aRIL->EnumerateCalls(telephony->mRILTelephonyCallback);
   NS_ENSURE_SUCCESS(rv, nsnull);
 
   rv = aRIL->RegisterCallback(telephony->mRILTelephonyCallback);
   NS_ENSURE_SUCCESS(rv, nsnull);
@@ -323,41 +323,52 @@ Telephony::SetSpeakerEnabled(bool aSpeak
 NS_IMETHODIMP
 Telephony::GetActive(jsval* aActive)
 {
   if (!mActiveCall) {
     aActive->setNull();
     return NS_OK;
   }
 
-  nsresult rv =
-    nsContentUtils::WrapNative(mScriptContext->GetNativeContext(),
-                               mScriptContext->GetNativeGlobal(),
-                               mActiveCall->ToISupports(), aActive);
+  nsresult rv;
+  nsIScriptContext* sc = GetContextForEventHandlers(&rv);
   NS_ENSURE_SUCCESS(rv, rv);
-
+  if (sc) {
+    rv =
+      nsContentUtils::WrapNative(sc->GetNativeContext(),
+                                 sc->GetNativeGlobal(),
+                                 mActiveCall->ToISupports(), aActive);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Telephony::GetCalls(jsval* aCalls)
 {
   JSObject* calls = mCallsArray;
   if (!calls) {
-    nsresult rv =
-      nsTArrayToJSArray(mScriptContext->GetNativeContext(),
-                        mScriptContext->GetNativeGlobal(), mCalls, &calls);
+    nsresult rv;
+    nsIScriptContext* sc = GetContextForEventHandlers(&rv);
     NS_ENSURE_SUCCESS(rv, rv);
+    if (sc) {
+      rv =
+        nsTArrayToJSArray(sc->GetNativeContext(),
+                          sc->GetNativeGlobal(), mCalls, &calls);
+      NS_ENSURE_SUCCESS(rv, rv);
 
-    if (!mRooted) {
-      NS_HOLD_JS_OBJECTS(this, Telephony);
-      mRooted = true;
+      if (!mRooted) {
+        NS_HOLD_JS_OBJECTS(this, Telephony);
+        mRooted = true;
+      }
+
+      mCallsArray = calls;
+    } else {
+      NS_ENSURE_SUCCESS(rv, rv);
     }
-
-    mCallsArray = calls;
   }
 
   aCalls->setObject(*calls);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Telephony::StartTone(const nsAString& aDTMFChar)
--- a/dom/telephony/Telephony.h
+++ b/dom/telephony/Telephony.h
@@ -113,28 +113,16 @@ public:
   }
 
   nsIRadioInterfaceLayer*
   RIL() const
   {
     return mRIL;
   }
 
-  nsPIDOMWindow*
-  Owner() const
-  {
-    return mOwner;
-  }
-
-  nsIScriptContext*
-  ScriptContext() const
-  {
-    return mScriptContext;
-  }
-
 private:
   Telephony();
   ~Telephony();
 
   already_AddRefed<TelephonyCall>
   CreateNewDialingCall(const nsAString& aNumber);
 
   void
--- a/dom/telephony/TelephonyCall.cpp
+++ b/dom/telephony/TelephonyCall.cpp
@@ -52,18 +52,18 @@ TelephonyCall::Create(Telephony* aTeleph
                       PRUint16 aCallState, PRUint32 aCallIndex)
 {
   NS_ASSERTION(aTelephony, "Null pointer!");
   NS_ASSERTION(!aNumber.IsEmpty(), "Empty number!");
   NS_ASSERTION(aCallIndex >= 1, "Invalid call index!");
 
   nsRefPtr<TelephonyCall> call = new TelephonyCall();
 
-  call->mOwner = aTelephony->Owner();
-  call->mScriptContext = aTelephony->ScriptContext();
+  call->BindToOwner(aTelephony->GetOwner());
+
   call->mTelephony = aTelephony;
   call->mNumber = aNumber;
   call->mCallIndex = aCallIndex;
 
   call->ChangeStateInternal(aCallState, false);
 
   return call.forget();
 }
--- a/embedding/android/GeckoAppShell.java
+++ b/embedding/android/GeckoAppShell.java
@@ -1556,17 +1556,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/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -50,18 +50,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
 PRUintn GLContext::sCurrentGLContextTLS = -1;
 #endif
 
 PRUint32 GLContext::sDebugMode = 0;
@@ -383,34 +386,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;
             }
         }
@@ -590,29 +600,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)
@@ -672,19 +762,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;
@@ -857,17 +944,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()
 {
 }
@@ -1966,26 +2053,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;
@@ -2220,16 +2314,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,
@@ -2410,47 +2539,65 @@ GLContext::TexSubImage2DWithoutUnpackSub
                     type,
                     newPixels);
     delete [] newPixels;
     fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
 }
 
 void
 GLContext::RectTriangles::addRect(GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1,
-                                  GLfloat tx0, GLfloat ty0, GLfloat tx1, GLfloat ty1)
+                                  GLfloat tx0, GLfloat ty0, GLfloat tx1, GLfloat ty1,
+                                  bool flip_y /* = false */)
 {
     vert_coord v;
     v.x = x0; v.y = y0;
     vertexCoords.AppendElement(v);
     v.x = x1; v.y = y0;
     vertexCoords.AppendElement(v);
     v.x = x0; v.y = y1;
     vertexCoords.AppendElement(v);
 
     v.x = x0; v.y = y1;
     vertexCoords.AppendElement(v);
     v.x = x1; v.y = y0;
     vertexCoords.AppendElement(v);
     v.x = x1; v.y = y1;
     vertexCoords.AppendElement(v);
 
-    tex_coord t;
-    t.u = tx0; t.v = ty0;
-    texCoords.AppendElement(t);
-    t.u = tx1; t.v = ty0;
-    texCoords.AppendElement(t);
-    t.u = tx0; t.v = ty1;
-    texCoords.AppendElement(t);
-
-    t.u = tx0; t.v = ty1;
-    texCoords.AppendElement(t);
-    t.u = tx1; t.v = ty0;
-    texCoords.AppendElement(t);
-    t.u = tx1; t.v = ty1;
-    texCoords.AppendElement(t);
+    if (flip_y) {
+        tex_coord t;
+        t.u = tx0; t.v = ty1;
+        texCoords.AppendElement(t);
+        t.u = tx1; t.v = ty1;
+        texCoords.AppendElement(t);
+        t.u = tx0; t.v = ty0;
+        texCoords.AppendElement(t);
+
+        t.u = tx0; t.v = ty0;
+        texCoords.AppendElement(t);
+        t.u = tx1; t.v = ty1;
+        texCoords.AppendElement(t);
+        t.u = tx1; t.v = ty0;
+        texCoords.AppendElement(t);
+    } else {
+        tex_coord t;
+        t.u = tx0; t.v = ty0;
+        texCoords.AppendElement(t);
+        t.u = tx1; t.v = ty0;
+        texCoords.AppendElement(t);
+        t.u = tx0; t.v = ty1;
+        texCoords.AppendElement(t);
+
+        t.u = tx0; t.v = ty1;
+        texCoords.AppendElement(t);
+        t.u = tx1; t.v = ty0;
+        texCoords.AppendElement(t);
+        t.u = tx1; t.v = ty1;
+        texCoords.AppendElement(t);
+    }
 }
 
 static GLfloat
 WrapTexCoord(GLfloat v)
 {
     // fmodf gives negative results for negative numbers;
     // that is, fmodf(0.75, 1.0) == 0.75, but
     // fmodf(-0.75, 1.0) == -0.75.  For the negative case,
@@ -2460,23 +2607,24 @@ WrapTexCoord(GLfloat v)
     }
 
     return fmodf(v, 1.0f);
 }
 
 void
 GLContext::DecomposeIntoNoRepeatTriangles(const nsIntRect& aTexCoordRect,
                                           const nsIntSize& aTexSize,
-                                          RectTriangles& aRects)
+                                          RectTriangles& aRects,
+                                          bool aFlipY /* = false */)
 {
     // normalize this
     nsIntRect tcr(aTexCoordRect);
-    while (tcr.x > aTexSize.width)
+    while (tcr.x >= aTexSize.width)
         tcr.x -= aTexSize.width;
-    while (tcr.y > aTexSize.height)
+    while (tcr.y >= aTexSize.height)
         tcr.y -= aTexSize.height;
 
     // Compute top left and bottom right tex coordinates
     GLfloat tl[2] =
         { GLfloat(tcr.x) / GLfloat(aTexSize.width),
           GLfloat(tcr.y) / GLfloat(aTexSize.height) };
     GLfloat br[2] =
         { GLfloat(tcr.XMost()) / GLfloat(aTexSize.width),
@@ -2526,57 +2674,68 @@ 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],
+                       aFlipY);
     } 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);
+                       br[0], 1.0f,
+                       aFlipY);
         aRects.addRect(0.0f, ymid,
                        1.0f, 1.0f,
                        tl[0], 0.0f,
-                       br[0], br[1]);
+                       br[0], br[1],
+                       aFlipY);
     } else if (xwrap && !ywrap) {
         GLfloat xmid = (1.0f - tl[0]) / xlen;
         aRects.addRect(0.0f, 0.0f,
                        xmid, 1.0f,
                        tl[0], tl[1],
-                       1.0f, br[1]);
+                       1.0f, br[1],
+                       aFlipY);
         aRects.addRect(xmid, 0.0f,
                        1.0f, 1.0f,
                        0.0f, tl[1],
-                       br[0], br[1]);
+                       br[0], br[1],
+                       aFlipY);
     } else {
         GLfloat xmid = (1.0f - tl[0]) / xlen;
         GLfloat ymid = (1.0f - tl[1]) / ylen;
         aRects.addRect(0.0f, 0.0f,
                        xmid, ymid,
                        tl[0], tl[1],
-                       1.0f, 1.0f);
+                       1.0f, 1.0f,
+                       aFlipY);
         aRects.addRect(xmid, 0.0f,
                        1.0f, ymid,
                        0.0f, tl[1],
-                       br[0], 1.0f);
+                       br[0], 1.0f,
+                       aFlipY);
         aRects.addRect(0.0f, ymid,
                        xmid, 1.0f,
                        tl[0], 0.0f,
-                       1.0f, br[1]);
+                       1.0f, br[1],
+                       aFlipY);
         aRects.addRect(xmid, ymid,
                        1.0f, 1.0f,
                        0.0f, 0.0f,
-                       br[0], br[1]);
+                       br[0], br[1],
+                       aFlipY);
     }
 }
 
 void
 GLContext::UseBlitProgram()
 {
     if (mBlitProgram) {
         fUseProgram(mBlitProgram);
--- a/gfx/gl/GLContext.h
+++ b/gfx/gl/GLContext.h
@@ -703,33 +703,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; }
 
@@ -1414,18 +1419,22 @@ public:
                                             GLenum format, GLenum type,
                                             const GLvoid* pixels);
 
     /** Helper for DecomposeIntoNoRepeatTriangles
      */
     struct RectTriangles {
         RectTriangles() { }
 
+        // Always pass texture coordinates upright. If you want to flip the
+        // texture coordinates emitted to the tex_coords array, set flip_y to
+        // true.
         void addRect(GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1,
-                     GLfloat tx0, GLfloat ty0, GLfloat tx1, GLfloat ty1);
+                     GLfloat tx0, GLfloat ty0, GLfloat tx1, GLfloat ty1,
+                     bool flip_y = false);
 
         /**
          * these return a float pointer to the start of each array respectively.
          * Use it for glVertexAttribPointer calls.
          * We can return NULL if we choose to use Vertex Buffer Objects here.
          */
         float* vertexPointer() {
             return &vertexCoords[0].x;
@@ -1446,28 +1455,30 @@ public:
         nsAutoTArray<vert_coord, 6> vertexCoords;
         nsAutoTArray<tex_coord, 6>  texCoords;
     };
 
     /**
      * Decompose drawing the possibly-wrapped aTexCoordRect rectangle
      * of a texture of aTexSize into one or more rectangles (represented
      * as 2 triangles) and associated tex coordinates, such that
-     * we don't have to use the REPEAT wrap mode.
+     * we don't have to use the REPEAT wrap mode. If aFlipY is true, the
+     * texture coordinates will be specified vertically flipped.
      *
      * The resulting triangle vertex coordinates will be in the space of
      * (0.0, 0.0) to (1.0, 1.0) -- transform the coordinates appropriately
      * if you need a different space.
      *
      * The resulting vertex coordinates should be drawn using GL_TRIANGLES,
      * and rects.numRects * 3 * 6
      */
     static void DecomposeIntoNoRepeatTriangles(const nsIntRect& aTexCoordRect,
                                                const nsIntSize& aTexSize,
-                                               RectTriangles& aRects);
+                                               RectTriangles& aRects,
+                                               bool aFlipY = false);
 
     /**
      * Known GL extensions that can be queried by
      * IsExtensionSupported.  The results of this are cached, and as
      * such it's safe to use this even in performance critical code.
      * If you add to this array, remember to add to the string names
      * in GLContext.cpp.
      */
--- a/gfx/gl/GLContextProviderEGL.cpp
+++ b/gfx/gl/GLContextProviderEGL.cpp
@@ -1173,22 +1173,22 @@ 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);
+                                                 mContext);
             if (!succeeded && sEGLLibrary.fGetError() == LOCAL_EGL_CONTEXT_LOST) {
                 mContextLost = true;
                 NS_WARNING("EGL context has been lost.");
             }
             NS_ASSERTION(succeeded, "Failed to make GL context current!");
             return succeeded;
         }
 #endif
@@ -2240,32 +2240,37 @@ 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
+    surface = mozilla::AndroidBridge::Bridge()->ProvideEGLSurface();
+#elif defined(MOZ_WIDGET_ANDROID)
+
     // 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.
-    printf_stderr("... requesting window surface from bridge\n");
+    AndroidGeckoSurfaceView& sview = mozilla::AndroidBridge::Bridge()->SurfaceView();
+    if (sview.isNull()) {
+        printf_stderr("got null surface\n");
+        return NULL;
+    }
+
     surface = mozilla::AndroidBridge::Bridge()->
-        CallEglCreateWindowSurface(EGL_DISPLAY(), config,
-                                   mozilla::AndroidBridge::Bridge()->SurfaceView());
-    printf_stderr("got surface %p\n", surface);
+        CallEglCreateWindowSurface(EGL_DISPLAY(), config, sview);
 #else
     surface = sEGLLibrary.fCreateWindowSurface(EGL_DISPLAY(), config, GET_NATIVE_WINDOW(aWidget), 0);
 #endif
 
 #ifdef MOZ_WIDGET_GONK
     gScreenBounds.x = 0;
     gScreenBounds.y = 0;
     sEGLLibrary.fQuerySurface(EGL_DISPLAY(), surface, LOCAL_EGL_WIDTH, &gScreenBounds.width);
@@ -2294,17 +2299,21 @@ 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
+    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
@@ -118,14 +118,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
@@ -481,16 +481,21 @@ public:
   { return mUserData.Has(aKey); }
   /**
    * This getter can be used anytime. Ownership is retained by the layer
    * manager.
    */
   LayerUserData* GetUserData(void* aKey)
   { return mUserData.Get(aKey); }
 
+  /**
+   * Flag the next paint as the first for a document.
+   */
+  virtual void SetIsFirstPaint() {}
+
   // We always declare the following logging symbols, because it's
   // extremely tricky to conditionally declare them.  However, for
   // ifndef MOZ_LAYERS_HAVE_LOG builds, they only have trivial
   // definitions in Layers.cpp.
   virtual const char* Name() const { return "???"; }
 
   /**
    * Dump information about this layer manager and its managed tree to
--- a/gfx/layers/Makefile.in
+++ b/gfx/layers/Makefile.in
@@ -129,16 +129,17 @@ EXPORTS_gfxipc = ShadowLayerUtils.h
 EXPORTS_mozilla/layers =\
         CompositorCocoaWidgetHelper.h \
         CompositorChild.h \
         CompositorParent.h \
         ShadowLayers.h \
         ShadowLayersChild.h \
         ShadowLayersParent.h \
         ShadowLayersManager.h \
+        RenderTrace.h \
         $(NULL)
 
 CPPSRCS += \
         CompositorCocoaWidgetHelper.cpp \
         CompositorChild.cpp \
         CompositorParent.cpp \
         ShadowLayers.cpp \
         ShadowLayerChild.cpp \
--- a/gfx/layers/RenderTrace.cpp
+++ b/gfx/layers/RenderTrace.cpp
@@ -42,22 +42,16 @@
 #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"
-    };
-
 static gfx3DMatrix GetRootTransform(Layer *aLayer) {
   gfx3DMatrix layerTrans = aLayer->GetTransform().ProjectTo2D();
   if (aLayer->GetParent() != NULL) {
     return GetRootTransform(aLayer->GetParent()) * layerTrans;
   }
   return layerTrans;
 }
 
@@ -65,20 +59,23 @@ void RenderTraceLayers(Layer *aLayer, co
   if (!aLayer)
     return;
 
   gfx3DMatrix trans = aRootTransform * aLayer->GetTransform().ProjectTo2D();
   nsIntRect clipRect = aLayer->GetEffectiveVisibleRegion().GetBounds();
   gfxRect rect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
   trans.TransformBounds(rect);
 
-  printf_stderr("%s RENDERTRACE %u rect #%02X%s %i %i %i %i\n",
-    aLayer->Name(), (int)PR_IntervalNow(),
-    colorId, aColor,
-    (int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
+  if (strcmp(aLayer->Name(), "ContainerLayer") != 0 &&
+      strcmp(aLayer->Name(), "ShadowContainerLayer") != 0) {
+    printf_stderr("%s RENDERTRACE %u rect #%02X%s %i %i %i %i\n",
+      aLayer->Name(), (int)PR_IntervalNow(),
+      colorId, aColor,
+      (int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
+  }
 
   colorId++;
 
   for (Layer* child = aLayer->GetFirstChild();
         child; child = child->GetNextSibling()) {
     RenderTraceLayers(child, aColor, aRootTransform, false);
   }
 
@@ -95,13 +92,27 @@ void RenderTraceInvalidateStart(Layer *a
     aColor,
     (int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
 }
 void RenderTraceInvalidateEnd(Layer *aLayer, const char *aColor) {
   // Clear with an empty rect
   RenderTraceInvalidateStart(aLayer, aColor, nsIntRect());
 }
 
+void renderTraceEventStart(const char *aComment, const char *aColor) {
+  printf_stderr("%s RENDERTRACE %u fillrect #%s 0 0 10 10\n",
+    aComment, (int)PR_IntervalNow(), aColor);
+}
+
+void renderTraceEventEnd(const char *aComment, const char *aColor) {
+  printf_stderr("%s RENDERTRACE %u fillrect #%s 0 0 0 0\n",
+    aComment, (int)PR_IntervalNow(), aColor);
+}
+
+void renderTraceEventEnd(const char *aColor) {
+  renderTraceEventEnd("", aColor);
+}
+
 }
 }
 
 #endif
 
--- a/gfx/layers/RenderTrace.h
+++ b/gfx/layers/RenderTrace.h
@@ -38,32 +38,70 @@
 // 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
-#ifdef MOZ_RENDERTRACE
+
+#ifndef GFX_RENDERTRACE_H
+#define GFX_RENDERTRACE_H
 
 #include "gfx3DMatrix.h"
 #include "nsRect.h"
 
-#ifndef GFX_RENDERTRACE_H
-#define GFX_RENDERTRACE_H
-
 namespace mozilla {
 namespace layers {
 
 class Layer;
 
-void RenderTraceLayers(Layer *aLayer, const char *aColor, gfx3DMatrix aRootTransform = gfx3DMatrix(), bool aReset = true);
+void RenderTraceLayers(Layer *aLayer, const char *aColor, const gfx3DMatrix aRootTransform = gfx3DMatrix(), bool aReset = true);
 
 void RenderTraceInvalidateStart(Layer *aLayer, const char *aColor, const nsIntRect aRect);
 void RenderTraceInvalidateEnd(Layer *aLayer, const char *aColor);
 
+void renderTraceEventStart(const char *aComment, const char *aColor);
+void renderTraceEventEnd(const char *aComment, const char *aColor);
+void renderTraceEventEnd(const char *aColor);
+
+struct RenderTraceScope {
+public:
+  RenderTraceScope(const char *aComment, const char *aColor)
+    : mComment(aComment)
+    , mColor(aColor)
+  {
+    renderTraceEventStart(mComment, mColor);
+  }
+  ~RenderTraceScope() {
+    renderTraceEventEnd(mComment, mColor);
+  }
+private:
+  const char *mComment;
+  const char *mColor;
+};
+
+#ifndef MOZ_RENDERTRACE
+inline void RenderTraceLayers(Layer *aLayer, const char *aColor, const gfx3DMatrix aRootTransform, bool aReset)
+{}
+
+inline void RenderTraceInvalidateStart(Layer *aLayer, const char *aColor, const nsIntRect aRect)
+{}
+
+inline void RenderTraceInvalidateEnd(Layer *aLayer, const char *aColor)
+{}
+
+inline void renderTraceEventStart(const char *aComment, const char *aColor)
+{}
+
+inline void renderTraceEventEnd(const char *aComment, const char *aColor)
+{}
+
+inline void renderTraceEventEnd(const char *aColor)
+{}
+
+#endif // MOZ_RENDERTRACE
+
 }
 }
 
 #endif //GFX_RENDERTRACE_H
-
-#endif // MOZ_RENDERTRACE
--- a/gfx/layers/basic/BasicLayers.cpp
+++ b/gfx/layers/basic/BasicLayers.cpp
@@ -685,19 +685,17 @@ BasicThebesLayer::PaintThebes(gfxContext
        !MustRetainContent())) {
     NS_ASSERTION(readbackUpdates.IsEmpty(), "Can't do readback for non-retained layer");
 
     mValidRegion.SetEmpty();
     mBuffer.Clear();
 
     nsIntRegion toDraw = IntersectWithClip(GetEffectiveVisibleRegion(), aContext);
 
-#ifdef MOZ_RENDERTRACE
     RenderTraceInvalidateStart(this, "FFFF00", toDraw.GetBounds());
-#endif
 
     if (!toDraw.IsEmpty() && !IsHidden()) {
       if (!aCallback) {
         BasicManager()->SetTransactionIncomplete();
         return;
       }
 
       aContext->Save();
@@ -725,19 +723,17 @@ BasicThebesLayer::PaintThebes(gfxContext
         }
         AutoSetOperator setOperator(aContext, GetOperator());
         aContext->Paint(opacity);
       }
 
       aContext->Restore();
     }
 
-#ifdef MOZ_RENDERTRACE
     RenderTraceInvalidateEnd(this, "FFFF00");
-#endif
     return;
   }
 
   {
     PRUint32 flags = 0;
 #ifndef MOZ_GFX_OPTIMIZE_MOBILE
     gfxMatrix transform;
     if (!GetEffectiveTransform().CanDraw2D(&transform) ||
@@ -754,29 +750,25 @@ BasicThebesLayer::PaintThebes(gfxContext
       // (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);
 
-#ifdef MOZ_RENDERTRACE
       RenderTraceInvalidateStart(this, "FFFF00", state.mRegionToDraw.GetBounds());
-#endif
 
       PaintBuffer(state.mContext,
                   state.mRegionToDraw, extendedDrawRegion, state.mRegionToInvalidate,
                   state.mDidSelfCopy,
                   aCallback, aCallbackData);
       Mutated();
 
-#ifdef MOZ_RENDERTRACE
       RenderTraceInvalidateEnd(this, "FFFF00");
-#endif
     } 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");
     }
   }
 
@@ -1623,20 +1615,18 @@ BasicLayerManager::EndTransactionInterna
   Log();
 #endif
 
   NS_ASSERTION(InConstruction(), "Should be in construction phase");
 #ifdef DEBUG
   mPhase = PHASE_DRAWING;
 #endif
 
-#ifdef MOZ_RENDERTRACE
   Layer* aLayer = GetRoot();
   RenderTraceLayers(aLayer, "FF00");
-#endif
 
   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.
@@ -1857,16 +1847,18 @@ Transform3D(gfxASurface* aSource, gfxCon
 
 void
 BasicLayerManager::PaintLayer(gfxContext* aTarget,
                               Layer* aLayer,
                               DrawThebesLayerCallback aCallback,
                               void* aCallbackData,
                               ReadbackProcessor* aReadback)
 {
+  RenderTraceScope trace("BasicLayerManager::PaintLayer", "707070");
+
   const nsIntRect* clipRect = aLayer->GetEffectiveClipRect();
   const gfx3DMatrix& effectiveTransform = aLayer->GetEffectiveTransform();
   BasicContainerLayer* container = static_cast<BasicContainerLayer*>(aLayer);
   bool needsGroup = aLayer->GetFirstChild() &&
     container->UseIntermediateSurface();
   BasicImplData* data = ToData(aLayer);
   bool needsClipToVisibleRegion =
     data->GetClipToVisibleRegion() && !aLayer->AsThebesLayer();
@@ -3418,16 +3410,17 @@ BasicShadowLayerManager::EndEmptyTransac
   }
   ForwardTransaction();
   return true;
 }
 
 void
 BasicShadowLayerManager::ForwardTransaction()
 {
+  RenderTraceScope rendertrace("Foward Transaction", "000090");
 #ifdef DEBUG
   mPhase = PHASE_FORWARD;
 #endif
 
   // forward this transaction's changeset to our ShadowLayerManager
   AutoInfallibleTArray<EditReply, 10> replies;
   if (HasShadowManager() && ShadowLayerForwarder::EndTransaction(&replies)) {
     for (nsTArray<EditReply>::size_type i = 0; i < replies.Length(); ++i) {
@@ -3515,10 +3508,16 @@ BasicShadowLayerManager::Hold(Layer* aLa
 bool
 BasicShadowLayerManager::IsCompositingCheap()
 {
   // Whether compositing is cheap depends on the parent backend.
   return mShadowManager &&
          LayerManager::IsCompositingCheap(GetParentBackendType());
 }
 
+void
+BasicShadowLayerManager::SetIsFirstPaint()
+{
+  ShadowLayerForwarder::SetIsFirstPaint();
+}
+
 }
 }
--- a/gfx/layers/basic/BasicLayers.h
+++ b/gfx/layers/basic/BasicLayers.h
@@ -262,16 +262,18 @@ public:
 
   ShadowableLayer* Hold(Layer* aLayer);
 
   bool HasShadowManager() const { return ShadowLayerForwarder::HasShadowManager(); }
 
   virtual bool IsCompositingCheap();
   virtual bool HasShadowManagerInternal() const { return HasShadowManager(); }
 
+  virtual void SetIsFirstPaint() MOZ_OVERRIDE;
+
 private:
   /**
    * Forward transaction results to the parent context.
    */
   void ForwardTransaction();
 
   LayerRefArray mKeepAlive;
 };
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -38,22 +38,35 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "CompositorParent.h"
 #include "RenderTrace.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
+
+using base::Thread;
 
 namespace mozilla {
 namespace layers {
 
-CompositorParent::CompositorParent(nsIWidget* aWidget)
-  : mStopped(false), mWidget(aWidget)
+CompositorParent::CompositorParent(nsIWidget* aWidget, base::Thread* aCompositorThread)
+  : mCompositorThread(aCompositorThread)
+  , mWidget(aWidget)
+  , mCurrentCompositeTask(NULL)
+  , mPaused(false)
+  , mIsFirstPaint(false)
 {
   MOZ_COUNT_CTOR(CompositorParent);
 }
 
 CompositorParent::~CompositorParent()
 {
   MOZ_COUNT_DTOR(CompositorParent);
 }
@@ -66,44 +79,171 @@ CompositorParent::Destroy()
 
   // Ensure that the layer manager is destroyed on the compositor thread.
   mLayerManager = NULL;
 }
 
 bool
 CompositorParent::RecvStop()
 {
-  mStopped = true;
+  mPaused = true;
   Destroy();
   return true;
 }
 
 void
+CompositorParent::ScheduleRenderOnCompositorThread()
+{
+  CancelableTask *renderTask = NewRunnableMethod(this, &CompositorParent::ScheduleComposition);
+  mCompositorThread->message_loop()->PostTask(FROM_HERE, renderTask);
+}
+
+void
+CompositorParent::PauseComposition()
+{
+  NS_ABORT_IF_FALSE(mCompositorThread->thread_id() == PlatformThread::CurrentId(),
+                    "PauseComposition() can only be called on the compositor thread");
+  if (!mPaused) {
+    mPaused = true;
+
+#ifdef MOZ_WIDGET_ANDROID
+    static_cast<LayerManagerOGL*>(mLayerManager.get())->gl()->ReleaseSurface();
+#endif
+  }
+}
+
+void
+CompositorParent::ResumeComposition()
+{
+  NS_ABORT_IF_FALSE(mCompositorThread->thread_id() == PlatformThread::CurrentId(),
+                    "ResumeComposition() can only be called on the compositor thread");
+  mPaused = false;
+
+#ifdef MOZ_WIDGET_ANDROID
+  static_cast<LayerManagerOGL*>(mLayerManager.get())->gl()->RenewSurface();
+#endif
+}
+
+void
+CompositorParent::SchedulePauseOnCompositorThread()
+{
+  CancelableTask *pauseTask = NewRunnableMethod(this,
+                                                &CompositorParent::PauseComposition);
+  mCompositorThread->message_loop()->PostTask(FROM_HERE, pauseTask);
+}
+
+void
+CompositorParent::ScheduleResumeOnCompositorThread()
+{
+  CancelableTask *resumeTask = NewRunnableMethod(this,
+                                                 &CompositorParent::ResumeComposition);
+  mCompositorThread->message_loop()->PostTask(FROM_HERE, resumeTask);
+}
+
+void
 CompositorParent::ScheduleComposition()
 {
-  CancelableTask *composeTask = NewRunnableMethod(this, &CompositorParent::Composite);
-  MessageLoop::current()->PostTask(FROM_HERE, composeTask);
+  if (mCurrentCompositeTask) {
+    return;
+  }
 
-#ifdef MOZ_RENDERTRACE
-  Layer* aLayer = mLayerManager->GetRoot();
-  mozilla::layers::RenderTraceLayers(aLayer, "0000");
+  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
 
+  mCurrentCompositeTask = NewRunnableMethod(this, &CompositorParent::Composite);
+
+  // Since 60 fps is the maximum frame rate we can acheive, scheduling composition
+  // events less than 15 ms apart wastes computation..
+  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) {
+  NS_ABORT_IF_FALSE(mCompositorThread->thread_id() == PlatformThread::CurrentId(),
+                    "Composite can only be called on the compositor thread");
+  mCurrentCompositeTask = NULL;
+
+  mLastCompose = mozilla::TimeStamp::Now();
+
+  if (mPaused || !mLayerManager || !mLayerManager->GetRoot()) {
     return;
   }
 
+#ifdef MOZ_WIDGET_ANDROID
+  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: Composite took %i ms.\n",
+                  15 + (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);
+  while (queue.Length()) {
+    ContainerLayer* containerLayer = queue[0]->AsContainerLayer();
+    queue.RemoveElementAt(0);
+    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());
@@ -112,18 +252,83 @@ SetShadowProperties(Layer* aLayer)
 
   for (Layer* child = aLayer->GetFirstChild();
       child; child = child->GetNextSibling()) {
     SetShadowProperties(child);
   }
 }
 
 void
-CompositorParent::ShadowLayersUpdated()
+CompositorParent::TransformShadowTree()
 {
+#ifdef MOZ_WIDGET_ANDROID
+  Layer* layer = GetPrimaryScrollableLayer();
+  ShadowLayer* shadow = layer->AsShadowLayer();
+  ContainerLayer* container = layer->AsContainerLayer();
+
+  const FrameMetrics* metrics = &container->GetFrameMetrics();
+  const gfx3DMatrix& rootTransform = mLayerManager->GetRoot()->GetTransform();
+  const gfx3DMatrix& currentTransform = layer->GetTransform();
+
+  float rootScaleX = rootTransform.GetXScale();
+  float rootScaleY = rootTransform.GetYScale();
+
+  if (mIsFirstPaint && metrics) {
+    nsIntPoint scrollOffset = metrics->mViewportScrollOffset;
+    mContentSize = metrics->mContentSize;
+    mozilla::AndroidBridge::Bridge()->SetFirstPaintViewport(scrollOffset.x, scrollOffset.y,
+                                                            1/rootScaleX, mContentSize.width,
+                                                            mContentSize.height);
+    mIsFirstPaint = false;
+  } else if (metrics && (metrics->mContentSize != mContentSize)) {
+    mContentSize = metrics->mContentSize;
+    mozilla::AndroidBridge::Bridge()->SetPageSize(1/rootScaleX, mContentSize.width,
+                                                  mContentSize.height);
+  }
+
+  // We request the view transform from Java after sending the above notifications,
+  // so that Java can take these into account in its response.
+  RequestViewTransform();
+
+  // Handle transformations for asynchronous panning and zooming. We determine the
+  // zoom used by Gecko from the transformation set on the root layer, and we
+  // determine the scroll offset used by Gecko from the frame metrics of the
+  // primary scrollable layer. We compare this to the desired zoom and scroll
+  // offset in the view transform we obtained from Java in order to compute the
+  // transformation we need to apply.
+  if (metrics && metrics->IsScrollable()) {
+    float tempScaleDiffX = rootScaleX * mXScale;
+    float tempScaleDiffY = rootScaleY * mYScale;
+
+    nsIntPoint metricsScrollOffset = metrics->mViewportScrollOffset;
+
+    nsIntPoint scrollCompensation(
+      (mScrollOffset.x / tempScaleDiffX - metricsScrollOffset.x) * mXScale,
+      (mScrollOffset.y / tempScaleDiffY - metricsScrollOffset.y) * mYScale);
+    ViewTransform treeTransform(-scrollCompensation, mXScale, mYScale);
+    shadow->SetShadowTransform(gfx3DMatrix(treeTransform) * currentTransform);
+  } else {
+    ViewTransform treeTransform(nsIntPoint(0,0), mXScale, mYScale);
+    shadow->SetShadowTransform(gfx3DMatrix(treeTransform) * currentTransform);
+  }
+#endif
+}
+
+#ifdef MOZ_WIDGET_ANDROID
+void
+CompositorParent::RequestViewTransform()
+{
+  mozilla::AndroidBridge::Bridge()->GetViewTransform(mScrollOffset, mXScale, mYScale);
+}
+#endif
+
+void
+CompositorParent::ShadowLayersUpdated(bool isFirstPaint)
+{
+  mIsFirstPaint = mIsFirstPaint || isFirstPaint;
   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,131 @@
  * 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
+// its 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 base {
+class Thread;
+}
+
 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);
+  CompositorParent(nsIWidget* aWidget, base::Thread* aCompositorThread);
   virtual ~CompositorParent();
 
   virtual bool RecvStop() MOZ_OVERRIDE;
 
-  virtual void ShadowLayersUpdated() MOZ_OVERRIDE;
+  virtual void ShadowLayersUpdated(bool isFirstPaint) MOZ_OVERRIDE;
   void Destroy();
 
   LayerManager* GetLayerManager() { return mLayerManager; }
 
+  void SetTransformation(float aScale, nsIntPoint aScrollOffset);
+  void AsyncRender();
+
+  // Can be called from any thread
+  void ScheduleRenderOnCompositorThread();
+  void SchedulePauseOnCompositorThread();
+  void ScheduleResumeOnCompositorThread();
+
 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;
+  base::Thread* mCompositorThread;
   nsIWidget* mWidget;
+  CancelableTask *mCurrentCompositeTask;
+  TimeStamp mLastCompose;
+#ifdef COMPOSITOR_PERFORMANCE_WARNING
+  TimeStamp mExpectedComposeTime;
+#endif
+
+  bool mPaused;
+  float mXScale;
+  float mYScale;
+  nsIntPoint mScrollOffset;
+  nsIntSize mContentSize;
+
+  // When this flag is set, the next composition will be the first for a
+  // particular document (i.e. the document displayed on the screen will change).
+  // This happens when loading a new page or switching tabs. We notify the
+  // front-end (e.g. Java on Android) about this so that it take the new page
+  // size and zoom into account when providing us with the next view transform.
+  bool mIsFirstPaint;
 
   DISALLOW_EVIL_CONSTRUCTORS(CompositorParent);
 };
 
 } // layers
 } // mozilla
 
 #endif // mozilla_layers_CompositorParent_h
--- a/gfx/layers/ipc/PLayers.ipdl
+++ b/gfx/layers/ipc/PLayers.ipdl
@@ -227,16 +227,18 @@ union EditReply {
 
 sync protocol PLayers {
   manager PRenderFrame or PCompositor;
   manages PLayer;
 
 parent:
   async PLayer();
 
-  sync Update(Edit[] cset)
+  // The isFirstPaint flag can be used to indicate that this is the first update
+  // for a particular document.
+  sync Update(Edit[] cset, bool isFirstPaint)
     returns (EditReply[] reply);
 
   async __delete__();
 };
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -45,16 +45,17 @@
 
 #include "mozilla/ipc/SharedMemorySysV.h"
 #include "mozilla/layers/PLayerChild.h"
 #include "mozilla/layers/PLayersChild.h"
 #include "mozilla/layers/PLayersParent.h"
 #include "ShadowLayers.h"
 #include "ShadowLayerChild.h"
 #include "gfxipc/ShadowLayerUtils.h"
+#include "RenderTrace.h"
 
 using namespace mozilla::ipc;
 
 namespace mozilla {
 namespace layers {
 
 typedef nsTArray<SurfaceDescriptor> BufferArray; 
 typedef std::vector<Edit> EditVector;
@@ -122,16 +123,17 @@ struct AutoTxnEnd {
   AutoTxnEnd(Transaction* aTxn) : mTxn(aTxn) {}
   ~AutoTxnEnd() { mTxn->End(); }
   Transaction* mTxn;
 };
 
 ShadowLayerForwarder::ShadowLayerForwarder()
  : mShadowManager(NULL)
  , mParentBackend(LayerManager::LAYERS_NONE)
+ , mIsFirstPaint(false)
 {
   mTxn = new Transaction();
 }
 
 ShadowLayerForwarder::~ShadowLayerForwarder()
 {
   NS_ABORT_IF_FALSE(mTxn->Finished(), "unfinished transaction?");
   delete mTxn;
@@ -251,16 +253,17 @@ ShadowLayerForwarder::PaintedCanvas(Shad
   mTxn->AddPaint(OpPaintCanvas(NULL, Shadow(aCanvas),
                                aNewFrontSurface,
                                aNeedYFlip));
 }
 
 bool
 ShadowLayerForwarder::EndTransaction(InfallibleTArray<EditReply>* aReplies)
 {
+  RenderTraceScope rendertrace("Foward Transaction", "000091");
   NS_ABORT_IF_FALSE(HasShadowManager(), "no manager to forward to");
   NS_ABORT_IF_FALSE(!mTxn->Finished(), "forgot BeginTransaction?");
 
   AutoTxnEnd _(mTxn);
 
   if (mTxn->Empty()) {
     MOZ_LAYERS_LOG(("[LayersForwarder] 0-length cset (?), skipping Update()"));
     return true;
@@ -273,16 +276,17 @@ ShadowLayerForwarder::EndTransaction(Inf
   }
 
   MOZ_LAYERS_LOG(("[LayersForwarder] building transaction..."));
 
   // We purposely add attribute-change ops to the final changeset
   // before we add paint ops.  This allows layers to record the
   // attribute changes before new pixels arrive, which can be useful
   // for setting up back/front buffers.
+  RenderTraceScope rendertrace2("Foward Transaction", "000092");
   for (ShadowableLayerSet::const_iterator it = mTxn->mMutants.begin();
        it != mTxn->mMutants.end(); ++it) {
     ShadowableLayer* shadow = *it;
     Layer* mutant = shadow->AsLayer();
     NS_ABORT_IF_FALSE(!!mutant, "unshadowable layer?");
 
     LayerAttributes attrs;
     CommonLayerAttributes& common = attrs.common();
@@ -316,21 +320,23 @@ ShadowLayerForwarder::EndTransaction(Inf
   if (!mTxn->mPaints.empty()) {
     cset.AppendElements(&mTxn->mPaints.front(), mTxn->mPaints.size());
   }
 
   MOZ_LAYERS_LOG(("[LayersForwarder] syncing before send..."));
   PlatformSyncBeforeUpdate();
 
   MOZ_LAYERS_LOG(("[LayersForwarder] sending transaction..."));
-  if (!mShadowManager->SendUpdate(cset, aReplies)) {
+  RenderTraceScope rendertrace3("Foward Transaction", "000093");
+  if (!mShadowManager->SendUpdate(cset, mIsFirstPaint, aReplies)) {
     MOZ_LAYERS_LOG(("[LayersForwarder] WARNING: sending transaction failed!"));
     return false;
   }
 
+  mIsFirstPaint = false;
   MOZ_LAYERS_LOG(("[LayersForwarder] ... done"));
   return true;
 }
 
 static gfxASurface::gfxImageFormat
 OptimalFormatFor(gfxASurface::gfxContentType aContent)
 {
   switch (aContent) {
--- a/gfx/layers/ipc/ShadowLayers.h
+++ b/gfx/layers/ipc/ShadowLayers.h
@@ -303,16 +303,21 @@ public:
   }
 
   /*
    * No need to use double buffer in system memory with GPU rendering,
    * texture used as front buffer.
    */
   bool ShouldDoubleBuffer() { return GetParentBackendType() == LayerManager::LAYERS_BASIC; }
 
+  /**
+   * Flag the next paint as the first for a document.
+   */
+  void SetIsFirstPaint() { mIsFirstPaint = true; }
+
 protected:
   ShadowLayerForwarder();
 
   PLayersChild* mShadowManager;
 
 private:
   bool PlatformAllocDoubleBuffer(const gfxIntSize& aSize,
                                    gfxASurface::gfxContentType aContent,
@@ -327,16 +332,18 @@ private:
   PlatformOpenDescriptor(const SurfaceDescriptor& aDescriptor);
 
   bool PlatformDestroySharedSurface(SurfaceDescriptor* aSurface);
 
   static void PlatformSyncBeforeUpdate();
 
   Transaction* mTxn;
   LayersBackend mParentBackend;
+
+  bool mIsFirstPaint;
 };
 
 
 class ShadowLayerManager : public LayerManager
 {
 public:
   virtual ~ShadowLayerManager() {}
 
--- a/gfx/layers/ipc/ShadowLayersManager.h
+++ b/gfx/layers/ipc/ShadowLayersManager.h
@@ -49,15 +49,15 @@ class RenderFrameParent;
 namespace layers {
 
 class CompositorParent;
 
 class ShadowLayersManager
 {
 
 public:
-  virtual void ShadowLayersUpdated() = 0;
+  virtual void ShadowLayersUpdated(bool isFirstPaint) = 0;
 };
 
 } // layers
 } // mozilla
 
 #endif // mozilla_layers_ShadowLayersManager_h
--- a/gfx/layers/ipc/ShadowLayersParent.cpp
+++ b/gfx/layers/ipc/ShadowLayersParent.cpp
@@ -143,18 +143,23 @@ ShadowLayersParent::Destroy()
     ShadowLayerParent* slp =
       static_cast<ShadowLayerParent*>(ManagedPLayerParent()[i]);
     slp->Destroy();
   }
 }
 
 bool
 ShadowLayersParent::RecvUpdate(const InfallibleTArray<Edit>& cset,
+                               const bool& isFirstPaint,
                                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;
 
@@ -314,83 +319,71 @@ 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();
 
-#ifdef MOZ_RENDERTRACE
       RenderTraceInvalidateStart(thebes, "FF00FF", op.updatedRegion().GetBounds());
-#endif
 
       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));
 
-#ifdef MOZ_RENDERTRACE
       RenderTraceInvalidateEnd(thebes, "FF00FF");
-#endif
       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());
 
-#ifdef MOZ_RENDERTRACE
       RenderTraceInvalidateStart(canvas, "FF00FF", canvas->GetVisibleRegion().GetBounds());
-#endif
 
       canvas->SetAllocator(this);
       CanvasSurface newBack;
       canvas->Swap(op.newFrontBuffer(), op.needYFlip(), &newBack);
       canvas->Updated();
       replyv.push_back(OpBufferSwap(shadow, NULL,
                                     newBack));
 
-#ifdef MOZ_RENDERTRACE
       RenderTraceInvalidateEnd(canvas, "FF00FF");
-#endif
       break;
     }
     case Edit::TOpPaintImage: {
       MOZ_LAYERS_LOG(("[ParentSide] Paint ImageLayer"));
 
       const OpPaintImage& op = edit.get_OpPaintImage();
       ShadowLayerParent* shadow = AsShadowLayer(op);
       ShadowImageLayer* image =
         static_cast<ShadowImageLayer*>(shadow->AsLayer());
 
-#ifdef MOZ_RENDERTRACE
       RenderTraceInvalidateStart(image, "FF00FF", image->GetVisibleRegion().GetBounds());
-#endif
 
       image->SetAllocator(this);
       SharedImage newBack;
       image->Swap(op.newFrontBuffer(), &newBack);
       replyv.push_back(OpImageSwap(shadow, NULL,
                                    newBack));
 
-#ifdef MOZ_RENDERTRACE
       RenderTraceInvalidateEnd(image, "FF00FF");
-#endif
       break;
     }
 
     default:
       NS_RUNTIMEABORT("not reached");
     }
   }
 
@@ -401,17 +394,22 @@ ShadowLayersParent::RecvUpdate(const Inf
     reply->AppendElements(&replyv.front(), replyv.size());
   }
 
   // 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();
+  mShadowLayersManager->ShadowLayersUpdated(isFirstPaint);
+
+#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/ipc/ShadowLayersParent.h
+++ b/gfx/layers/ipc/ShadowLayersParent.h
@@ -73,16 +73,17 @@ public:
 
   ContainerLayer* GetRoot() const { return mRoot; }
 
   virtual void DestroySharedSurface(gfxSharedImageSurface* aSurface);
   virtual void DestroySharedSurface(SurfaceDescriptor* aSurface);
 
 protected:
   NS_OVERRIDE virtual bool RecvUpdate(const EditArray& cset,
+                                      const bool& isFirstPaint,
                                       EditReplyArray* reply);
 
   NS_OVERRIDE virtual PLayerParent* AllocPLayer();
   NS_OVERRIDE virtual bool DeallocPLayer(PLayerParent* actor);
 
 private:
   nsRefPtr<ShadowLayerManager> mLayerManager;
   ShadowLayersManager* mShadowLayersManager;
--- 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,38 @@ 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(),
+                                                  mTexImage->GetWrapMode(),
+                                                  mNeedsYFlip);
+    } 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.
@@ -337,18 +339,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
@@ -782,21 +785,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
@@ -63,19 +63,24 @@
 
 #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
@@ -672,17 +677,18 @@ LayerManagerOGL::FPSState::DrawFPS(GLCon
 // square from 0,0 to 1,1.
 //
 // |aTexSize| is the actual size of the texture, as it can be larger
 // than the rectangle given by |aTexCoordRect|.
 void 
 LayerManagerOGL::BindAndDrawQuadWithTextureRect(LayerProgram *aProg,
                                                 const nsIntRect& aTexCoordRect,
                                                 const nsIntSize& aTexSize,
-                                                GLenum aWrapMode)
+                                                GLenum aWrapMode /* = LOCAL_GL_REPEAT */,
+                                                bool aFlipped /* = false */)
 {
   GLuint vertAttribIndex =
     aProg->AttribLocation(LayerProgram::VertexAttrib);
   GLuint texCoordAttribIndex =
     aProg->AttribLocation(LayerProgram::TexCoordAttrib);
   NS_ASSERTION(texCoordAttribIndex != GLuint(-1), "no texture coords?");
 
   // clear any bound VBO so that glVertexAttribPointer() goes back to
@@ -693,26 +699,34 @@ 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),
+                  aFlipped);
   } else {
-    GLContext::DecomposeIntoNoRepeatTriangles(aTexCoordRect, aTexSize, rects);
+    GLContext::DecomposeIntoNoRepeatTriangles(aTexCoordRect, realTexSize,
+                                              rects, aFlipped);
   }
 
   mGLContext->fVertexAttribPointer(vertAttribIndex, 2,
                                    LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0,
                                    rects.vertexPointer());
 
   mGLContext->fVertexAttribPointer(texCoordAttribIndex, 2,
                                    LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0,
@@ -783,20 +797,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);
 
 #ifdef MOZ_DUMP_PAINTING
   if (gfxUtils::sDumpPainting) {
     nsIntRect rect;
     mWidget->GetBounds(rect);
     nsRefPtr<gfxASurface> surf = gfxPlatform::GetPlatform()->CreateOffscreenSurface(rect.Size(), gfxASurface::CONTENT_COLOR_ALPHA);
     nsRefPtr<gfxContext> ctx = new gfxContext(surf);
--- a/gfx/layers/opengl/LayerManagerOGL.h
+++ b/gfx/layers/opengl/LayerManagerOGL.h
@@ -365,18 +365,19 @@ public:
     BindAndDrawQuad(aProg->AttribLocation(LayerProgram::VertexAttrib),
                     aProg->AttribLocation(LayerProgram::TexCoordAttrib),
                     aFlipped);
   }
 
   void BindAndDrawQuadWithTextureRect(LayerProgram *aProg,
                                       const nsIntRect& aTexCoordRect,
                                       const nsIntSize& aTexSize,
-                                      GLenum aWrapMode = LOCAL_GL_REPEAT);
-                                      
+                                      GLenum aWrapMode = LOCAL_GL_REPEAT,
+                                      bool aFlipped = false);
+
 
 #ifdef MOZ_LAYERS_HAVE_LOG
   virtual const char* Name() const { return "OGL"; }
 #endif // MOZ_LAYERS_HAVE_LOG
 
   const nsIntSize& GetWigetSize() {
     return mWidgetSize;
   }
@@ -386,17 +387,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/thebes/gfx3DMatrix.h
+++ b/gfx/thebes/gfx3DMatrix.h
@@ -177,16 +177,23 @@ public:
    * | aX 0  0  0 |
    * | 0  aY 0  0 |
    * | 0  0  aZ 0 |
    * | 0  0  0  1 |
    */
   void Scale(float aX, float aY, float aZ);
 
   /**
+   * Return the currently set scaling factors.
+   */
+  float GetXScale() const { return _11; }
+  float GetYScale() const { return _22; }
+  float GetZScale() const { return _33; }
+
+  /**
    * Rotate around the X axis..
    *
    * This creates this temporary matrix:
    * | 1 0            0           0 |
    * | 0 cos(aTheta)  sin(aTheta) 0 |
    * | 0 -sin(aTheta) cos(aTheta) 0 |
    * | 0 0            0           1 |
    */
--- a/gfx/thebes/gfxUtils.h
+++ b/gfx/thebes/gfxUtils.h
@@ -169,10 +169,76 @@ public:
     static void CopyAsDataURL(mozilla::gfx::DrawTarget* aDT);
 
     static bool sDumpPainting;
     static bool sDumpPaintingToFile;
     static FILE* sDumpPaintFile;
 #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.
+#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/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -501,16 +501,28 @@ public:
   ~nsPrintEventDispatcher()
   {
     DocumentViewerImpl::DispatchAfterPrint(mTop);
   }
 
   nsCOMPtr<nsIDocument> mTop;
 };
 
+class nsBeforeFirstPaintDispatcher : public nsRunnable
+{
+public:
+  nsBeforeFirstPaintDispatcher(nsIDocument* aDocument)
+  : mDocument(aDocument) {}
+
+  NS_IMETHOD Run();
+
+private:
+  nsCOMPtr<nsIDocument> mDocument;
+};
+
 class nsDocumentShownDispatcher : public nsRunnable
 {
 public:
   nsDocumentShownDispatcher(nsCOMPtr<nsIDocument> aDocument)
   : mDocument(aDocument) {}
 
   NS_IMETHOD Run();
 
@@ -2042,18 +2054,23 @@ DocumentViewerImpl::Show(void)
     // shown...
 
     if (mPresShell) {
       nsCOMPtr<nsIPresShell> shellDeathGrip(mPresShell); // bug 378682
       mPresShell->UnsuppressPainting();
     }
   }
 
-  // Notify observers that a new page has been shown. (But not right now;
-  // running JS at this time is not safe.)
+  // Notify observers that a new page is about to be drawn. Execute this
+  // as soon as it is safe to run JS, which is guaranteed to be before we
+  // go back to the event loop and actually draw the page.
+  nsContentUtils::AddScriptRunner(new nsBeforeFirstPaintDispatcher(mDocument));
+
+  // Notify observers that a new page has been shown. This will get run
+  // from the event loop after we actually draw the page.
   NS_DispatchToMainThread(new nsDocumentShownDispatcher(mDocument));
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 DocumentViewerImpl::Hide(void)
 {
@@ -4365,19 +4382,31 @@ DocumentViewerImpl::SetPrintPreviewPrese
   }
 
   mWindow = nsnull;
   mViewManager = aViewManager;
   mPresContext = aPresContext;
   mPresShell = aPresShell;
 }
 
-// Fires the "document-shown" event so that interested parties (right now, the
+// Fires the "before-first-paint" event so that interested parties (right now, the
 // mobile browser) are aware of it.
 NS_IMETHODIMP
+nsBeforeFirstPaintDispatcher::Run()
+{
+  nsCOMPtr<nsIObserverService> observerService =
+    mozilla::services::GetObserverService();
+  if (observerService) {
+    observerService->NotifyObservers(mDocument, "before-first-paint", NULL);
+  }
+  return NS_OK;
+}
+
+// Fires the "document-shown" event so that interested parties are aware of it.
+NS_IMETHODIMP
 nsDocumentShownDispatcher::Run()
 {
   nsCOMPtr<nsIObserverService> observerService =
     mozilla::services::GetObserverService();
   if (observerService) {
     observerService->NotifyObservers(mDocument, "document-shown", NULL);
   }
   return NS_OK;
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -1142,16 +1142,26 @@ public:
    *
    * The resolution defaults to 1.0.
    */
   virtual nsresult SetResolution(float aXResolution, float aYResolution) = 0;
   float GetXResolution() { return mXResolution; }
   float GetYResolution() { return mYResolution; }
 
   /**
+   * Set the isFirstPaint flag.
+   */
+  void SetIsFirstPaint(bool aIsFirstPaint) { mIsFirstPaint = aIsFirstPaint; }
+
+  /**
+   * Get the isFirstPaint flag.
+   */
+  bool GetIsFirstPaint() const { return mIsFirstPaint; }
+
+  /**
    * Dispatch a mouse move event based on the most recent mouse position if
    * this PresShell is visible. This is used when the contents of the page
    * moved (aFromScroll is false) or scrolled (aFromScroll is true).
    */
   virtual void SynthesizeMouseMove(bool aFromScroll) = 0;
 
   virtual void Paint(nsIView* aViewToPaint, nsIWidget* aWidget,
                      const nsRegion& aDirtyRegion, const nsIntRegion& aIntDirtyRegion,
@@ -1256,16 +1266,18 @@ protected:
   bool                      mDidInitialReflow;
   bool                      mIsDestroying;
   bool                      mIsReflowing;
   bool                      mPaintingSuppressed;  // For all documents we initially lock down painting.
   bool                      mIsThemeSupportDisabled;  // Whether or not form controls should use nsITheme in this shell.
   bool                      mIsActive;
   bool                      mFrozen;
 
+  bool                      mIsFirstPaint;
+
   bool                      mObservesMutationsForPrint;
 
   bool                      mReflowScheduled; // If true, we have a reflow
                                               // scheduled. Guaranteed to be
                                               // false if mReflowContinueTimer
                                               // is non-null.
 
   bool                      mSuppressInterruptibleReflows;
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -818,16 +818,17 @@ PresShell::PresShell()
 #endif
 #ifdef PR_LOGGING
   if (! gLog)
     gLog = PR_NewLogModule("PresShell");
 #endif
   mSelectionFlags = nsISelectionDisplay::DISPLAY_TEXT | nsISelectionDisplay::DISPLAY_IMAGES;
   mIsThemeSupportDisabled = false;
   mIsActive = true;
+  mIsFirstPaint = false;
   mFrozen = false;
 #ifdef DEBUG
   mPresArenaAllocCount = 0;
 #endif
   mRenderFlags = 0;
   mXResolution = 1.0;
   mYResolution = 1.0;
   mViewportOverridden = false;
@@ -5367,16 +5368,21 @@ PresShell::Paint(nsIView*           aVie
   AUTO_LAYOUT_PHASE_ENTRY_POINT(presContext, Paint);
 
   nsIFrame* frame = aViewToPaint->GetFrame();
 
   bool isRetainingManager;
   LayerManager* layerManager =
     aWidgetToPaint->GetLayerManager(&isRetainingManager);
   NS_ASSERTION(layerManager, "Must be in paint event");
+
+  if (mIsFirstPaint) {
+    layerManager->SetIsFirstPaint();
+    mIsFirstPaint = false;
+  }
   layerManager->BeginTransaction();
 
   if (frame && isRetainingManager) {
     // Try to do an empty transaction, if the frame tree does not
     // need to be updated. Do not try to do an empty transaction on
     // a non-retained layer manager (like the BasicLayerManager that
     // draws the window title bar on Mac), because a) it won't work
     // and b) below we don't want to clear NS_FRAME_UPDATE_LAYER_TREE,
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -2162,26 +2162,27 @@ 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 &&
      (styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN ||
       styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN) &&
-     (scrollRange.width > 0 ||
-      scrollRange.height > 0) &&
-     (!mIsRoot || !mOuter->PresContext()->IsRootContentDocument()));
+     (usingDisplayport ||
+      (XRE_GetProcessType() == GeckoProcessType_Content &&
+       (scrollRange.width > 0 || scrollRange.height > 0) &&
+       (!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/layout/generic/nsObjectFrame.cpp
+++ b/layout/generic/nsObjectFrame.cpp
@@ -1639,36 +1639,24 @@ nsObjectFrame::BuildLayer(nsDisplayListB
 
 void
 nsObjectFrame::PaintPlugin(nsDisplayListBuilder* aBuilder,
                            nsRenderingContext& aRenderingContext,
                            const nsRect& aDirtyRect, const nsRect& aPluginRect)
 {
 #if defined(MOZ_WIDGET_ANDROID)
   if (mInstanceOwner) {
-    NPWindow *window;
-    mInstanceOwner->GetWindow(window);
-
     gfxRect frameGfxRect =
       PresContext()->AppUnitsToGfxUnits(aPluginRect);
     gfxRect dirtyGfxRect =
       PresContext()->AppUnitsToGfxUnits(aDirtyRect);
+
     gfxContext* ctx = aRenderingContext.ThebesContext();
 
-    gfx3DMatrix matrix3d = nsLayoutUtils::GetTransformToAncestor(this, nsnull);
-
-    gfxMatrix matrix2d;
-    if (!matrix3d.Is2D(&matrix2d))
-      return;
-
-    // The matrix includes the frame's position, so we need to transform
-    // from 0,0 to get the correct coordinates.
-    frameGfxRect.MoveTo(0, 0);
-
-    mInstanceOwner->Paint(ctx, matrix2d.Transform(frameGfxRect), dirtyGfxRect);
+    mInstanceOwner->Paint(ctx, frameGfxRect, dirtyGfxRect);
     return;
   }
 #endif
 
   // Screen painting code
 #if defined(XP_MACOSX)
   // delegate all painting to the plugin instance.
   if (mInstanceOwner) {
--- a/layout/ipc/RenderFrameParent.cpp
+++ b/layout/ipc/RenderFrameParent.cpp
@@ -511,17 +511,17 @@ void
 RenderFrameParent::ContentViewScaleChanged(nsContentView* aView)
 {
   // Since the scale has changed for a view, it and its descendents need their
   // shadow-space attributes updated. It's easiest to rebuild the view map.
   BuildViewMap();
 }
 
 void
-RenderFrameParent::ShadowLayersUpdated()
+RenderFrameParent::ShadowLayersUpdated(bool isFirstPaint)
 {
   mFrameLoader->SetCurrentRemoteFrame(this);
 
   // View map must only contain views that are associated with the current
   // shadow layer tree. We must always update the map when shadow layers
   // are updated.
   BuildViewMap();
 
--- a/layout/ipc/RenderFrameParent.h
+++ b/layout/ipc/RenderFrameParent.h
@@ -81,17 +81,17 @@ public:
   /**
    * Helper function for getting a non-owning reference to a scrollable.
    * @param aId The ID of the frame.
    */
   nsContentView* GetContentView(ViewID aId = FrameMetrics::ROOT_SCROLL_ID);
 
   void ContentViewScaleChanged(nsContentView* aView);
 
-  virtual void ShadowLayersUpdated() MOZ_OVERRIDE;
+  virtual void ShadowLayersUpdated(bool isFirstPaint) MOZ_OVERRIDE;
 
   NS_IMETHOD BuildDisplayList(nsDisplayListBuilder* aBuilder,
                               nsSubDocumentFrame* aFrame,
                               const nsRect& aDirtyRect,
                               const nsDisplayListSet& aLists);
 
   already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                      nsIFrame* aFrame,
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -596,16 +596,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", false);
+
 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
@@ -4,17 +4,17 @@
       package="@ANDROID_PACKAGE_NAME@"
       android:installLocation="auto"
       android:versionCode="@ANDROID_VERSION_CODE@"
       android:versionName="@MOZ_APP_VERSION@"
 #ifdef MOZ_ANDROID_SHARED_ID
       android:sharedUserId="@MOZ_ANDROID_SHARED_ID@"
 #endif
       >
-    <uses-sdk android:minSdkVersion="5"
+    <uses-sdk android:minSdkVersion="8"
               android:targetSdkVersion="11"/>
 
 #include ../sync/manifests/SyncAndroidManifest_permissions.xml.in
 
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.INTERNET"/>
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
@@ -38,16 +38,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,25 +37,26 @@
  * 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.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;
+import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.Tab.HistoryEntry;
 
 import java.io.*;
 import java.util.*;
 import java.util.regex.Pattern;
 import java.util.regex.Matcher;
 import java.util.zip.*;
 import java.net.URL;
@@ -136,17 +137,17 @@ abstract public class GeckoApp
 
     public static BrowserToolbar mBrowserToolbar;
     public static DoorHangerPopup mDoorHangerPopup;
     public static FormAssistPopup mFormAssistPopup;
     public Favicons mFavicons;
 
     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;
@@ -530,20 +531,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();
 
@@ -557,31 +554,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
                 // Ordering of .equals() below is important since mLast* variables may be null
                 if (viewportJSON.equals(mLastViewport) &&
@@ -594,34 +588,34 @@ 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.getState() == Tab.STATE_DELAYED) {
                 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();
+            View view = mLayerController.getView();
+            int sw = forceBigSceenshot ? view.getWidth() : tab.getMinScreenshotWidth();
+            int sh = forceBigSceenshot ? view.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)) {
@@ -882,17 +876,26 @@ abstract public class GeckoApp
             } else if (event.equals("Toast:Show")) {
                 final String msg = message.getString("message");
                 final String duration = message.getString("duration");
                 handleShowToast(msg, duration);
             } else if (event.equals("DOMContentLoaded")) {
                 final int tabId = message.getInt("tabID");
                 final String uri = message.getString("uri");
                 final String title = message.getString("title");
+                final String backgroundColor = message.getString("bgColor");
                 handleContentLoaded(tabId, uri, title);
+                if (getLayerController() != null) {
+                    if (backgroundColor != null) {
+                        getLayerController().setCheckerboardColor(backgroundColor);
+                    } else {
+                        // Default to black if no color is given
+                        getLayerController().setCheckerboardColor(0);
+                    }
+                }
                 Log.i(LOGTAG, "URI - " + uri + ", title - " + title);
             } else if (event.equals("DOMTitleChanged")) {
                 final int tabId = message.getInt("tabID");
                 final String title = message.getString("title");
                 handleTitleChanged(tabId, title);
                 Log.i(LOGTAG, "title - " + title);
             } else if (event.equals("DOMLinkAdded")) {
                 final int tabId = message.getInt("tabID");
@@ -1355,22 +1358,22 @@ abstract public class GeckoApp
                 PluginLayoutParams lp;
 
                 Tabs tabs = Tabs.getInstance();
                 Tab tab = tabs.getSelectedTab();
 
                 if (tab == null)
                     return;
 
-                ViewportMetrics targetViewport = mLayerController.getViewportMetrics();
-                ViewportMetrics pluginViewport;
+                ImmutableViewportMetrics targetViewport = mLayerController.getViewportMetrics();
+                ImmutableViewportMetrics pluginViewport;
                 
                 try {
                     JSONObject viewportObject = new JSONObject(metadata);
-                    pluginViewport = new ViewportMetrics(viewportObject);
+                    pluginViewport = new ImmutableViewportMetrics(new ViewportMetrics(viewportObject));
                 } catch (JSONException e) {
                     Log.e(LOGTAG, "Bad viewport metadata: ", e);
                     return;
                 }
 
                 if (mPluginContainer.indexOfChild(view) == -1) {
                     lp = new PluginLayoutParams(x, y, w, h, pluginViewport);
 
@@ -1455,26 +1458,26 @@ abstract public class GeckoApp
         ViewportMetrics metrics;
         try {
             metrics = new ViewportMetrics(new JSONObject(metadata));
         } catch (JSONException e) {
             Log.e(LOGTAG, "Bad viewport metadata: ", e);
             return;
         }
 
-        PointF origin = metrics.getDisplayportOrigin();
+        PointF origin = metrics.getOrigin();
         x = x + (int)origin.x;
         y = y + (int)origin.y;
 
         LayerView layerView = mLayerController.getView();
         SurfaceTextureLayer layer = (SurfaceTextureLayer)tab.getPluginLayer(surface);
         if (layer == null)
             return;
 
-        layer.update(new Point(x, y), new IntSize(w, h), metrics.getZoomFactor(), inverted, blend);
+        layer.update(new Rect(x, y, x + w, y + h), metrics.getZoomFactor(), inverted, blend);
         layerView.addLayer(layer);
 
         // FIXME: shouldn't be necessary, layer will request
         // one when it gets first frame
         layerView.requestRender();
     }
     
     private void hidePluginLayer(Layer layer) {
@@ -1550,17 +1553,17 @@ abstract public class GeckoApp
 
         if (tab == null)
             return;
 
         repositionPluginViews(tab, setVisible);
     }
 
     public void repositionPluginViews(Tab tab, boolean setVisible) {
-        ViewportMetrics targetViewport = mLayerController.getViewportMetrics();
+        ImmutableViewportMetrics targetViewport = mLayerController.getViewportMetrics();
 
         if (targetViewport == null)
             return;
 
         for (View view : tab.getPluginViews()) {
             PluginLayoutParams lp = (PluginLayoutParams)view.getLayoutParams();
             lp.reposition(targetViewport);
 
@@ -1722,33 +1725,31 @@ abstract public class GeckoApp
 
         if (cameraView == null) {
             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.
+             * Create a layer client, but don't hook it up to the layer controller yet.
              */
-            mSoftwareLayerClient = new GeckoSoftwareLayerClient(this);
+            mLayerClient = new GeckoLayerClient(this);
 
             /*
              * 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);
         mFormAssistPopup = (FormAssistPopup) findViewById(R.id.form_assist_popup);
@@ -2597,17 +2598,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)
     {
         Log.w(LOGTAG, "onAccuracyChanged "+accuracy);
         GeckoAppShell.sendEventToGecko(GeckoEvent.createSensorAccuracyEvent(accuracy));
     }
@@ -2639,17 +2640,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;
@@ -2670,55 +2671,55 @@ class PluginLayoutParams extends Absolut
 {
     private static final int MAX_DIMENSION = 2048;
     private static final String LOGTAG = "GeckoApp.PluginLayoutParams";
 
     private int mOriginalX;
     private int mOriginalY;
     private int mOriginalWidth;
     private int mOriginalHeight;
-    private ViewportMetrics mOriginalViewport;
+    private ImmutableViewportMetrics mOriginalViewport;
     private float mLastResolution;
 
-    public PluginLayoutParams(int aX, int aY, int aWidth, int aHeight, ViewportMetrics aViewport) {
+    public PluginLayoutParams(int aX, int aY, int aWidth, int aHeight, ImmutableViewportMetrics aViewport) {
         super(aWidth, aHeight, aX, aY);
 
-        Log.i(LOGTAG, "Creating plugin at " + aX + ", " + aY + ", " + aWidth + "x" + aHeight + ", (" + (aViewport.getZoomFactor() * 100) + "%)");
+        Log.i(LOGTAG, "Creating plugin at " + aX + ", " + aY + ", " + aWidth + "x" + aHeight + ", (" + (aViewport.zoomFactor * 100) + "%)");
 
         mOriginalX = aX;
         mOriginalY = aY;
         mOriginalWidth = aWidth;
         mOriginalHeight = aHeight;
         mOriginalViewport = aViewport;
-        mLastResolution = aViewport.getZoomFactor();
+        mLastResolution = aViewport.zoomFactor;
 
         clampToMaxSize();
     }
 
     private void clampToMaxSize() {
         if (width > MAX_DIMENSION || height > MAX_DIMENSION) {
             if (width > height) {
                 height = (int)(((float)height/(float)width) * MAX_DIMENSION);
                 width = MAX_DIMENSION;
             } else {
                 width = (int)(((float)width/(float)height) * MAX_DIMENSION);
                 height = MAX_DIMENSION;
             }
         }
     }
 
-    public void reset(int aX, int aY, int aWidth, int aHeight, ViewportMetrics aViewport) {
-        PointF origin = aViewport.getDisplayportOrigin();
+    public void reset(int aX, int aY, int aWidth, int aHeight, ImmutableViewportMetrics aViewport) {
+        PointF origin = aViewport.getOrigin();
 
         x = mOriginalX = aX + (int)origin.x;
         y = mOriginalY = aY + (int)origin.y;
         width = mOriginalWidth = aWidth;
         height = mOriginalHeight = aHeight;
         mOriginalViewport = aViewport;
-        mLastResolution = aViewport.getZoomFactor();
+        mLastResolution = aViewport.zoomFactor;
 
         clampToMaxSize();
     }
 
     private void reposition(Point aOffset, float aResolution) {
         x = mOriginalX + aOffset.x;
         y = mOriginalY + aOffset.y;
 
@@ -2726,22 +2727,22 @@ class PluginLayoutParams extends Absolut
             width = Math.round(aResolution * mOriginalWidth);
             height = Math.round(aResolution * mOriginalHeight);
             mLastResolution = aResolution;
 
             clampToMaxSize();
         }
     }
 
-    public void reposition(ViewportMetrics viewport) {
-        PointF targetOrigin = viewport.getDisplayportOrigin();
-        PointF originalOrigin = mOriginalViewport.getDisplayportOrigin();
+    public void reposition(ImmutableViewportMetrics viewport) {
+        PointF targetOrigin = viewport.getOrigin();
+        PointF originalOrigin = mOriginalViewport.getOrigin();
 
         Point offset = new Point(Math.round(originalOrigin.x - targetOrigin.x),
                                  Math.round(originalOrigin.y - targetOrigin.y));
 
-        reposition(offset, viewport.getZoomFactor());
+        reposition(offset, viewport.zoomFactor);
     }
 
     public float getLastResolution() {
         return mLastResolution;
     }
 }
--- 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.text.*;
 import java.util.*;
@@ -138,17 +138,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);
     public static native void loadNSSLibsNative(String apkName);
@@ -193,17 +193,19 @@ public class GeckoAppShell
     public static native void notifySmsDeleteFailed(int aError, int aRequestId, long aProcessId);
     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();
 
     private static class GeckoMediaScannerClient implements MediaScannerConnectionClient {
         private String mFile = "";
         private String mMimeType = "";
         private MediaScannerConnection mScanner = null;
 
         public GeckoMediaScannerClient(Context aContext, String aFile, String aMimeType) {
             mFile = aFile;
@@ -410,19 +412,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)
@@ -441,18 +443,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;
             }
@@ -540,18 +541,16 @@ public class GeckoAppShell
         CountDownLatch tmp = sGeckoPendingAcks;
         if (tmp != null)
             tmp.countDown();
     }
 
     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
@@ -33,16 +33,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.IntSize;
+import org.mozilla.gecko.gfx.RectUtils;
 import org.mozilla.gecko.gfx.ViewportMetrics;
 import android.os.*;
 import android.app.*;
 import android.view.*;
 import android.content.*;
 import android.graphics.*;
 import android.widget.*;
 import android.hardware.*;
@@ -375,36 +376,42 @@ public class GeckoEvent {
     }
 
     public static GeckoEvent createDrawEvent(Rect rect) {
         GeckoEvent event = new GeckoEvent(DRAW);
         event.mRect = rect;
         return event;
     }
 
-    public static GeckoEvent createSizeChangedEvent(int w, int h, int screenw, int screenh, int tilew, int tileh) {
+    public static GeckoEvent createSizeChangedEvent(int w, int h, int screenw, int screenh) {
         GeckoEvent event = new GeckoEvent(SIZE_CHANGED);
-        event.mPoints = new Point[3];
+        event.mPoints = new Point[2];
         event.mPoints[0] = new Point(w, h);
         event.mPoints[1] = new Point(screenw, screenh);
-        event.mPoints[2] = new Point(tilew, tileh);
         return event;
     }
 
     public static GeckoEvent createBroadcastEvent(String subject, String data) {
         GeckoEvent event = new GeckoEvent(BROADCAST);
         event.mCharacters = subject;
         event.mCharactersExtra = data;
         return event;
     }
 
-    public static GeckoEvent createViewportEvent(ViewportMetrics viewport) {
+    public static GeckoEvent createViewportEvent(ViewportMetrics viewport, RectF displayPort) {
         GeckoEvent event = new GeckoEvent(VIEWPORT);
         event.mCharacters = "Viewport:Change";
-        event.mCharactersExtra = viewport.toJSON();
+        PointF origin = viewport.getOrigin();
+        StringBuffer sb = new StringBuffer(256);
+        sb.append("{ \"x\" : ").append(origin.x)
+          .append(", \"y\" : ").append(origin.y)
+          .append(", \"zoom\" : ").append(viewport.getZoomFactor())
+          .append(", \"displayPort\" :").append(RectUtils.toJSON(displayPort))
+          .append('}');
+        event.mCharactersExtra = sb.toString();
         return event;
     }
 
     public static GeckoEvent createLoadEvent(String uri) {
         GeckoEvent event = new GeckoEvent(LOAD_URI);
         event.mCharacters = uri;
         return event;
     }
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -112,40 +112,43 @@ FENNEC_JAVA_FILES = \
   TabsTray.java \
   TabsAccessor.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/GeckoLayerClient.java \
+  gfx/GLController.java \
+  gfx/GLThread.java \
+  gfx/ImmutableViewportMetrics.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/WidgetTileLayer.java \
+  gfx/VirtualLayer.java \
   ui/Axis.java \
   ui/PanZoomController.java \
   ui/SimpleScaleGestureDetector.java \
   ui/SubdocumentScrollHelper.java \
   GeckoNetworkManager.java \
   $(NULL)
 
 ifdef MOZ_WEBSMS_BACKEND
--- a/mobile/android/base/gfx/CheckerboardImage.java
+++ b/mobile/android/base/gfx/CheckerboardImage.java
@@ -74,17 +74,21 @@ public class CheckerboardImage extends C
     public boolean getShowChecks() {
         return mShowChecks;
     }
 
     /** Updates the checkerboard image. If showChecks is true, then create a
         checkerboard image that is tinted to the color. Otherwise just return a flat
         image of the color. */
     public void update(boolean showChecks, int color) {
-        mMainColor = color;
+        // XXX We don't handle setting the color to white (-1),
+        //     there a bug somewhere but I'm not sure where,
+        //     let's hardcode the color for now to black and come back and fix it.
+        //mMainColor = color;
+        mMainColor = 0;
         mShowChecks = showChecks;
 
         short mainColor16 = convertTo16Bit(mMainColor);
 
         mBuffer.rewind();
         ShortBuffer shortBuffer = mBuffer.asShortBuffer();
 
         if (!mShowChecks) {
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/FlexibleGLSurfaceView.java
@@ -0,0 +1,223 @@
+/* -*- 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;
+
+/*
+ * This class extends SurfaceView and allows dynamically switching between two modes
+ * of operation. In one mode, it is used like a GLSurfaceView, and has it's own GL
+ * thread. In the other mode, it allows external code to perform GL composition, by
+ * exposing the GL controller.
+ *
+ * In our case, we start off in the first mode because we are rendering the placeholder
+ * image. This mode is initiated by a call to createGLThread(). Once Gecko comes up,
+ * it invokes registerCxxCompositor() via a JNI call, which shuts down the GL thread and
+ * returns the GL controller. The JNI code then takes the EGL surface from the GL
+ * controller and allows the off-main thread compositor to deal with it directly.
+ */
+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!");
+        }
+
+        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.
+        while (mGLThread == null) {
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        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;
+    }
+
+    /** Implementation of SurfaceHolder.Callback */
+    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);
+        }
+    }
+
+    /** Implementation of SurfaceHolder.Callback */
+    public synchronized void surfaceCreated(SurfaceHolder holder) {
+        mController.surfaceCreated();
+        if (mGLThread != null) {
+            mGLThread.surfaceCreated();
+        }
+    }
+
+    /** Implementation of SurfaceHolder.Callback */
+    public synchronized void surfaceDestroyed(SurfaceHolder holder) {
+        mController.surfaceDestroyed();
+        if (mGLThread != null) {
+            mGLThread.surfaceDestroyed();
+        }
+        
+        if (mListener != null) {
+            mListener.compositionPauseRequested();
+        }
+    }
+
+    /** This function is invoked by Gecko (compositor thread) via JNI; be careful when modifying signature. */
+    public static GLController registerCxxCompositor() {
+        try {
+            FlexibleGLSurfaceView flexView = (FlexibleGLSurfaceView)GeckoApp.mAppContext.getLayerController().getView();
+            try {
+                flexView.destroyGLThread().join();
+            } catch (InterruptedException e) {}
+            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,283 @@
+/* -*- 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.
+     * This class does not keep a reference to the provided EGL surface; the
+     * caller assumes ownership of the surface once it is returned.
+     */
+    private EGLSurface provideEGLSurface() {
+        if (mEGL == null) {
+            initEGL();
+        }
+
+        SurfaceHolder surfaceHolder = mView.getHolder();
+        EGLSurface surface = mEGL.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surfaceHolder, null);
+        if (surface == null || surface == EGL10.EGL_NO_SURFACE) {
+            throw new GLControllerException("EGL window surface could not be created!");
+        }
+
+        return surface;
+    }
+
+    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,178 @@
+/* -*- 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();
+        }
+    }
+
+    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/GeckoLayerClient.java
@@ -0,0 +1,430 @@
+/* -*- 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.GeckoEventResponder;
+import org.json.JSONException;
+import org.json.JSONObject;
+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.os.SystemClock;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.View;
+
+public class GeckoLayerClient implements GeckoEventResponder,
+                                         FlexibleGLSurfaceView.Listener {
+    private static final String LOGTAG = "GeckoLayerClient";
+
+    private static final int DEFAULT_DISPLAY_PORT_MARGIN = 300;
+
+    private LayerController mLayerController;
+    private LayerRenderer mLayerRenderer;
+    private boolean mLayerRendererInitialized;
+
+    private IntSize mScreenSize;
+    private IntSize mWindowSize;
+    private RectF mDisplayPort;
+
+    private VirtualLayer mRootLayer;
+
+    /* The viewport that Gecko is currently displaying. */
+    private ViewportMetrics mGeckoViewport;
+
+    private String mLastCheckerboardColor;
+
+    /* Used by robocop for testing purposes */
+    private DrawListener mDrawListener;
+
+    /* Used as a temporary ViewTransform by getViewTransform */
+    private ViewTransform mCurrentViewTransform;
+
+    public GeckoLayerClient(Context context) {
+        // we can fill these in with dummy values because they are always written
+        // to before being read
+        mScreenSize = new IntSize(0, 0);
+        mWindowSize = new IntSize(0, 0);
+        mDisplayPort = new RectF();
+        mCurrentViewTransform = new ViewTransform(0, 0, 1);
+    }
+
+    /** Attaches the root layer to the layer controller so that Gecko appears. */
+    void setLayerController(LayerController layerController) {
+        LayerView view = layerController.getView();
+
+        mLayerController = layerController;
+
+        mRootLayer = new VirtualLayer(new IntSize(view.getWidth(), view.getHeight()));
+        mLayerRenderer = new LayerRenderer(view);
+
+        GeckoAppShell.registerGeckoEventListener("Viewport:Update", this);
+
+        view.setListener(this);
+        view.setLayerRenderer(mLayerRenderer);
+        layerController.setRoot(mRootLayer);
+
+        sendResizeEventIfNecessary(true);
+    }
+
+    /** This function is invoked by Gecko via JNI; be careful when modifying signature. */
+    public boolean beginDrawing(int width, int height, String metadata) {
+        try {
+            JSONObject viewportObject = new JSONObject(metadata);
+            mGeckoViewport = new ViewportMetrics(viewportObject);
+        } catch (JSONException e) {
+            Log.e(LOGTAG, "Aborting draw, bad viewport description: " + metadata);
+            return false;
+        }
+
+        return true;
+    }
+
+    /** This function is invoked by Gecko via JNI; be careful when modifying signature. */
+    public void endDrawing() {
+        synchronized (mLayerController) {
+            RectF position = mGeckoViewport.getViewport();
+            mRootLayer.setPositionAndResolution(RectUtils.round(position), mGeckoViewport.getZoomFactor());
+        }
+
+        Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - endDrawing");
+        /* Used by robocop for testing purposes */
+        if (mDrawListener != null) {
+            mDrawListener.drawFinished();
+        }
+    }
+
+    RectF getDisplayPort() {
+        return mDisplayPort;
+    }
+
+    /* Informs Gecko that the screen size has changed. */
+    private void sendResizeEventIfNecessary(boolean force) {
+        DisplayMetrics metrics = new DisplayMetrics();
+        GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+        View view = mLayerController.getView();
+
+        IntSize newScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels);
+        IntSize newWindowSize = new IntSize(view.getWidth(), view.getHeight());
+
+        boolean screenSizeChanged = !mScreenSize.equals(newScreenSize);
+        boolean windowSizeChanged = !mWindowSize.equals(newWindowSize);
+
+        if (!force && !screenSizeChanged && !windowSizeChanged) {
+            return;
+        }
+
+        mScreenSize = newScreenSize;
+        mWindowSize = newWindowSize;
+
+        if (screenSizeChanged) {
+            Log.d(LOGTAG, "Screen-size changed to " + mScreenSize);
+        }
+
+        if (windowSizeChanged) {
+            Log.d(LOGTAG, "Window-size changed to " + mWindowSize);
+        }
+
+        GeckoEvent event = GeckoEvent.createSizeChangedEvent(mWindowSize.width, mWindowSize.height,
+                                                             mScreenSize.width, mScreenSize.height);
+        GeckoAppShell.sendEventToGecko(event);
+    }
+
+    public Bitmap getBitmap() {
+        return null;
+    }
+
+    void viewportSizeChanged() {
+        // here we send gecko a resize message. The code in browser.js is responsible for
+        // picking up on that resize event, modifying the viewport as necessary, and informing
+        // us of the new viewport.
+        sendResizeEventIfNecessary(true);
+        // the following call also sends gecko a message, which will be processed after the resize
+        // message above has updated the viewport. this message ensures that if we have just put
+        // focus in a text field, we scroll the content so that the text field is in view.
+        GeckoAppShell.viewSizeChanged();
+    }
+
+    private void updateDisplayPort() {
+        float desiredXMargins = 2 * DEFAULT_DISPLAY_PORT_MARGIN;
+        float desiredYMargins = 2 * DEFAULT_DISPLAY_PORT_MARGIN;
+
+        ImmutableViewportMetrics metrics = mLayerController.getViewportMetrics(); 
+
+        // we need to avoid having a display port that is larger than the page, or we will end up
+        // painting things outside the page bounds (bug 729169). we simultaneously need to make
+        // the display port as large as possible so that we redraw less.
+
+        // figure out how much of the desired buffer amount we can actually use on the horizontal axis
+        float xBufferAmount = Math.min(desiredXMargins, metrics.pageSizeWidth - metrics.getWidth());
+        // if we reduced the buffer amount on the horizontal axis, we should take that saved memory and
+        // use it on the vertical axis
+        float savedPixels = (desiredXMargins - xBufferAmount) * (metrics.getHeight() + desiredYMargins);
+        float extraYAmount = (float)Math.floor(savedPixels / (metrics.getWidth() + xBufferAmount));
+        float yBufferAmount = Math.min(desiredYMargins + extraYAmount, metrics.pageSizeHeight - metrics.getHeight());
+        // and the reverse - if we shrunk the buffer on the vertical axis we can add it to the horizontal
+        if (xBufferAmount == desiredXMargins && yBufferAmount < desiredYMargins) {
+            savedPixels = (desiredYMargins - yBufferAmount) * (metrics.getWidth() + xBufferAmount);
+            float extraXAmount = (float)Math.floor(savedPixels / (metrics.getHeight() + yBufferAmount));
+            xBufferAmount = Math.min(xBufferAmount + extraXAmount, metrics.pageSizeWidth - metrics.getWidth());
+        }
+
+        // and now calculate the display port margins based on how much buffer we've decided to use and
+        // the page bounds, ensuring we use all of the available buffer amounts on one side or the other
+        // on any given axis. (i.e. if we're scrolled to the top of the page, the vertical buffer is
+        // entirely below the visible viewport, but if we're halfway down the page, the vertical buffer
+        // is split).
+        float leftMargin = Math.min(DEFAULT_DISPLAY_PORT_MARGIN, metrics.viewportRectLeft);
+        float rightMargin = Math.min(DEFAULT_DISPLAY_PORT_MARGIN, metrics.pageSizeWidth - (metrics.viewportRectLeft + metrics.getWidth()));
+        if (leftMargin < DEFAULT_DISPLAY_PORT_MARGIN) {
+            rightMargin = xBufferAmount - leftMargin;
+        } else if (rightMargin < DEFAULT_DISPLAY_PORT_MARGIN) {
+            leftMargin = xBufferAmount - rightMargin;
+        } else if (!FloatUtils.fuzzyEquals(leftMargin + rightMargin, xBufferAmount)) {
+            float delta = xBufferAmount - leftMargin - rightMargin;
+            leftMargin += delta / 2;
+            rightMargin += delta / 2;
+        }
+
+        float topMargin = Math.min(DEFAULT_DISPLAY_PORT_MARGIN, metrics.viewportRectTop);
+        float bottomMargin = Math.min(DEFAULT_DISPLAY_PORT_MARGIN, metrics.pageSizeHeight - (metrics.viewportRectTop + metrics.getHeight()));
+        if (topMargin < DEFAULT_DISPLAY_PORT_MARGIN) {
+            bottomMargin = yBufferAmount - topMargin;
+        } else if (bottomMargin < DEFAULT_DISPLAY_PORT_MARGIN) {
+            topMargin = yBufferAmount - bottomMargin;
+        } else if (!FloatUtils.fuzzyEquals(topMargin + bottomMargin, yBufferAmount)) {
+            float delta = yBufferAmount - topMargin - bottomMargin;
+            topMargin += delta / 2;
+            bottomMargin += delta / 2;
+        }
+
+        // note that unless the viewport size changes, or the page dimensions change (either because of
+        // content changes or zooming), the size of the display port should remain constant. this
+        // is intentional to avoid re-creating textures and all sorts of other reallocations in the
+        // draw and composition code.
+
+        mDisplayPort.left = metrics.viewportRectLeft - leftMargin;
+        mDisplayPort.top = metrics.viewportRectTop - topMargin;
+        mDisplayPort.right = metrics.viewportRectRight + rightMargin;
+        mDisplayPort.bottom = metrics.viewportRectBottom + bottomMargin;
+    }
+
+    private void adjustViewport() {
+        ViewportMetrics viewportMetrics =
+            new ViewportMetrics(mLayerController.getViewportMetrics());
+
+        viewportMetrics.setViewport(viewportMetrics.getClampedViewport());
+
+        updateDisplayPort();
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createViewportEvent(viewportMetrics, mDisplayPort));
+    }
+
+    /** Implementation of GeckoEventResponder/GeckoEventListener. */
+    public void handleMessage(String event, JSONObject message) {
+        if ("Viewport:Update".equals(event)) {
+            try {
+                ViewportMetrics newMetrics = new ViewportMetrics(message);
+                synchronized (mLayerController) {
+                    // keep the old viewport size, but update everything else
+                    ImmutableViewportMetrics oldMetrics = mLayerController.getViewportMetrics();
+                    newMetrics.setSize(oldMetrics.getSize());
+                    mLayerController.setViewportMetrics(newMetrics);
+                    mLayerController.abortPanZoomAnimation(false);
+                }
+            } catch (JSONException e) {
+                Log.e(LOGTAG, "Unable to create viewport metrics in " + event + " handler", e);
+            }
+        }
+    }
+
+    /** Implementation of GeckoEventResponder. */
+    public String getResponse() {
+        // We are responding to the events handled in handleMessage() above with the
+        // display port we want. Note that all messages we are currently handling
+        // (just Viewport:Update) require this response, so we can just return this
+        // indiscriminately.
+        updateDisplayPort();
+        return RectUtils.toJSON(mDisplayPort);
+    }
+
+    void geometryChanged() {
+        /* Let Gecko know if the screensize has changed */
+        sendResizeEventIfNecessary(false);
+        if (mLayerController.getRedrawHint())
+            adjustViewport();
+    }
+
+    public ViewportMetrics getGeckoViewportMetrics() {
+        // Return a copy, as we modify this inside the Gecko thread
+        if (mGeckoViewport != null)
+            return new ViewportMetrics(mGeckoViewport);
+        return null;
+    }
+
+    /** This function is invoked by Gecko via JNI; be careful when modifying signature.
+      * The compositor invokes this function just before compositing a frame where the document
+      * is different from the document composited on the last frame. In these cases, the viewport
+      * information we have in Java is no longer valid and needs to be replaced with the new
+      * viewport information provided. setPageSize will never be invoked on the same frame that
+      * this function is invoked on; and this function will always be called prior to getViewTransform.
+      */
+    public void setFirstPaintViewport(float offsetX, float offsetY, float zoom, float pageWidth, float pageHeight) {
+        synchronized (mLayerController) {
+            ViewportMetrics currentMetrics = new ViewportMetrics(mLayerController.getViewportMetrics());
+            currentMetrics.setOrigin(new PointF(offsetX, offsetY));
+            currentMetrics.setZoomFactor(zoom);
+            currentMetrics.setPageSize(new FloatSize(pageWidth, pageHeight));
+            mLayerController.setViewportMetrics(currentMetrics);
+            // At this point, we have just switched to displaying a different document than we
+            // we previously displaying. This means we need to abort any panning/zooming animations
+            // that are in progress and send an updated display port request to browser.js as soon
+            // as possible. We accomplish this by passing true to abortPanZoomAnimation, which
+            // sends the request after aborting the animation. The display port request is actually
+            // a full viewport update, which is fine because if browser.js has somehow moved to
+            // be out of sync with this first-paint viewport, then we force them back in sync.
+            mLayerController.abortPanZoomAnimation(true);
+        }
+    }
+
+    /** This function is invoked by Gecko via JNI; be careful when modifying signature.
+      * The compositor invokes this function whenever it determines that the page size
+      * has changed (based on the information it gets from layout). If setFirstPaintViewport
+      * is invoked on a frame, then this function will not be. For any given frame, this
+      * function will be invoked before getViewTransform.
+      */
+    public void setPageSize(float zoom, float pageWidth, float pageHeight) {
+        synchronized (mLayerController) {
+            // adjust the page dimensions to account for differences in zoom
+            // between the rendered content (which is what the compositor tells us)
+            // and our zoom level (which may have diverged).
+            float ourZoom = mLayerController.getZoomFactor();
+            pageWidth = pageWidth * ourZoom / zoom;
+            pageHeight = pageHeight * ourZoom /zoom;
+            mLayerController.setPageSize(new FloatSize(pageWidth, pageHeight));
+            // Here the page size of the document has changed, but the document being displayed
+            // is still the same. Therefore, we don't need to send anything to browser.js; any
+            // changes we need to make to the display port will get sent the next time we call
+            // adjustViewport().
+        }
+    }
+
+    /** This function is invoked by Gecko via JNI; be careful when modifying signature.
+      * The compositor invokes this function on every frame to figure out what part of the
+      * page to display. Since it is called on every frame, it needs to be ultra-fast.
+      * It avoids taking any locks or allocating any objects. We keep around a
+      * mCurrentViewTransform so we don't need to allocate a new ViewTransform
+      * everytime we're called. NOTE: we might be able to return a ImmutableViewportMetrics
+      * which would avoid the copy into mCurrentViewTransform.
+      */
+    public ViewTransform getViewTransform() {
+        // getViewportMetrics is thread safe so we don't need to synchronize
+        // on myLayerController.
+        ImmutableViewportMetrics viewportMetrics = mLayerController.getViewportMetrics();
+        mCurrentViewTransform.x = viewportMetrics.viewportRectLeft;
+        mCurrentViewTransform.y = viewportMetrics.viewportRectTop;
+        mCurrentViewTransform.scale = viewportMetrics.zoomFactor;
+        return mCurrentViewTransform;
+    }
+
+    /** 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.checkMonitoringEnabled();
+            mLayerRenderer.createDefaultProgram();
+            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.activateDefaultProgram();
+    }
+
+    /** This function is invoked by Gecko via JNI; be careful when modifying signature. */
+    public void deactivateProgram() {
+        mLayerRenderer.deactivateDefaultProgram();
+    }
+
+    /** Implementation of FlexibleGLSurfaceView.Listener */
+    public void renderRequested() {
+        GeckoAppShell.scheduleComposite();
+    }
+
+    /** Implementation of FlexibleGLSurfaceView.Listener */
+    public void compositionPauseRequested() {
+        GeckoAppShell.schedulePauseComposition();
+    }
+
+    /** Implementation of FlexibleGLSurfaceView.Listener */
+    public void compositionResumeRequested() {
+        GeckoAppShell.scheduleResumeComposition();
+    }
+
+    /** Implementation of FlexibleGLSurfaceView.Listener */
+    public void surfaceChanged(int width, int height) {
+        compositionPauseRequested();
+        mLayerController.setViewportSize(new FloatSize(width, height));
+        compositionResumeRequested();
+        renderRequested();
+    }
+
+    /** 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();
+    }
+}
+
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);
-    }
-}
-
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/ImmutableViewportMetrics.java
@@ -0,0 +1,70 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.gfx;
+
+import android.graphics.PointF;
+import android.graphics.RectF;
+
+/**
+ * ImmutableViewportMetrics are used to store the viewport metrics
+ * in way that we can access a version of them from multiple threads
+ * without having to take a lock
+ */
+public class ImmutableViewportMetrics {
+
+    // We need to flatten the RectF and FloatSize structures
+    // because Java doesn't have the concept of const classes
+    public final float pageSizeWidth;
+    public final float pageSizeHeight;
+    public final float viewportRectBottom;
+    public final float viewportRectLeft;
+    public final float viewportRectRight;
+    public final float viewportRectTop;
+    public final float zoomFactor;
+
+    public ImmutableViewportMetrics(ViewportMetrics m) {
+        RectF viewportRect = m.getViewport();
+        viewportRectBottom = viewportRect.bottom;
+        viewportRectLeft = viewportRect.left;
+        viewportRectRight = viewportRect.right;
+        viewportRectTop = viewportRect.top;
+
+        FloatSize pageSize = m.getPageSize();
+        pageSizeWidth = pageSize.width;
+        pageSizeHeight = pageSize.height;
+
+        zoomFactor = m.getZoomFactor();
+    }
+
+    public float getWidth() {
+        return viewportRectRight - viewportRectLeft;
+    }
+
+    public float getHeight() {
+        return viewportRectBottom - viewportRectTop;
+    }
+
+    // some helpers to make ImmutableViewportMetrics act more like ViewportMetrics
+
+    public PointF getOrigin() {
+        return new PointF(viewportRectLeft, viewportRectTop);
+    }
+
+    public FloatSize getSize() {
+        return new FloatSize(viewportRectRight - viewportRectLeft, viewportRectBottom - viewportRectTop);
+    }
+
+    public RectF getViewport() {
+        return new RectF(viewportRectLeft,
+                         viewportRectTop,
+                         viewportRectRight,
+                         viewportRectBottom);
+    }
+
+    public FloatSize getPageSize() {
+        return new FloatSize(pageSizeWidth, pageSizeHeight);
+    }
+}
--- 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
@@ -33,131 +34,123 @@
  * 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;
-import android.graphics.PointF;
+import android.graphics.Rect;
 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 Rect mNewPosition;
     private float mNewResolution;
-    private LayerView mView;
 
-    protected Point mOrigin;
+    protected Rect mPosition;
     protected float mResolution;
 
     public Layer() {
+        this(null);
+    }
+
+    public Layer(IntSize size) {
         mTransactionLock = new ReentrantLock();
-        mOrigin = new Point(0, 0);
+        if (size == null) {
+            mPosition = new Rect();
+        } else {
+            mPosition = new Rect(0, 0, size.width, size.height);
+        }
         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;
     }
 
     /** Subclasses override this function to draw the layer. */
     public abstract void draw(RenderContext context);
 
-    /** Subclasses override this function to provide access to the size of the layer. */
-    public abstract IntSize getSize();
-
     /** Given the intrinsic size of the layer, returns the pixel boundaries of the layer rect. */
-    protected RectF getBounds(RenderContext context, FloatSize size) {
-        float scaleFactor = context.zoomFactor / mResolution;
-        float x = mOrigin.x * scaleFactor, y = mOrigin.y * scaleFactor;
-        float width = size.width * scaleFactor, height = size.height * scaleFactor;
-        return new RectF(x, y, x + width, y + height);
+    protected RectF getBounds(RenderContext context) {
+        return RectUtils.scale(new RectF(mPosition), context.zoomFactor / mResolution);
     }
 
     /**
      * Returns the region of the layer that is considered valid. The default
      * implementation of this will return the bounds of the layer, but this
      * may be overridden.
      */
     public Region getValidRegion(RenderContext context) {
-        return new Region(RectUtils.round(getBounds(context, new FloatSize(getSize()))));
+        return new Region(RectUtils.round(getBounds(context)));
     }
 
     /**
      * 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. */
-    public Point getOrigin() {
-        return mOrigin;
+    /** Returns the current layer position. */
+    public Rect getPosition() {
+        return mPosition;
     }
 
-    /** Sets the origin. Only valid inside a transaction. */
-    public void setOrigin(Point newOrigin) {
+    /** Sets the position. Only valid inside a transaction. */
+    public void setPosition(Rect newPosition) {
         if (!mInTransaction)
-            throw new RuntimeException("setOrigin() is only valid inside a transaction");
-        mNewOrigin = newOrigin;
+            throw new RuntimeException("setPosition() is only valid inside a transaction");
+        mNewPosition = newPosition;
     }
 
     /** Returns the current layer's resolution. */
     public float getResolution() {
         return mResolution;
     }
 
     /**
@@ -172,38 +165,45 @@ 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) {
-        if (mNewOrigin != null) {
-            mOrigin = mNewOrigin;
-            mNewOrigin = null;
+    protected boolean performUpdates(RenderContext context) {
+        if (mNewPosition != null) {
+            mPosition = mNewPosition;
+            mNewPosition = null;
         }
         if (mNewResolution != 0.0f) {
             mResolution = mNewResolution;
             mNewResolution = 0.0f;
         }
 
         return true;
     }
 
     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,67 +35,81 @@
  * 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 org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.Tab;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.GestureDetector;
 import android.view.ScaleGestureDetector;
 import android.view.View.OnTouchListener;
 import android.view.ViewConfiguration;
 import java.lang.Math;
 import java.util.Timer;
 import java.util.TimerTask;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * The layer controller manages a tile that represents the visible page. It does panning and
  * zooming natively by delegating to a panning/zooming controller. Touch events can be dispatched
  * to a higher-level view.
  *
  * Many methods require that the monitor be held, with a synchronized (controller) { ... } block.
  */
 public class LayerController implements Tabs.OnTabsChangedListener {
     private static final String LOGTAG = "GeckoLayerController";
 
     private Layer mRootLayer;                   /* The root layer. */
     private LayerView mView;                    /* The main rendering view. */
     private Context mContext;                   /* The current context. */
-    private ViewportMetrics mViewportMetrics;   /* The current viewport metrics. */
+
+    /* This is volatile so that we can read and write to it from different threads.
+     * We avoid synchronization to make getting the viewport metrics from
+     * the compositor as cheap as possible. The viewport is immutable so
+     * we don't need to worry about anyone mutating it while we're reading from it.
+     * Specifically:
+     * 1) reading mViewportMetrics from any thread is fine without synchronization
+     * 2) writing to mViewportMetrics requires synchronizing on the layer controller object
+     * 3) whenver reading multiple fields from mViewportMetrics without synchronization (i.e. in
+     *    case 1 above) you should always frist grab a local copy of the reference, and then use
+     *    that because mViewportMetrics might get reassigned in between reading the different
+     *    fields. */
+    private volatile ImmutableViewportMetrics mViewportMetrics;   /* The current viewport metrics. */
+
     private boolean mWaitForTouchListeners;
 
     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 mForceRedraw;
 
     /* The extra area on the sides of the page that we want to buffer to help with
@@ -112,50 +126,52 @@ public class LayerController implements 
     /* The time limit for pages to respond with preventDefault on touchevents
      * before we begin panning the page */
     private int mTimeout = 200;
 
     private boolean allowDefaultActions = true;
     private Timer allowDefaultTimer =  null;
     private PointF initialTouchLocation = null;
 
+    private static Pattern sColorPattern;
+
     public LayerController(Context context) {
         mContext = context;
 
         mForceRedraw = true;
-        mViewportMetrics = new ViewportMetrics();
+        mViewportMetrics = new ImmutableViewportMetrics(new ViewportMetrics());
         mPanZoomController = new PanZoomController(this);
         mView = new LayerView(context, this);
+        mCheckerboardShouldShowChecks = true;
 
         Tabs.getInstance().registerOnTabsChangedListener(this);
 
         ViewConfiguration vc = ViewConfiguration.get(mContext); 
         mTimeout = vc.getLongPressTimeout();
     }
 
     public void onDestroy() {
         Tabs.getInstance().unregisterOnTabsChangedListener(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 ImmutableViewportMetrics getViewportMetrics()   { return mViewportMetrics; }
 
     public RectF getViewport() {
         return mViewportMetrics.getViewport();
     }
 
     public FloatSize getViewportSize() {
         return mViewportMetrics.getSize();
     }
@@ -164,17 +180,17 @@ public class LayerController implements 
         return mViewportMetrics.getPageSize();
     }
 
     public PointF getOrigin() {
         return mViewportMetrics.getOrigin();
     }
 
     public float getZoomFactor() {
-        return mViewportMetrics.getZoomFactor();
+        return mViewportMetrics.zoomFactor;
     }
 
     public Bitmap getBackgroundPattern()    { return getDrawable("background"); }
     public Bitmap getShadowPattern()        { return getDrawable("shadow"); }
 
     public PanZoomController getPanZoomController()                                 { return mPanZoomController; }
     public GestureDetector.OnGestureListener getGestureListener()                   { return mPanZoomController; }
     public SimpleScaleGestureDetector.SimpleScaleGestureListener getScaleGestureListener() {
@@ -194,72 +210,48 @@ public class LayerController implements 
      * The view calls this function to indicate that the viewport changed size. It must hold the
      * monitor while calling it.
      *
      * TODO: Refactor this to use an interface. Expose that interface only to the view and not
      * to the layer client. That way, the layer client won't be tempted to call this, which might
      * result in an infinite loop.
      */
     public void setViewportSize(FloatSize size) {
-        // Resize the viewport, and modify its zoom factor so that the page retains proportionally
-        // zoomed relative to the screen.
-        float oldHeight = mViewportMetrics.getSize().height;
-        float oldWidth = mViewportMetrics.getSize().width;
-        float oldZoomFactor = mViewportMetrics.getZoomFactor();
-        mViewportMetrics.setSize(size);
+        ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
+        viewportMetrics.setSize(size);
+        mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics);
 
-        // if the viewport got larger (presumably because the vkb went away), and the page
-        // is smaller than the new viewport size, increase the page size so that the panzoomcontroller
-        // doesn't zoom in to make it fit (bug 718270). this page size change is in anticipation of
-        // gecko increasing the page size to match the new viewport size, which will happen the next
-        // time we get a draw update.
-        if (size.width >= oldWidth && size.height >= oldHeight) {
-            FloatSize pageSize = mViewportMetrics.getPageSize();
-            if (pageSize.width < size.width || pageSize.height < size.height) {
-                mViewportMetrics.setPageSize(new FloatSize(Math.max(pageSize.width, size.width),
-                                                           Math.max(pageSize.height, size.height)));
-            }
+        if (mLayerClient != null) {
+            mLayerClient.viewportSizeChanged();
         }
-
-        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)
-            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();
+        ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
+        PointF origin = viewportMetrics.getOrigin();
         origin.offset(point.x, point.y);
-        mViewportMetrics.setOrigin(origin);
-        Log.d(LOGTAG, "scrollBy: " + mViewportMetrics);
+        viewportMetrics.setOrigin(origin);
+        mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics);
 
         notifyLayerClientOfGeometryChange();
         GeckoApp.mAppContext.repositionPluginViews(false);
         mView.requestRender();
     }
 
     /** 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);
+        ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
+        viewportMetrics.setPageSize(size);
+        mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics);
 
-        // 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();
             }
         });
@@ -267,35 +259,35 @@ public class LayerController implements 
 
     /**
      * Sets the entire viewport metrics at once. This function does not notify the layer client or
      * the pan/zoom controller, so you will need to call notifyLayerClientOfGeometryChange() or
      * notifyPanZoomControllerOfGeometryChange() after calling this. You must hold the monitor
      * while calling this.
      */
     public void setViewportMetrics(ViewportMetrics viewport) {
-        mViewportMetrics = new ViewportMetrics(viewport);
-        Log.d(LOGTAG, "setViewportMetrics: " + mViewportMetrics);
+        mViewportMetrics = new ImmutableViewportMetrics(viewport);
         // this function may or may not be called on the UI thread,
         // but repositionPluginViews must only be called on the UI thread.
         GeckoApp.mAppContext.runOnUiThread(new Runnable() {
             public void run() {
                 GeckoApp.mAppContext.repositionPluginViews(false);
             }
         });
         mView.requestRender();
     }
 
     /**
      * Scales the viewport, keeping the given focus point in the same place before and after the
      * scale operation. You must hold the monitor while calling this.
      */
     public void scaleWithFocus(float zoomFactor, PointF focus) {
-        mViewportMetrics.scaleTo(zoomFactor, focus);
-        Log.d(LOGTAG, "scaleWithFocus: " + mViewportMetrics + "; zf=" + zoomFactor);
+        ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
+        viewportMetrics.scaleTo(zoomFactor, focus);
+        mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics);
 
         // We assume the zoom level will only be modified by the
         // PanZoomController, so no need to notify it of this change.
         notifyLayerClientOfGeometryChange();
         GeckoApp.mAppContext.repositionPluginViews(false);
         mView.requestRender();
     }
 
@@ -310,83 +302,89 @@ public class LayerController implements 
      * the geometry changed.
      */
     public void notifyLayerClientOfGeometryChange() {
         if (mLayerClient != null)
             mLayerClient.geometryChanged();
     }
 
     /** Aborts any pan/zoom animation that is currently in progress. */
-    public void abortPanZoomAnimation() {
+    public void abortPanZoomAnimation(final boolean notifyLayerClient) {
         if (mPanZoomController != null) {
             mView.post(new Runnable() {
                 public void run() {
                     mPanZoomController.abortAnimation();
+                    if (notifyLayerClient) {
+                        notifyLayerClientOfGeometryChange();
+                    }
                 }
             });
         }
     }
 
     /**
      * 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() {
         if (mForceRedraw) {
             mForceRedraw = false;
             return true;
         }
 
-        return aboutToCheckerboard() && mPanZoomController.getRedrawHint();
-    }
+        if (!mPanZoomController.getRedrawHint()) {
+            return false;
+        }
 
-    private RectF getTileRect() {
-        if (mRootLayer == null)
-            return new RectF();
-
-        float x = mRootLayer.getOrigin().x, y = mRootLayer.getOrigin().y;
-        IntSize layerSize = mRootLayer.getSize();
-        return new RectF(x, y, x + layerSize.width, y + layerSize.height);
+        return aboutToCheckerboard();
     }
 
     // Returns true if a checkerboard is about to be visible.
     private boolean aboutToCheckerboard() {
         // Increase the size of the viewport (and clamp to page boundaries), and
         // intersect it with the tile's displayport to determine whether we're
         // close to checkerboarding.
         FloatSize pageSize = getPageSize();
         RectF adjustedViewport = RectUtils.expand(getViewport(), DANGER_ZONE_X, DANGER_ZONE_Y);
         if (adjustedViewport.top < 0) adjustedViewport.top = 0;
         if (adjustedViewport.left < 0) adjustedViewport.left = 0;
         if (adjustedViewport.right > pageSize.width) adjustedViewport.right = pageSize.width;
         if (adjustedViewport.bottom > pageSize.height) adjustedViewport.bottom = pageSize.height;
 
-        return !getTileRect().contains(adjustedViewport);
+        RectF displayPort = (mLayerClient == null ? new RectF() : mLayerClient.getDisplayPort());
+        return !displayPort.contains(adjustedViewport);
     }
 
     /**
      * Converts a point from layer view coordinates to layer coordinates. In other words, given a
      * point measured in pixels from the top left corner of the layer view, returns the point in
      * pixels measured from the top left corner of the root layer, in the coordinate system of the
-     * layer itself. This method is used by the viewport controller as part of the process of
-     * translating touch events to Gecko's coordinate system.
+     * layer itself (CSS pixels). This method is used as part of the process of translating touch
+     * events to Gecko's coordinate system.
      */
     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);
-        newPoint.offset(viewPoint.x, viewPoint.y);
+        ImmutableViewportMetrics viewportMetrics = mViewportMetrics;
+        PointF origin = viewportMetrics.getOrigin();
+        float zoom = viewportMetrics.zoomFactor;
+        Rect rootPosition = mRootLayer.getPosition();
+        float rootScale = mRootLayer.getResolution();
 
-        Point rootOrigin = mRootLayer.getOrigin();
-        newPoint.offset(-rootOrigin.x, -rootOrigin.y);
+        // viewPoint + origin gives the coordinate in device pixels from the top-left corner of the page.
+        // Divided by zoom, this gives us the coordinate in CSS pixels from the top-left corner of the page.
+        // rootPosition / rootScale is where Gecko thinks it is (scrollTo position) in CSS pixels from
+        // the top-left corner of the page. Subtracting the two gives us the offset of the viewPoint from
+        // the current Gecko coordinate in CSS pixels.
+        PointF layerPoint = new PointF(
+                ((viewPoint.x + origin.x) / zoom) - (rootPosition.left / rootScale),
+                ((viewPoint.y + origin.y) / zoom) - (rootPosition.top / rootScale));
 
-        return newPoint;
+        return layerPoint;
     }
 
     /*
      * Gesture detection. This is handled only at a high level in this class; we dispatch to the
      * pan/zoom controller to do the dirty work.
      */
     public boolean onTouchEvent(MotionEvent event) {
         int action = event.getAction();
@@ -475,10 +473,34 @@ public class LayerController implements 
         mView.requestRender();
     }
 
     /** Sets a new color for the checkerboard. */
     public void setCheckerboardColor(int newColor) {
         mCheckerboardColor = newColor;
         mView.requestRender();
     }
+
+    /** Parses and sets a new color for the checkerboard. */
+    public void setCheckerboardColor(String newColor) {
+        setCheckerboardColor(parseColorFromGecko(newColor));
+    }
+
+    // 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);
+    } 
+
 }
 
--- 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,123 +38,209 @@
  * ***** 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";
     private static final String PROFTAG = "GeckoLayerRendererProf";
 
     /*
      * The amount of time a frame is allowed to take to render before we declare it a dropped
      * frame.
      */
     private static final int MAX_FRAME_TIME = 16;   /* 1000 ms / 60 FPS */
 
-    private static final int FRAME_RATE_METER_WIDTH = 64;
+    private static final int FRAME_RATE_METER_WIDTH = 128;
     private static final int FRAME_RATE_METER_HEIGHT = 32;
 
     private final LayerView mView;
     private final SingleTileLayer mBackgroundLayer;
     private final CheckerboardImage mCheckerboardImage;
     private final SingleTileLayer mCheckerboardLayer;
     private final NinePatchTileLayer mShadowLayer;
-    private final TextLayer mFrameRateLayer;
+    private 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;
-    private boolean mShowFrameRate;
 
     // Render profiling output
     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
+    public static final float[] DEFAULT_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
+    public static final String DEFAULT_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.
+    public static final String DEFAULT_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);
 
         mCheckerboardImage = new CheckerboardImage();
         mCheckerboardLayer = new SingleTileLayer(true, mCheckerboardImage);
 
         CairoImage shadowImage = new BufferedCairoImage(controller.getShadowPattern());
         mShadowLayer = new NinePatchTileLayer(shadowImage);
 
-        IntSize frameRateLayerSize = new IntSize(FRAME_RATE_METER_WIDTH, FRAME_RATE_METER_HEIGHT);
-        mFrameRateLayer = TextLayer.create(frameRateLayerSize, "-- ms/--");
-
-        mHorizScrollLayer = ScrollbarLayer.create(false);
-        mVertScrollLayer = ScrollbarLayer.create(true);
+        mHorizScrollLayer = ScrollbarLayer.create(this, false);
+        mVertScrollLayer = ScrollbarLayer.create(this, 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();
+        createDefaultProgram();
+        activateDefaultProgram();
+    }
 
-        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
-        gl.glDisable(GL10.GL_DITHER);
-        gl.glEnable(GL10.GL_TEXTURE_2D);
+    public void createDefaultProgram() {
+        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, DEFAULT_VERTEX_SHADER);
+        int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, DEFAULT_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];
+    }
 
-        TextureGenerator.get().fill();
+    // Activates the shader program.
+    public void activateDefaultProgram() {
+        // Add the program to the OpenGL environment
+        GLES20.glUseProgram(mProgram);
+
+        // Set the transformation matrix
+        GLES20.glUniformMatrix4fv(mTMatrixHandle, 1, false, DEFAULT_TEXTURE_MATRIX, 0);
+
+        // Enable the arrays from which we get the vertex and texture coordinates
+        GLES20.glEnableVertexAttribArray(mPositionHandle);
+        GLES20.glEnableVertexAttribArray(mTextureHandle);
+
+        GLES20.glUniform1i(mSampleHandle, 0);
+
+        // 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 deactivateDefaultProgram() {
+        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 +261,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 +293,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,24 +343,27 @@ 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);
+
+        if (mFrameRateLayer != null) {
+            moveFrameRateLayer(width, height);
+        }
 
         // updating the state in the view/controller/client should be
         // done on the main UI thread, not the GL renderer thread
         mView.post(new Runnable() {
             public void run() {
                 mView.setViewportSize(new IntSize(width, height));
-                moveFrameRateLayer(width, height);
             }
         });
 
         /* TODO: Throw away tile images? */
     }
 
     private void updateDroppedFrames(long frameStartTime) {
         int frameElapsedTime = (int)(SystemClock.uptimeMillis() - frameStartTime);
@@ -413,66 +373,87 @@ public class LayerRenderer implements GL
         mFrameTimingsSum += frameElapsedTime;
         mDroppedFrames -= (mFrameTimings[mCurrentFrame] + 1) / MAX_FRAME_TIME;
         mDroppedFrames += (frameElapsedTime + 1) / MAX_FRAME_TIME;
 
         mFrameTimings[mCurrentFrame] = frameElapsedTime;
         mCurrentFrame = (mCurrentFrame + 1) % mFrameTimings.length;
 
         int averageTime = mFrameTimingsSum / mFrameTimings.length;
-        mFrameRateLayer.beginTransaction();
+        mFrameRateLayer.beginTransaction();     // called on compositor thread
         try {
             mFrameRateLayer.setText(averageTime + " ms/" + mDroppedFrames);
         } finally {
             mFrameRateLayer.endTransaction();
         }
     }
 
     /* Given the new dimensions for the surface, moves the frame rate layer appropriately. */
     private void moveFrameRateLayer(int width, int height) {
-        mFrameRateLayer.beginTransaction();
+        mFrameRateLayer.beginTransaction();     // called on compositor thread
         try {
-            Point origin = new Point(width - FRAME_RATE_METER_WIDTH - 8,
-                                     height - FRAME_RATE_METER_HEIGHT + 8);
-            mFrameRateLayer.setOrigin(origin);
+            Rect position = new Rect(width - FRAME_RATE_METER_WIDTH - 8,
+                                    height - FRAME_RATE_METER_HEIGHT + 8,
+                                    width - 8,
+                                    height + 8);
+            mFrameRateLayer.setPosition(position);
         } finally {
             mFrameRateLayer.endTransaction();
         }
     }
 
-    private void checkMonitoringEnabled() {
+    void checkMonitoringEnabled() {
         /* Do this I/O off the main thread to minimize its impact on startup time. */
         new Thread(new Runnable() {
             @Override
             public void run() {
                 Context context = mView.getContext();
                 SharedPreferences preferences = context.getSharedPreferences("GeckoApp", 0);
-                mShowFrameRate = preferences.getBoolean("showFrameRate", false);
+                if (preferences.getBoolean("showFrameRate", false)) {
+                    IntSize frameRateLayerSize = new IntSize(FRAME_RATE_METER_WIDTH, FRAME_RATE_METER_HEIGHT);
+                    mFrameRateLayer = TextLayer.create(frameRateLayerSize, "-- ms/--");
+                    moveFrameRateLayer(mView.getWidth(), mView.getHeight());
+                }
                 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();
+        mCheckerboardLayer.beginTransaction();  // called on compositor thread
         try {
             mCheckerboardImage.update(showChecks, checkerboardColor);
             mCheckerboardLayer.invalidate();
         } finally {
             mCheckerboardLayer.endTransaction();
         }
 
-        mCheckerboardLayer.update(gl, renderContext);
+        mCheckerboardLayer.update(renderContext);   // called on compositor thread
+    }
+
+    /*
+     * create a vertex shader type (GLES20.GL_VERTEX_SHADER)
+     * or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
+     */
+    public static 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 +481,226 @@ 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());
+        }
+
+        /** This function is invoked via JNI; be careful when modifying signature. */
+        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);  // called on compositor thread
+            mUpdated &= mBackgroundLayer.update(mScreenContext);    // called on compositor thread
+            mUpdated &= mShadowLayer.update(mPageContext);  // called on compositor thread
+            updateCheckerboardLayer(mScreenContext);
+            if (mFrameRateLayer != null) mUpdated &= mFrameRateLayer.update(mScreenContext); // called on compositor thread
+            mUpdated &= mVertScrollLayer.update(mPageContext);  // called on compositor thread
+            mUpdated &= mHorizScrollLayer.update(mPageContext); // called on compositor thread
+
+            for (Layer layer : mExtraLayers)
+                mUpdated &= layer.update(mPageContext); // called on compositor thread
+
+            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();
+                }
+            }
+        }
+
+        /** This function is invoked via JNI; be careful when modifying signature. */
+        public void drawBackground() {
+            /* Draw the background. */
+            mBackgroundLayer.setMask(getPageRect());
+            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);
+
+            /* Find the area the root layer will render into, to mask the scissor rect */
+            Rect rootMask = null;
+            Layer rootLayer = mView.getController().getRoot();
+            if (rootLayer != null) {
+                RectF rootBounds = rootLayer.getBounds(mPageContext);
+                rootBounds.offset(-mPageContext.viewport.left, -mPageContext.viewport.top);
+                rootMask = new Rect();
+                rootBounds.roundOut(rootMask);
+            }
+
+            /* Draw the checkerboard. */
+            setScissorRect();
+            mCheckerboardLayer.setMask(rootMask);
+            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;
+            }
+
+            rootLayer.draw(mPageContext);
+        }
+
+        /** This function is invoked via JNI; be careful when modifying signature. */
+        public void drawForeground() {
+            Rect pageRect = getPageRect();
+            LayerController controller = mView.getController();
+
+            /* Draw any extra layers that were added (likely plugins) */
+            if (mExtraLayers.size() > 0) {
+                // This is a hack. SurfaceTextureLayer draws with its own program, so disable ours here
+                // and re-enable when done. If we end up adding other types of Layer here we'll need
+                // to do something different.
+                deactivateDefaultProgram();
+                
+                for (Layer layer : mExtraLayers)
+                    layer.draw(mPageContext);
+
+                activateDefaultProgram();
+            }
+
+            /* 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 (mFrameRateLayer != null) {
+                updateDroppedFrames(mFrameStartTime);
+
+                GLES20.glEnable(GLES20.GL_BLEND);
+                GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+                mFrameRateLayer.draw(mScreenContext);
+            }
+        }
+
+        /** This function is invoked via JNI; be careful when modifying signature. */
+        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;
     }
@@ -213,10 +221,19 @@ public class LayerView extends GLSurface
     public int getMaxTextureSize() {
         return mRenderer.getMaxTextureSize();
     }
 
     /** Used by robocop for testing purposes. Not for production use! This is called via reflection by robocop. */
     public IntBuffer getPixels() {
         return mRenderer.getPixels();
     }
+
+    public void setLayerRenderer(LayerRenderer renderer) {
+        mRenderer = renderer;
+        setRenderer(mRenderer);
+    }
+
+    public LayerRenderer getLayerRenderer() {
+        return mRenderer;
+    }
 }
 
deleted file mode 100644
--- a/mobile/android/base/gfx/MultiTileLayer.java
+++ /dev/null
@@ -1,303 +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) 2011-2012
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   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.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.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";
-
-    private final CairoImage mImage;
-    private IntSize mTileSize;
-    private IntSize mBufferSize;
-    private final ArrayList<SubTile> mTiles;
-
-    public MultiTileLayer(CairoImage image, IntSize tileSize) {
-        super();
-
-        mImage = image;
-        mTileSize = tileSize;
-        mBufferSize = new IntSize(0, 0);
-        mTiles = new ArrayList<SubTile>();
-    }
-
-    public void invalidate(Rect dirtyRect) {
-        if (!inTransaction()) {
-            throw new RuntimeException("invalidate() is only valid inside a transaction");
-        }
-
-        for (SubTile layer : mTiles) {
-            IntSize tileSize = layer.getSize();
-            Rect tileRect = new Rect(layer.x, layer.y, layer.x + tileSize.width, layer.y + tileSize.height);
-
-            if (tileRect.intersect(dirtyRect)) {
-                tileRect.offset(-layer.x, -layer.y);
-                layer.invalidate(tileRect);
-            }
-        }
-    }
-
-    public void invalidate() {
-        for (SubTile layer : mTiles) {
-            layer.invalidate();
-        }
-    }
-
-    @Override
-    public IntSize getSize() {
-        return mImage.getSize();
-    }
-
-    private void validateTiles() {
-        IntSize size = getSize();
-
-        if (size.equals(mBufferSize)) {
-            return;
-        }
-
-        // Regenerate tiles
-        mTiles.clear();
-        int offset = 0;
-        final int format = mImage.getFormat();
-        final ByteBuffer buffer = mImage.getBuffer().slice();
-        final int bpp = CairoUtils.bitsPerPixelForCairoFormat(format) / 8;
-        for (int y = 0; y < size.height; y += mTileSize.height) {
-            for (int x = 0; x < size.width; x += mTileSize.width) {
-                // Create a CairoImage implementation that returns a
-                // tile from the parent CairoImage. It's assumed that
-                // the tiles are stored in series.
-                final IntSize layerSize =
-                    new IntSize(Math.min(mTileSize.width, size.width - x),
-                                Math.min(mTileSize.height, size.height - y));
-                final int tileOffset = offset;
-
-                CairoImage subImage = new CairoImage() {
-                    @Override
-                    public ByteBuffer getBuffer() {
-                        // Create a ByteBuffer that shares the data of the original
-                        // buffer, but is positioned and limited so that only the
-                        // tile data is accessible.
-                        buffer.position(tileOffset);
-                        ByteBuffer tileBuffer = buffer.slice();
-                        tileBuffer.limit(layerSize.getArea() * bpp);
-
-                        return tileBuffer;
-                    }
-
-                    @Override
-                    public IntSize getSize() {
-                        return layerSize;
-                    }
-
-                    @Override
-                    public int getFormat() {
-                        return format;
-                    }
-                };
-
-                mTiles.add(new SubTile(subImage, x, y));
-                offset += layerSize.getArea() * bpp;
-            }
-        }
-
-        // Set tile origins and resolution
-        refreshTileMetrics(getOrigin(), getResolution(), false);
-
-        mBufferSize = size;
-    }
-
-    @Override
-    protected boolean performUpdates(GL10 gl, RenderContext context) {
-        super.performUpdates(gl, 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);
-
-            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);
-                    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.
-            layer.setSkipTextureUpdate(invalid);
-        }
-
-        // 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);
-            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);
-            }
-
-            if (origin != null) {
-                layer.setOrigin(new Point(origin.x + layer.x, origin.y + layer.y));
-            }
-            if (resolution >= 0.0f) {
-                layer.setResolution(resolution);
-            }
-
-            if (!inTransaction) {
-                layer.endTransaction();
-            }
-        }
-    }
-
-    @Override
-    public void setOrigin(Point newOrigin) {
-        super.setOrigin(newOrigin);
-        refreshTileMetrics(newOrigin, -1, true);
-    }
-
-    @Override
-    public void setResolution(float newResolution) {
-        super.setResolution(newResolution);
-        refreshTileMetrics(null, newResolution, true);
-    }
-
-    @Override
-    public void beginTransaction(LayerView aView) {
-        super.beginTransaction(aView);
-
-        for (SubTile layer : mTiles) {
-            layer.beginTransaction(aView);
-        }
-    }
-
-    @Override
-    public void endTransaction() {
-        for (SubTile layer : mTiles) {
-            layer.endTransaction();
-        }
-
-        super.endTransaction();
-    }
-
-    @Override
-    public void draw(RenderContext context) {
-        for (SubTile layer : mTiles) {
-            // We use the SkipTextureUpdate flag as a validity flag. If it's false,
-            // the contents of this tile are invalid and we shouldn't draw it.
-            if (layer.getSkipTextureUpdate())
-                continue;
-
-            // Avoid work, only draw tiles that intersect with the viewport
-            RectF layerBounds = layer.getBounds(context, new FloatSize(layer.getSize()));
-            if (RectF.intersects(layerBounds, context.viewport))
-                layer.draw(context);
-        }
-    }
-
-    @Override
-    public Region getValidRegion(RenderContext context) {
-        Region validRegion = new Region();
-        for (SubTile tile : mTiles) {
-            if (tile.getSkipTextureUpdate())
-                continue;
-            validRegion.op(tile.getValidRegion(context), Region.Op.UNION);
-        }
-
-        return validRegion;
-    }
-
-    class SubTile extends SingleTileLayer {
-        public int x;
-        public int y;
-
-        public SubTile(CairoImage mImage, int mX, int mY) {
-            super(mImage);
-            x = mX;
-            y = mY;
-        }
-    }
-}
-
--- 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,90 @@
  * 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.graphics.Point;
+import android.graphics.Rect;
 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();   // calling thread irrelevant; nobody else has a ref to tileLayer yet
+        try {
+            Point origin = PointUtils.round(mViewport.getOrigin());
+            tileLayer.setPosition(new Rect(origin.x, origin.y, origin.x + mWidth, origin.y + mHeight));
+        } 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 +123,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/RectUtils.java
+++ b/mobile/android/base/gfx/RectUtils.java
@@ -53,16 +53,26 @@ public final class RectUtils {
             int width = json.getInt("width");
             int height = json.getInt("height");
             return new Rect(x, y, x + width, y + height);
         } catch (JSONException e) {
             throw new RuntimeException(e);
         }
     }
 
+    public static String toJSON(RectF rect) {
+        StringBuffer sb = new StringBuffer(256);
+        sb.append("{ \"left\": ").append(rect.left)
+          .append(", \"top\": ").append(rect.top)
+          .append(", \"right\": ").append(rect.right)
+          .append(", \"bottom\": ").append(rect.bottom)
+          .append('}');
+        return sb.toString();
+    }
+
     public static Rect contract(Rect rect, int lessWidth, int lessHeight) {
         float halfLessWidth = lessWidth / 2.0f;
         float halfLessHeight = lessHeight / 2.0f;
         return new Rect(Math.round(rect.left + halfLessWidth),
                         Math.round(rect.top + halfLessHeight),
                         Math.round(rect.right - halfLessWidth),
                         Math.round(rect.bottom - halfLessHeight));
     }
--- 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,168 +41,438 @@ 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;
 
-    private ScrollbarLayer(CairoImage image, boolean vertical, ByteBuffer buffer) {
+    private LayerRenderer mRenderer;
+    private int mProgram;
+    private int mPositionHandle;
+    private int mTextureHandle;
+    private int mSampleHandle;
+    private int mTMatrixHandle;
+    private int mOpacityHandle;
+
+    // Fragment shader used to draw the scroll-bar with opacity
+    private static final String FRAGMENT_SHADER =
+        "precision mediump float;\n" +
+        "varying vec2 vTexCoord;\n" +
+        "uniform sampler2D sTexture;\n" +
+        "uniform float uOpacity;\n" +
+        "void main() {\n" +
+        "    gl_FragColor = texture2D(sTexture, vec2(vTexCoord.x, 1.0 - vTexCoord.y));\n" +
+        "    gl_FragColor.a *= uOpacity;\n" +
+        "}\n";
+
+    // 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(LayerRenderer renderer, CairoImage image, boolean vertical, ByteBuffer buffer) {
         super(false, image);
         mVertical = vertical;
         mBuffer = buffer;
+        mRenderer = renderer;
 
         IntSize size = image.getSize();
         mBitmap = Bitmap.createBitmap(size.width, size.height, Bitmap.Config.ARGB_8888);
         mCanvas = new Canvas(mBitmap);
+
+        // Paint a spot to use as the scroll indicator
+        Paint foregroundPaint = new Paint();
+        foregroundPaint.setAntiAlias(true);
+        foregroundPaint.setStyle(Paint.Style.FILL);
+        foregroundPaint.setColor(Color.argb(127, 0, 0, 0));
+
+        mCanvas.drawColor(Color.argb(0, 0, 0, 0), PorterDuff.Mode.CLEAR);
+        mCanvas.drawCircle(CAP_RADIUS, CAP_RADIUS, CAP_RADIUS, foregroundPaint);
+
+        mBitmap.copyPixelsToBuffer(mBuffer.asIntBuffer());
     }
 
     protected void finalize() throws Throwable {
         try {
             if (!mFinalized && mBuffer != null)
                 GeckoAppShell.freeDirectBuffer(mBuffer);
             mFinalized = true;
         } finally {
             super.finalize();
         }
     }
 
-
-    public static ScrollbarLayer create(boolean vertical) {
+    public static ScrollbarLayer create(LayerRenderer renderer, 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);
-        return new ScrollbarLayer(image, vertical, buffer);
+        CairoImage image = new BufferedCairoImage(buffer, imageSize, imageSize,
+                                                  CairoImage.FORMAT_ARGB32);
+        return new ScrollbarLayer(renderer, image, vertical, buffer);
+    }
+
+    private void createProgram() {
+        int vertexShader = LayerRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
+                                                    LayerRenderer.DEFAULT_VERTEX_SHADER);
+        int fragmentShader = LayerRenderer.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 shaders' 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");
+        mOpacityHandle = GLES20.glGetUniformLocation(mProgram, "uOpacity");
+    }
+
+    private void activateProgram() {
+        // Add the program to the OpenGL environment
+        GLES20.glUseProgram(mProgram);
+
+        // Set the transformation matrix
+        GLES20.glUniformMatrix4fv(mTMatrixHandle, 1, false,
+                                  LayerRenderer.DEFAULT_TEXTURE_MATRIX, 0);
+
+        // Enable the arrays from which we get the vertex and texture coordinates
+        GLES20.glEnableVertexAttribArray(mPositionHandle);
+        GLES20.glEnableVertexAttribArray(mTextureHandle);
+
+        GLES20.glUniform1i(mSampleHandle, 0);
+        GLES20.glUniform1f(mOpacityHandle, mOpacity);
+    }
+
+    private void deactivateProgram() {
+        GLES20.glDisableVertexAttribArray(mTextureHandle);
+        GLES20.glDisableVertexAttribArray(mPositionHandle);
+        GLES20.glUseProgram(0);
     }
 
     /**
      * 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.
      */
     public boolean fade() {
         if (FloatUtils.fuzzyEquals(mOpacity, 0.0f)) {
             return false;
         }
-        beginTransaction();
+        beginTransaction(); // called on compositor thread
         try {
-            setOpacity(Math.max(mOpacity - FADE_AMOUNT, 0.0f));
+            mOpacity = Math.max(mOpacity - FADE_AMOUNT, 0.0f);
             invalidate();
         } finally {
             endTransaction();
         }
         return true;
     }
 
     /**
      * Restore the opacity of the scrollbar to fully opaque.
      * Return true if the opacity was changed, or false if the scrollbars
      * are already fully opaque.
      */
     public boolean unfade() {
         if (FloatUtils.fuzzyEquals(mOpacity, 1.0f)) {
             return false;
         }
-        beginTransaction();
+        beginTransaction(); // called on compositor thread
         try {
-            setOpacity(1.0f);
+            mOpacity = 1.0f;
             invalidate();
         } finally {
             endTransaction();
         }
         return true;
     }
 
-    private void setOpacity(float opacity) {
-        mOpacity = opacity;
-
-        Paint foregroundPaint = new Paint();
-        foregroundPaint.setAntiAlias(true);
-        foregroundPaint.setStyle(Paint.Style.FILL);
-        // use a (a,r,g,b) color of (127,0,0,0), and multiply the alpha by mOpacity for fading
-        foregroundPaint.setColor(Color.argb(Math.round(mOpacity * 127), 0, 0, 0));
-
-        mCanvas.drawColor(Color.argb(0, 0, 0, 0), PorterDuff.Mode.CLEAR);
-        mCanvas.drawCircle(CAP_RADIUS, CAP_RADIUS, CAP_RADIUS, foregroundPaint);
-
-        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);
+        // Create the shader program, if necessary
+        if (mProgram == 0) {
+            createProgram();
+        }
+
+        // Enable the shader program
+        mRenderer.deactivateDefaultProgram();
+        activateProgram();
+
+        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;
 
-            Rect rect = RectUtils.round(mVertical ? getVerticalRect(context) : getHorizontalRect(context));
-            GLES11.glBindTexture(GL10.GL_TEXTURE_2D, getTextureID());
+        // 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.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 = mPositionHandle;
+        int textureHandle = mTextureHandle;
+
+        // 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);
 
-            float viewHeight = context.viewport.height();
+        // 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
+            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],
 
-            // 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/viewHeight, 0,
+                TOP_CAP_TEX_COORDS[4], TOP_CAP_TEX_COORDS[5],
+
+                (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);
         }
+
+        // Enable the default shader program again
+        deactivateProgram();
+        mRenderer.activateDefaultProgram();
     }
 
     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;
         if (barStart > barEnd) {
--- 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
@@ -37,65 +38,125 @@
 
 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.Rect;
 import android.graphics.RectF;
-import android.opengl.GLES11;
-import android.opengl.GLES11Ext;
+import android.graphics.Region;
+import android.graphics.RegionIterator;
+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.
  */
 public class SingleTileLayer extends TileLayer {
+    private static final String LOGTAG = "GeckoSingleTileLayer";
+
+    private Rect mMask;
+
     public SingleTileLayer(CairoImage image) { this(false, image); }
 
     public SingleTileLayer(boolean repeat, CairoImage image) {
         super(repeat, image);
     }
 
+    /**
+     * Set an area to mask out when rendering.
+     */
+    public void setMask(Rect aMaskRect) {
+        mMask = aMaskRect;
+    }
+
     @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());
-
         RectF bounds;
-        int[] cropRect;
-        IntSize size = getSize();
+        Rect position = getPosition();
         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());
         } else {
-            bounds = getBounds(context, new FloatSize(size));
-            cropRect = new int[] { 0, size.height, size.width, -size.height };
+            bounds = getBounds(context);
+        }
+
+        Rect intBounds = new Rect();
+        bounds.roundOut(intBounds);
+        Region maskedBounds = new Region(intBounds);
+        if (mMask != null) {
+            maskedBounds.op(mMask, Region.Op.DIFFERENCE);
+            if (maskedBounds.isEmpty())
+                return;
         }
 
-        GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, cropRect,
-                                0);
+        // XXX Possible optimisation here, form this array so we can draw it in
+        //     a single call.
+        RegionIterator i = new RegionIterator(maskedBounds);
+        for (Rect subRect = new Rect(); i.next(subRect);) {
+            // Compensate for rounding errors at the edge of the tile caused by
+            // the roundOut above
+            RectF subRectF = new RectF(Math.max(bounds.left, (float)subRect.left),
+                                       Math.max(bounds.top, (float)subRect.top),
+                                       Math.min(bounds.right, (float)subRect.right),
+                                       Math.min(bounds.bottom, (float)subRect.bottom));
+
+            int[] cropRect = new int[] { Math.round(subRectF.left - bounds.left),
+                                         Math.round(subRectF.bottom - bounds.top),
+                                         Math.round(subRectF.right - bounds.left),
+                                         Math.round(subRectF.top - bounds.top) };
+
+            float height = subRectF.height();
+            float left = subRectF.left - viewport.left;
+            float top = viewport.height() - (subRectF.top + height - viewport.top);
+
+            float[] coords = {
+                //x, y, z, texture_x, texture_y
+                left/viewport.width(), top/viewport.height(), 0,
+                cropRect[0]/(float)position.width(), cropRect[1]/(float)position.height(),
 
-        float height = bounds.height();
-        float left = bounds.left - viewport.left;
-        float top = viewport.height() - (bounds.top + height - viewport.top);
+                left/viewport.width(), (top+height)/viewport.height(), 0,
+                cropRect[0]/(float)position.width(), cropRect[3]/(float)position.height(),
+
+                (left+subRectF.width())/viewport.width(), top/viewport.height(), 0,
+                cropRect[2]/(float)position.width(), cropRect[1]/(float)position.height(),
+
+                (left+subRectF.width())/viewport.width(), (top+height)/viewport.height(), 0,
+                cropRect[2]/(float)position.width(), cropRect[3]/(float)position.height()
+            };
+
+            FloatBuffer coordBuffer = context.coordBuffer;
+            int positionHandle = context.positionHandle;
+            int textureHandle = context.textureHandle;
 
-        GLES11Ext.glDrawTexfOES(left, top, 0.0f, bounds.width(), height);
+            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTextureID());
+
+            // Make sure we are at position zero in the buffer
+            coordBuffer.position(0);
+            coordBuffer.put(coords);
+
+            // Vertex coordinates are x,y,z starting at position 0 into the buffer.
+            coordBuffer.position(0);
+            GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer);
+
+            // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer.
+            coordBuffer.position(3);
+            GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer);
+            GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+        }
     }
 }
 
--- a/mobile/android/base/gfx/SurfaceTextureLayer.java
+++ b/mobile/android/base/gfx/SurfaceTextureLayer.java
@@ -38,129 +38,133 @@
 package org.mozilla.gecko.gfx;
 
 import org.mozilla.gecko.GeckoApp;
 
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.SurfaceTexture;
-import android.opengl.GLES11;
-import android.opengl.GLES11Ext;
-import android.opengl.Matrix;
+import android.opengl.GLES20;
 import android.util.Log;
 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;
 
 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;
-
-    private IntSize mSize;
-    private IntSize mNewSize;
+    private float[] mTextureTransform = new float[16];
 
     private boolean mInverted;
     private boolean mNewInverted;
     private boolean mBlend;
     private boolean mNewBlend;
 
-	private FloatBuffer textureBuffer;
-    private FloatBuffer textureBufferInverted;
+    private static int mProgram;
+    private static int mPositionHandle;
+    private static int mTextureHandle;
+    private static int mSampleHandle;
+    private static int mProjectionMatrixHandle;
+    private static int mTextureMatrixHandle;
+
+    private static final float[] PROJECTION_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 String VERTEX_SHADER =
+        "uniform mat4 projectionMatrix;\n" +
+        "uniform mat4 textureMatrix;\n" +
+        "attribute vec4 vPosition;\n" +
+        "attribute vec4 aTexCoord;\n" +
+        "varying vec2 vTexCoord;\n" +
+        "void main() {\n" +
+        "  gl_Position = projectionMatrix * vPosition;\n" +
+        "  vTexCoord = (textureMatrix * vec4(aTexCoord.x, aTexCoord.y, 0.0, 1.0)).xy;\n" +
+        "}\n";
 
-    public SurfaceTextureLayer(int textureId) {
+    private static String FRAGMENT_SHADER_OES =
+        "#extension GL_OES_EGL_image_external : require\n" +
+        "precision mediump float;\n" +
+        "varying vec2 vTexCoord; \n" +
+        "uniform samplerExternalOES sTexture; \n" +
+        "void main() {\n" +
+        "  gl_FragColor = texture2D(sTexture, vTexCoord); \n" +
+        "}\n";
+  
+
+    private static final float TEXTURE_MAP[] = {
+                0.0f, 1.0f, // top left
+                0.0f, 0.0f, // bottom left
+                1.0f, 1.0f, // top right
+                1.0f, 0.0f, // bottom right
+    };
+
+    private static final float TEXTURE_MAP_INVERTED[] = {
+                0.0f, 0.0f, // bottom left
+                0.0f, 1.0f, // top left
+                1.0f, 0.0f, // bottom right
+                1.0f, 1.0f, // top right
+    };
+
+    private SurfaceTextureLayer(int textureId) {
         mTextureId = textureId;
         mHaveFrame = true;
         mInverted = false;
 
         mSurfaceTexture = new SurfaceTexture(mTextureId);
         mSurfaceTexture.setOnFrameAvailableListener(this);
 
         Surface tmp = null;
         try {
             tmp = Surface.class.getConstructor(SurfaceTexture.class).newInstance(mSurfaceTexture); }
         catch (Exception ie) {
             Log.e(LOGTAG, "error constructing the surface", ie);
         }
 
         mSurface = tmp;
-
-        float textureMap[] = {
-                0.0f, 1.0f,	// top left
-                0.0f, 0.0f,	// bottom left
-                1.0f, 1.0f,	// top right
-                1.0f, 0.0f,	// bottom right
-        };
-
-        textureBuffer = createBuffer(textureMap);
-
-        float textureMapInverted[] = {
-                0.0f, 0.0f,	// bottom left
-                0.0f, 1.0f,	// top left
-                1.0f, 0.0f,	// bottom right
-                1.0f, 1.0f,	// top right
-        };
-
-        textureBufferInverted = createBuffer(textureMapInverted);
     }
 
     public static SurfaceTextureLayer create() {
         int textureId = TextureGenerator.get().take();
         if (textureId == 0)
             return null;
 
         return new SurfaceTextureLayer(textureId);
     }
 
     // For SurfaceTexture.OnFrameAvailableListener
     public void onFrameAvailable(SurfaceTexture texture) {
-        // FIXME: for some reason this doesn't get called
         mHaveFrame = true;
         GeckoApp.mAppContext.requestRender();
     }
 
-    private FloatBuffer createBuffer(float[] input) {
-        // a float has 4 bytes so we allocate for each coordinate 4 bytes
-		ByteBuffer byteBuffer = ByteBuffer.allocateDirect(input.length * 4);
-		byteBuffer.order(ByteOrder.nativeOrder());
+    public void update(Rect position, float resolution, boolean inverted, boolean blend) {
+        beginTransaction(); // this is called on the Gecko thread
 
-        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);
-
-        setOrigin(origin);
+        setPosition(position);
         setResolution(resolution);
 
-        mNewSize = size;
         mNewInverted = inverted;
         mNewBlend = blend;
 
         endTransaction();
     }
 
     @Override
-    public IntSize getSize() { return mSize; }
-
-    @Override
     protected void finalize() throws Throwable {
         if (mSurfaceTexture != null) {
             try {
                 SurfaceTexture.class.getDeclaredMethod("release").invoke(mSurfaceTexture);
             } catch (NoSuchMethodException nsme) {
                 Log.e(LOGTAG, "error finding release method on mSurfaceTexture", nsme);
             } catch (IllegalAccessException iae) {
                 Log.e(LOGTAG, "error invoking release method on mSurfaceTexture", iae);
@@ -168,104 +172,139 @@ 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);
-
-        if (mNewSize != null) {
-            mSize = mNewSize;
-            mNewSize = null;
-        }
+    protected boolean performUpdates(RenderContext context) {
+        super.performUpdates(context);
 
         mInverted = mNewInverted;
         mBlend = mNewBlend;
 
-        gl.glEnable(LOCAL_GL_TEXTURE_EXTERNAL_OES);
-        gl.glBindTexture(LOCAL_GL_TEXTURE_EXTERNAL_OES, mTextureId);
-        mSurfaceTexture.updateTexImage();
-        gl.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;
+        return true;
     }
 
-    private float mapToGLCoords(float input, float viewport, boolean flip) {
-        if (flip) input = viewport - input;
-        return ((input / viewport) * 2.0f) - 1.0f;
+    private static boolean ensureProgram() {
+        if (mProgram != 0)
+            return true;
+
+        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER);
+        int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_OES);
+
+        mProgram = GLES20.glCreateProgram();
+        GLES20.glAttachShader(mProgram, vertexShader);
+        GLES20.glAttachShader(mProgram, fragmentShader);
+        GLES20.glLinkProgram(mProgram);
+
+        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
+        mTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoord");
+        mSampleHandle = GLES20.glGetUniformLocation(mProgram, "sTexture");
+        mProjectionMatrixHandle = GLES20.glGetUniformLocation(mProgram, "projectionMatrix");
+        mTextureMatrixHandle = GLES20.glGetUniformLocation(mProgram, "textureMatrix");
+
+        return mProgram != 0;
+    }
+
+    private static int loadShader(int type, String shaderCode) {
+        int shader = GLES20.glCreateShader(type);
+        GLES20.glShaderSource(shader, shaderCode);
+        GLES20.glCompileShader(shader);
+        return shader;
+    }
+
+    private static void activateProgram() {
+        GLES20.glUseProgram(mProgram);
+    }
+
+    public static void deactivateProgram() {
+        GLES20.glDisableVertexAttribArray(mTextureHandle);
+        GLES20.glDisableVertexAttribArray(mPositionHandle);
+        GLES20.glUseProgram(0);
     }
 
     @Override
     public void draw(RenderContext context) {
+        if (!ensureProgram() || !mHaveFrame)
+            return;
 
-        // Enable GL_TEXTURE_EXTERNAL_OES and bind our texture
-        GLES11.glEnable(LOCAL_GL_TEXTURE_EXTERNAL_OES);
-        GLES11.glBindTexture(LOCAL_GL_TEXTURE_EXTERNAL_OES, mTextureId);
+        RectF rect = getBounds(context);
+        RectF viewport = context.viewport;
+        rect.offset(-viewport.left, -viewport.top);
 
-        // Enable vertex and texture coordinate buffers
-        GLES11.glEnableClientState(GL10.GL_VERTEX_ARRAY);
-        GLES11.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
+        float viewWidth = viewport.width();
+        float viewHeight = viewport.height();
+
+        float top = viewHeight - rect.top;
+        float bot = viewHeight - rect.bottom;
+
+        float[] textureCoords = mInverted ? TEXTURE_MAP_INVERTED : TEXTURE_MAP;
 
-        // Load whatever texture transform the SurfaceMatrix needs
-        float[] matrix = new float[16];
-        mSurfaceTexture.getTransformMatrix(matrix);
-        GLES11.glMatrixMode(GLES11.GL_TEXTURE);
-        GLES11.glLoadMatrixf(matrix, 0);
+        // Coordinates for the scrollbar's body combined with the texture coordinates
+        float[] coords = {
+            // x, y, z, texture_x, texture_y
+            rect.left/viewWidth, bot/viewHeight, 0,
+            textureCoords[0], textureCoords[1],
 
-        // Figure out vertices to put the texture in the right spot on the screen
-        IntSize size = getSize();
-        RectF bounds = getBounds(context, new FloatSize(size));
-        RectF viewport = context.viewport;
-        bounds.offset(-viewport.left, -viewport.top);
+            rect.left/viewWidth, (bot+rect.height())/viewHeight, 0,
+            textureCoords[2], textureCoords[3],
+
+            (rect.left+rect.width())/viewWidth, bot/viewHeight, 0,
+            textureCoords[4], textureCoords[5],
+
+            (rect.left+rect.width())/viewWidth, (bot+rect.height())/viewHeight, 0,
+            textureCoords[6], textureCoords[7]
+        };
 
-        float vertices[] = new float[8];
+        FloatBuffer coordBuffer = context.coordBuffer;
+        coordBuffer.position(0);
+        coordBuffer.put(coords);
+
+        activateProgram();
 
-        // Bottom left
-        vertices[0] = mapToGLCoords(bounds.left, viewport.width(), false);
-        vertices[1] = mapToGLCoords(bounds.bottom, viewport.height(), true);
+        // Set the transformation matrix
+        GLES20.glUniformMatrix4fv(mProjectionMatrixHandle, 1, false, PROJECTION_MATRIX, 0);
 
-        // Top left
-        vertices[2] = mapToGLCoords(bounds.left, viewport.width(), false);
-        vertices[3] = mapToGLCoords(bounds.top, viewport.height(), true);
+        // Enable the arrays from which we get the vertex and texture coordinates
+        GLES20.glEnableVertexAttribArray(mPositionHandle);
+        GLES20.glEnableVertexAttribArray(mTextureHandle);
 
-        // Bottom right
-        vertices[4] = mapToGLCoords(bounds.right, viewport.width(), false);
-        vertices[5] = mapToGLCoords(bounds.bottom, viewport.height(), true);
+        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+        GLES20.glUniform1i(mSampleHandle, 0);
+        GLES20.glBindTexture(LOCAL_GL_TEXTURE_EXTERNAL_OES, mTextureId);
+      
+        mSurfaceTexture.updateTexImage();
+        mSurfaceTexture.getTransformMatrix(mTextureTransform);
+
+        GLES20.glUniformMatrix4fv(mTextureMatrixHandle, 1, false, mTextureTransform, 0);
 
-        // Top right
-        vertices[6] = mapToGLCoords(bounds.right, viewport.width(), false);
-        vertices[7] = mapToGLCoords(bounds.top, viewport.height(), true);
+        // Vertex coordinates are x,y,z starting at position 0 into the buffer.
+        coordBuffer.position(0);
+        GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false, 20,
+                coordBuffer);
 
-        // Set texture and vertex buffers
-        GLES11.glVertexPointer(2, GL10.GL_FLOAT, 0, createBuffer(vertices));
-        GLES11.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mInverted ? textureBufferInverted : textureBuffer);
+        // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer.
+        coordBuffer.position(3);
+        GLES20.glVertexAttribPointer(mTextureHandle, 3, GLES20.GL_FLOAT, false, 20,
+                coordBuffer);
 
         if (mBlend) {
-            GLES11.glEnable(GL10.GL_BLEND);
-            GLES11.glBlendFunc(GL10.GL_ONE, GL10.GL_ONE_MINUS_SRC_ALPHA);
+            GLES20.glEnable(GLES20.GL_BLEND);
+            GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
         }
-
-        // Draw the vertices as triangle strip
-        GLES11.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, vertices.length / 2);
+        
+        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
 
-        // 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);
-        }
+        if (mBlend)
+            GLES20.glDisable(GLES20.GL_BLEND);
+        
+        deactivateProgram();
     }
 
     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,42 +32,76 @@
  * 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 java.util.Stack;
+import android.util.Log;
+import android.opengl.GLES20;
+import java.util.concurrent.ArrayBlockingQueue;
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLContext;
 
 public class TextureGenerator {
-    private static final int MIN_TEXTURES = 5;
+    private static final String LOGTAG = "TextureGenerator";
+    private static final int POOL_SIZE = 5;
 
     private static TextureGenerator sSharedInstance;
-    private Stack<Integer> mTextureIds;
 
-    private TextureGenerator() { mTextureIds = new Stack<Integer>(); }
+    private ArrayBlockingQueue<Integer> mTextureIds;
+    private EGLContext mContext;
+
+    private TextureGenerator() { mTextureIds = new ArrayBlockingQueue<Integer>(POOL_SIZE); }
 
     public static TextureGenerator get() {
         if (sSharedInstance == null)
             sSharedInstance = new TextureGenerator();
         return sSharedInstance;
     }
 
     public synchronized int take() {
-        if (mTextureIds.empty())
+        try {
+            // Will block until one becomes available
+            return (int)mTextureIds.take();
+        } catch (InterruptedException e) {
             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);
-            mTextureIds.push(textures[0]);
+        EGL10 egl = (EGL10)EGLContext.getEGL();
+        EGLContext context = egl.eglGetCurrentContext();
+
+        if (mContext != null && mContext != context) {
+            mTextureIds.clear();
+        }
+
+        mContext = context;
+
+        int numNeeded = mTextureIds.remainingCapacity();
+        if (numNeeded == 0)
+            return;
+
+        // Clear existing GL errors
+        int error;
+        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+            Log.w(LOGTAG, String.format("Clearing GL error: %#x", error));
+        }
+
+        int[] textures = new int[numNeeded];
+        GLES20.glGenTextures(numNeeded, textures, 0);
+
+        error = GLES20.glGetError();
+        if (error != GLES20.GL_NO_ERROR) {
+            Log.e(LOGTAG, String.format("Failed to generate textures: %#x", error), new Exception());
+            return;
+        }
+        
+        for (int i = 0; i < numNeeded; i++) {
+            mTextureIds.offer(textures[i]);
         }
     }
 }
 
 
--- 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.
@@ -60,38 +59,43 @@ public abstract class TileLayer extends 
     private final Rect mDirtyRect;
     private final CairoImage mImage;
     private final boolean mRepeat;
     private IntSize mSize;
     private boolean mSkipTextureUpdate;
     private int[] mTextureIDs;
 
     public TileLayer(boolean repeat, CairoImage image) {
+        super(image.getSize());
+
         mRepeat = repeat;
         mImage = image;
         mSize = new IntSize(0, 0);
         mSkipTextureUpdate = false;
-
-        IntSize bufferSize = mImage.getSize();
         mDirtyRect = new Rect();
     }
 
-    @Override
-    public IntSize getSize() { return mImage.getSize(); }
-
     protected boolean repeats() { return mRepeat; }
     protected int getTextureID() { return mTextureIDs[0]; }
     protected boolean initialized() { return mImage != null && mTextureIDs != null; }
 
     @Override
     protected void finalize() throws Throwable {
         if (mTextureIDs != null)
             TextureReaper.get().add(mTextureIDs);
     }
 
+    @Override
+    public void setPosition(Rect newPosition) {
+        if (newPosition.width() != mImage.getSize().width || newPosition.height() != mImage.getSize().height) {
+            throw new RuntimeException("Error: changing the size of a tile layer is not allowed!");
+        }
+        super.setPosition(newPosition);
+    }
+
     /**
      * Invalidates the given rect so that it will be uploaded again. Only valid inside a
      * transaction.
      */
     public void invalidate(Rect rect) {
         if (!inTransaction())
             throw new RuntimeException("invalidate() is only valid inside a transaction");
         mDirtyRect.union(rect);
@@ -101,17 +105,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 +128,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 +228,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,105 +57,61 @@ 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;
-    private boolean mAllowZoom;
-
-    // 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);
-        mAllowZoom = true;
     }
 
     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;
-        mAllowZoom = viewport.mAllowZoom;
     }
 
+    public ViewportMetrics(ImmutableViewportMetrics viewport) {
+        mPageSize = new FloatSize(viewport.pageSizeWidth, viewport.pageSizeHeight);
+        mViewportRect = new RectF(viewport.viewportRectLeft,
+                                  viewport.viewportRectTop,
+                                  viewport.viewportRectRight,
+                                  viewport.viewportRectBottom);
+        mZoomFactor = viewport.zoomFactor;
+    }
+
+
     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");
 
-        mAllowZoom = json.getBoolean("allowZoom");
-
         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);
-    }
-
     public FloatSize getSize() {
         return new FloatSize(mViewportRect.width(), mViewportRect.height());
     }
 
     public RectF getViewport() {
         return mViewportRect;
     }
 
@@ -174,72 +130,43 @@ 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;
     }
 
-    public boolean getAllowZoom() {
-        return mAllowZoom;
-    }
-
     public void setPageSize(FloatSize pageSize) {
         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.
      */
@@ -250,72 +177,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,74 @@
+/* -*- 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.Rect;
+
+public class VirtualLayer extends Layer {
+    public VirtualLayer(IntSize size) {
+        super(size);
+    }
+
+    @Override
+    public void draw(RenderContext context) {
+        // No-op.
+    }
+
+    void setPositionAndResolution(Rect newPosition, float newResolution) {
+        // This is an optimized version of the following code:
+        // beginTransaction();
+        // try {
+        //     setPosition(newPosition);
+        //     setResolution(newResolution);
+        //     performUpdates(null);
+        // } finally {
+        //     endTransaction();
+        // }
+
+        // it is safe to drop the transaction lock in this instance (i.e. for the
+        // VirtualLayer that is just a shadow of what gecko is painting) because
+        // the position and resolution of this layer are never used for anything
+        // meaningful.
+        // XXX The above is not true any more; the compositor uses these values
+        // in order to determine where to draw the checkerboard. The values are
+        // also used in LayerController's convertViewPointToLayerPoint function.
+        mPosition = newPosition;
+        mResolution = newResolution;
+    }
+}
deleted file mode 100644
--- a/mobile/android/base/gfx/WidgetTileLayer.java
+++ /dev/null
@@ -1,128 +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):
- *   James Willcox <jwillcox@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.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;
-
-/**
- * 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;
-    private CairoImage mImage;
-
-    public WidgetTileLayer(CairoImage image) {
-        mImage = image;
-    }
-
-    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);
-    }
-
-    @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);
-
-        if (mTextureIDs == null) {
-            mTextureIDs = new int[1];
-            GLES11.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]);
-
-        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 };
-        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);
-
-        GLES11Ext.glDrawTexfOES(bounds.left, top, 0.0f, bounds.width(), bounds.height());
-        int error = GLES11.glGetError();
-        if (error != GLES11.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
@@ -114,17 +114,18 @@ public class PanZoomController
         FLING,          /* all touches removed, but we're still scrolling page */
         TOUCHING,       /* one touch-start event received */
         PANNING_LOCKED, /* touch-start followed by move (i.e. panning with axis lock) */
         PANNING,        /* panning without axis lock */
         PANNING_HOLD,   /* in panning, but not moving.
                          * similar to TOUCHING but after starting a pan */
         PANNING_HOLD_LOCKED, /* like PANNING_HOLD, but axis lock still in effect */
         PINCHING,       /* nth touch-start, where n > 1. this mode allows pan and zoom */
-        ANIMATED_ZOOM   /* animated zoom to a new rect */
+        ANIMATED_ZOOM,  /* animated zoom to a new rect */
+        BOUNCE          /* in a bounce animation */
     }
 
     private final LayerController mController;
     private final SubdocumentScrollHelper mSubscroller;
     private final Axis mX;
     private final Axis mY;
 
     private Thread mMainThread;
@@ -216,59 +217,60 @@ public class PanZoomController
         // this happens when gecko changes the viewport on us or if the device is rotated.
         // if that's the case, abort any animation in progress and re-zoom so that the page
         // snaps to edges. for other cases (where the user's finger(s) are down) don't do
         // anything special.
         switch (mState) {
         case FLING:
             mX.stopFling();
             mY.stopFling();
-            mState = PanZoomState.NOTHING;
             // fall through
+        case BOUNCE:
         case ANIMATED_ZOOM:
             // the zoom that's in progress likely makes no sense any more (such as if
             // the screen orientation changed) so abort it
+            mState = PanZoomState.NOTHING;
             // fall through
         case NOTHING:
             // Don't do animations here; they're distracting and can cause flashes on page
             // transitions.
             mController.setViewportMetrics(getValidViewportMetrics());
             mController.notifyLayerClientOfGeometryChange();
             break;
         }
     }
 
     /** This must be called on the UI thread. */
     public void pageSizeUpdated() {
         if (mState == PanZoomState.NOTHING) {
             ViewportMetrics validated = getValidViewportMetrics();
-            if (! mController.getViewportMetrics().fuzzyEquals(validated)) {
+            if (! (new ViewportMetrics(mController.getViewportMetrics())).fuzzyEquals(validated)) {
                 // page size changed such that we are now in overscroll. snap to the
                 // the nearest valid viewport
                 mController.setViewportMetrics(validated);
                 mController.notifyLayerClientOfGeometryChange();
             }
         }
     }
 
     /*
      * Panning/scrolling
      */
 
     private boolean onTouchStart(MotionEvent event) {
-        Log.d(LOGTAG, "onTouchStart in state " + mState);
         // user is taking control of movement, so stop
         // any auto-movement we have going
         stopAnimationTimer();
         mSubscroller.cancel();
 
         switch (mState) {
         case ANIMATED_ZOOM:
             return false;
         case FLING:
+        case BOUNCE:
         case NOTHING:
             startTouch(event.getX(0), event.getY(0), event.getEventTime());
             return false;
         case TOUCHING:
         case PANNING:
         case PANNING_LOCKED:
         case PANNING_HOLD:
         case PANNING_HOLD_LOCKED:
@@ -276,21 +278,21 @@ public class PanZoomController
             Log.e(LOGTAG, "Received impossible touch down while in " + mState);
             return false;
         }
         Log.e(LOGTAG, "Unhandled case " + mState + " in onTouchStart");
         return false;
     }
 
     private boolean onTouchMove(MotionEvent event) {
-        Log.d(LOGTAG, "onTouchMove in state " + mState);
 
         switch (mState) {
         case NOTHING:
         case FLING:
+        case BOUNCE:
             // should never happen
             Log.e(LOGTAG, "Received impossible touch move while in " + mState);
             return false;
 
         case TOUCHING:
             if (panDistance(event) < PAN_THRESHOLD) {
                 return false;
             }
@@ -322,21 +324,21 @@ public class PanZoomController
             // scale gesture listener will handle this
             return false;
         }
         Log.e(LOGTAG, "Unhandled case " + mState + " in onTouchMove");
         return false;
     }
 
     private boolean onTouchEnd(MotionEvent event) {
-        Log.d(LOGTAG, "onTouchEnd in " + mState);
 
         switch (mState) {
         case NOTHING:
         case FLING:
+        case BOUNCE:
             // should never happen
             Log.e(LOGTAG, "Received impossible touch end while in " + mState);
             return false;
         case TOUCHING:
             mState = PanZoomState.NOTHING;
             // the switch into TOUCHING might have happened while the page was
             // snapping back after overscroll. we need to finish the snap if that
             // was the case
@@ -355,18 +357,16 @@ public class PanZoomController
         case ANIMATED_ZOOM:
             return false;
         }
         Log.e(LOGTAG, "Unhandled case " + mState + " in onTouchEnd");
         return false;
     }
 
     private boolean onTouchCancel(MotionEvent event) {
-        Log.d(LOGTAG, "onTouchCancel in " + mState);
-
         mState = PanZoomState.NOTHING;
         // ensure we snap back if we're overscrolled
         bounce();
         return false;
     }
 
     private void startTouch(float x, float y, long time) {
         mX.startTouch(x);
@@ -462,18 +462,17 @@ public class PanZoomController
         stopAnimationTimer();
 
         ViewportMetrics bounceStartMetrics = new ViewportMetrics(mController.getViewportMetrics());
         if (bounceStartMetrics.fuzzyEquals(metrics)) {
             mState = PanZoomState.NOTHING;
             return;
         }
 
-        mState = PanZoomState.FLING;
-        Log.d(LOGTAG, "end bounce at " + metrics);
+        mState = PanZoomState.BOUNCE;
 
         startAnimationTimer(new BounceRunnable(bounceStartMetrics, metrics));
     }
 
     /* Performs a bounce-back animation to the nearest valid viewport metrics. */
     private void bounce() {
         bounce(getValidViewportMetrics());
     }
@@ -576,17 +575,17 @@ public class PanZoomController
         }
 
         protected void animateFrame() {
             /*
              * The pan/zoom controller might have signaled to us that it wants to abort the
              * animation by setting the state to PanZoomState.NOTHING. Handle this case and bail
              * out.
              */
-            if (mState != PanZoomState.FLING) {
+            if (mState != PanZoomState.BOUNCE) {
                 finishAnimation();
                 return;
             }
 
             /* Perform the next frame of the bounce-back animation. */
             if (mBounceFrame < EASE_OUT_ANIMATION_FRAMES.length) {
                 advanceBounce();
                 return;
@@ -768,19 +767,16 @@ public class PanZoomController
 
     @Override
     public boolean onScale(SimpleScaleGestureDetector detector) {
         Log.d(LOGTAG, "onScale in state " + mState);
 
         if (GeckoApp.mDOMFullScreen)
             return false;
 
-        if (!mController.getViewportMetrics().getAllowZoom())
-            return false;
-
         if (mState == PanZoomState.ANIMATED_ZOOM)
             return false;
 
         float prevSpan = detector.getPreviousSpan();
         if (FloatUtils.fuzzyEquals(prevSpan, 0.0f)) {
             // let's eat this one to avoid setting the new zoom to infinity (bug 711453)
             return true;
         }
@@ -831,17 +827,28 @@ public class PanZoomController
 
         // Force a viewport sy