Bug 727116 - Draw Flash plugins with OpenGL during pan/zoom on legacy Android r=blassey a=blassey
authorJames Willcox <jwillcox@mozilla.com>
Fri, 27 Apr 2012 16:04:47 -0400
changeset 94171 5b11cd229b48b6b33ad9956fc5f1840f83e8e406
parent 94170 317262a2c3b7b8745854c54e0f99d525beaf7bed
child 94172 b14c92737b6aacead4c32ea2ac65e7445ae13009
push idunknown
push userunknown
push dateunknown
reviewersblassey, blassey
bugs727116
milestone14.0a2
Bug 727116 - Draw Flash plugins with OpenGL during pan/zoom on legacy Android r=blassey a=blassey
dom/plugins/base/nsPluginInstanceOwner.cpp
mobile/android/base/GeckoApp.java
mobile/android/base/GeckoAppShell.java
mobile/android/base/Makefile.in
mobile/android/base/SurfaceBits.java
mobile/android/base/SurfaceLockInfo.java
mobile/android/base/Tab.java
mobile/android/base/Tabs.java
mobile/android/base/gfx/BufferedCairoImage.java
mobile/android/base/gfx/GeckoLayerClient.java
mobile/android/base/gfx/Layer.java
mobile/android/base/gfx/LayerController.java
mobile/android/base/gfx/LayerRenderer.java
mobile/android/base/gfx/PluginLayer.java
mobile/android/base/gfx/RectUtils.java
mobile/android/base/gfx/SurfaceTextureLayer.java
mobile/android/base/gfx/TileLayer.java
mobile/android/base/ui/PanZoomController.java
mobile/android/chrome/content/browser.js
mozglue/android/APKOpen.cpp
widget/android/AndroidBridge.cpp
widget/android/AndroidBridge.h
widget/android/AndroidJNI.cpp
widget/android/AndroidMediaLayer.cpp
widget/android/AndroidMediaLayer.h
widget/android/nsWindow.cpp
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -1754,71 +1754,33 @@ void nsPluginInstanceOwner::SendSize(int
 bool nsPluginInstanceOwner::AddPluginView(const gfxRect& aRect)
 {
   void* javaSurface = mInstance->GetJavaSurface();
   if (!javaSurface) {
     mInstance->RequestJavaSurface();
     return false;
   }
 
-  JNIEnv* env = GetJNIForThread();
-  if (!env)
-    return false;
-
-  AndroidBridge::AutoLocalJNIFrame frame(env, 1);
-
-  jclass cls = env->FindClass("org/mozilla/gecko/GeckoAppShell");
-
-#ifdef MOZ_JAVA_COMPOSITOR
-  nsAutoString metadata;
-  nsCOMPtr<nsIAndroidDrawMetadataProvider> metadataProvider =
-      AndroidBridge::Bridge()->GetDrawMetadataProvider();
-  metadataProvider->GetDrawMetadata(metadata);
-
-  jstring jMetadata = env->NewString(nsPromiseFlatString(metadata).get(), metadata.Length());
-
-  jmethodID method = env->GetStaticMethodID(cls,
-                                            "addPluginView",
-                                            "(Landroid/view/View;IIIILjava/lang/String;)V");
-
-  env->CallStaticVoidMethod(cls,
-                            method,
-                            javaSurface,
-                            (int)aRect.x,
-                            (int)aRect.y,
-                            (int)aRect.width,
-                            (int)aRect.height,
-                            jMetadata);
-#else
-  jmethodID method = env->GetStaticMethodID(cls,
-                                            "addPluginView",
-                                            "(Landroid/view/View;DDDD)V");
-
-  env->CallStaticVoidMethod(cls,
-                            method,
-                            javaSurface,
-                            aRect.x,
-                            aRect.y,
-                            aRect.width,
-                            aRect.height);
-#endif
+  if (AndroidBridge::Bridge())
+    AndroidBridge::Bridge()->AddPluginView((jobject)javaSurface, aRect);
 
   return true;
 }
 
 void nsPluginInstanceOwner::RemovePluginView()
 {
   if (!mInstance)
     return;
 
   void* surface = mInstance->GetJavaSurface();
   if (!surface)
     return;
 
-  AndroidBridge::RemovePluginView(surface);
+  if (AndroidBridge::Bridge())
+    AndroidBridge::Bridge()->RemovePluginView((jobject)surface);
 }
 
 void nsPluginInstanceOwner::Invalidate() {
   NPRect rect;
   rect.left = rect.top = 0;
   rect.right = mPluginWindow->width;
   rect.bottom = mPluginWindow->height;
   InvalidateRect(&rect);
@@ -2894,36 +2856,47 @@ 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);
+  // Get the offset of the content relative to the page
+
+  nsPoint offset = nsPoint(0, 0);
+  nsIFrame* current = (nsIFrame*)mObjectFrame;
+  while (current && current->GetContent() && current->GetContent()->Tag() != nsGkAtoms::html) {    
+    offset += current->GetPosition();
+    current = current->GetParent();
+  }
+
+  nsRect bounds = nsRect(offset, mObjectFrame->GetSize());
+  nsIntRect intBounds = bounds.ToNearestPixels(mObjectFrame->PresContext()->AppUnitsPerDevPixel());
+  gfxRect pluginRect(intBounds);
 
   if (model == kSurface_ANPDrawingModel) {
-    if (!AddPluginView(scaledFrameRect)) {
+    if (!AddPluginView(pluginRect)) {
       Invalidate();
     }
     return;
   }
 
   if (model == kOpenGL_ANPDrawingModel) {
     if (!mLayer)
       mLayer = new AndroidMediaLayer();
 
-    mLayer->UpdatePosition(scaledFrameRect, xResolution);
-
-    SendSize((int)scaledFrameRect.width, (int)scaledFrameRect.height);
+    mLayer->UpdatePosition(pluginRect);
+
+    float xResolution = mObjectFrame->PresContext()->GetRootPresContext()->PresShell()->GetXResolution();
+    float yResolution = mObjectFrame->PresContext()->GetRootPresContext()->PresShell()->GetYResolution();
+    pluginRect.Scale(xResolution, yResolution);
+
+    SendSize((int)pluginRect.width, (int)pluginRect.height);
     return;
   }
 
   if (model != kBitmap_ANPDrawingModel)
     return;
 
 #ifdef ANP_BITMAP_DRAWING_MODEL
   static nsRefPtr<gfxImageSurface> pluginSurface;
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -36,22 +36,25 @@
  * 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.db.BrowserDB;
+import org.mozilla.gecko.gfx.CairoImage;
+import org.mozilla.gecko.gfx.BufferedCairoImage;
 import org.mozilla.gecko.gfx.FloatSize;
 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.PluginLayer;
 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.*;
@@ -672,17 +675,17 @@ abstract public class GeckoApp
                 if (Tabs.getInstance().isSelectedTab(tab)) {
                     mBrowserToolbar.setTitle(uri);
                     mBrowserToolbar.setFavicon(null);
                     mBrowserToolbar.setSecurityMode("unknown");
                     mDoorHangerPopup.updatePopup();
                     mBrowserToolbar.setShadowVisibility(!(tab.getURL().startsWith("about:")));
 
                     if (tab != null)
-                        hidePlugins(tab, true);
+                        hidePlugins(tab);
                 }
             }
         });
     }
 
     void handleSecurityChange(final int tabId, final String mode) {
         final Tab tab = Tabs.getInstance().getTab(tabId);
         if (tab == null)
@@ -1313,85 +1316,45 @@ abstract public class GeckoApp
             DownloadManager dm = (DownloadManager) mAppContext.getSystemService(Context.DOWNLOAD_SERVICE);
             dm.addCompletedDownload(displayName, displayName,
                 false /* do not use media scanner */,
                 mimeType, path, size,
                 false /* no notification */);
         }
     }
 
