Backed out changeset b63cae045a63 (bug 1462018) for geckoview failures
authorOana Pop Rus <opoprus@mozilla.com>
Wed, 27 Feb 2019 14:42:59 +0200
changeset 519312 9c42526c4442304d733d16f1e10365a91a702f7f
parent 519311 bec37c9a80b9dfc9e924a7a657ec2f6eb96782cf
child 519313 ebc59bbdd7ac2b4ef4be3ce9d1799f90a2f961b0
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1462018
milestone67.0a1
backs outb63cae045a638021aa932f4b120735391fcdffe2
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out changeset b63cae045a63 (bug 1462018) for geckoview failures
mobile/android/geckoview/api.txt
mobile/android/geckoview/src/androidTest/assets/www/colors.html
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ScreenshotTest.kt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/CompositorController.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoDisplay.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/FennecNativeDriver.java
widget/android/bindings/AndroidGraphics-classes.txt
widget/android/bindings/JavaExceptions-classes.txt
widget/android/bindings/moz.build
widget/android/nsWindow.cpp
--- a/mobile/android/geckoview/api.txt
+++ b/mobile/android/geckoview/api.txt
@@ -30,21 +30,26 @@ package org.mozilla.geckoview {
     field protected final android.graphics.RectF mTempRect;
     field protected final boolean mUseFloatingToolbar;
   }
 
   @android.support.annotation.UiThread public final class CompositorController {
     method public void addDrawCallback(@android.support.annotation.NonNull java.lang.Runnable);
     method public int getClearColor();
     method @android.support.annotation.Nullable public java.lang.Runnable getFirstPaintCallback();
+    method public void getPixels(@android.support.annotation.NonNull org.mozilla.geckoview.CompositorController.GetPixelsCallback);
     method public void removeDrawCallback(@android.support.annotation.NonNull java.lang.Runnable);
     method public void setClearColor(int);
     method public void setFirstPaintCallback(@android.support.annotation.Nullable java.lang.Runnable);
   }
 
