summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoDisplay.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoDisplay.java')
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoDisplay.java528
1 files changed, 528 insertions, 0 deletions
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoDisplay.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoDisplay.java
new file mode 100644
index 0000000000..1fc34cb8bb
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoDisplay.java
@@ -0,0 +1,528 @@
+/* -*- 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.graphics.Rect;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import androidx.annotation.AnyThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+import org.mozilla.gecko.util.ThreadUtils;
+
+/**
+ * Applications use a GeckoDisplay instance to provide {@link GeckoSession} with a {@link Surface}
+ * for displaying content. To ensure drawing only happens on a valid {@link Surface}, {@link
+ * GeckoSession} will only use the provided {@link Surface} after {@link
+ * #surfaceChanged(SurfaceInfo)} is called and before {@link #surfaceDestroyed()} returns.
+ */
+public class GeckoDisplay {
+ private final GeckoSession mSession;
+
+ protected GeckoDisplay(final GeckoSession session) {
+ mSession = session;
+ }
+
+ /**
+ * Interface that allows Gecko the request a new Surface from the application. An implementation
+ * of this should be set on the {@link GeckoDisplay.SurfaceInfo} object passed to {@link
+ * GeckoDisplay#surfaceChanged(SurfaceInfo)}, by using {@link
+ * GeckoDisplay.SurfaceInfo.Builder#newSurfaceProvider(NewSurfaceProvider)}.
+ */
+ public interface NewSurfaceProvider {
+ /**
+ * Called by Gecko to request a new Surface from the application.
+ *
+ * <p>Occasionally the Surface provided to Gecko via {@link #surfaceChanged(SurfaceInfo)} is
+ * invalid and Gecko is unable to render in to it. This function will be called in such
+ * circumstances. It is the implementation's responsibility to ensure that {@link
+ * #surfaceChanged(SurfaceInfo)} gets called soon afterwards with a new Surface, allowing Gecko
+ * to resume rendering.
+ *
+ * <p>Failure to implement this function may result in Gecko either crashing or not rendering
+ * correctly should it encounter an invalid Surface.
+ */
+ @UiThread
+ void requestNewSurface();
+ }
+
+ /**
+ * Wrapper class containing a Surface and associated information that the compositor should render
+ * in to. Should be constructed using {@link SurfaceInfo.Builder}.
+ */
+ public static class SurfaceInfo {
+ /* package */ final @NonNull Surface mSurface;
+ /* package */ final @Nullable SurfaceControl mSurfaceControl;
+ /* package */ final @Nullable NewSurfaceProvider mNewSurfaceProvider;
+ /* package */ final int mLeft;
+ /* package */ final int mTop;
+ /* package */ final int mWidth;
+ /* package */ final int mHeight;
+
+ private SurfaceInfo(final @NonNull Builder builder) {
+ mSurface = builder.mSurface;
+ mSurfaceControl = builder.mSurfaceControl;
+ mNewSurfaceProvider = builder.mNewSurfaceProvider;
+ mLeft = builder.mLeft;
+ mTop = builder.mTop;
+ mWidth = builder.mWidth;
+ mHeight = builder.mHeight;
+ }
+
+ /** Helper class for constructing a {@link SurfaceInfo} object. */
+ public static class Builder {
+ private Surface mSurface;
+ private SurfaceControl mSurfaceControl;
+ private NewSurfaceProvider mNewSurfaceProvider;
+ private int mLeft;
+ private int mTop;
+ private int mWidth;
+ private int mHeight;
+
+ /**
+ * Creates a new Builder and sets the new Surface.
+ *
+ * @param surface The new Surface.
+ */
+ public Builder(final @NonNull Surface surface) {
+ mSurface = surface;
+ }
+
+ /**
+ * Sets the SurfaceControl associated with the new Surface's SurfaceView.
+ *
+ * <p>This must be called when rendering in to a {@link android.view.SurfaceView} on SDK level
+ * 29 or above. On earlier SDK levels, or when rendering in to something other than a
+ * SurfaceView, this call can be omitted or the value can be null.
+ *
+ * @param surfaceControl The SurfaceControl associated with the new Surface's SurfaceView, or
+ * null.
+ * @return The builder object
+ */
+ @UiThread
+ public @NonNull Builder surfaceControl(final @Nullable SurfaceControl surfaceControl) {
+ mSurfaceControl = surfaceControl;
+ return this;
+ }
+
+ /**
+ * Sets a NewSurfaceProvider from which Gecko can request a new Surface.
+ *
+ * <p>This allows Gecko to recover from situations where the current Surface is for whatever
+ * reason invalid and Gecko is unable to render in to it. Failure to set this field correctly
+ * may result in Gecko either crashing or not rendering correctly should it encounter an
+ * invalid Surface.
+ *
+ * @param newSurfaceProvider A NewSurfaceProvider from which Gecko can request a new Surface.
+ * @return The builder object
+ */
+ @UiThread
+ public @NonNull Builder newSurfaceProvider(
+ final @Nullable NewSurfaceProvider newSurfaceProvider) {
+ mNewSurfaceProvider = newSurfaceProvider;
+ return this;
+ }
+
+ /**
+ * Sets the new compositor origin offset.
+ *
+ * @param left The compositor origin offset in the X axis. Can not be negative.
+ * @param top The compositor origin offset in the Y axis. Can not be negative.
+ * @return The builder object
+ */
+ @UiThread
+ public @NonNull Builder offset(final int left, final int top) {
+ mLeft = left;
+ mTop = top;
+ return this;
+ }
+
+ /**
+ * Sets the new surface size.
+ *
+ * @param width New width of the Surface. Can not be negative.
+ * @param height New height of the Surface. Can not be negative.
+ * @return The builder object
+ */
+ @UiThread
+ public @NonNull Builder size(final int width, final int height) {
+ mWidth = width;
+ mHeight = height;
+ return this;
+ }
+
+ /**
+ * Builds the {@link SurfaceInfo} object with the specified properties.
+ *
+ * @return The SurfaceInfo object
+ */
+ @UiThread
+ public @NonNull SurfaceInfo build() {
+ if ((mLeft < 0) || (mTop < 0)) {
+ throw new IllegalArgumentException("Left and Top offsets can not be negative.");
+ }
+
+ return new SurfaceInfo(this);
+ }
+ }
+ }
+
+ /**
+ * Sets a surface for the compositor render a surface.
+ *
+ * <p>Required call. The display's Surface has been created or changed. Must be called on the
+ * application main thread. GeckoSession may block this call to ensure the Surface is valid while
+ * resuming drawing.
+ *
+ * <p>If rendering in to a {@link android.view.SurfaceView} on SDK level 29 or above, please
+ * ensure that the SurfaceControl field of the {@link SurfaceInfo} object is set.
+ *
+ * @param surfaceInfo Information about the new Surface.
+ */
+ @UiThread
+ public void surfaceChanged(@NonNull final SurfaceInfo surfaceInfo) {
+ ThreadUtils.assertOnUiThread();
+
+ if (mSession.getDisplay() == this) {
+ mSession.onSurfaceChanged(surfaceInfo);
+ }
+ }
+
+ /**
+ * Removes the current surface registered with the compositor.
+ *
+ * <p>Required call. The display's Surface has been destroyed. Must be called on the application
+ * main thread. GeckoSession may block this call to ensure the Surface is valid while pausing
+ * drawing.
+ */
+ @UiThread
+ public void surfaceDestroyed() {
+ ThreadUtils.assertOnUiThread();
+
+ if (mSession.getDisplay() == this) {
+ mSession.onSurfaceDestroyed();
+ }
+ }
+
+ /**
+ * Update the position of the surface on the screen.
+ *
+ * <p>Optional call. The display's coordinates on the screen has changed. Must be called on the
+ * application main thread.
+ *
+ * @param left The X coordinate of the display on the screen, in screen pixels.
+ * @param top The Y coordinate of the display on the screen, in screen pixels.
+ */
+ @UiThread
+ public void screenOriginChanged(final int left, final int top) {
+ ThreadUtils.assertOnUiThread();
+
+ if (mSession.getDisplay() == this) {
+ mSession.onScreenOriginChanged(left, top);
+ }
+ }
+
+ /**
+ * Update the safe area insets of the surface on the screen.
+ *
+ * @param left left margin of safe area
+ * @param top top margin of safe area
+ * @param right right margin of safe area
+ * @param bottom bottom margin of safe area
+ */
+ @UiThread
+ public void safeAreaInsetsChanged(
+ final int top, final int right, final int bottom, final int left) {
+ ThreadUtils.assertOnUiThread();
+
+ if (mSession.getDisplay() == this) {
+ mSession.onSafeAreaInsetsChanged(top, right, bottom, left);
+ }
+ }
+
+ /**
+ * Set the maximum height of the dynamic toolbar(s).
+ *
+ * <p>If the toolbar is dynamic, this function needs to be called with the maximum possible
+ * toolbar height so that Gecko can make the ICB static even during the dynamic toolbar height is
+ * being changed.
+ *
+ * @param height The maximum height of the dynamic toolbar(s).
+ */
+ @UiThread
+ public void setDynamicToolbarMaxHeight(final int height) {
+ ThreadUtils.assertOnUiThread();
+
+ if (mSession != null) {
+ mSession.setDynamicToolbarMaxHeight(height);
+ }
+ }
+
+ /**
+ * Update the amount of vertical space that is clipped or visibly obscured in the bottom portion
+ * of the display. Tells gecko where to put bottom fixed elements so they are fully visible.
+ *
+ * <p>Optional call. The display's visible vertical space has changed. Must be called on the
+ * application main thread.
+ *
+ * @param clippingHeight The height of the bottom clipped space in screen pixels.
+ */
+ @UiThread
+ public void setVerticalClipping(final int clippingHeight) {
+ ThreadUtils.assertOnUiThread();
+
+ if (mSession != null) {
+ mSession.setFixedBottomOffset(clippingHeight);
+ }
+ }
+
+ /**
+ * Return whether the display should be pinned on the screen.
+ *
+ * <p>When pinned, the display should not be moved on the screen due to animation, scrolling, etc.
+ * A common reason for the display being pinned is when the user is dragging a selection caret
+ * inside the display; normal user interaction would be disrupted in that case if the display was
+ * moved on screen.
+ *
+ * @return True if display should be pinned on the screen.
+ */
+ @UiThread
+ public boolean shouldPinOnScreen() {
+ ThreadUtils.assertOnUiThread();
+ return mSession.getDisplay() == this && mSession.shouldPinOnScreen();
+ }
+
+ /**
+ * Request a {@link Bitmap} of the visible portion of the web page currently being rendered.
+ *
+ * <p>Returned {@link Bitmap} will have the same dimensions as the {@link Surface} the {@link
+ * GeckoDisplay} is currently using.
+ *
+ * <p>If the {@link GeckoSession#isCompositorReady} is false the {@link GeckoResult} will complete
+ * with an {@link IllegalStateException}.
+ *
+ * <p>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() {
+ return screenshot().capture();
+ }
+
+ /** Builder to construct screenshot requests. */
+ public static final class ScreenshotBuilder {
+ private static final int NONE = 0;
+ private static final int SCALE = 1;
+ private static final int ASPECT = 2;
+ private static final int FULL = 3;
+ private static final int RECYCLE = 4;
+
+ private final GeckoSession mSession;
+ private int mOffsetX;
+ private int mOffsetY;
+ private int mSrcWidth;
+ private int mSrcHeight;
+ private int mOutWidth;
+ private int mOutHeight;
+ private int mAspectPreservingWidth;
+ private float mScale;
+ private Bitmap mRecycle;
+ private int mSizeType;
+
+ /* package */ ScreenshotBuilder(final GeckoSession session) {
+ this.mSizeType = NONE;
+ this.mSession = session;
+ }
+
+ /**
+ * The screenshot will be of a region instead of the entire screen
+ *
+ * @param x Left most pixel of the source region.
+ * @param y Top most pixel of the source region.
+ * @param width Width of the source region in screen pixels
+ * @param height Height of the source region in screen pixels
+ * @return The builder
+ */
+ @AnyThread
+ public @NonNull ScreenshotBuilder source(
+ final int x, final int y, final int width, final int height) {
+ mOffsetX = x;
+ mOffsetY = y;
+ mSrcWidth = width;
+ mSrcHeight = height;
+ return this;
+ }
+
+ /**
+ * The screenshot will be of a region instead of the entire screen
+ *
+ * @param source Region of the screen to capture in screen pixels
+ * @return The builder
+ */
+ @AnyThread
+ public @NonNull ScreenshotBuilder source(final @NonNull Rect source) {
+ mOffsetX = source.left;
+ mOffsetY = source.top;
+ mSrcWidth = source.width();
+ mSrcHeight = source.height();
+ return this;
+ }
+
+ private void checkAndSetSizeType(final int sizeType) {
+ if (mSizeType != NONE) {
+ throw new IllegalStateException("Size has already been set.");
+ }
+ mSizeType = sizeType;
+ }
+
+ /**
+ * The width of the bitmap to create when taking the screenshot. The height will be calculated
+ * to match the aspect ratio of the source as closely as possible. The source screenshot will be
+ * scaled into the resulting Bitmap.
+ *
+ * @param width of the result Bitmap in screen pixels.
+ * @return The builder
+ * @throws IllegalStateException if the size has already been set in some other way.
+ */
+ @AnyThread
+ public @NonNull ScreenshotBuilder aspectPreservingSize(final int width) {
+ checkAndSetSizeType(ASPECT);
+ mAspectPreservingWidth = width;
+ return this;
+ }
+
+ /**
+ * The scale of the bitmap relative to the source. The height and width of the output bitmap
+ * will be within one pixel of this multiple of the source dimensions. The source screenshot
+ * will be scaled into the resulting Bitmap.
+ *
+ * @param scale of the result Bitmap relative to the source.
+ * @return The builder
+ * @throws IllegalStateException if the size has already been set in some other way.
+ */
+ @AnyThread
+ public @NonNull ScreenshotBuilder scale(final float scale) {
+ checkAndSetSizeType(SCALE);
+ mScale = scale;
+ return this;
+ }
+
+ /**
+ * Size of the bitmap to create when taking the screenshot. The source screenshot will be scaled
+ * into the resulting Bitmap
+ *
+ * @param width of the result Bitmap in screen pixels.
+ * @param height of the result Bitmap in screen pixels.
+ * @return The builder
+ * @throws IllegalStateException if the size has already been set in some other way.
+ */
+ @AnyThread
+ public @NonNull ScreenshotBuilder size(final int width, final int height) {
+ checkAndSetSizeType(FULL);
+ mOutWidth = width;
+ mOutHeight = height;
+ return this;
+ }
+
+ /**
+ * Instead of creating a new Bitmap for the result, the builder will use the passed Bitmap.
+ *
+ * @param bitmap The Bitmap to use in the result.
+ * @return The builder.
+ * @throws IllegalStateException if the size has already been set in some other way.
+ */
+ @AnyThread
+ public @NonNull ScreenshotBuilder bitmap(final @Nullable Bitmap bitmap) {
+ checkAndSetSizeType(RECYCLE);
+ mRecycle = bitmap;
+ return this;
+ }
+
+ /**
+ * Request a {@link Bitmap} of the requested portion of the web page currently being rendered
+ * using any parameters specified with the builder.
+ *
+ * <p>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 requested portion of the visible web page.
+ */
+ @UiThread
+ public @NonNull GeckoResult<Bitmap> capture() {
+ ThreadUtils.assertOnUiThread();
+ if (!mSession.isCompositorReady()) {
+ throw new IllegalStateException("Compositor must be ready before pixels can be captured");
+ }
+
+ final GeckoResult<Bitmap> result = new GeckoResult<>();
+ final Bitmap target;
+ final Rect rect = new Rect();
+
+ if (mSrcWidth == 0 || mSrcHeight == 0) {
+ // Source is unset or invalid, use defaults.
+ mSession.getSurfaceBounds(rect);
+ mSrcWidth = rect.width();
+ mSrcHeight = rect.height();
+ }
+
+ switch (mSizeType) {
+ case NONE:
+ mOutWidth = mSrcWidth;
+ mOutHeight = mSrcHeight;
+ break;
+ case SCALE:
+ mSession.getSurfaceBounds(rect);
+ mOutWidth = (int) (rect.width() * mScale);
+ mOutHeight = (int) (rect.height() * mScale);
+ break;
+ case ASPECT:
+ mSession.getSurfaceBounds(rect);
+ mOutWidth = mAspectPreservingWidth;
+ mOutHeight = (int) (rect.height() * (mAspectPreservingWidth / (double) rect.width()));
+ break;
+ case RECYCLE:
+ mOutWidth = mRecycle.getWidth();
+ mOutHeight = mRecycle.getHeight();
+ break;
+ // case FULL does not need to be handled, as width and height are already set.
+ }
+
+ if (mRecycle == null) {
+ try {
+ target = Bitmap.createBitmap(mOutWidth, mOutHeight, Bitmap.Config.ARGB_8888);
+ } catch (final Throwable e) {
+ if (e instanceof NullPointerException || e instanceof OutOfMemoryError) {
+ return GeckoResult.fromException(
+ new OutOfMemoryError("Not enough memory to allocate for bitmap"));
+ }
+ return GeckoResult.fromException(new Throwable("Failed to create bitmap", e));
+ }
+ } else {
+ target = mRecycle;
+ }
+
+ mSession.mCompositor.requestScreenPixels(
+ result, target, mOffsetX, mOffsetY, mSrcWidth, mSrcHeight, mOutWidth, mOutHeight);
+
+ return result;
+ }
+ }
+
+ /**
+ * Creates a new screenshot builder.
+ *
+ * @return The new {@link ScreenshotBuilder}
+ */
+ @UiThread
+ public @NonNull ScreenshotBuilder screenshot() {
+ return new ScreenshotBuilder(mSession);
+ }
+}