-    void addPluginView(final View view,
-                       final int x, final int y,
-                       final int w, final int h,
-                       final String metadata) {
+    void addPluginView(final View view, final Rect rect) {
         mMainHandler.post(new Runnable() { 
             public void run() {
-                PluginLayoutParams lp;
-
                 Tabs tabs = Tabs.getInstance();
                 Tab tab = tabs.getSelectedTab();
 
-                if (tab == null)
-                    return;
-
-                ImmutableViewportMetrics targetViewport = mLayerController.getViewportMetrics();
-                ImmutableViewportMetrics pluginViewport;
-                
-                try {
-                    JSONObject viewportObject = new JSONObject(metadata);
-                    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);
-
-                    view.setWillNotDraw(false);
-                    if (view instanceof SurfaceView) {
-                        SurfaceView sview = (SurfaceView)view;
-
-                        sview.setZOrderOnTop(false);
-                        sview.setZOrderMediaOverlay(true);
-                    }
-
-                    mPluginContainer.addView(view, lp);
-                    tab.addPluginView(view);
+                PluginLayer layer = (PluginLayer) tab.getPluginLayer(view);
+                if (layer == null) {
+                    layer = new PluginLayer(view, rect, mLayerController.getView().getRenderer().getMaxTextureSize());
+                    tab.addPluginLayer(view, layer);
+                    mLayerController.getView().addLayer(layer);
                 } else {
-                    lp = (PluginLayoutParams)view.getLayoutParams();
-                    lp.reset(x, y, w, h, pluginViewport);
-                    lp.reposition(targetViewport);
-                    try {
-                        mPluginContainer.updateViewLayout(view, lp);
-                        view.setVisibility(View.VISIBLE);
-                    } catch (IllegalArgumentException e) {
-                        Log.i(LOGTAG, "e:" + e);
-                        // it can be the case where we
-                        // get an update before the view
-                        // is actually attached.
-                    }
+                    layer.reset(rect);
+                    layer.setVisible(true);
                 }
             }
         });
     }
 
     void removePluginView(final View view) {
         mMainHandler.post(new Runnable() { 
             public void run() {
-                try {
-                    mPluginContainer.removeView(view);
-
-                    Tabs tabs = Tabs.getInstance();
-                    Tab tab = tabs.getSelectedTab();
-                    if (tab == null)
-                        return;
-
-                    tab.removePluginView(view);
-                } catch (Exception e) {}
+                Tabs tabs = Tabs.getInstance();
+                Tab tab = tabs.getSelectedTab();
+
+                PluginLayer layer = (PluginLayer) tab.removePluginLayer(view);
+                if (layer != null) {
+                    layer.destroy();
+                }
             }
         });
     }
 
     public Surface createSurface() {
         Tabs tabs = Tabs.getInstance();
         Tab tab = tabs.getSelectedTab();
         if (tab == null)
@@ -1412,41 +1375,28 @@ abstract public class GeckoApp
         if (tab == null)
             return;
 
         Layer layer = tab.removePluginLayer(surface);
         hidePluginLayer(layer);
     }
 
     public void showSurface(Surface surface, int x, int y,
-                            int w, int h, boolean inverted, boolean blend,
-                            String metadata) {
+                            int w, int h, boolean inverted, boolean blend) {
         Tabs tabs = Tabs.getInstance();
         Tab tab = tabs.getSelectedTab();
         if (tab == null)
             return;
 
-        ViewportMetrics metrics;
-        try {
-            metrics = new ViewportMetrics(new JSONObject(metadata));
-        } catch (JSONException e) {
-            Log.e(LOGTAG, "Bad viewport metadata: ", e);
-            return;
-        }
-
-        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 Rect(x, y, x + w, y + h), metrics.getZoomFactor(), inverted, blend);
+        layer.update(new Rect(x, y, x + w, y + h), 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) {
@@ -1473,81 +1423,55 @@ abstract public class GeckoApp
 
         hidePluginLayer(layer);
     }
 
     public void requestRender() {
         mLayerController.getView().requestRender();
     }
 
-    public void hidePlugins(boolean hideLayers) {
+    public void hidePlugins() {
         Tabs tabs = Tabs.getInstance();
         Tab tab = tabs.getSelectedTab();
 
         if (tab == null)
             return;
 
-        hidePlugins(tab, hideLayers);
+        hidePlugins(tab);
     }
-
-    public void hidePlugins(Tab tab, boolean hideLayers) {
-        for (View view : tab.getPluginViews()) {
-            view.setVisibility(View.GONE);
-        }
-
-        if (hideLayers) {
-            for (Layer layer : tab.getPluginLayers()) {
-                hidePluginLayer(layer);
+    
+    public void hidePlugins(Tab tab) {
+        for (Layer layer : tab.getPluginLayers()) {
+            if (layer instanceof PluginLayer) {
+                ((PluginLayer) layer).setVisible(false);
             }
 
-            requestRender();
-        }
-    }
-
-    public void showPlugins() {
-        repositionPluginViews(true);
-    }
-
-    public void showPlugins(Tab tab) {
-        repositionPluginViews(tab, true);
-
-        for (Layer layer : tab.getPluginLayers()) {
-            showPluginLayer(layer);
+            hidePluginLayer(layer);
         }
 
         requestRender();
     }
 
-    public void repositionPluginViews(boolean setVisible) {
+    public void showPlugins() {
         Tabs tabs = Tabs.getInstance();
         Tab tab = tabs.getSelectedTab();
 
-        if (tab == null)
-            return;
-
-        repositionPluginViews(tab, setVisible);
+        showPlugins(tab);
     }
 
-    public void repositionPluginViews(Tab tab, boolean setVisible) {
-        ImmutableViewportMetrics targetViewport = mLayerController.getViewportMetrics();
-
-        if (targetViewport == null)
-            return;
-
-        for (View view : tab.getPluginViews()) {
-            PluginLayoutParams lp = (PluginLayoutParams)view.getLayoutParams();
-            lp.reposition(targetViewport);
-
-            if (setVisible) {
-                view.setVisibility(View.VISIBLE);
+    public void showPlugins(Tab tab) {
+        for (Layer layer : tab.getPluginLayers()) {
+            showPluginLayer(layer);
+
+            if (layer instanceof PluginLayer) {
+                ((PluginLayer) layer).setVisible(true);
             }
-
-            if (mPluginContainer.indexOfChild(view) >= 0)
-                mPluginContainer.updateViewLayout(view, lp);
         }
+
+        requestRender();
     }
 
     public void setFullScreen(final boolean fullscreen) {
         mMainHandler.post(new Runnable() { 
             public void run() {
                 // Hide/show the system notification bar
                 Window window = getWindow();
                 window.setFlags(fullscreen ?
@@ -2850,16 +2774,18 @@ abstract public class GeckoApp
         Log.i(LOGTAG, "Sending message to Gecko: " + SystemClock.uptimeMillis() + " - Tab:Add");
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Add", args.toString()));
     }
 
     /* This method is referenced by Robocop via reflection. */
     public GeckoLayerClient getLayerClient() { return mLayerClient; }
     public LayerController getLayerController() { return mLayerController; }
 
+    public AbsoluteLayout getPluginContainer() { return mPluginContainer; }
+
     // accelerometer
     public void onAccuracyChanged(Sensor sensor, int accuracy) {}
 
     public void onSensorChanged(SensorEvent event)
     {
         GeckoAppShell.sendEventToGecko(GeckoEvent.createSensorEvent(event));
     }
 
@@ -2891,89 +2817,9 @@ abstract public class GeckoApp
             public boolean onTouch(View view, MotionEvent event) {
                 if (event == null)
                     return true;
                 GeckoAppShell.sendEventToGecko(GeckoEvent.createMotionEvent(event));
                 return true;
             }
         });
     }
-}
-
-class PluginLayoutParams extends AbsoluteLayout.LayoutParams
-{
-    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 ImmutableViewportMetrics mOriginalViewport;
-    private float mLastResolution;
-
-    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.zoomFactor * 100) + "%)");
-
-        mOriginalX = aX;
-        mOriginalY = aY;
-        mOriginalWidth = aWidth;
-        mOriginalHeight = aHeight;
-        mOriginalViewport = aViewport;
-        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, ImmutableViewportMetrics aViewport) {
-        PointF origin = aViewport.getOrigin();
-
-        this.x = mOriginalX = aX;
-        this.y = mOriginalY = aY;
-        width = mOriginalWidth = aWidth;
-        height = mOriginalHeight = aHeight;
-        mOriginalViewport = aViewport;
-        mLastResolution = aViewport.zoomFactor;
-
-        clampToMaxSize();
-    }
-
-    private void reposition(Point aOffset, float aResolution) {
-        this.x = mOriginalX + aOffset.x;
-        this.y = mOriginalY + aOffset.y;
-
-        if (!FloatUtils.fuzzyEquals(mLastResolution, aResolution)) {
-            width = Math.round(aResolution * mOriginalWidth);
-            height = Math.round(aResolution * mOriginalHeight);
-            mLastResolution = aResolution;
-
-            clampToMaxSize();
-        }
-    }
-
-    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.zoomFactor);
-    }
-
-    public float getLastResolution() {
-        return mLastResolution;
-    }
-}
+}
\ No newline at end of file
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -41,16 +41,18 @@ package org.mozilla.gecko;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.IntSize;
 import org.mozilla.gecko.gfx.GeckoLayerClient;
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.gfx.LayerController;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.ScreenshotLayer;
 import org.mozilla.gecko.FloatUtils;
+import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
+import org.mozilla.gecko.gfx.ViewportMetrics;
 
 import java.io.*;
 import java.lang.reflect.*;
 import java.nio.*;
 import java.text.*;
 import java.util.*;
 import java.util.zip.*;
 import java.util.concurrent.*;
@@ -226,16 +228,18 @@ public class GeckoAppShell
     public static native ByteBuffer allocateDirectBuffer(long size);
     public static native void freeDirectBuffer(ByteBuffer buf);
     public static native void scheduleComposite();
     public static native void schedulePauseComposition();
     public static native void scheduleResumeComposition(int width, int height);
 
     public static native void unlockDatabaseFile(String databasePath);
 
+    public static native SurfaceBits getSurfaceBits(Surface surface);
+
     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;
             mMimeType = aMimeType;
@@ -1510,43 +1514,44 @@ public class GeckoAppShell
         }
         catch (Exception e) {
             return true;
         }
     }
 
     public static void addPluginView(View view,
                                      int x, int y,
-                                     int w, int h,
-                                     String metadata)
-    {
-        Log.i(LOGTAG, "addPluginView:" + view + " @ x:" + x + " y:" + y + " w:" + w + " h:" + h + " metadata: " + metadata);
-        GeckoApp.mAppContext.addPluginView(view, x, y, w, h, metadata);
+                                     int w, int h)
+{
+        ImmutableViewportMetrics pluginViewport;
+
+        Log.i(LOGTAG, "addPluginView:" + view + " @ x:" + x + " y:" + y + " w:" + w + " h:" + h);
+        
+        GeckoApp.mAppContext.addPluginView(view, new Rect(x, y, x + w, y + h));
     }
 
     public static void removePluginView(View view) {
         Log.i(LOGTAG, "removePluginView:" + view);
         GeckoApp.mAppContext.removePluginView(view);
     }
 
     public static Surface createSurface() {
         Log.i(LOGTAG, "createSurface");
         return GeckoApp.mAppContext.createSurface();
     }
 
     public static void showSurface(Surface surface,
                                    int x, int y,
                                    int w, int h,
                                    boolean inverted,
-                                   boolean blend,
-                                   String metadata)
+                                   boolean blend)
     {
-        Log.i(LOGTAG, "showSurface:" + surface + " @ x:" + x + " y:" + y + " w:" + w + " h:" + h + " inverted: " + inverted + " blend: " + blend + " metadata: " + metadata);
+        Log.i(LOGTAG, "showSurface:" + surface + " @ x:" + x + " y:" + y + " w:" + w + " h:" + h + " inverted: " + inverted + " blend: " + blend);
         try {
-            GeckoApp.mAppContext.showSurface(surface, x, y, w, h, inverted, blend, metadata);
+            GeckoApp.mAppContext.showSurface(surface, x, y, w, h, inverted, blend);
         } catch (Exception e) {
             Log.i(LOGTAG, "Error in showSurface:", e);
         }
     }
 
     public static void hideSurface(Surface surface) {
         Log.i(LOGTAG, "hideSurface:" + surface);
         GeckoApp.mAppContext.hideSurface(surface);
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -105,17 +105,17 @@ FENNEC_JAVA_FILES = \
   ProfileMigrator.java \
   PromptService.java \
   sqlite/ByteBufferInputStream.java \
   sqlite/MatrixBlobCursor.java \
   sqlite/SQLiteBridge.java \
   sqlite/SQLiteBridgeException.java \
   RemoteTabs.java \
   SetupScreen.java \
-  SurfaceLockInfo.java \
+  SurfaceBits.java \
   Tab.java \
   Tabs.java \
   TabsTray.java \
   TabsAccessor.java \
   Telemetry.java \
   gfx/BitmapUtils.java \
   gfx/BufferedCairoImage.java \
   gfx/CairoGLInfo.java \
@@ -130,16 +130,17 @@ FENNEC_JAVA_FILES = \
   gfx/GLController.java \
   gfx/ImmutableViewportMetrics.java \
   gfx/InputConnectionHandler.java \
   gfx/IntSize.java \
   gfx/Layer.java \
   gfx/LayerController.java \
   gfx/LayerRenderer.java \
   gfx/LayerView.java \
+  gfx/PluginLayer.java \
   gfx/NinePatchTileLayer.java \
   gfx/PanningPerfAPI.java \
   gfx/PointUtils.java \
   gfx/RectUtils.java \
   gfx/ScreenshotLayer.java \
   gfx/ScrollbarLayer.java \
   gfx/SingleTileLayer.java \
   gfx/SurfaceTextureLayer.java \
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/SurfaceBits.java
@@ -0,0 +1,10 @@
+package org.mozilla.gecko;
+
+import java.nio.ByteBuffer;
+
+public class SurfaceBits {
+    public int width;
+    public int height;
+    public int format;
+    public ByteBuffer buffer;
+}
deleted file mode 100644
--- a/mobile/android/base/SurfaceLockInfo.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.mozilla.gecko;
-
-import android.graphics.Canvas;
-import java.nio.Buffer;
-
-public class SurfaceLockInfo {
-    public int dirtyTop;
-    public int dirtyLeft;
-    public int dirtyRight;
-    public int dirtyBottom;
-
-    public int bpr;
-    public int format;
-    public int width;
-    public int height;
-    public Buffer buffer;
-    public Canvas canvas;
-}
--- a/mobile/android/base/Tab.java
+++ b/mobile/android/base/Tab.java
@@ -85,17 +85,17 @@ public final class Tab {
     private boolean mExternal;
     private boolean mBookmark;
     private HashMap<String, DoorHanger> mDoorHangers;
     private long mFaviconLoadId;
     private String mDocumentURI;
     private String mContentType;
     private boolean mHasTouchListeners;
     private ArrayList<View> mPluginViews;
-    private HashMap<Surface, Layer> mPluginLayers;
+    private HashMap<Object, Layer> mPluginLayers;
     private ContentResolver mContentResolver;
     private ContentObserver mContentObserver;
     private int mCheckerboardColor = Color.WHITE;
     private int mState;
 
     public static final int STATE_DELAYED = 0;
     public static final int STATE_LOADING = 1;
     public static final int STATE_SUCCESS = 2;
@@ -124,17 +124,17 @@ public final class Tab {
         mHistory = new ArrayList<HistoryEntry>();
         mHistoryIndex = -1;
         mBookmark = false;
         mDoorHangers = new HashMap<String, DoorHanger>();
         mFaviconLoadId = 0;
         mDocumentURI = "";
         mContentType = "";
         mPluginViews = new ArrayList<View>();
-        mPluginLayers = new HashMap<Surface, Layer>();
+        mPluginLayers = new HashMap<Object, Layer>();
         mState = "about:home".equals(url) ? STATE_SUCCESS : STATE_LOADING;
         mContentResolver = Tabs.getInstance().getContentResolver();
         mContentObserver = new ContentObserver(GeckoAppShell.getHandler()) {
             public void onChange(boolean selfChange) {
                 updateBookmark();
             }
         };
         BrowserDB.registerBookmarkObserver(mContentResolver, mContentObserver);
@@ -542,30 +542,30 @@ public final class Tab {
     public void removePluginView(View view) {
         mPluginViews.remove(view);
     }
 
     public View[] getPluginViews() {
         return mPluginViews.toArray(new View[mPluginViews.size()]);
     }
 
-    public void addPluginLayer(Surface surface, Layer layer) {
-        mPluginLayers.put(surface, layer);
+    public void addPluginLayer(Object surfaceOrView, Layer layer) {
+        mPluginLayers.put(surfaceOrView, layer);
     }
 
-    public Layer getPluginLayer(Surface surface) {
-        return mPluginLayers.get(surface);
+    public Layer getPluginLayer(Object surfaceOrView) {
+        return mPluginLayers.get(surfaceOrView);
     }
 
     public Collection<Layer> getPluginLayers() {
         return mPluginLayers.values();
     }
 
-    public Layer removePluginLayer(Surface surface) {
-        return mPluginLayers.remove(surface);
+    public Layer removePluginLayer(Object surfaceOrView) {
+        return mPluginLayers.remove(surfaceOrView);
     }
 
     public int getCheckerboardColor() {
         return mCheckerboardColor;
     }
 
     /** Sets a new color for the checkerboard. */
     public void setCheckerboardColor(int color) {
--- a/mobile/android/base/Tabs.java
+++ b/mobile/android/base/Tabs.java
@@ -137,17 +137,17 @@ public class Tabs implements GeckoEventL
                     GeckoApp.mBrowserToolbar.setFavicon(tab.getFavicon());
                     GeckoApp.mBrowserToolbar.setSecurityMode(tab.getSecurityMode());
                     GeckoApp.mBrowserToolbar.setProgressVisibility(tab.getState() == Tab.STATE_LOADING);
                     GeckoApp.mDoorHangerPopup.updatePopup();
                     GeckoApp.mBrowserToolbar.setShadowVisibility((url == null) || !url.startsWith("about:"));
                     notifyListeners(tab, TabEvents.SELECTED);
 
                     if (oldTab != null)
-                        GeckoApp.mAppContext.hidePlugins(oldTab, true);
+                        GeckoApp.mAppContext.hidePlugins(oldTab);
                 }
             }
         });
 
         // Pass a message to Gecko to update tab state in BrowserApp
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Selected", String.valueOf(tab.getId())));
         return selectedTab = tab;
     }
@@ -199,17 +199,17 @@ public class Tabs implements GeckoEventL
         int tabId = tab.getId();
         removeTab(tabId);
 
         GeckoApp.mAppContext.mMainHandler.post(new Runnable() { 
             public void run() {
                 notifyListeners(tab, TabEvents.CLOSED);
                 GeckoApp.mBrowserToolbar.updateTabCountAndAnimate(Tabs.getInstance().getCount());
                 GeckoApp.mDoorHangerPopup.updatePopup();
-                GeckoApp.mAppContext.hidePlugins(tab, true);
+                GeckoApp.mAppContext.hidePlugins(tab);
                 tab.onDestroy();
             }
         });
 
         // Pass a message to Gecko to update tab state in BrowserApp
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Closed", String.valueOf(tabId)));
     }
 
--- a/mobile/android/base/gfx/BufferedCairoImage.java
+++ b/mobile/android/base/gfx/BufferedCairoImage.java
@@ -47,27 +47,22 @@ import java.nio.ByteBuffer;
 public class BufferedCairoImage extends CairoImage {
     private ByteBuffer mBuffer;
     private IntSize mSize;
     private int mFormat;
     private boolean mNeedToFreeBuffer = false;
 
     /** Creates a buffered Cairo image from a byte buffer. */
     public BufferedCairoImage(ByteBuffer inBuffer, int inWidth, int inHeight, int inFormat) {
-        mBuffer = inBuffer; mSize = new IntSize(inWidth, inHeight); mFormat = inFormat;
+        setBuffer(inBuffer, inWidth, inHeight, inFormat);
     }
 
     /** Creates a buffered Cairo image from an Android bitmap. */
     public BufferedCairoImage(Bitmap bitmap) {
-        mFormat = CairoUtils.bitmapConfigToCairoFormat(bitmap.getConfig());
-        mSize = new IntSize(bitmap.getWidth(), bitmap.getHeight());
-        mNeedToFreeBuffer = true;
-        // XXX Why is this * 4? Shouldn't it depend on mFormat?
-        mBuffer = GeckoAppShell.allocateDirectBuffer(mSize.getArea() * 4);
-        bitmap.copyPixelsToBuffer(mBuffer.asIntBuffer());
+        setBitmap(bitmap);
     }
 
      protected void finalize() throws Throwable {
         try {
             if (mNeedToFreeBuffer && mBuffer != null)
                 GeckoAppShell.freeDirectBuffer(mBuffer);
             mNeedToFreeBuffer = false;
             mBuffer = null;
@@ -77,10 +72,27 @@ public class BufferedCairoImage extends 
     }
 
    @Override
     public ByteBuffer getBuffer() { return mBuffer; }
     @Override
     public IntSize getSize() { return mSize; }
     @Override
     public int getFormat() { return mFormat; }
+
+
+    public void setBuffer(ByteBuffer buffer, int width, int height, int format) {
+        mBuffer = buffer;
+        mSize = new IntSize(width, height);
+        mFormat = format;
+    }
+
+    public void setBitmap(Bitmap bitmap) {
+        mFormat = CairoUtils.bitmapConfigToCairoFormat(bitmap.getConfig());
+        mSize = new IntSize(bitmap.getWidth(), bitmap.getHeight());
+        mNeedToFreeBuffer = true;
+
+        int bpp = CairoUtils.bitsPerPixelForCairoFormat(mFormat);
+        mBuffer = GeckoAppShell.allocateDirectBuffer(mSize.getArea() * bpp);
+        bitmap.copyPixelsToBuffer(mBuffer.asIntBuffer());
+    }
 }
 
--- a/mobile/android/base/gfx/GeckoLayerClient.java
+++ b/mobile/android/base/gfx/GeckoLayerClient.java
@@ -125,16 +125,17 @@ public class GeckoLayerClient implements
         view.setListener(this);
         view.setLayerRenderer(mLayerRenderer);
         layerController.setRoot(mRootLayer);
 
         sendResizeEventIfNecessary(true);
 
         JSONArray prefs = new JSONArray();
         DisplayPortCalculator.addPrefNames(prefs);
+        PluginLayer.addPrefNames(prefs);
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Preferences:Get", prefs.toString()));
     }
 
     DisplayPortMetrics getDisplayPort() {
         return mDisplayPort;
     }
 
     /* Informs Gecko that the screen size has changed. */
@@ -276,17 +277,17 @@ public class GeckoLayerClient implements
                     } catch (JSONException je) {
                         // the pref value couldn't be parsed as an int. drop this pref
                         // and continue with the rest
                     }
                 }
                 // check return value from setStrategy to make sure that this is the
                 // right batch of prefs, since other java code may also have sent requests
                 // for prefs.