+  public static interface CompositorController.GetPixelsCallback {
+    method @android.support.annotation.UiThread public void onPixelsResult(int, int, @android.support.annotation.Nullable java.nio.IntBuffer);
+  }
+
   @android.support.annotation.AnyThread public class ContentBlocking {
     ctor public ContentBlocking();
     field public static final int AT_AD = 2;
     field public static final int AT_ALL = 62;
     field public static final int AT_ANALYTIC = 4;
     field public static final int AT_CONTENT = 16;
     field public static final int AT_SOCIAL = 8;
     field public static final int AT_TEST = 32;
@@ -126,17 +131,16 @@ package org.mozilla.geckoview {
   public static interface DynamicToolbarAnimator.ToolbarChromeProxy {
     method @android.support.annotation.UiThread @android.support.annotation.Nullable public android.graphics.Bitmap getBitmapOfToolbarChrome();
     method @android.support.annotation.UiThread public boolean isToolbarChromeVisible();
     method @android.support.annotation.UiThread public void toggleToolbarChrome(boolean);
   }
 
   public class GeckoDisplay {
     ctor protected GeckoDisplay(org.mozilla.geckoview.GeckoSession);
-    method @android.support.annotation.UiThread @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoResult<android.graphics.Bitmap> capturePixels();
     method @android.support.annotation.UiThread public void screenOriginChanged(int, int);
     method @android.support.annotation.UiThread public boolean shouldPinOnScreen();
     method @android.support.annotation.UiThread public void surfaceChanged(@android.support.annotation.NonNull android.view.Surface, int, int);
     method @android.support.annotation.UiThread public void surfaceChanged(@android.support.annotation.NonNull android.view.Surface, int, int, int, int);
     method @android.support.annotation.UiThread public void surfaceDestroyed();
   }
 
   public interface GeckoResponse<T> {
@@ -722,17 +726,16 @@ package org.mozilla.geckoview {
   }
 
   public static class GeckoSessionSettings.Key<T> {
   }
 
   @android.support.annotation.UiThread public class GeckoView extends android.widget.FrameLayout {
     ctor public GeckoView(android.content.Context);
     ctor public GeckoView(android.content.Context, android.util.AttributeSet);
-    method @android.support.annotation.UiThread @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoResult<android.graphics.Bitmap> capturePixels();
     method public void coverUntilFirstPaint(int);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.DynamicToolbarAnimator getDynamicToolbarAnimator();
     method @android.support.annotation.AnyThread @android.support.annotation.NonNull public org.mozilla.gecko.EventDispatcher getEventDispatcher();
     method @android.support.annotation.NonNull public org.mozilla.geckoview.PanZoomController getPanZoomController();
     method @android.support.annotation.AnyThread @android.support.annotation.Nullable public org.mozilla.geckoview.GeckoSession getSession();
     method @android.support.annotation.UiThread @android.support.annotation.Nullable public org.mozilla.geckoview.GeckoSession releaseSession();
     method @android.support.annotation.UiThread public void setSession(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession);
     method @android.support.annotation.UiThread public void setSession(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, @android.support.annotation.Nullable org.mozilla.geckoview.GeckoRuntime);
deleted file mode 100644
--- a/mobile/android/geckoview/src/androidTest/assets/www/colors.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<html>
-    <head><title>Colours</title></head>
-    <body style="background-color:green;">
-        <div id="fullscreen" style="width:100%;height:100%;position:fixed;top:0;left:0;z-index:100;"></div>
-    </body>
-</html>
\ No newline at end of file
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt
@@ -48,17 +48,16 @@ open class BaseSessionTest(noErrorCollec
         const val VIDEO_BAD_PATH = "/assets/www/badVideoPath.html"
         const val UNKNOWN_HOST_URI = "http://www.test.invalid/"
         const val FULLSCREEN_PATH = "/assets/www/fullscreen.html"
         const val VIEWPORT_PATH = "/assets/www/viewport.html"
         const val IFRAME_REDIRECT_LOCAL = "/assets/www/iframe_redirect_local.html"
         const val IFRAME_REDIRECT_AUTOMATION = "/assets/www/iframe_redirect_automation.html"
         const val AUTOPLAY_PATH = "/assets/www/autoplay.html"
         const val SCROLL_TEST_PATH = "/assets/www/scroll.html"
-        const val COLORS_HTML_PATH = "/assets/www/colors.html"
     }
 
     @get:Rule val sessionRule = GeckoSessionTestRule()
 
     @get:Rule val errors = ErrorCollector()
 
     val mainSession get() = sessionRule.session
 
deleted file mode 100644
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ScreenshotTest.kt
+++ /dev/null
@@ -1,116 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-package org.mozilla.geckoview.test
-
-
-import android.graphics.*
-import android.support.test.filters.MediumTest
-import android.support.test.runner.AndroidJUnit4
-import android.view.Surface
-import org.hamcrest.Matchers.notNullValue
-import org.junit.Assert.fail
-import org.junit.Rule
-import org.junit.Test
-import org.junit.rules.ExpectedException
-import org.junit.runner.RunWith
-import org.mozilla.geckoview.GeckoResult
-import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
-import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
-
-const val SCREEN_HEIGHT = 100
-const val SCREEN_WIDTH = 100
-
-@RunWith(AndroidJUnit4::class)
-@MediumTest
-@ReuseSession(false)
-class ScreenshotTest : BaseSessionTest() {
-
-    @get:Rule
-    val expectedEx: ExpectedException = ExpectedException.none()
-
-    private fun getComparisonScreenshot(width: Int, height: Int): Bitmap {
-        val screenshotFile = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
-        val canvas = Canvas(screenshotFile)
-        val paint = Paint()
-        paint.color = Color.rgb(0, 128, 0)
-        canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
-        return screenshotFile
-    }
-
-    private fun assertScreenshotResult(result: GeckoResult<Bitmap>, comparisonImage: Bitmap) {
-        sessionRule.waitForResult(result).let {
-            assertThat("Screenshot is not null",
-                    it, notNullValue())
-            assert(it.sameAs(comparisonImage)) {  "Screenshots are the same" }
-        }
-    }
-
-    @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
-    @Test
-    fun capturePixelsSucceeds() {
-        val screenshotFile = getComparisonScreenshot(SCREEN_WIDTH, SCREEN_HEIGHT)
-
-        sessionRule.session.loadTestPath(COLORS_HTML_PATH)
-        sessionRule.waitForPageStop()
-
-        sessionRule.display?.let {
-            assertScreenshotResult(it.capturePixels(), screenshotFile)
-        }
-    }
-
-    @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
-    @Test
-    fun capturePixelsCanBeCalledMultipleTimes() {
-        val screenshotFile = getComparisonScreenshot(SCREEN_WIDTH, SCREEN_HEIGHT)
-
-        sessionRule.session.loadTestPath(COLORS_HTML_PATH)
-        sessionRule.waitForPageStop()
-
-        sessionRule.display?.let {
-            val call1 = it.capturePixels()
-            val call2 = it.capturePixels()
-            val call3 = it.capturePixels()
-            assertScreenshotResult(call1, screenshotFile)
-            assertScreenshotResult(call2, screenshotFile)
-            assertScreenshotResult(call3, screenshotFile)
-        }
-    }
-
-    @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
-    @Test
-    fun capturePixelsThrowsSessionClosed() {
-        expectedEx.expect(IllegalStateException::class.java)
-        expectedEx.expectMessage("The compositor has detached from the session")
-        mainSession.close()
-
-        sessionRule.display?.let { sessionRule.waitForResult(it.capturePixels()) }
-        fail("IllegalStateException expected to be thrown")
-    }
-
-    @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
-    @Test
-    fun capturePixelsCompletesCompositorPausedRestarted() {
-        sessionRule.display?.let {
-            it.surfaceDestroyed()
-            val result = it.capturePixels()
-            val texture = SurfaceTexture(0)
-            texture.setDefaultBufferSize(SCREEN_WIDTH, SCREEN_HEIGHT)
-            val surface = Surface(texture)
-            it.surfaceChanged(surface, SCREEN_WIDTH, SCREEN_HEIGHT)
-            sessionRule.waitForResult(result)
-        }
-    }
-
-    @Test
-    fun capturePixelsThrowsCompositorNotReady() {
-        expectedEx.expect(IllegalStateException::class.java)
-        expectedEx.expectMessage("Compositor must be ready before pixels can be captured")
-        val session = sessionRule.createClosedSession()
-        val display = session.acquireDisplay()
-
-        sessionRule.waitForResult(display.capturePixels())
-        fail("IllegalStateException expected to be thrown")
-    }
-}
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
@@ -31,17 +31,16 @@ import org.hamcrest.Matcher;
 import org.json.JSONObject;
 
 import org.junit.rules.ErrorCollector;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
 import android.app.Instrumentation;
-import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.SurfaceTexture;
 import android.net.LocalSocketAddress;
 import android.os.Looper;
 import android.os.SystemClock;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.test.InstrumentationRegistry;
@@ -954,20 +953,16 @@ public class GeckoSessionTestRule implem
      * Get the runtime set up for the current test.
      *
      * @return GeckoRuntime object.
      */
     public @NonNull GeckoRuntime getRuntime() {
         return RuntimeCreator.getRuntime();
     }
 
-    public @Nullable GeckoDisplay getDisplay() {
-        return mDisplay;
-    }
-
     protected static Object setDelegate(final @NonNull Class<?> cls,
                                         final @NonNull GeckoSession session,
                                         final @Nullable Object delegate)
             throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
         if (cls == GeckoSession.TextInputDelegate.class) {
             return SessionTextInput.class.getMethod("setDelegate", cls)
                    .invoke(session.getTextInput(), delegate);
         }
@@ -1211,20 +1206,19 @@ public class GeckoSessionTestRule implem
         if (useDefaultSession && mReuseSession && sCachedSession != null) {
             mMainSession = sCachedSession;
         } else {
             mMainSession = new GeckoSession(settings);
         }
         prepareSession(mMainSession);
 
         if (mDisplaySize != null) {
+            mDisplayTexture = new SurfaceTexture(0);
+            mDisplaySurface = new Surface(mDisplayTexture);
             mDisplay = mMainSession.acquireDisplay();
-            mDisplayTexture = new SurfaceTexture(0);
-            mDisplayTexture.setDefaultBufferSize(mDisplaySize.x, mDisplaySize.y);
-            mDisplaySurface = new Surface(mDisplayTexture);
             mDisplay.surfaceChanged(mDisplaySurface, mDisplaySize.x, mDisplaySize.y);
         }
 
         if (useDefaultSession && mReuseSession) {
             if (sCachedSession == null) {
                 // We are creating a cached session.
                 final boolean withDevTools = mWithDevTools;
                 mWithDevTools = true; // Always get an RDP tab for cached session.
@@ -1289,16 +1283,17 @@ public class GeckoSessionTestRule implem
         // For the cached session, we may get multiple initial loads. We should specifically look
         // for an about:blank load, and wait until that has stopped.
         final boolean lookForAboutBlank = session.equals(sCachedSession);
 
         try {
             // We cannot detect initial page load without progress delegate.
             assertThat("ProgressDelegate cannot be null-delegate when opening session",
                        GeckoSession.ProgressDelegate.class, not(isIn(mNullDelegates)));
+
             mCallRecordHandler = new CallRecordHandler() {
                 private boolean mIsAboutBlank = !lookForAboutBlank;
 
                 @Override
                 public boolean handleCall(final Method method, final Object[] args) {
                     final boolean matching = DEFAULT_DELEGATES.contains(
                             method.getDeclaringClass()) && session.equals(args[0]);
                     if (matching && sOnPageStart.equals(method)) {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/CompositorController.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/CompositorController.java
@@ -3,32 +3,36 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.geckoview;
 
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.util.ThreadUtils;
 
-import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.UiThread;
 
-import java.nio.ByteBuffer;
 import java.nio.IntBuffer;
 import java.util.ArrayList;
 import java.util.List;
 
 @UiThread
 public final class CompositorController {
     private final GeckoSession.Compositor mCompositor;
 
+    public interface GetPixelsCallback {
+        @UiThread
+        void onPixelsResult(int width, int height, @Nullable IntBuffer pixels);
+    }
+
     private List<Runnable> mDrawCallbacks;
+    private GetPixelsCallback mGetPixelsCallback;
     private int mDefaultClearColor = Color.WHITE;
     private Runnable mFirstPaintCallback;
 
     /* package */ CompositorController(final GeckoSession session) {
         mCompositor = session.mCompositor;
     }
 
     /* package */ void onCompositorReady() {
@@ -84,16 +88,42 @@ public final class CompositorController 
         }
 
         if (mDrawCallbacks.remove(callback) && mDrawCallbacks.isEmpty() &&
                 mCompositor.isReady()) {
             mCompositor.enableLayerUpdateNotifications(false);
         }
     }
 
+    /* package */ void recvScreenPixels(final int width, final int height,
+                                        final int[] pixels) {
+        if (mGetPixelsCallback != null) {
+            mGetPixelsCallback.onPixelsResult(width, height, IntBuffer.wrap(pixels));
+            mGetPixelsCallback = null;
+        }
+    }
+
+    /**
+     * Request current pixel values from the compositor. May be called on any thread. Must
+     * not be called again until the callback is invoked.
+     *
+     * @param callback Callback for getting pixels.
+     */
+    @RobocopTarget
+    public void getPixels(final @NonNull GetPixelsCallback callback) {
+        ThreadUtils.assertOnUiThread();
+
+        if (mCompositor.isReady()) {
+            mGetPixelsCallback = callback;
+            mCompositor.requestScreenPixels();
+        } else {
+            callback.onPixelsResult(0, 0, null);
+        }
+    }
+
     /**
      * Get the current clear color when drawing.
      *
      * @return Curent clear color.
      */
     public int getClearColor() {
         ThreadUtils.assertOnUiThread();
         return mDefaultClearColor;
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoDisplay.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoDisplay.java
@@ -1,17 +1,16 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * vim: ts=4 sw=4 expandtab:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.geckoview;
 
-import android.graphics.Bitmap;
 import android.support.annotation.NonNull;
 import android.support.annotation.UiThread;
 import android.view.Surface;
 
 import org.mozilla.gecko.util.ThreadUtils;
 
 /**
  * Applications use a GeckoDisplay instance to provide {@link GeckoSession} with a {@link Surface} for
@@ -114,36 +113,9 @@ public class GeckoDisplay {
      *
      * @return True if display should be pinned on the screen.
      */
     @UiThread
     public boolean shouldPinOnScreen() {
         ThreadUtils.assertOnUiThread();
         return session.getDisplay() == this && session.shouldPinOnScreen();
     }
-
-    /**
-     * Request a {@link Bitmap} of the visible portion of the web page currently being
-     * rendered.
-     *
-     * Returned {@link Bitmap} will have the same dimensions as the {@link Surface} the
-     * {@link GeckoDisplay} is currently using.
-     *
-     * If the {@link GeckoSession#isCompositorReady} is false the {@link GeckoResult} will complete
-     * with an {@link IllegalStateException}.
-     *
-     * This function must be called on the UI thread.
-     *
-     * @return A {@link GeckoResult} that completes with a {@link Bitmap} containing
-     * the pixels and size information of the currently visible rendered web page.
-     */
-    @UiThread
-    public @NonNull GeckoResult<Bitmap> capturePixels() {
-        ThreadUtils.assertOnUiThread();
-        if (!session.isCompositorReady()) {
-            return GeckoResult.fromException(
-                    new IllegalStateException("Compositor must be ready before pixels can be captured"));
-        }
-        GeckoResult<Bitmap> result = new GeckoResult<>();
-        session.mCompositor.requestScreenPixels(result);
-        return result;
-    }
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -4,16 +4,19 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.geckoview;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.UUID;
 
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoThread;
 import org.mozilla.gecko.IGeckoEditableParent;
 import org.mozilla.gecko.mozglue.JNIObject;
@@ -23,17 +26,16 @@ import org.mozilla.gecko.util.EventCallb
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.IntentUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
 import android.database.Cursor;
-import android.graphics.Bitmap;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.IInterface;
 import android.os.Parcel;
@@ -209,17 +211,22 @@ public class GeckoSession implements Par
         private void recvToolbarAnimatorMessage(int message) {
             GeckoSession.this.handleCompositorMessage(message);
         }
 
         @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
         public native void setDefaultClearColor(int color);
 
         @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
-        /* package */ native void requestScreenPixels(final GeckoResult<Bitmap> result);
+        public native void requestScreenPixels();
+
+        @WrapForJNI(calledFrom = "ui")
+        private void recvScreenPixels(int width, int height, int[] pixels) {
+            GeckoSession.this.recvScreenPixels(width, height, pixels);
+        }
 
         @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
         public native void enableLayerUpdateNotifications(boolean enable);
 
         @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
         public native void sendToolbarPixelsToCompositor(final int width, final int height,
                                                          final int[] pixels);
 
@@ -4352,16 +4359,22 @@ public class GeckoSession implements Par
                 } else {
                     Log.w(LOGTAG, "Unexpected message: " + message);
                 }
                 break;
             }
         }
     }
 
+    /* package */ void recvScreenPixels(int width, int height, int[] pixels) {
+        if (mController != null) {
+            mController.recvScreenPixels(width, height, pixels);
+        }
+    }
+
     /* package */ boolean isCompositorReady() {
         return mCompositorReady;
     }
 
     /* package */ void onCompositorReady() {
         if (DEBUG) {
             ThreadUtils.assertOnUiThread();
         }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
@@ -12,17 +12,16 @@ import org.mozilla.gecko.InputMethods;
 import org.mozilla.gecko.util.ActivityUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.app.Activity;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -162,32 +161,16 @@ public class GeckoView extends FrameLayo
                 GeckoView.this.mSurfaceView.getLocationOnScreen(mOrigin);
                 mDisplay.screenOriginChanged(mOrigin[0], mOrigin[1]);
             }
         }
 
         public boolean shouldPinOnScreen() {
             return mDisplay != null ? mDisplay.shouldPinOnScreen() : false;
         }
-
-        /**
-         * Request a {@link Bitmap} of the visible portion of the web page currently being
-         * rendered.
-         *
-         * @return A {@link GeckoResult} that completes with a {@link Bitmap} containing
-         * the pixels and size information of the currently visible rendered web page.
-         */
-        @UiThread
-        @NonNull GeckoResult<Bitmap> capturePixels() {
-            if (mDisplay == null) {
-                return GeckoResult.fromException(new IllegalStateException("Display must be created before pixels can be captured"));
-            }
-
-            return mDisplay.capturePixels();
-        }
     }
 
     public GeckoView(final Context context) {
         super(context);
         init();
     }
 
     public GeckoView(final Context context, final AttributeSet attrs) {
@@ -721,23 +704,9 @@ public class GeckoView extends FrameLayo
             final AutofillValue value = values.valueAt(i);
             if (value.isText()) {
                 // Only text is currently supported.
                 strValues.put(values.keyAt(i), value.getTextValue());
             }
         }
         mSession.getTextInput().autofill(strValues);
     }
-
-    /**
-     * Request a {@link Bitmap} of the visible portion of the web page currently being
-     * rendered.
-     *
-     * See {@link GeckoDisplay#capturePixels} for more details.
-     *
-     * @return A {@link GeckoResult} that completes with a {@link Bitmap} containing
-     * the pixels and size information of the currently visible rendered web page.
-     */
-    @UiThread
-    public @NonNull GeckoResult<Bitmap> capturePixels() {
-        return mDisplay.capturePixels();
-    }
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
@@ -65,28 +65,16 @@ exclude: true
 
 [67.1]: ../GeckoSession.html#getDefaultUserAgent--
 
 - Initial WebExtension support. [`GeckoRuntime#registerWebExtension`][67.15]
   allows embedders to register a local web extension.
 
 [67.15]: ../GeckoRuntime.html#registerWebExtension-org.mozilla.geckoview.WebExtension-
 
-- Added API to [`GeckoView`][65.5] to take screenshot of the visible page. Calling [`capturePixels`][67.16] returns a ['GeckoResult'][65.25] that completes to a [`Bitmap`][67.17] of the current [`Surface`][67.18] contents, or an [`IllegalStateException`][67.19] if the [`GeckoSession`][65.9] is not ready to render content.
-
-[67.16]: ../GeckoView.html#capturePixels
-[67.17]: https://developer.android.com/reference/android/graphics/Bitmap
-[67.18]: https://developer.android.com/reference/android/view/Surface
-[67.19]: https://developer.android.com/reference/java/lang/IllegalStateException
-
-- Added API to capture a screenshot to [`GeckoDisplay`][67.20]. [`capturePixels`][67.21] returns a ['GeckoResult'][65.25] that completes to a [`Bitmap`][67.16] of the current [`Surface`][67.17] contents, or an [`IllegalStateException`][67.18] if the [`GeckoSession`][65.9] is not ready to render content.
-
-[67.20]: ../GeckoDisplay.html
-[67.21]: ../GeckoDisplay.html#capturePixels
-
 ## v66
 - Removed redundant field `trackingMode` from [`SecurityInformation`][66.6].
   Use `TrackingProtectionDelegate.onTrackerBlocked` for notification of blocked
   elements during page load.
 
 [66.6]: ../GeckoSession.ProgressDelegate.SecurityInformation.html
 
 - Added [`@NonNull`][66.1] or [`@Nullable`][66.2] to all APIs.
@@ -196,9 +184,9 @@ exclude: true
 [65.23]: ../GeckoSession.FinderResult.html
 
 - Update [`CrashReporter#sendCrashReport`][65.24] to return the crash ID as a
   [`GeckoResult<String>`][65.25].
 
 [65.24]: ../CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
 [65.25]: ../GeckoResult.html
 
-[api-version]: 798a3341eb4223da901d9e37146d90781bd7ef2b
+[api-version]: b26e5e12a78512a9c18d1ba3441864ca66d3dde8
--- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
+++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
@@ -38,16 +38,17 @@ import android.view.MenuItem;
 import android.view.View;
 import android.view.WindowManager;
 import android.widget.ProgressBar;
 
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.Locale;
 
 public class GeckoViewActivity extends AppCompatActivity {
     private static final String LOGTAG = "GeckoViewActivity";
     private static final String DEFAULT_URL = "about:blank";
     private static final String USE_MULTIPROCESS_EXTRA = "use_multiprocess";
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/FennecNativeDriver.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/FennecNativeDriver.java
@@ -8,40 +8,38 @@ import java.io.BufferedOutputStream;
 import java.io.BufferedReader;
 import java.io.DataOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.nio.ByteBuffer;
 import java.nio.IntBuffer;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.lang.StringBuffer;
 import java.lang.Math;
 
+import org.mozilla.geckoview.CompositorController;
 import org.mozilla.gecko.gfx.PanningPerfAPI;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.StrictModeContext;
-import org.mozilla.geckoview.GeckoResult;
 import org.mozilla.geckoview.GeckoView;
 
 import android.app.Activity;
-import android.graphics.Bitmap;
 import android.util.Log;
 import android.view.View;
 
 import com.robotium.solo.Solo;
 
-public class FennecNativeDriver implements Driver {
+public class FennecNativeDriver implements Driver, CompositorController.GetPixelsCallback {
     private static final int FRAME_TIME_THRESHOLD = 25;     // allow 25ms per frame (40fps)
 
     private final Activity mActivity;
     private final Solo mSolo;
     private final String mRootPath;
 
     private static String mLogFile;
     private static LogLevel mLogLevel = LogLevel.INFO;
@@ -188,20 +186,25 @@ public class FennecNativeDriver implemen
             for (final View v : mSolo.getViews()) {
                 log(LogLevel.WARN, "  View: " + v);
             }
         }
         return geckoView;
     }
 
     private volatile boolean mGotPixelsResult;
-    private Bitmap mPixelsResult;
+    private int mPixelsWidth;
+    private int mPixelsHeight;
+    private IntBuffer mPixelsResult;
 
-    public synchronized void onPixelsResult(final Bitmap aResult) {
-        mPixelsResult = aResult;
+    @Override
+    public synchronized void onPixelsResult(int aWidth, int aHeight, IntBuffer aPixels) {
+        mPixelsWidth = aWidth;
+        mPixelsHeight = aHeight;
+        mPixelsResult = aPixels;
         mGotPixelsResult = true;
         notifyAll();
     }
 
     private static final int COLOR_DEVIATION = 3;
 
     // Due to anti-aliasing, border pixels can be blended. This should filter them out.
     private static boolean differentColor(final int c1, final int c2) {
@@ -251,62 +254,68 @@ public class FennecNativeDriver implemen
         final GeckoView view = getSurfaceView();
         if (view == null) {
             return null;
         }
 
         view.post(new Runnable() {
             @Override
             public void run() {
-                view.capturePixels().then( value -> {
-                    FennecNativeDriver.this.onPixelsResult(value);
-                    return null;
-                });
+                view.getSession().getCompositorController().getPixels(FennecNativeDriver.this);
             }
         });
 
         synchronized (this) {
             while (!mGotPixelsResult) {
                 try {
                     wait();
                 } catch (InterruptedException ie) {
                 }
             }
         }
 
-        final ByteBuffer pixelBuffer = ByteBuffer.allocate(mPixelsResult.getByteCount());
-        mPixelsResult.copyPixelsToBuffer(pixelBuffer);
-        int w = mPixelsResult.getWidth();
-        int h = mPixelsResult.getHeight();
+        final IntBuffer pixelBuffer = mPixelsResult;
+        int w = mPixelsWidth;
+        int h = mPixelsHeight;
 
         mGotPixelsResult = false;
+        mPixelsWidth = 0;
+        mPixelsHeight = 0;
         mPixelsResult = null;
 
 
         if ((pixelBuffer == null) || (w == 0) || (h == 0)) {
             return null;
         }
 
         // The page used in robocop tests is a grid of different colored squares.
         // The function will log the color of each square found in the screen capture.
         // This allows the screen capture to be examined in the log output in a human
         // readable format.
         // logPixels(pixelBuffer, w, h);
-        
+
+        // now we need to (1) flip the image, because GL likes to do things up-side-down,
+        // and (2) rearrange the bits from AGBR-8888 to ARGB-8888.
         pixelBuffer.position(0);
         String mapFile = mRootPath + "/pixels.map";
 
         FileOutputStream fos = null;
         BufferedOutputStream bos = null;
         DataOutputStream dos = null;
         try {
             fos = new FileOutputStream(mapFile);
             bos = new BufferedOutputStream(fos);
             dos = new DataOutputStream(bos);
-            dos.write(pixelBuffer.array());
+
+            for (int y = h - 1; y >= 0; y--) {
+                for (int x = 0; x < w; x++) {
+                    int agbr = pixelBuffer.get();
+                    dos.writeInt((agbr & 0xFF00FF00) | ((agbr >> 16) & 0x000000FF) | ((agbr << 16) & 0x00FF0000));
+                }
+            }
         } catch (IOException e) {
             throw new RoboCopException("exception with pixel writer on file: " + mapFile);
         } finally {
             try {
                 if (dos != null) {
                     dos.flush();
                     dos.close();
                 }
deleted file mode 100644
--- a/widget/android/bindings/AndroidGraphics-classes.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-[android.graphics.Bitmap = skip:true]
-copyPixelsFromBuffer(Ljava/nio/Buffer;)V =
-createBitmap(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap; =
-
-[android.graphics.Bitmap$Config = skip:true]
-valueOf(Ljava/lang/String;)Landroid/graphics/Bitmap$Config; =
-ALPHA_8 =
-ARGB_8888 =
-RGBA_F16 =
-RGB_565 =
\ No newline at end of file
deleted file mode 100644
--- a/widget/android/bindings/JavaExceptions-classes.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-[java.lang.IllegalStateException = skip:true]
-<init>(Ljava/lang/String;)V =
\ No newline at end of file
--- a/widget/android/bindings/moz.build
+++ b/widget/android/bindings/moz.build
@@ -7,22 +7,20 @@
 with Files("**"):
     BUG_COMPONENT = ("Firefox for Android", "Graphics, Panning and Zooming")
 
 # List of stems to generate .cpp and .h files for.  To add a stem, add it to
 # this list and ensure that $(stem)-classes.txt exists in this directory.
 generated = [
     'AccessibilityEvent',
     'AndroidBuild',
-    'AndroidGraphics',
     'AndroidInputType',
     'AndroidRect',
     'InetAddress',
     'JavaBuiltins',
-    'JavaExceptions',
     'KeyEvent',
     'MediaCodec',
     'MotionEvent',
     'SurfaceTexture',
     'ViewConfiguration'
 ]
 
 SOURCES += ['!%s.cpp' % stem for stem in generated]
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -3,46 +3,40 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <android/log.h>
 #include <android/native_window.h>
 #include <android/native_window_jni.h>
 #include <math.h>
-#include <queue>
 #include <unistd.h>
 
 #include "mozilla/MiscEvents.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TouchEvents.h"
 #include "mozilla/TypeTraits.h"
 #include "mozilla/WeakPtr.h"
 #include "mozilla/WheelHandlingHelper.h"  // for WheelDeltaAdjustmentStrategy
 
 #include "mozilla/a11y/SessionAccessibility.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/MouseEventBinding.h"
 #include "mozilla/Unused.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/layers/RenderTrace.h"
-#include "mozilla/gfx/DataSurfaceHelpers.h"
-#include "mozilla/gfx/2D.h"
 #include <algorithm>
 
 using mozilla::Unused;
 using mozilla::dom::ContentChild;
 using mozilla::dom::ContentParent;
 
 #include "nsWindow.h"
 
-#include "AndroidGraphics.h"
-#include "JavaExceptions.h"
-
 #include "nsIBaseWindow.h"
 #include "nsIBrowserDOMWindow.h"
 #include "nsIDOMChromeWindow.h"
 #include "nsIObserverService.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIWidgetListener.h"
 #include "nsIWindowWatcher.h"
 #include "nsIXULWindow.h"
@@ -798,17 +792,16 @@ nsresult nsWindow::AndroidView::GetInitD
 class nsWindow::LayerViewSupport final
     : public GeckoSession::Compositor::Natives<LayerViewSupport> {
   using LockedWindowPtr = WindowPtr<LayerViewSupport>::Locked;
 
   WindowPtr<LayerViewSupport> mWindow;
   GeckoSession::Compositor::WeakRef mCompositor;
   Atomic<bool, ReleaseAcquire> mCompositorPaused;
   jni::Object::GlobalRef mSurface;
-  std::queue<java::GeckoResult::GlobalRef> mCapturePixelsResults;
 
   // In order to use Event::HasSameTypeAs in PostTo(), we cannot make
   // LayerViewEvent a template because each template instantiation is
   // a different type. So implement LayerViewEvent as a ProxyEvent.
   class LayerViewEvent final : public nsAppShell::ProxyEvent {
     using Event = nsAppShell::Event;
 
    public:
@@ -859,21 +852,17 @@ class nsWindow::LayerViewSupport final
     if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
       GeckoSession::Compositor::GlobalRef compositor(mCompositor);
       if (!compositor) {
         return;
       }
 
       uiThread->Dispatch(NS_NewRunnableFunction(
           "LayerViewSupport::OnDetach",
-          [compositor, disposer = RefPtr<Runnable>(aDisposer), result = &mCapturePixelsResults] {
-            while (!result->empty()) {
-              result->front()->CompleteExceptionally(java::sdk::IllegalStateException::New("The compositor has detached from the session").Cast<jni::Throwable>());
-              result->pop();
-            }
+          [compositor, disposer = RefPtr<Runnable>(aDisposer)] {
             compositor->OnCompositorDetached();
             disposer->Run();
           }));
     }
   }
 
   const GeckoSession::Compositor::Ref& GetJavaCompositor() const {
     return mCompositor;
@@ -888,37 +877,16 @@ class nsWindow::LayerViewSupport final
   GetUiCompositorControllerChild() {
     RefPtr<UiCompositorControllerChild> child;
     if (LockedWindowPtr window{mWindow}) {
       child = window->GetUiCompositorControllerChild();
     }
     return child.forget();
   }
 
-  int8_t* FlipScreenPixels(Shmem& aMem, const ScreenIntSize& aSize) {
-    const IntSize size(aSize.width, aSize.height);
-    RefPtr<DataSourceSurface> image = gfx::CreateDataSourceSurfaceFromData(
-      size, 
-      SurfaceFormat::B8G8R8A8, 
-      aMem.get<uint8_t>(), 
-      StrideForFormatAndWidth(SurfaceFormat::B8G8R8A8, aSize.width));
-    RefPtr<DrawTarget> drawTarget = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
-      size, SurfaceFormat::B8G8R8A8);
-    drawTarget->SetTransform(Matrix::Scaling(1.0, -1.0) * Matrix::Translation(0, aSize.height));
-      
-    gfx::Rect drawRect(0, 0, aSize.width, aSize.height);
-    drawTarget->DrawSurface(image, drawRect, drawRect);
-
-    RefPtr<SourceSurface> snapshot = drawTarget->Snapshot();
-    RefPtr<DataSourceSurface> data = snapshot->GetDataSurface();
-    DataSourceSurface::ScopedMap* smap =
-      new DataSourceSurface::ScopedMap(data, DataSourceSurface::READ);
-    return reinterpret_cast<int8_t*>(smap->GetData());
-  }
-
   /**
    * Compositor methods
    */
  public:
   void AttachNPZC(jni::Object::Param aNPZC) {
     MOZ_ASSERT(NS_IsMainThread());
     if (!mWindow) {
       return;  // Already shut down.
@@ -1105,47 +1073,38 @@ class nsWindow::LayerViewSupport final
   void SetDefaultClearColor(int32_t aColor) {
     MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
     if (RefPtr<UiCompositorControllerChild> child =
             GetUiCompositorControllerChild()) {
       child->SetDefaultClearColor((uint32_t)aColor);
     }
   }
 
-  void RequestScreenPixels(jni::Object::Param aResult) {
+  void RequestScreenPixels() {
     MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
-    mCapturePixelsResults.push(java::GeckoResult::GlobalRef(java::GeckoResult::LocalRef(aResult)));
-    if (mCapturePixelsResults.size() == 1) {
-      if (RefPtr<UiCompositorControllerChild> child =
-              GetUiCompositorControllerChild()) {
-        child->RequestScreenPixels();
-      }
+    if (RefPtr<UiCompositorControllerChild> child =
+            GetUiCompositorControllerChild()) {
+      child->RequestScreenPixels();
     }
-  } 
+  }
 
   void RecvScreenPixels(Shmem&& aMem, const ScreenIntSize& aSize) {
     MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
-    auto aResult = java::GeckoResult::LocalRef(mCapturePixelsResults.front());
-    if (aResult) {
-      auto pixels =
-          mozilla::jni::ByteBuffer::New(FlipScreenPixels(aMem, aSize), aMem.Size<int8_t>());
-      auto bitmap = java::sdk::Bitmap::CreateBitmap(aSize.width, aSize.height, java::sdk::Config::ARGB_8888());
-      bitmap->CopyPixelsFromBuffer(pixels);
-      aResult->Complete(bitmap);
-      mCapturePixelsResults.pop();
+
+    auto pixels =
+        mozilla::jni::IntArray::New(aMem.get<int>(), aMem.Size<int>());
+    auto compositor = GeckoSession::Compositor::LocalRef(mCompositor);
+    if (compositor) {
+      compositor->RecvScreenPixels(aSize.width, aSize.height, pixels);
     }
 
     // Pixels have been copied, so Dealloc Shmem
     if (RefPtr<UiCompositorControllerChild> child =
             GetUiCompositorControllerChild()) {
       child->DeallocPixelBuffer(aMem);
-
-      if (!mCapturePixelsResults.empty()) {
-        child->RequestScreenPixels();
-      }
     }
   }
 
   void EnableLayerUpdateNotifications(bool aEnable) {
     MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
     if (RefPtr<UiCompositorControllerChild> child =
             GetUiCompositorControllerChild()) {
       child->EnableLayerUpdateNotifications(aEnable);