-                if (DisplayPortCalculator.setStrategy(prefValues)) {
+                if (DisplayPortCalculator.setStrategy(prefValues) && PluginLayer.setUsePlaceholder(prefValues)) {
                     GeckoAppShell.unregisterGeckoEventListener("Preferences:Data", this);
                 }
             }
         } catch (JSONException e) {
             Log.e(LOGTAG, "Error decoding JSON in " + event + " handler", e);
         }
     }
 
--- a/mobile/android/base/gfx/Layer.java
+++ b/mobile/android/base/gfx/Layer.java
@@ -50,16 +50,17 @@ import org.mozilla.gecko.FloatUtils;
 public abstract class Layer {
     private final ReentrantLock mTransactionLock;
     private boolean mInTransaction;
     private Rect mNewPosition;
     private float mNewResolution;
 
     protected Rect mPosition;
     protected float mResolution;
+    protected boolean mUsesDefaultProgram = true;
 
     public Layer() {
         this(null);
     }
 
     public Layer(IntSize size) {
         mTransactionLock = new ReentrantLock();
         if (size == null) {
@@ -160,16 +161,20 @@ public abstract class Layer {
      * the reciprocal of the resolution in the layer's transform() function.
      * Only valid inside a transaction. */
     public void setResolution(float newResolution) {
         if (!mInTransaction)
             throw new RuntimeException("setResolution() is only valid inside a transaction");
         mNewResolution = newResolution;
     }
 
+    public boolean usesDefaultProgram() {
+        return mUsesDefaultProgram;
+    }
+
     /**
      * 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 void performUpdates(RenderContext context) {
         if (mNewPosition != null) {
--- a/mobile/android/base/gfx/LayerController.java
+++ b/mobile/android/base/gfx/LayerController.java
@@ -188,17 +188,16 @@ public class LayerController {
     public void scrollBy(PointF point) {
         ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
         PointF origin = viewportMetrics.getOrigin();
         origin.offset(point.x, point.y);
         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, FloatSize cssSize) {
         if (mViewportMetrics.getCssPageSize().equals(cssSize))
             return;
 
@@ -220,23 +219,16 @@ public class LayerController {
     /**
      * 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 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();
     }
 
     public void setAnimationTarget(ViewportMetrics viewport) {
         if (mLayerClient != null) {
             // We know what the final viewport of the animation is going to be, so
             // immediately request a draw of that area by setting the display port
             // accordingly. This way we should have the content pre-rendered by the
@@ -254,17 +246,16 @@ public class LayerController {
     public void scaleWithFocus(float zoomFactor, PointF focus) {
         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();
     }
 
     public boolean post(Runnable action) { return mView.post(action); }
 
     /**
      * The view as well as the controller itself use this method to notify the layer client that
      * the geometry changed.
--- a/mobile/android/base/gfx/LayerRenderer.java
+++ b/mobile/android/base/gfx/LayerRenderer.java
@@ -646,25 +646,25 @@ public class LayerRenderer implements GL
 
             rootLayer.draw(mPageContext);
         }
 
         /** This function is invoked via JNI; be careful when modifying signature. */
         public void drawForeground() {
             /* 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)
+                for (Layer layer : mExtraLayers) {
+                    if (!layer.usesDefaultProgram())
+                        deactivateDefaultProgram();
+
                     layer.draw(mPageContext);
 
-                activateDefaultProgram();
+                    if (!layer.usesDefaultProgram())
+                        activateDefaultProgram();
+                }
             }
 
             /* Draw the vertical scrollbar. */
             if (mPageRect.height() > mFrameMetrics.getHeight())
                 mVertScrollLayer.draw(mPageContext);
 
             /* Draw the horizontal scrollbar. */
             if (mPageRect.width() > mFrameMetrics.getWidth())
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/PluginLayer.java
@@ -0,0 +1,320 @@
+package org.mozilla.gecko.gfx;
+
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.PixelFormat;
+import android.view.View;
+import android.view.Surface;
+import android.view.SurfaceView;
+import android.widget.AbsoluteLayout;
+import android.util.Log;
+import android.opengl.GLES20;
+import java.nio.FloatBuffer;
+import java.util.Map;
+import org.json.JSONArray;
+
+import org.mozilla.gecko.FloatUtils;
+import org.mozilla.gecko.SurfaceBits;
+import org.mozilla.gecko.GeckoApp;
+import org.mozilla.gecko.GeckoAppShell;
+
+public class PluginLayer extends TileLayer
+{
+    private static final String LOGTAG = "PluginLayer";
+    private static final String PREF_PLUGIN_USE_PLACEHOLDER = "plugins.use_placeholder";
+
+    private static boolean sUsePlaceholder = true;
+
+    private View mView;
+    private SurfaceView mSurfaceView;
+    private PluginLayoutParams mLayoutParams;
+    private AbsoluteLayout mContainer;
+
+    private boolean mViewVisible;
+    private boolean mShowPlaceholder;
+    private boolean mDestroyed;
+
+    private RectF mLastViewport;
+    private float mLastZoomFactor;
+
+    private ShowViewRunnable mShowViewRunnable;
+
+    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
+    };
+
+    public PluginLayer(View view, Rect rect, int maxDimension) {
+        super(new BufferedCairoImage(null, 0, 0, 0), TileLayer.PaintMode.NORMAL);
+
+        mView = view;
+        mContainer = GeckoApp.mAppContext.getPluginContainer();
+        mShowViewRunnable = new ShowViewRunnable(this);
+
+        mView.setWillNotDraw(false);
+        if (mView instanceof SurfaceView) {
+            mSurfaceView = (SurfaceView)view;
+            mSurfaceView.setZOrderOnTop(false);
+            mSurfaceView.setZOrderMediaOverlay(true);
+        }
+
+        mLayoutParams = new PluginLayoutParams(rect, maxDimension);
+    }
+
+    static void addPrefNames(JSONArray prefs) {
+        prefs.put(PREF_PLUGIN_USE_PLACEHOLDER);
+    }
+
+    static boolean setUsePlaceholder(Map<String, Integer> prefs) {
+        Integer usePlaceholder = prefs.get(PREF_PLUGIN_USE_PLACEHOLDER);
+        if (usePlaceholder == null) {
+            return false;
+        }
+
+        sUsePlaceholder = (int)usePlaceholder == 1 ? true : false;
+        Log.i(LOGTAG, "Using plugin placeholder: " + sUsePlaceholder);
+        return true;
+    }
+
+    public void setVisible(boolean newVisible) {
+        if (newVisible && !mShowPlaceholder) {
+            showView();
+        } else {
+            hideView();
+        }
+    }
+
+    private void hideView() {
+        if (mViewVisible) {
+            GeckoApp.mAppContext.mMainHandler.post(new Runnable() {
+                public void run() {
+                    mView.setVisibility(View.GONE);
+                }
+            });
+
+            mViewVisible = false;
+        }
+    }
+
+    private void suspendView() {
+        // Right now we can only show a placeholder ("suspend") a 
+        // SurfaceView plugin (Flash)
+        if (mSurfaceView != null) {
+            hideView();
+
+            GeckoApp.mAppContext.mMainHandler.removeCallbacks(mShowViewRunnable);
+            GeckoApp.mAppContext.mMainHandler.postDelayed(mShowViewRunnable, 250);
+        }
+    }
+
+    public void updateView() {
+        showView(true);
+    }
+
+    public void showView() {
+        showView(false);
+    }
+
+    public void showView(boolean forceUpdate) {
+        if (!mViewVisible || forceUpdate) {
+            GeckoApp.mAppContext.mMainHandler.post(new Runnable() {
+                public void run() {
+                    if (mContainer.indexOfChild(mView) < 0) {
+                        mContainer.addView(mView, mLayoutParams);
+                    } else {
+                        mContainer.updateViewLayout(mView, mLayoutParams);
+                        mView.setVisibility(View.VISIBLE);
+                    }
+                }
+            });
+            mViewVisible = true;
+            mShowPlaceholder = false;
+        }
+    }
+
+    public void destroy() {
+        mDestroyed = true;
+
+        mContainer.removeView(mView);
+        GeckoApp.mAppContext.mMainHandler.removeCallbacks(mShowViewRunnable);
+    }
+
+    public void reset(Rect rect) {
+        mLayoutParams.reset(rect);
+    }
+
+    @Override
+    protected void performUpdates(RenderContext context) {
+        if (mDestroyed)
+            return;
+
+        if (!RectUtils.fuzzyEquals(context.viewport, mLastViewport) ||
+            !FloatUtils.fuzzyEquals(context.zoomFactor, mLastZoomFactor)) {
+
+            // Viewport has changed from the last update
+            
+            if (mLastViewport != null && mSurfaceView != null && !mShowPlaceholder && sUsePlaceholder) {
+                // We have a SurfaceView that we can snapshot for a placeholder, and we are
+                // not currently showing a placeholder.
+
+                Surface surface = mSurfaceView.getHolder().getSurface();
+                SurfaceBits bits = GeckoAppShell.getSurfaceBits(surface);
+                if (bits != null) {
+                    int cairoFormat = -1;
+                    switch (bits.format) {
+                    case PixelFormat.RGBA_8888:
+                        cairoFormat = CairoImage.FORMAT_ARGB32;
+                        break;
+                    case PixelFormat.RGB_565:
+                        cairoFormat = CairoImage.FORMAT_RGB16_565;
+                        break;
+                    default:
+                        Log.w(LOGTAG, "Unable to handle format " + bits.format);
+                        break;
+                    }
+
+                    if (cairoFormat >= 0) {
+                        BufferedCairoImage image = (BufferedCairoImage)mImage;
+                        image.setBuffer(bits.buffer, bits.width, bits.height, cairoFormat);
+
+                        mPosition = new Rect(mLayoutParams.x, mLayoutParams.y, mLayoutParams.x + bits.width, mLayoutParams.y + bits.height);
+                        mPosition.offset(Math.round(mLastViewport.left), Math.round(mLastViewport.top));
+
+                        mResolution = mLastZoomFactor;
+
+                        // We've uploaded the snapshot to the texture now (or will in super.performUpdates)
+                        // so we can draw the placeholder
+                        mShowPlaceholder = true;
+                        super.performUpdates(context);
+                    }
+                }
+            }
+
+            mLastZoomFactor = context.zoomFactor;
+            mLastViewport = context.viewport;
+            mLayoutParams.reposition(context.viewport, context.zoomFactor);
+
+            if (mShowPlaceholder) {
+                suspendView();
+            } else {
+                // We aren't showing the placeholder so we need to update the view position immediately
+                updateView();
+            }
+        }
+    }
+
+    @Override
+    public void draw(RenderContext context) {
+        if (!mShowPlaceholder || mDestroyed || !initialized())
+            return;
+
+        RectF bounds;
+        Rect position = getPosition();
+        RectF viewport = context.viewport;
+
+        bounds = getBounds(context);
+
+        float height = mLayoutParams.height;
+        float left = bounds.left - viewport.left;
+        float top = viewport.height() - (bounds.top + height - viewport.top);
+
+        float[] coords = {
+            //x, y, z, texture_x, texture_y
+            left/viewport.width(), top/viewport.height(), 0,
+            0.0f, mLayoutParams.height / bounds.height(),
+
+            left/viewport.width(), (top+height)/viewport.height(), 0,
+            0.0f, 0.0f,
+
+            (left+mLayoutParams.width)/viewport.width(), top/viewport.height(), 0,
+            mLayoutParams.width / bounds.width(), mLayoutParams.height / bounds.height(),
+
+            (left+mLayoutParams.width)/viewport.width(), (top+height)/viewport.height(), 0,
+            mLayoutParams.width / bounds.width(), 0.0f
+        };
+
+        FloatBuffer coordBuffer = context.coordBuffer;
+        int positionHandle = context.positionHandle;
+        int textureHandle = context.textureHandle;
+
+        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTextureID());
+
+        // Make sure we are at position zero in the buffer
+        coordBuffer.position(0);
+        coordBuffer.put(coords);
+
+        // Vertex coordinates are x,y,z starting at position 0 into the buffer.
+        coordBuffer.position(0);
+        GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer);
+
+        // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer.
+        coordBuffer.position(3);
+        GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer);
+        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+    }
+
+    class PluginLayoutParams extends AbsoluteLayout.LayoutParams
+    {
+        private static final String LOGTAG = "GeckoApp.PluginLayoutParams";
+
+        private RectF mRect;
+        private int mMaxDimension;
+        private float mLastResolution;
+
+        public PluginLayoutParams(Rect rect, int maxDimension) {
+            super(0, 0, 0, 0);
+
+            mMaxDimension = maxDimension;
+            reset(rect);
+        }
+
+        private void clampToMaxSize() {
+            if (width > mMaxDimension || height > mMaxDimension) {
+                if (width > height) {
+                    height = Math.round(((float)height/(float)width) * mMaxDimension);
+                    width = mMaxDimension;
+                } else {
+                    width = Math.round(((float)width/(float)height) * mMaxDimension);
+                    height = mMaxDimension;
+                }
+            }
+        }
+
+        public void reset(Rect rect) {
+            mRect = new RectF(rect);
+        }
+
+        public void reposition(RectF viewport, float zoomFactor) {
+
+            RectF scaled = RectUtils.scale(mRect, zoomFactor);
+
+            this.x = Math.round(scaled.left - viewport.left);
+            this.y = Math.round(scaled.top - viewport.top);
+
+            if (!FloatUtils.fuzzyEquals(mLastResolution, zoomFactor)) {
+                width = Math.round(mRect.width() * zoomFactor);
+                height = Math.round(mRect.height() * zoomFactor);
+                mLastResolution = zoomFactor;
+
+                clampToMaxSize();
+            }
+        }
+    }
+
+    class ShowViewRunnable implements Runnable {
+
+        private PluginLayer mLayer;
+
+        public ShowViewRunnable(PluginLayer layer) {
+            mLayer = layer;
+        }
+
+        public void run() {
+            mLayer.showView();
+        }
+    }
+}
\ No newline at end of file
--- a/mobile/android/base/gfx/RectUtils.java
+++ b/mobile/android/base/gfx/RectUtils.java
@@ -132,14 +132,19 @@ public final class RectUtils {
     public static RectF interpolate(RectF from, RectF to, float t) {
         return new RectF(FloatUtils.interpolate(from.left, to.left, t),
                          FloatUtils.interpolate(from.top, to.top, t),
                          FloatUtils.interpolate(from.right, to.right, t),
                          FloatUtils.interpolate(from.bottom, to.bottom, t));
     }
 
     public static boolean fuzzyEquals(RectF a, RectF b) {
-        return FloatUtils.fuzzyEquals(a.top, b.top)
-            && FloatUtils.fuzzyEquals(a.left, b.left)
-            && FloatUtils.fuzzyEquals(a.right, b.right)
-            && FloatUtils.fuzzyEquals(a.bottom, b.bottom);
+        if (a == null && b == null)
+            return true;
+        else if ((a == null && b != null) || (a != null && b == null))
+            return false;
+        else
+            return FloatUtils.fuzzyEquals(a.top, b.top)
+                && FloatUtils.fuzzyEquals(a.left, b.left)
+                && FloatUtils.fuzzyEquals(a.right, b.right)
+                && FloatUtils.fuzzyEquals(a.bottom, b.bottom);
     }
 }
--- a/mobile/android/base/gfx/SurfaceTextureLayer.java
+++ b/mobile/android/base/gfx/SurfaceTextureLayer.java
@@ -56,16 +56,18 @@ public class SurfaceTextureLayer extends
     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 float[] mTextureTransform = new float[16];
 
+    private Rect mPageRect;
+
     private boolean mInverted;
     private boolean mNewInverted;
     private boolean mBlend;
     private boolean mNewBlend;
 
     private static int mProgram;
     private static int mPositionHandle;
     private static int mTextureHandle;
@@ -115,16 +117,19 @@ public class SurfaceTextureLayer extends
                 1.0f, 1.0f, // top right
     };
 
     private SurfaceTextureLayer(int textureId) {
         mTextureId = textureId;
         mHaveFrame = true;
         mInverted = false;
 
+        // We have our own special shaders necessary for rendering the SurfaceTexture
+        this.mUsesDefaultProgram = 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);
@@ -142,21 +147,20 @@ public class SurfaceTextureLayer extends
     }
 
     // For SurfaceTexture.OnFrameAvailableListener
     public void onFrameAvailable(SurfaceTexture texture) {
         mHaveFrame = true;
         GeckoApp.mAppContext.requestRender();
     }
 
-    public void update(Rect position, float resolution, boolean inverted, boolean blend) {
-        beginTransaction(); // this is called on the Gecko thread
+    public void update(Rect rect, boolean inverted, boolean blend) {
+        beginTransaction();
 
-        setPosition(position);
-        setResolution(resolution);
+        setPosition(rect);
 
         mNewInverted = inverted;
         mNewBlend = blend;
 
         endTransaction();
     }
 
     @Override
@@ -234,17 +238,16 @@ public class SurfaceTextureLayer extends
         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;
 
-        // 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],
 
             rect.left/viewWidth, (bot+rect.height())/viewHeight, 0,
             textureCoords[2], textureCoords[3],
 
@@ -265,16 +268,17 @@ public class SurfaceTextureLayer extends
         GLES20.glUniformMatrix4fv(mProjectionMatrixHandle, 1, false, PROJECTION_MATRIX, 0);
 
         // Enable the arrays from which we get the vertex and texture coordinates
         GLES20.glEnableVertexAttribArray(mPositionHandle);
         GLES20.glEnableVertexAttribArray(mTextureHandle);
 
         GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
         GLES20.glUniform1i(mSampleHandle, 0);
+        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
         GLES20.glBindTexture(LOCAL_GL_TEXTURE_EXTERNAL_OES, mTextureId);
       
         mSurfaceTexture.updateTexImage();
         mSurfaceTexture.getTransformMatrix(mTextureTransform);
 
         GLES20.glUniformMatrix4fv(mTextureMatrixHandle, 1, false, mTextureTransform, 0);
 
         // Unbind any the current array buffer so we can use client side buffers
@@ -282,17 +286,17 @@ public class SurfaceTextureLayer extends
         
         // 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);
 
         // 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,
+        GLES20.glVertexAttribPointer(mTextureHandle, 2, GLES20.GL_FLOAT, false, 20,
                 coordBuffer);
 
         if (mBlend) {
             GLES20.glEnable(GLES20.GL_BLEND);
             GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
         }
         
         GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
--- a/mobile/android/base/gfx/TileLayer.java
+++ b/mobile/android/base/gfx/TileLayer.java
@@ -52,20 +52,21 @@ import java.nio.FloatBuffer;
 /**
  * Base class for tile layers, which encapsulate the logic needed to draw textured tiles in OpenGL
  * ES.
  */
 public abstract class TileLayer extends Layer {
     private static final String LOGTAG = "GeckoTileLayer";
 
     private final Rect mDirtyRect;
-    private final CairoImage mImage;
     private IntSize mSize;
     private int[] mTextureIDs;
 
+    protected final CairoImage mImage;
+
     public enum PaintMode { NORMAL, REPEAT, STRETCH };
     private PaintMode mPaintMode;
 
     public TileLayer(CairoImage image, PaintMode paintMode) {
         super(image.getSize());
 
         mPaintMode = paintMode;
         mImage = image;
--- a/mobile/android/base/ui/PanZoomController.java
+++ b/mobile/android/base/ui/PanZoomController.java
@@ -364,17 +364,16 @@ public class PanZoomController
             return false;
 
         case TOUCHING:
             if (panDistance(event) < PAN_THRESHOLD) {
                 return false;
             }
             cancelTouch();
             startPanning(event.getX(0), event.getY(0), event.getEventTime());
-            GeckoApp.mAppContext.hidePlugins(false /* don't hide layers */);
             GeckoApp.mFormAssistPopup.hide();
             track(event);
             return true;
 
         case PANNING_HOLD_LOCKED:
             GeckoApp.mFormAssistPopup.hide();
             mState = PanZoomState.PANNING_LOCKED;
             // fall through
@@ -559,18 +558,16 @@ public class PanZoomController
 
     /* Starts the fling or bounce animation. */
     private void startAnimationTimer(final AnimationRunnable runnable) {
         if (mAnimationTimer != null) {
             Log.e(LOGTAG, "Attempted to start a new fling without canceling the old one!");
             stopAnimationTimer();
         }
 
-        GeckoApp.mAppContext.hidePlugins(false /* don't hide layers */);
-
         mAnimationTimer = new Timer("Animation Timer");
         mAnimationRunnable = runnable;
         mAnimationTimer.scheduleAtFixedRate(new TimerTask() {
             @Override
             public void run() { mController.post(runnable); }
         }, 0, 1000L/60L);
     }
 
@@ -579,18 +576,16 @@ public class PanZoomController
         if (mAnimationTimer != null) {
             mAnimationTimer.cancel();
             mAnimationTimer = null;
         }
         if (mAnimationRunnable != null) {
             mAnimationRunnable.terminate();
             mAnimationRunnable = null;
         }
-
-        GeckoApp.mAppContext.showPlugins();
     }
 
     private float getVelocity() {
         float xvel = mX.getRealVelocity();
         float yvel = mY.getRealVelocity();
         return FloatMath.sqrt(xvel * xvel + yvel * yvel);
     }
 
@@ -756,17 +751,16 @@ public class PanZoomController
 
     private void finishAnimation() {
         checkMainThread();
 
         Log.d(LOGTAG, "Finishing animation at " + mController.getViewportMetrics());
         stopAnimationTimer();
 
         // Force a viewport synchronisation
-        GeckoApp.mAppContext.showPlugins();
         mController.setForceRedraw();
         mController.notifyLayerClientOfGeometryChange();
     }
 
     /* Returns the nearest viewport metrics with no overscroll visible. */
     private ViewportMetrics getValidViewportMetrics() {
         return getValidViewportMetrics(new ViewportMetrics(mController.getViewportMetrics()));
     }
@@ -840,17 +834,16 @@ public class PanZoomController
     public boolean onScaleBegin(SimpleScaleGestureDetector detector) {
         Log.d(LOGTAG, "onScaleBegin in " + mState);
 
         if (mState == PanZoomState.ANIMATED_ZOOM)
             return false;
 
         mState = PanZoomState.PINCHING;
         mLastZoomFocus = new PointF(detector.getFocusX(), detector.getFocusY());
-        GeckoApp.mAppContext.hidePlugins(false /* don't hide layers, only views */);
         GeckoApp.mFormAssistPopup.hide();
         cancelTouch();
 
         return true;
     }
 
     @Override
     public boolean onScale(SimpleScaleGestureDetector detector) {
@@ -908,17 +901,16 @@ public class PanZoomController
 
         if (mState == PanZoomState.ANIMATED_ZOOM)
             return;
 
         // switch back to the touching state
         startTouch(detector.getFocusX(), detector.getFocusY(), detector.getEventTime());
 
         // Force a viewport synchronisation
-        GeckoApp.mAppContext.showPlugins();
         mController.setForceRedraw();
         mController.notifyLayerClientOfGeometryChange();
     }
 
     public boolean getRedrawHint() {
         switch (mState) {
             case PINCHING:
             case ANIMATED_ZOOM:
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -148,17 +148,19 @@ var Strings = {};
   let [name, bundle] = aStringBundle;
   XPCOMUtils.defineLazyGetter(Strings, name, function() {
     return Services.strings.createBundle(bundle);
   });
 });
 
 var MetadataProvider = {
   getDrawMetadata: function getDrawMetadata() {
-    return JSON.stringify(BrowserApp.selectedTab.getViewport());
+    let viewport = BrowserApp.selectedTab.getViewport();
+    viewport.zoom = BrowserApp.selectedTab._drawZoom;
+    return JSON.stringify(viewport);
   },
 };
 
 var BrowserApp = {
   _tabs: [],
   _selectedTab: null,
 
   deck: null,
@@ -1902,17 +1904,17 @@ Tab.prototype = {
       height: gScreenHeight,
       cssWidth: gScreenWidth / this._zoom,
       cssHeight: gScreenHeight / this._zoom,
       pageWidth: gScreenWidth,
       pageHeight: gScreenHeight,
       // We make up matching css page dimensions
       cssPageWidth: gScreenWidth / this._zoom,
       cssPageHeight: gScreenHeight / this._zoom,
-      zoom: this._zoom
+      zoom: this._zoom,
     };
 
     // Set the viewport offset to current scroll offset
     viewport.cssX = this.browser.contentWindow.scrollX || 0;
     viewport.cssY = this.browser.contentWindow.scrollY || 0;
 
     // Transform coordinates based on zoom
     viewport.x = Math.round(viewport.cssX * viewport.zoom);
--- a/mozglue/android/APKOpen.cpp
+++ b/mozglue/android/APKOpen.cpp
@@ -345,16 +345,17 @@ SHELL_WRAPPER7(notifyGetSms, jint, jstri
 SHELL_WRAPPER3(notifyGetSmsFailed, jint, jint, jlong)
 SHELL_WRAPPER3(notifySmsDeleted, jboolean, jint, jlong)
 SHELL_WRAPPER3(notifySmsDeleteFailed, jint, jint, jlong)
 SHELL_WRAPPER2(notifyNoMessageInList, jint, jlong)
 SHELL_WRAPPER8(notifyListCreated, jint, jint, jstring, jstring, jstring, jlong, jint, jlong)
 SHELL_WRAPPER7(notifyGotNextMessage, jint, jstring, jstring, jstring, jlong, jint, jlong)
 SHELL_WRAPPER3(notifyReadingMessageListFailed, jint, jint, jlong)
 SHELL_WRAPPER2(notifyFilePickerResult, jstring, jlong)
+SHELL_WRAPPER1_WITH_RETURN(getSurfaceBits, jobject, jobject)
 
 static void * xul_handle = NULL;
 static void * sqlite_handle = NULL;
 static void * nss_handle = NULL;
 static void * nspr_handle = NULL;
 static void * plc_handle = NULL;
 static bool simple_linker_initialized = false;
 
@@ -761,16 +762,17 @@ loadGeckoLibs(const char *apkName)
   GETFUNC(notifyGetSmsFailed);
   GETFUNC(notifySmsDeleted);
   GETFUNC(notifySmsDeleteFailed);
   GETFUNC(notifyNoMessageInList);
   GETFUNC(notifyListCreated);
   GETFUNC(notifyGotNextMessage);
   GETFUNC(notifyReadingMessageListFailed);
   GETFUNC(notifyFilePickerResult);
+  GETFUNC(getSurfaceBits);
 #undef GETFUNC
   sStartupTimeline = (uint64_t *)__wrap_dlsym(xul_handle, "_ZN7mozilla15StartupTimeline16sStartupTimelineE");
   gettimeofday(&t1, 0);
   struct rusage usage2;
   getrusage(RUSAGE_THREAD, &usage2);
   __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Loaded libs in %ldms total, %ldms user, %ldms system, %ld faults",
                       (t1.tv_sec - t0.tv_sec)*1000 + (t1.tv_usec - t0.tv_usec)/1000, 
                       (usage2.ru_utime.tv_sec - usage1.ru_utime.tv_sec)*1000 + (usage2.ru_utime.tv_usec - usage1.ru_utime.tv_usec)/1000,
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -99,21 +99,24 @@ AndroidBridge::ConstructBridge(JNIEnv *j
 
 bool
 AndroidBridge::Init(JNIEnv *jEnv,
                     jclass jGeckoAppShellClass)
 {
     ALOG_BRIDGE("AndroidBridge::Init");
     jEnv->GetJavaVM(&mJavaVM);
 
+    AutoLocalJNIFrame frame(jEnv);
+
     mJNIEnv = nsnull;
     mThread = nsnull;
     mOpenedGraphicsLibraries = false;
     mHasNativeBitmapAccess = false;
     mHasNativeWindowAccess = false;
+    mHasNativeWindowFallback = false;
 
     mGeckoAppShellClass = (jclass) jEnv->NewGlobalRef(jGeckoAppShellClass);
 
     jNotifyIME = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyIME", "(II)V");
     jNotifyIMEEnabled = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyIMEEnabled", "(ILjava/lang/String;Ljava/lang/String;Z)V");
     jNotifyIMEChange = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyIMEChange", "(Ljava/lang/String;III)V");
     jNotifyScreenShot = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyScreenShot", "(Ljava/nio/ByteBuffer;IIIIII)V");
     jAcknowledgeEventSync = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "acknowledgeEventSync", "()V");
@@ -195,21 +198,40 @@ AndroidBridge::Init(JNIEnv *jEnv,
     jEGL10Class = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("javax/microedition/khronos/egl/EGL10"));
     jEGLSurfaceImplClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("com/google/android/gles_jni/EGLSurfaceImpl"));
     jEGLContextImplClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("com/google/android/gles_jni/EGLContextImpl"));
     jEGLConfigImplClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("com/google/android/gles_jni/EGLConfigImpl"));
     jEGLDisplayImplClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("com/google/android/gles_jni/EGLDisplayImpl"));
 
     jStringClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("java/lang/String"));
 
+    jSurfaceClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("android/view/Surface"));
+
+    PRInt32 apiVersion = 0;
+    if (!GetStaticIntField("android/os/Build$VERSION", "SDK_INT", &apiVersion, jEnv))
+        ALOG_BRIDGE("Failed to find API version");
+
+    if (apiVersion <= 8 /* Froyo */)
+        jSurfacePointerField = jEnv->GetFieldID(jSurfaceClass, "mSurface", "I");
+    else /* not Froyo */
+        jSurfacePointerField = jEnv->GetFieldID(jSurfaceClass, "mNativeSurface", "I");
+
 #ifdef MOZ_JAVA_COMPOSITOR
+    jAddPluginView = jEnv->GetStaticMethodID(jGeckoAppShellClass, "addPluginView", "(Landroid/view/View;IIII)V");
+    jCreateSurface = jEnv->GetStaticMethodID(jGeckoAppShellClass, "createSurface", "()Landroid/view/Surface;");
+    jShowSurface = jEnv->GetStaticMethodID(jGeckoAppShellClass, "showSurface", "(Landroid/view/Surface;IIIIZZ)V");
+    jHideSurface = jEnv->GetStaticMethodID(jGeckoAppShellClass, "hideSurface", "(Landroid/view/Surface;)V");
+    jDestroySurface = jEnv->GetStaticMethodID(jGeckoAppShellClass, "destroySurface", "(Landroid/view/Surface;)V");
+
     jLayerView = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("org/mozilla/gecko/gfx/LayerView"));
 
     AndroidGLController::Init(jEnv);
     AndroidEGLObject::Init(jEnv);
+#else
+    jAddPluginView = jEnv->GetStaticMethodID(jGeckoAppShellClass, "addPluginView", "(Landroid/view/View;DDDD)V"); 
 #endif
 
     InitAndroidJavaWrappers(jEnv);
 
     // jEnv should NOT be cached here by anything -- the jEnv here
     // is not valid for the real gecko main thread, which is set
     // at SetMainThread time.
 
@@ -1118,45 +1140,50 @@ AndroidBridge::RegisterCompositor()
 EGLSurface
 AndroidBridge::ProvideEGLSurface()
 {
     sController.WaitForValidSurface();
     return sController.ProvideEGLSurface();
 }
 
 bool
-AndroidBridge::GetStaticIntField(const char *className, const char *fieldName, PRInt32* aInt)
+AndroidBridge::GetStaticIntField(const char *className, const char *fieldName, PRInt32* aInt, JNIEnv* env /* = nsnull */)
 {
     ALOG_BRIDGE("AndroidBridge::GetStaticIntField %s", fieldName);
-    JNIEnv *env = GetJNIEnv();
-    if (!env)
-        return false;
+
+    if (!env) {
+        env = GetJNIEnv();
+        if (!env)
+            return false;
+    }
 
     AutoLocalJNIFrame jniFrame(env, 3);
     jclass cls = env->FindClass(className);
     if (!cls)
         return false;
 
     jfieldID field = env->GetStaticFieldID(cls, fieldName, "I");
     if (!field)
         return false;
 
     *aInt = static_cast<PRInt32>(env->GetStaticIntField(cls, field));
 
     return true;
 }
 
 bool
-AndroidBridge::GetStaticStringField(const char *className, const char *fieldName, nsAString &result)
+AndroidBridge::GetStaticStringField(const char *className, const char *fieldName, nsAString &result, JNIEnv* env /* = nsnull */)
 {
     ALOG_BRIDGE("AndroidBridge::GetStaticIntField %s", fieldName);
 
-    JNIEnv *env = GetJNIEnv();
-    if (!env)
-        return false;
+    if (!env) {
+        env = GetJNIEnv();
+        if (!env)
+            return false;
+    }
 
     AutoLocalJNIFrame jniFrame(env, 3);
     jclass cls = env->FindClass(className);
     if (!cls)
         return false;
 
     jfieldID field = env->GetStaticFieldID(cls, fieldName, "Ljava/lang/String;");
     if (!field)
@@ -1245,24 +1272,33 @@ AndroidBridge::ExecuteNextRunnable(JNIEn
 {
     if (mRunnableQueue.Count() > 0) {
         nsIRunnable* r = mRunnableQueue[0];
         r->Run();
         mRunnableQueue.RemoveObjectAt(0);
     }
 }
 
+void*
+AndroidBridge::GetNativeSurface(JNIEnv* env, jobject surface) {
+    if (!env || !mHasNativeWindowFallback)
+        return nsnull;
+
+    return (void*)env->GetIntField(surface, jSurfacePointerField);
+}
+
 void
 AndroidBridge::OpenGraphicsLibraries()
 {
     if (!mOpenedGraphicsLibraries) {
         // Try to dlopen libjnigraphics.so for direct bitmap access on
         // Android 2.2+ (API level 8)
         mOpenedGraphicsLibraries = true;
         mHasNativeWindowAccess = false;
+        mHasNativeWindowFallback = false;
         mHasNativeBitmapAccess = false;
 
         void *handle = dlopen("libjnigraphics.so", RTLD_LAZY | RTLD_LOCAL);
         if (handle) {
             AndroidBitmap_getInfo = (int (*)(JNIEnv *, jobject, void *))dlsym(handle, "AndroidBitmap_getInfo");
             AndroidBitmap_lockPixels = (int (*)(JNIEnv *, jobject, void **))dlsym(handle, "AndroidBitmap_lockPixels");
             AndroidBitmap_unlockPixels = (int (*)(JNIEnv *, jobject))dlsym(handle, "AndroidBitmap_unlockPixels");
 
@@ -1280,16 +1316,34 @@ AndroidBridge::OpenGraphicsLibraries()
             ANativeWindow_setBuffersGeometry = (int (*)(void*, int, int, int)) dlsym(handle, "ANativeWindow_setBuffersGeometry");
             ANativeWindow_lock = (int (*)(void*, void*, void*)) dlsym(handle, "ANativeWindow_lock");
             ANativeWindow_unlockAndPost = (int (*)(void*))dlsym(handle, "ANativeWindow_unlockAndPost");
 
             mHasNativeWindowAccess = ANativeWindow_fromSurface && ANativeWindow_release && ANativeWindow_lock && ANativeWindow_unlockAndPost;
 
             ALOG_BRIDGE("Successfully opened libandroid.so, have native window access? %d", mHasNativeWindowAccess);
         }
+
+        if (mHasNativeWindowAccess)
+            return;
+
+        // Look up Surface functions, used for native window (surface) fallback
+        handle = dlopen("libsurfaceflinger_client.so", RTLD_LAZY);
+        if (handle) {
+            Surface_lock = (int (*)(void*, void*, void*, bool))dlsym(handle, "_ZN7android7Surface4lockEPNS0_11SurfaceInfoEPNS_6RegionEb");
+            Surface_unlockAndPost = (int (*)(void*))dlsym(handle, "_ZN7android7Surface13unlockAndPostEv");
+
+            handle = dlopen("libui.so", RTLD_LAZY);
+            if (handle) {
+                Region_constructor = (void (*)(void*))dlsym(handle, "_ZN7android6RegionC1Ev");
+                Region_set = (void (*)(void*, void*))dlsym(handle, "_ZN7android6Region3setERKNS_4RectE");
+
+                mHasNativeWindowFallback = Surface_lock && Surface_unlockAndPost && Region_constructor && Region_set;
+            }
+        }
     }
 }
 
 void
 AndroidBridge::FireAndWaitForTracerEvent() {
     JNIEnv *env = GetJNIEnv();
     if (!env)
         return;
@@ -1768,45 +1822,53 @@ AndroidBridge::UnlockBitmap(jobject bitm
 }
 
 
 bool
 AndroidBridge::HasNativeWindowAccess()
 {
     OpenGraphicsLibraries();
 
-    return mHasNativeWindowAccess;
+    // We have a fallback hack in place, so return true if that will work as well
+    return mHasNativeWindowAccess || mHasNativeWindowFallback;
 }
 
 void*
-AndroidBridge::AcquireNativeWindow(jobject surface)
+AndroidBridge::AcquireNativeWindow(JNIEnv* aEnv, jobject aSurface)
 {
-    if (!HasNativeWindowAccess())
-        return nsnull;
+    OpenGraphicsLibraries();
 
-    JNIEnv *env = GetJNIEnv();
-    if (!env)
+    if (mHasNativeWindowAccess)
+        return ANativeWindow_fromSurface(aEnv, aSurface);
+    else if (mHasNativeWindowFallback)
+        return GetNativeSurface(aEnv, aSurface);
+    else
         return nsnull;
-
-    return ANativeWindow_fromSurface(env, surface);
 }
 
 void
 AndroidBridge::ReleaseNativeWindow(void *window)
 {
     if (!window)
         return;
 
-    ANativeWindow_release(window);
+    if (mHasNativeWindowAccess)
+        ANativeWindow_release(window);
+
+    // XXX: we don't ref the pointer we get from the fallback (GetNativeSurface), so we
+    // have nothing to do here. We should probably ref it.
 }
 
 bool
 AndroidBridge::SetNativeWindowFormat(void *window, int width, int height, int format)
 {
-    return ANativeWindow_setBuffersGeometry(window, width, height, format) == 0;
+    if (mHasNativeWindowAccess)
+        return ANativeWindow_setBuffersGeometry(window, width, height, format) == 0;
+    else
+        return false; //unimplemented in fallback
 }
 
 bool
 AndroidBridge::LockWindow(void *window, unsigned char **bits, int *width, int *height, int *format, int *stride)
 {
     /* Copied from native_window.h in Android NDK (platform-9) */
     typedef struct ANativeWindow_Buffer {
         // The number of pixels that are show horizontally.
@@ -1824,42 +1886,77 @@ AndroidBridge::LockWindow(void *window, 
 
         // The actual bits.
         void* bits;
 
         // Do not touch.
         uint32_t reserved[6];
     } ANativeWindow_Buffer;
 
+    // Very similar to the above, but the 'usage' field is included. We use this
+    // in the fallback case when NDK support is not available
+    typedef struct SurfaceInfo {
+        uint32_t    w;
+        uint32_t    h;
+        uint32_t    s;
+        uint32_t    usage;
+        uint32_t    format;
+        unsigned char* bits;
+        uint32_t    reserved[2];
+    };
+
     int err;
-    ANativeWindow_Buffer buffer;
-
     *bits = NULL;
     *width = *height = *format = 0;
-    if ((err = ANativeWindow_lock(window, (void*)&buffer, NULL)) != 0) {
-        ALOG_BRIDGE("ANativeWindow_lock failed! (error %d)", err);
-        return false;
-    }
+    
+    if (mHasNativeWindowAccess) {
+        ANativeWindow_Buffer buffer;
+
+        if ((err = ANativeWindow_lock(window, (void*)&buffer, NULL)) != 0) {
+            ALOG_BRIDGE("ANativeWindow_lock failed! (error %d)", err);
+            return false;
+        }
 
-    *bits = (unsigned char*)buffer.bits;
-    *width = buffer.width;
-    *height = buffer.height;
-    *format = buffer.format;
-    *stride = buffer.stride;
+        *bits = (unsigned char*)buffer.bits;
+        *width = buffer.width;
+        *height = buffer.height;
+        *format = buffer.format;
+        *stride = buffer.stride;
+    } else if (mHasNativeWindowFallback) {
+        SurfaceInfo info;
+
+        if ((err = Surface_lock(window, &info, NULL, true)) != 0) {
+            ALOG_BRIDGE("Surface_lock failed! (error %d)", err);
+            return false;
+        }
+
+        *bits = info.bits;
+        *width = info.w;
+        *height = info.h;
+        *format = info.format;
+        *stride = info.s;
+    } else return false;
 
     return true;
 }
 
 bool
 AndroidBridge::UnlockWindow(void* window)
 {
     int err;
-    if ((err = ANativeWindow_unlockAndPost(window)) != 0) {
+
+    if (!HasNativeWindowAccess())
+        return false;
+
+    if (mHasNativeWindowAccess && (err = ANativeWindow_unlockAndPost(window)) != 0) {
         ALOG_BRIDGE("ANativeWindow_unlockAndPost failed! (error %d)", err);
         return false;
+    } else if (mHasNativeWindowFallback && (err = Surface_unlockAndPost(window)) != 0) {
+        ALOG_BRIDGE("Surface_unlockAndPost failed! (error %d)", err);
+        return false;
     }
 
     return true;
 }
 
 bool
 AndroidBridge::IsTablet()
 {
@@ -1989,89 +2086,73 @@ extern "C" {
 }
 
 jobject
 AndroidBridge::CreateSurface()
 {
 #ifndef MOZ_JAVA_COMPOSITOR
   return NULL;
 #else
-  AutoLocalJNIFrame frame(1);
-
-  JNIEnv* env = GetJNIForThread();
-  jclass cls = env->FindClass("org/mozilla/gecko/GeckoAppShell");
+  JNIEnv* env = GetJNIEnv();
+  if (!env)
+    return nsnull;
 
-  jmethodID method = env->GetStaticMethodID(cls,
-                                            "createSurface",
-                                            "()Landroid/view/Surface;");
+  AutoLocalJNIFrame frame(env);
 
-  jobject surface = env->CallStaticObjectMethod(cls, method);
+  jobject surface = env->CallStaticObjectMethod(mGeckoAppShellClass, jCreateSurface);
   if (surface)
-    surface = env->NewGlobalRef(surface);
+   surface =  env->NewGlobalRef(surface);
   
   return surface;
 #endif
 }
 
 void
 AndroidBridge::DestroySurface(jobject surface)
 {
 #ifdef MOZ_JAVA_COMPOSITOR
-  AutoLocalJNIFrame frame(1);
-
-  JNIEnv* env = GetJNIForThread();
-  jclass cls = env->FindClass("org/mozilla/gecko/GeckoAppShell");
+  JNIEnv* env = GetJNIEnv();
+  if (!env)
+    return;
 
-  jmethodID method = env->GetStaticMethodID(cls,
-                                            "destroySurface",
-                                            "(Landroid/view/Surface;)V");
-  env->CallStaticVoidMethod(cls, method, surface);
+  AutoLocalJNIFrame frame(env);
+
+  env->CallStaticVoidMethod(mGeckoAppShellClass, jDestroySurface, surface);
   env->DeleteGlobalRef(surface);
 #endif
 }
 
 void
 AndroidBridge::ShowSurface(jobject surface, const gfxRect& aRect, bool aInverted, bool aBlend)
 {
 #ifdef MOZ_JAVA_COMPOSITOR
-  AutoLocalJNIFrame frame;
-
-  JNIEnv* env = GetJNIForThread();
-  jclass cls = env->FindClass("org/mozilla/gecko/GeckoAppShell");
-
-  nsAutoString metadata;
-  nsCOMPtr<nsIAndroidDrawMetadataProvider> metadataProvider = GetDrawMetadataProvider();
-  metadataProvider->GetDrawMetadata(metadata);
+    JNIEnv* env = GetJNIEnv();
+    if (!env)
+        return;
 
-  jstring jMetadata = env->NewString(nsPromiseFlatString(metadata).get(), metadata.Length());
+    AutoLocalJNIFrame frame(env);
 
-  jmethodID method = env->GetStaticMethodID(cls,
-                                            "showSurface",
-                                            "(Landroid/view/Surface;IIIIZZLjava/lang/String;)V");
-
-  env->CallStaticVoidMethod(cls, method, surface,
-                            (int)aRect.x, (int)aRect.y,
-                            (int)aRect.width, (int)aRect.height,
-                            aInverted, aBlend, jMetadata);
+    env->CallStaticVoidMethod(mGeckoAppShellClass, jShowSurface, surface,
+                             (int)aRect.x, (int)aRect.y,
+                             (int)aRect.width, (int)aRect.height,
+                             aInverted, aBlend);
 #endif
 }
 
 void
 AndroidBridge::HideSurface(jobject surface)
 {
 #ifdef MOZ_JAVA_COMPOSITOR
-  AutoLocalJNIFrame frame(1);
-
-  JNIEnv* env = GetJNIForThread();
-  jclass cls = env->FindClass("org/mozilla/gecko/GeckoAppShell");
+    JNIEnv* env = GetJNIEnv();
+    if (!env)
+        return;
 
-  jmethodID method = env->GetStaticMethodID(cls,
-                                            "hideSurface",
-                                            "(Landroid/view/Surface;)V");
-  env->CallStaticVoidMethod(cls, method, surface);
+    AutoLocalJNIFrame frame(env);
+
+    env->CallStaticVoidMethod(mGeckoAppShellClass, jHideSurface, surface);
 #endif
 }
 
 void
 AndroidBridge::GetScreenOrientation(dom::ScreenOrientationWrapper& aOrientation)
 {
     ALOG_BRIDGE("AndroidBridge::GetScreenOrientation");
     aOrientation.orientation = static_cast<dom::ScreenOrientation>(mJNIEnv->CallStaticShortMethod(mGeckoAppShellClass, jGetScreenOrientation));
@@ -2115,24 +2196,42 @@ NS_IMETHODIMP nsAndroidBridge::GetBrowse
 NS_IMETHODIMP nsAndroidBridge::SetBrowserApp(nsIAndroidBrowserApp *aBrowserApp)
 {
     if (nsAppShell::gAppShell)
         nsAppShell::gAppShell->SetBrowserApp(aBrowserApp);
     return NS_OK;
 }
 
 void
-AndroidBridge::RemovePluginView(void* surface) {
-  JNIEnv *env = AndroidBridge::GetJNIEnv();
+AndroidBridge::AddPluginView(jobject view, const gfxRect& rect) {
+    JNIEnv *env = AndroidBridge::GetJNIEnv();
+    if (!env)
+        return;
+
+    AndroidBridge::AutoLocalJNIFrame frame(env);
+
+#if MOZ_JAVA_COMPOSITOR
+    env->CallStaticVoidMethod(sBridge->mGeckoAppShellClass,
+                              sBridge->jAddPluginView, view,
+                              (int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
+#else
+    env->CallStaticVoidMethod(sBridge->mGeckoAppShellClass,
+                              sBridge->jAddPluginView, view,
+                              rect.x, rect.y, rect.width, rect.height);
+#endif
+}
+
+void
+AndroidBridge::RemovePluginView(jobject view) {
+  JNIEnv *env = GetJNIEnv();
   if (!env)
     return;
 
-  AndroidBridge::AutoLocalJNIFrame frame(env, 1);
-  env->CallStaticVoidMethod(sBridge->mGeckoAppShellClass,
-                            sBridge->jRemovePluginView, surface);
+  AndroidBridge::AutoLocalJNIFrame frame(env);
+  env->CallStaticVoidMethod(mGeckoAppShellClass, jRemovePluginView, view);
 }
 
 extern "C"
 __attribute__ ((visibility("default")))
 jobject JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_allocateDirectBuffer(JNIEnv *jenv, jclass, jlong size);
 
 
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -176,18 +176,16 @@ public:
     /* These are all implemented in Java */
     static void NotifyIME(int aType, int aState);
 
     static void NotifyIMEEnabled(int aState, const nsAString& aTypeHint,
                                  const nsAString& aActionHint);
 
     static void NotifyIMEChange(const PRUnichar *aText, PRUint32 aTextLen, int aStart, int aEnd, int aNewEnd);
 
-    static void RemovePluginView(void* surface);
-
     /* These are defined in mobile/android/base/GeckoAppShell.java */
     enum {
         SCREENSHOT_THUMBNAIL = 0,
         SCREENSHOT_WHOLE_PAGE = 1,
         SCREENSHOT_UPDATE = 2
     };
 
     nsresult TakeScreenshot(nsIDOMWindow *window, PRInt32 srcX, PRInt32 srcY, PRInt32 srcW, PRInt32 srcH, PRInt32 dstW, PRInt32 dstH, PRInt32 tabId, float scale, PRInt32 token);
@@ -313,25 +311,32 @@ public:
         // any local refs that you need to keep around in global refs!
         void Purge() {
             if (mJNIEnv) {
                 mJNIEnv->PopLocalFrame(NULL);
                 Push();
             }
         }
 
+        bool CheckForException() {
+            jthrowable exception = mJNIEnv->ExceptionOccurred();
+            if (exception) {
+                mJNIEnv->ExceptionDescribe();
+                mJNIEnv->ExceptionClear();
+                return true;
+            }
+
+            return false;
+        }
+
         ~AutoLocalJNIFrame() {
             if (!mJNIEnv)
                 return;
 
-            jthrowable exception = mJNIEnv->ExceptionOccurred();
-            if (exception) {
-                mJNIEnv->ExceptionDescribe();
-                mJNIEnv->ExceptionClear();
-            }
+            CheckForException();
 
             mJNIEnv->PopLocalFrame(NULL);
         }
 
     private:
         void Push() {
             if (!mJNIEnv)
                 return;
@@ -348,19 +353,19 @@ public:
 
     /* See GLHelpers.java as to why this is needed */
     void *CallEglCreateWindowSurface(void *dpy, void *config, AndroidGeckoSurfaceView& surfaceView);
 
     // Switch Java to composite with the Gecko Compositor thread
     void RegisterCompositor();
     EGLSurface ProvideEGLSurface();
 
-    bool GetStaticStringField(const char *classID, const char *field, nsAString &result);
+    bool GetStaticStringField(const char *classID, const char *field, nsAString &result, JNIEnv* env = nsnull);
 
-    bool GetStaticIntField(const char *className, const char *fieldName, PRInt32* aInt);
+    bool GetStaticIntField(const char *className, const char *fieldName, PRInt32* aInt, JNIEnv* env = nsnull);
 
     void SetKeepScreenOn(bool on);
 
     void ScanMedia(const nsAString& aFile, const nsACString& aMimeType);
 
     void CreateShortcut(const nsAString& aTitle, const nsAString& aURI, const nsAString& aIconData, const nsAString& aIntent);
 
     // These next four functions are for native Bitmap access in Android 2.2+
@@ -380,17 +385,17 @@ public:
     enum {
         WINDOW_FORMAT_RGBA_8888          = 1,
         WINDOW_FORMAT_RGBX_8888          = 2,
         WINDOW_FORMAT_RGB_565            = 4,
     };
 
     bool HasNativeWindowAccess();
 
-    void *AcquireNativeWindow(jobject surface);
+    void *AcquireNativeWindow(JNIEnv* aEnv, jobject aSurface);
     void ReleaseNativeWindow(void *window);
     bool SetNativeWindowFormat(void *window, int width, int height, int format);
 
     bool LockWindow(void *window, unsigned char **bits, int *width, int *height, int *format, int *stride);
     bool UnlockWindow(void *window);
     
     void HandleGeckoMessage(const nsAString& message, nsAString &aRet);
 
@@ -428,16 +433,19 @@ public:
     void SyncViewportInfo(const nsIntRect& aDisplayPort, float aDisplayResolution, bool aLayersUpdated,
                           nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY);
 
     jobject CreateSurface();
     void DestroySurface(jobject surface);
     void ShowSurface(jobject surface, const gfxRect& aRect, bool aInverted, bool aBlend);
     void HideSurface(jobject surface);
 
+    void AddPluginView(jobject view, const gfxRect& rect);
+    void RemovePluginView(jobject view);
+
     // This method doesn't take a ScreenOrientation because it's an enum and
     // that would require including the header which requires include IPC
     // headers which requires including basictypes.h which requires a lot of
     // changes...
     void GetScreenOrientation(dom::ScreenOrientationWrapper& aOrientation);
     void EnableScreenOrientationNotifications();
     void DisableScreenOrientationNotifications();
     void LockScreenOrientation(const dom::ScreenOrientationWrapper& aOrientation);
@@ -463,19 +471,21 @@ protected:
 
     AndroidBridge();
     ~AndroidBridge();
 
     bool Init(JNIEnv *jEnv, jclass jGeckoApp);
 
     bool mOpenedGraphicsLibraries;
     void OpenGraphicsLibraries();
+    void* GetNativeSurface(JNIEnv* env, jobject surface);
 
     bool mHasNativeBitmapAccess;
     bool mHasNativeWindowAccess;
+    bool mHasNativeWindowFallback;
 
     nsCOMArray<nsIRunnable> mRunnableQueue;
 
     // other things
     jmethodID jNotifyIME;
     jmethodID jNotifyIMEEnabled;
     jmethodID jNotifyIMEChange;
     jmethodID jNotifyScreenShot;
@@ -527,17 +537,23 @@ protected:
     jmethodID jCloseCamera;
     jmethodID jIsTablet;
     jmethodID jEnableBatteryNotifications;
     jmethodID jDisableBatteryNotifications;
     jmethodID jGetCurrentBatteryInformation;
     jmethodID jHandleGeckoMessage;
     jmethodID jCheckUriVisited;
     jmethodID jMarkUriVisited;
+    jmethodID jAddPluginView;
     jmethodID jRemovePluginView;
+    jmethodID jCreateSurface;
+    jmethodID jShowSurface;
+    jmethodID jHideSurface;
+    jmethodID jDestroySurface;
+
     jmethodID jNotifyPaintedRect;
 
     jmethodID jNumberOfMessages;
     jmethodID jSendMessage;
     jmethodID jSaveSentMessage;
     jmethodID jGetMessage;
     jmethodID jDeleteMessage;
     jmethodID jCreateMessageList;
@@ -549,16 +565,20 @@ protected:
     jmethodID jDisableNetworkNotifications;
 
     jmethodID jGetScreenOrientation;
     jmethodID jEnableScreenOrientationNotifications;
     jmethodID jDisableScreenOrientationNotifications;
     jmethodID jLockScreenOrientation;
     jmethodID jUnlockScreenOrientation;
 
+    // For native surface stuff
+    jclass jSurfaceClass;
+    jfieldID jSurfacePointerField;
+
     // stuff we need for CallEglCreateWindowSurface
     jclass jEGLSurfaceImplClass;
     jclass jEGLContextImplClass;
     jclass jEGLConfigImplClass;
     jclass jEGLDisplayImplClass;
     jclass jEGLContextClass;
     jclass jEGL10Class;
 
@@ -574,16 +594,21 @@ protected:
     int (* AndroidBitmap_unlockPixels)(JNIEnv *env, jobject bitmap);
 
     void* (*ANativeWindow_fromSurface)(JNIEnv *env, jobject surface);
     void (*ANativeWindow_release)(void *window);
     int (*ANativeWindow_setBuffersGeometry)(void *window, int width, int height, int format);
 
     int (* ANativeWindow_lock)(void *window, void *outBuffer, void *inOutDirtyBounds);
     int (* ANativeWindow_unlockAndPost)(void *window);
+
+    int (* Surface_lock)(void* surface, void* surfaceInfo, void* region, bool block);
+    int (* Surface_unlockAndPost)(void* surface);
+    void (* Region_constructor)(void* region);
+    void (* Region_set)(void* region, void* rect);
 };
 
 }
 
 #define NS_ANDROIDBRIDGE_CID \
 { 0x0FE2321D, 0xEBD9, 0x467D, \
     { 0xA7, 0x43, 0x03, 0xA6, 0x8D, 0x40, 0x59, 0x9E } }
 
--- a/widget/android/AndroidJNI.cpp
+++ b/widget/android/AndroidJNI.cpp
@@ -41,16 +41,17 @@
 
 #include "AndroidBridge.h"
 #include "AndroidGraphicBuffer.h"
 
 #include <jni.h>
 #include <pthread.h>
 #include <dlfcn.h>
 #include <stdio.h>
+#include <unistd.h>
 
 #include "nsAppShell.h"
 #include "nsWindow.h"
 #include <android/log.h>
 #include "nsIObserverService.h"
 #include "mozilla/Services.h"
 #include "nsINetworkLinkService.h"
 
@@ -892,10 +893,100 @@ Java_org_mozilla_gecko_GeckoAppShell_not
     };
     nsString path = nsJNIString(filePath, jenv);
     
     nsCOMPtr<nsIRunnable> runnable =
         new NotifyFilePickerResultRunnable(path, (long)callback);
     NS_DispatchToMainThread(runnable);
 }
 
+static int
+NextPowerOfTwo(int value) {
+    // code taken from http://acius2.blogspot.com/2007/11/calculating-next-power-of-2.html
+    if (0 == value--) {
+        return 1;
+    }
+    value = (value >> 1) | value;
+    value = (value >> 2) | value;
+    value = (value >> 4) | value;
+    value = (value >> 8) | value;
+    value = (value >> 16) | value;
+    return value + 1;
+}
+
+NS_EXPORT jobject JNICALL
+Java_org_mozilla_gecko_GeckoAppShell_getSurfaceBits(JNIEnv* jenv, jclass, jobject surface)
+{
+    static jclass jSurfaceBitsClass = nsnull;
+    static jmethodID jSurfaceBitsCtor = 0;
+    static jfieldID jSurfaceBitsWidth, jSurfaceBitsHeight, jSurfaceBitsFormat, jSurfaceBitsBuffer;
+
+    jobject surfaceBits = nsnull;
+    unsigned char* bitsCopy = nsnull;
+    int dstWidth, dstHeight, dstSize;
+
+    void* window = AndroidBridge::Bridge()->AcquireNativeWindow(jenv, surface);
+    if (!window)
+        return nsnull;
+
+    unsigned char* bits;
+    int srcWidth, srcHeight, format, srcStride;
+
+    // So we lock/unlock once here in order to get whatever is currently the front buffer. It sucks.
+    while (!AndroidBridge::Bridge()->LockWindow(window, &bits, &srcWidth, &srcHeight, &format, &srcStride)) {
+        usleep(1000);
+    }
+    AndroidBridge::Bridge()->UnlockWindow(window);
+
+    // This is lock will result in the front buffer, since the last unlock rotated it to the back. Probably.
+    while (!AndroidBridge::Bridge()->LockWindow(window, &bits, &srcWidth, &srcHeight, &format, &srcStride)) {
+        usleep(1000);
+    }
+
+    // These are from android.graphics.PixelFormat
+    int bpp;
+    switch (format) {
+    case 1: // RGBA_8888
+        bpp = 4;
+        break;
+    case 4: // RGB_565
+        bpp = 2;
+        break;
+    default:
+        goto cleanup;
+    }
+
+    dstWidth = NextPowerOfTwo(srcWidth);
+    dstHeight = NextPowerOfTwo(srcHeight);
+    dstSize = dstWidth * dstHeight * bpp;
+
+    bitsCopy = (unsigned char*)malloc(dstSize);
+    bzero(bitsCopy, dstSize);
+    for (int i = 0; i < srcHeight; i++) {
+        memcpy(bitsCopy + ((dstHeight - i - 1) * dstWidth * bpp), bits + (i * srcStride * bpp), srcStride * bpp);
+    }
+    
+    if (!jSurfaceBitsClass) {
+        jSurfaceBitsClass = (jclass)jenv->NewGlobalRef(jenv->FindClass("org/mozilla/gecko/SurfaceBits"));
+        jSurfaceBitsCtor = jenv->GetMethodID(jSurfaceBitsClass, "<init>", "()V");
+
+        jSurfaceBitsWidth = jenv->GetFieldID(jSurfaceBitsClass, "width", "I");
+        jSurfaceBitsHeight = jenv->GetFieldID(jSurfaceBitsClass, "height", "I");
+        jSurfaceBitsFormat = jenv->GetFieldID(jSurfaceBitsClass, "format", "I");
+        jSurfaceBitsBuffer = jenv->GetFieldID(jSurfaceBitsClass, "buffer", "Ljava/nio/ByteBuffer;");
+    }
+
+    surfaceBits = jenv->NewObject(jSurfaceBitsClass, jSurfaceBitsCtor);
+    jenv->SetIntField(surfaceBits, jSurfaceBitsWidth, dstWidth);
+    jenv->SetIntField(surfaceBits, jSurfaceBitsHeight, dstHeight);
+    jenv->SetIntField(surfaceBits, jSurfaceBitsFormat, format);
+    jenv->SetObjectField(surfaceBits, jSurfaceBitsBuffer, jenv->NewDirectByteBuffer(bitsCopy, dstSize));
+
+cleanup:
+    AndroidBridge::Bridge()->UnlockWindow(window);
+    AndroidBridge::Bridge()->ReleaseNativeWindow(window);
+
+    return surfaceBits;
+}
+
+
 #endif
 }
--- a/widget/android/AndroidMediaLayer.cpp
+++ b/widget/android/AndroidMediaLayer.cpp
@@ -44,79 +44,85 @@
 
 namespace mozilla {
 
 AndroidMediaLayer::AndroidMediaLayer()
   : mInverted(false), mVisible(true) {
 }
 
 AndroidMediaLayer::~AndroidMediaLayer() {
-  if (mContentData.window) {
+  if (mContentData.window && AndroidBridge::Bridge()) {
     AndroidBridge::Bridge()->ReleaseNativeWindow(mContentData.window);
     mContentData.window = NULL;
   }
 
-  if (mContentData.surface) {
+  if (mContentData.surface && AndroidBridge::Bridge()) {
     AndroidBridge::Bridge()->DestroySurface(mContentData.surface);
     mContentData.surface = NULL;
   }
 
   std::map<void*, SurfaceData*>::iterator it;
 
   for (it = mVideoSurfaces.begin(); it != mVideoSurfaces.end(); it++) {
     SurfaceData* data = it->second;
 
-    AndroidBridge::Bridge()->ReleaseNativeWindow(data->window);
-    AndroidBridge::Bridge()->DestroySurface(data->surface);
+    if (AndroidBridge::Bridge()) {
+      AndroidBridge::Bridge()->ReleaseNativeWindow(data->window);
+      AndroidBridge::Bridge()->DestroySurface(data->surface);
+    }
+
     delete data;
   }
 
   mVideoSurfaces.clear();
 }
 
 bool AndroidMediaLayer::EnsureContentSurface() {
-  if (!mContentData.surface) {
+  if (!mContentData.surface && AndroidBridge::Bridge()) {
     mContentData.surface = AndroidBridge::Bridge()->CreateSurface();
     if (mContentData.surface) {
-      mContentData.window = AndroidBridge::Bridge()->AcquireNativeWindow(mContentData.surface);
+      mContentData.window = AndroidBridge::Bridge()->AcquireNativeWindow(AndroidBridge::GetJNIEnv(), mContentData.surface);
       AndroidBridge::Bridge()->SetNativeWindowFormat(mContentData.window, 0, 0, AndroidBridge::WINDOW_FORMAT_RGBA_8888);
     }
   }
 
   return mContentData.surface && mContentData.window;
 }
 
 void* AndroidMediaLayer::GetNativeWindowForContent() {
   if (!EnsureContentSurface())
     return NULL;
 
   return mContentData.window;
 }
 
 void* AndroidMediaLayer::RequestNativeWindowForVideo() {
+  if (!AndroidBridge::Bridge())
+    return NULL;
+
   jobject surface = AndroidBridge::Bridge()->CreateSurface();
   if (surface) {
-    void* window = AndroidBridge::Bridge()->AcquireNativeWindow(surface);
+    void* window = AndroidBridge::Bridge()->AcquireNativeWindow(AndroidBridge::GetJNIEnv(), surface);
     if (window) {
       AndroidBridge::Bridge()->SetNativeWindowFormat(window, 0, 0, AndroidBridge::WINDOW_FORMAT_RGBA_8888);
       mVideoSurfaces[window] = new SurfaceData(surface, window);
       return window;
     } else {
       LOG("Failed to create native window from surface");
 
       // Cleanup
       AndroidBridge::Bridge()->DestroySurface(surface);
     }
   }
 
   return NULL;
 }
 
 void AndroidMediaLayer::ReleaseNativeWindowForVideo(void* aWindow) {
-  if (mVideoSurfaces.find(aWindow) == mVideoSurfaces.end())
+  if (mVideoSurfaces.find(aWindow) == mVideoSurfaces.end() || !AndroidBridge::Bridge())
     return;
 
   SurfaceData* data = mVideoSurfaces[aWindow];
 
   AndroidBridge::Bridge()->ReleaseNativeWindow(data->window);
   AndroidBridge::Bridge()->DestroySurface(data->surface);
 
   mVideoSurfaces.erase(aWindow);
@@ -126,42 +132,38 @@ void AndroidMediaLayer::ReleaseNativeWin
 void AndroidMediaLayer::SetNativeWindowDimensions(void* aWindow, const gfxRect& aDimensions) {
   if (mVideoSurfaces.find(aWindow) == mVideoSurfaces.end())
     return;
 
   SurfaceData* data = mVideoSurfaces[aWindow];
   data->dimensions = aDimensions;
 }
 
-void AndroidMediaLayer::UpdatePosition(const gfxRect& aRect, float aZoomLevel) {
-  if (!mVisible)
+void AndroidMediaLayer::UpdatePosition(const gfxRect& aRect) {
+  if (!mVisible || !AndroidBridge::Bridge())
     return;
 
   std::map<void*, SurfaceData*>::iterator it;
 
   for (it = mVideoSurfaces.begin(); it != mVideoSurfaces.end(); it++) {
     SurfaceData* data = it->second;
 
-    // The video window dimension we get is not adjusted by zoom factor (unlike the
-    // content window). Fix it up here.
-    gfxRect scaledDimensions = data->dimensions;
-    scaledDimensions.Scale(aZoomLevel);
+    gfxRect videoRect(aRect.x + data->dimensions.x, aRect.y + data->dimensions.y,
+                      data->dimensions.width, data->dimensions.height);
 
-    gfxRect videoRect(aRect.x + scaledDimensions.x, aRect.y + scaledDimensions.y,
-                      scaledDimensions.width, scaledDimensions.height);
     AndroidBridge::Bridge()->ShowSurface(data->surface, videoRect, mInverted, false);
   }
 
   if (EnsureContentSurface()) {
     AndroidBridge::Bridge()->ShowSurface(mContentData.surface, aRect, mInverted, true);
   }
 }
 
 void AndroidMediaLayer::SetVisible(bool aVisible) {
-  if (aVisible == mVisible)
+  if (aVisible == mVisible || !AndroidBridge::Bridge())
     return;
 
   mVisible = aVisible;
   if (mVisible)
     return;
 
   // Hide all surfaces
   std::map<void*, SurfaceData*>::iterator it;
--- a/widget/android/AndroidMediaLayer.h
+++ b/widget/android/AndroidMediaLayer.h
@@ -55,17 +55,17 @@ public:
   
   void* GetNativeWindowForContent();
 
   void* RequestNativeWindowForVideo();
   void  ReleaseNativeWindowForVideo(void* aWindow);
 
   void SetNativeWindowDimensions(void* aWindow, const gfxRect& aDimensions);
 
-  void UpdatePosition(const gfxRect& aRect, float aZoomLevel);
+  void UpdatePosition(const gfxRect& aRect);
 
   bool Inverted() {
     return mInverted;
   }
 
   void SetInverted(bool aInverted) {
     mInverted = aInverted;
   }
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -911,17 +911,17 @@ nsWindow::OnGlobalAndroidEvent(AndroidGe
 
         case AndroidGeckoEvent::SURFACE_CREATED:
             sSurfaceExists = true;
 
             if (AndroidBridge::Bridge()->HasNativeWindowAccess()) {
                 AndroidGeckoSurfaceView& sview(AndroidBridge::Bridge()->SurfaceView());
                 jobject surface = sview.GetSurface();
                 if (surface) {
-                    sNativeWindow = AndroidBridge::Bridge()->AcquireNativeWindow(surface);
+                    sNativeWindow = AndroidBridge::Bridge()->AcquireNativeWindow(AndroidBridge::GetJNIEnv(), surface);
                     if (sNativeWindow) {
                         AndroidBridge::Bridge()->SetNativeWindowFormat(sNativeWindow, 0, 0, AndroidBridge::WINDOW_FORMAT_RGB_565);
                     }
                 }
             }
             break;
 
         case AndroidGeckoEvent::SURFACE_DESTROYED: