summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java')
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java694
1 files changed, 694 insertions, 0 deletions
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
new file mode 100644
index 000000000..d504fe13e
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
@@ -0,0 +1,694 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.annotation.RobocopTarget;
+import org.mozilla.gecko.annotation.WrapForJNI;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.gfx.LayerView.DrawListener;
+import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.util.FloatUtils;
+import org.mozilla.gecko.AppConstants;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.os.SystemClock;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
+{
+ private static final String LOGTAG = "GeckoLayerClient";
+ private static int sPaintSyncId = 1;
+
+ private LayerRenderer mLayerRenderer;
+ private boolean mLayerRendererInitialized;
+
+ private final Context mContext;
+ private IntSize mScreenSize;
+ private IntSize mWindowSize;
+
+ /*
+ * The viewport metrics being used to draw the current frame. This is only
+ * accessed by the compositor thread, and so needs no synchronisation.
+ */
+ private ImmutableViewportMetrics mFrameMetrics;
+
+ private final List<DrawListener> mDrawListeners;
+
+ /* Used as temporaries by syncViewportInfo */
+ private final ViewTransform mCurrentViewTransform;
+
+ private boolean mForceRedraw;
+
+ /* The current viewport metrics.
+ * This is volatile so that we can read and write to it from different threads.
+ * We avoid synchronization to make getting the viewport metrics from
+ * the compositor as cheap as possible. The viewport is immutable so
+ * we don't need to worry about anyone mutating it while we're reading from it.
+ * Specifically:
+ * 1) reading mViewportMetrics from any thread is fine without synchronization
+ * 2) writing to mViewportMetrics requires synchronizing on the layer controller object
+ * 3) whenever reading multiple fields from mViewportMetrics without synchronization (i.e. in
+ * case 1 above) you should always first grab a local copy of the reference, and then use
+ * that because mViewportMetrics might get reassigned in between reading the different
+ * fields. */
+ private volatile ImmutableViewportMetrics mViewportMetrics;
+
+ private volatile boolean mGeckoIsReady;
+
+ /* package */ final PanZoomController mPanZoomController;
+ private final DynamicToolbarAnimator mToolbarAnimator;
+ /* package */ final LayerView mView;
+
+ /* This flag is true from the time that browser.js detects a first-paint is about to start,
+ * to the time that we receive the first-paint composite notification from the compositor.
+ * Note that there is a small race condition with this; if there are two paints that both
+ * have the first-paint flag set, and the second paint happens concurrently with the
+ * composite for the first paint, then this flag may be set to true prematurely. Fixing this
+ * is possible but risky; see https://bugzilla.mozilla.org/show_bug.cgi?id=797615#c751
+ */
+ private volatile boolean mContentDocumentIsDisplayed;
+
+ private SynthesizedEventState mPointerState;
+
+ @WrapForJNI(stubName = "ClearColor")
+ private volatile int mClearColor = Color.WHITE;
+
+ public GeckoLayerClient(Context context, LayerView view, EventDispatcher eventDispatcher) {
+ // we can fill these in with dummy values because they are always written
+ // to before being read
+ mContext = context;
+ mScreenSize = new IntSize(0, 0);
+ mWindowSize = new IntSize(0, 0);
+ mCurrentViewTransform = new ViewTransform(0, 0, 1);
+
+ mForceRedraw = true;
+ DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+ mViewportMetrics = new ImmutableViewportMetrics(displayMetrics)
+ .setViewportSize(view.getWidth(), view.getHeight());
+
+ mFrameMetrics = mViewportMetrics;
+
+ mDrawListeners = new ArrayList<DrawListener>();
+ mToolbarAnimator = new DynamicToolbarAnimator(this);
+ mPanZoomController = PanZoomController.Factory.create(this, view, eventDispatcher);
+ mView = view;
+ mView.setListener(this);
+ mContentDocumentIsDisplayed = true;
+ }
+
+ public void setOverscrollHandler(final Overscroll listener) {
+ mPanZoomController.setOverscrollHandler(listener);
+ }
+
+ public void setGeckoReady(boolean ready) {
+ mGeckoIsReady = ready;
+ }
+
+ @Override // PanZoomTarget
+ public boolean isGeckoReady() {
+ return mGeckoIsReady;
+ }
+
+ /** Attaches to root layer so that Gecko appears. */
+ @WrapForJNI(calledFrom = "gecko")
+ private void onGeckoReady() {
+ mGeckoIsReady = true;
+
+ mLayerRenderer = mView.getRenderer();
+
+ sendResizeEventIfNecessary(true, null);
+
+ // Gecko being ready is one of the two conditions (along with having an available
+ // surface) that cause us to create the compositor. So here, now that we know gecko
+ // is ready, call updateCompositor() to see if we can actually do the creation.
+ // This needs to run on the UI thread so that the surface validity can't change on
+ // us while we're in the middle of creating the compositor.
+ mView.post(new Runnable() {
+ @Override
+ public void run() {
+ mPanZoomController.attach();
+ mView.updateCompositor();
+ }
+ });
+ }
+
+ public void destroy() {
+ mPanZoomController.destroy();
+ mToolbarAnimator.destroy();
+ mDrawListeners.clear();
+ mGeckoIsReady = false;
+ }
+
+ public LayerView getView() {
+ return mView;
+ }
+
+ public FloatSize getViewportSize() {
+ return mViewportMetrics.getSize();
+ }
+
+ /**
+ * The view calls this function to indicate that the viewport changed size. It must hold the
+ * monitor while calling it.
+ *
+ * TODO: Refactor this to use an interface. Expose that interface only to the view and not
+ * to the layer client. That way, the layer client won't be tempted to call this, which might
+ * result in an infinite loop.
+ */
+ boolean setViewportSize(int width, int height, PointF scrollChange) {
+ if (mViewportMetrics.viewportRectWidth == width &&
+ mViewportMetrics.viewportRectHeight == height &&
+ (scrollChange == null || (scrollChange.x == 0 && scrollChange.y == 0))) {
+ return false;
+ }
+ mViewportMetrics = mViewportMetrics.setViewportSize(width, height);
+ if (scrollChange != null) {
+ mViewportMetrics = mPanZoomController.adjustScrollForSurfaceShift(mViewportMetrics, scrollChange);
+ }
+
+ if (mGeckoIsReady) {
+ // here we send gecko a resize message. The code in browser.js is responsible for
+ // picking up on that resize event, modifying the viewport as necessary, and informing
+ // us of the new viewport.
+ sendResizeEventIfNecessary(true, scrollChange);
+
+ // the following call also sends gecko a message, which will be processed after the resize
+ // message above has updated the viewport. this message ensures that if we have just put
+ // focus in a text field, we scroll the content so that the text field is in view.
+ GeckoAppShell.viewSizeChanged();
+ }
+ return true;
+ }
+
+ PanZoomController getPanZoomController() {
+ return mPanZoomController;
+ }
+
+ DynamicToolbarAnimator getDynamicToolbarAnimator() {
+ return mToolbarAnimator;
+ }
+
+ /* Informs Gecko that the screen size has changed. */
+ private void sendResizeEventIfNecessary(boolean force, PointF scrollChange) {
+ DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
+
+ IntSize newScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels);
+ IntSize newWindowSize = new IntSize(mViewportMetrics.viewportRectWidth,
+ mViewportMetrics.viewportRectHeight);
+
+ boolean screenSizeChanged = !mScreenSize.equals(newScreenSize);
+ boolean windowSizeChanged = !mWindowSize.equals(newWindowSize);
+
+ if (!force && !screenSizeChanged && !windowSizeChanged) {
+ return;
+ }
+
+ mScreenSize = newScreenSize;
+ mWindowSize = newWindowSize;
+
+ if (screenSizeChanged) {
+ Log.d(LOGTAG, "Screen-size changed to " + mScreenSize);
+ }
+
+ if (windowSizeChanged) {
+ Log.d(LOGTAG, "Window-size changed to " + mWindowSize);
+ }
+
+ if (mView != null) {
+ mView.notifySizeChanged(mWindowSize.width, mWindowSize.height,
+ mScreenSize.width, mScreenSize.height);
+ }
+
+ String json = "";
+ try {
+ if (scrollChange != null) {
+ int id = ++sPaintSyncId;
+ if (id == 0) {
+ // never use 0 as that is the default value for "this is not
+ // a special transaction"
+ id = ++sPaintSyncId;
+ }
+ JSONObject jsonObj = new JSONObject();
+ jsonObj.put("x", scrollChange.x / mViewportMetrics.zoomFactor);
+ jsonObj.put("y", scrollChange.y / mViewportMetrics.zoomFactor);
+ jsonObj.put("id", id);
+ json = jsonObj.toString();
+ }
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Unable to convert point to JSON", e);
+ }
+ GeckoAppShell.notifyObservers("Window:Resize", json);
+ }
+
+ /**
+ * The different types of Viewport messages handled. All viewport events
+ * expect a display-port to be returned, but can handle one not being
+ * returned.
+ */
+ private enum ViewportMessageType {
+ UPDATE, // The viewport has changed and should be entirely updated
+ PAGE_SIZE // The viewport's page-size has changed
+ }
+
+ @WrapForJNI(calledFrom = "gecko")
+ void contentDocumentChanged() {
+ mContentDocumentIsDisplayed = false;
+ }
+
+ @WrapForJNI(calledFrom = "gecko")
+ boolean isContentDocumentDisplayed() {
+ return mContentDocumentIsDisplayed;
+ }
+
+ /** The compositor invokes this function just before compositing a frame where the document
+ * is different from the document composited on the last frame. In these cases, the viewport
+ * information we have in Java is no longer valid and needs to be replaced with the new
+ * viewport information provided.
+ */
+ @WrapForJNI
+ public void setFirstPaintViewport(float offsetX, float offsetY, float zoom,
+ float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom) {
+ synchronized (getLock()) {
+ ImmutableViewportMetrics currentMetrics = getViewportMetrics();
+
+ RectF cssPageRect = new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom);
+ RectF pageRect = RectUtils.scaleAndRound(cssPageRect, zoom);
+
+ final ImmutableViewportMetrics newMetrics = currentMetrics
+ .setViewportOrigin(offsetX, offsetY)
+ .setZoomFactor(zoom)
+ .setPageRect(pageRect, cssPageRect);
+ // Since we have switched to displaying a different document, we need to update any
+ // viewport-related state we have lying around (i.e. mViewportMetrics).
+ // Usually this information is updated via handleViewportMessage
+ // while we remain on the same document.
+ setViewportMetrics(newMetrics, true);
+
+ // Indicate that the document is about to be composited so the
+ // LayerView background can be removed.
+ if (mView.getPaintState() == LayerView.PAINT_START) {
+ mView.setPaintState(LayerView.PAINT_BEFORE_FIRST);
+ }
+ }
+
+ mContentDocumentIsDisplayed = true;
+ }
+
+ /** The compositor invokes this function on every frame to figure out what part of the
+ * page to display, and to inform Java of the current display port. Since it is called
+ * on every frame, it needs to be ultra-fast.
+ * It avoids taking any locks or allocating any objects. We keep around a
+ * mCurrentViewTransform so we don't need to allocate a new ViewTransform
+ * every time we're called. NOTE: we might be able to return a ImmutableViewportMetrics
+ * which would avoid the copy into mCurrentViewTransform.
+ */
+ private ViewTransform syncViewportInfo(int x, int y, int width, int height, float resolution, boolean layersUpdated,
+ int paintSyncId) {
+ // getViewportMetrics is thread safe so we don't need to synchronize.
+ // We save the viewport metrics here, so we later use it later in
+ // createFrame (which will be called by nsWindow::DrawWindowUnderlay on
+ // the native side, by the compositor). The viewport
+ // metrics can change between here and there, as it's accessed outside
+ // of the compositor thread.
+ mFrameMetrics = getViewportMetrics();
+
+ if (paintSyncId == sPaintSyncId) {
+ mToolbarAnimator.scrollChangeResizeCompleted();
+ }
+ mToolbarAnimator.populateViewTransform(mCurrentViewTransform, mFrameMetrics);
+
+ if (layersUpdated) {
+ for (DrawListener listener : mDrawListeners) {
+ listener.drawFinished();
+ }
+ }
+
+ return mCurrentViewTransform;
+ }
+
+ @WrapForJNI
+ public ViewTransform syncFrameMetrics(float scrollX, float scrollY, float zoom,
+ float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom,
+ int dpX, int dpY, int dpWidth, int dpHeight, float paintedResolution,
+ boolean layersUpdated, int paintSyncId)
+ {
+ // TODO: optimize this so it doesn't create so much garbage - it's a
+ // hot path
+ RectF cssPageRect = new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom);
+ synchronized (getLock()) {
+ mViewportMetrics = mViewportMetrics.setViewportOrigin(scrollX, scrollY)
+ .setZoomFactor(zoom)
+ .setPageRect(RectUtils.scale(cssPageRect, zoom), cssPageRect);
+ }
+ return syncViewportInfo(dpX, dpY, dpWidth, dpHeight, paintedResolution,
+ layersUpdated, paintSyncId);
+ }
+
+ class PointerInfo {
+ // We reserve one pointer ID for the mouse, so that tests don't have
+ // to worry about tracking pointer IDs if they just want to test mouse
+ // event synthesization. If somebody tries to use this ID for a
+ // synthesized touch event we'll throw an exception.
+ public static final int RESERVED_MOUSE_POINTER_ID = 100000;
+
+ public int pointerId;
+ public int source;
+ public int screenX;
+ public int screenY;
+ public double pressure;
+ public int orientation;
+
+ public MotionEvent.PointerCoords getCoords() {
+ MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
+ coords.orientation = orientation;
+ coords.pressure = (float)pressure;
+ coords.x = screenX;
+ coords.y = screenY;
+ return coords;
+ }
+ }
+
+ class SynthesizedEventState {
+ public final ArrayList<PointerInfo> pointers;
+ public long downTime;
+
+ SynthesizedEventState() {
+ pointers = new ArrayList<PointerInfo>();
+ }
+
+ int getPointerIndex(int pointerId) {
+ for (int i = 0; i < pointers.size(); i++) {
+ if (pointers.get(i).pointerId == pointerId) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ int addPointer(int pointerId, int source) {
+ PointerInfo info = new PointerInfo();
+ info.pointerId = pointerId;
+ info.source = source;
+ pointers.add(info);
+ return pointers.size() - 1;
+ }
+
+ int getPointerCount(int source) {
+ int count = 0;
+ for (int i = 0; i < pointers.size(); i++) {
+ if (pointers.get(i).source == source) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ MotionEvent.PointerProperties[] getPointerProperties(int source) {
+ MotionEvent.PointerProperties[] props = new MotionEvent.PointerProperties[getPointerCount(source)];
+ int index = 0;
+ for (int i = 0; i < pointers.size(); i++) {
+ if (pointers.get(i).source == source) {
+ MotionEvent.PointerProperties p = new MotionEvent.PointerProperties();
+ p.id = pointers.get(i).pointerId;
+ switch (source) {
+ case InputDevice.SOURCE_TOUCHSCREEN:
+ p.toolType = MotionEvent.TOOL_TYPE_FINGER;
+ break;
+ case InputDevice.SOURCE_MOUSE:
+ p.toolType = MotionEvent.TOOL_TYPE_MOUSE;
+ break;
+ }
+ props[index++] = p;
+ }
+ }
+ return props;
+ }
+
+ MotionEvent.PointerCoords[] getPointerCoords(int source) {
+ MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[getPointerCount(source)];
+ int index = 0;
+ for (int i = 0; i < pointers.size(); i++) {
+ if (pointers.get(i).source == source) {
+ coords[index++] = pointers.get(i).getCoords();
+ }
+ }
+ return coords;
+ }
+ }
+
+ private void synthesizeNativePointer(int source, int pointerId,
+ int eventType, int screenX, int screenY, double pressure,
+ int orientation)
+ {
+ Log.d(LOGTAG, "Synthesizing pointer from " + source + " id " + pointerId + " at " + screenX + ", " + screenY);
+
+ if (mPointerState == null) {
+ mPointerState = new SynthesizedEventState();
+ }
+
+ // Find the pointer if it already exists
+ int pointerIndex = mPointerState.getPointerIndex(pointerId);
+
+ // Event-specific handling
+ switch (eventType) {
+ case MotionEvent.ACTION_POINTER_UP:
+ if (pointerIndex < 0) {
+ Log.d(LOGTAG, "Requested synthesis of a pointer-up for a pointer that doesn't exist!");
+ return;
+ }
+ if (mPointerState.pointers.size() == 1) {
+ // Last pointer is going up
+ eventType = MotionEvent.ACTION_UP;
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ if (pointerIndex < 0) {
+ Log.d(LOGTAG, "Requested synthesis of a pointer-cancel for a pointer that doesn't exist!");
+ return;
+ }
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (pointerIndex < 0) {
+ // Adding a new pointer
+ pointerIndex = mPointerState.addPointer(pointerId, source);
+ if (pointerIndex == 0) {
+ // first pointer
+ eventType = MotionEvent.ACTION_DOWN;
+ mPointerState.downTime = SystemClock.uptimeMillis();
+ }
+ } else {
+ // We're moving an existing pointer
+ eventType = MotionEvent.ACTION_MOVE;
+ }
+ break;
+ case MotionEvent.ACTION_HOVER_MOVE:
+ if (pointerIndex < 0) {
+ // Mouse-move a pointer without it going "down". However
+ // in order to send the right MotionEvent without a lot of
+ // duplicated code, we add the pointer to mPointerState,
+ // and then remove it at the bottom of this function.
+ pointerIndex = mPointerState.addPointer(pointerId, source);
+ } else {
+ // We're moving an existing mouse pointer that went down.
+ eventType = MotionEvent.ACTION_MOVE;
+ }
+ break;
+ }
+
+ // Update the pointer with the new info
+ PointerInfo info = mPointerState.pointers.get(pointerIndex);
+ info.screenX = screenX;
+ info.screenY = screenY;
+ info.pressure = pressure;
+ info.orientation = orientation;
+
+ // Dispatch the event
+ int action = (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
+ action &= MotionEvent.ACTION_POINTER_INDEX_MASK;
+ action |= (eventType & MotionEvent.ACTION_MASK);
+ boolean isButtonDown = (source == InputDevice.SOURCE_MOUSE) &&
+ (eventType == MotionEvent.ACTION_DOWN || eventType == MotionEvent.ACTION_MOVE);
+ final MotionEvent event = MotionEvent.obtain(
+ /*downTime*/ mPointerState.downTime,
+ /*eventTime*/ SystemClock.uptimeMillis(),
+ /*action*/ action,
+ /*pointerCount*/ mPointerState.getPointerCount(source),
+ /*pointerProperties*/ mPointerState.getPointerProperties(source),
+ /*pointerCoords*/ mPointerState.getPointerCoords(source),
+ /*metaState*/ 0,
+ /*buttonState*/ (isButtonDown ? MotionEvent.BUTTON_PRIMARY : 0),
+ /*xPrecision*/ 0,
+ /*yPrecision*/ 0,
+ /*deviceId*/ 0,
+ /*edgeFlags*/ 0,
+ /*source*/ source,
+ /*flags*/ 0);
+ mView.post(new Runnable() {
+ @Override
+ public void run() {
+ mView.dispatchTouchEvent(event);
+ }
+ });
+
+ // Forget about removed pointers
+ if (eventType == MotionEvent.ACTION_POINTER_UP ||
+ eventType == MotionEvent.ACTION_UP ||
+ eventType == MotionEvent.ACTION_CANCEL ||
+ eventType == MotionEvent.ACTION_HOVER_MOVE)
+ {
+ mPointerState.pointers.remove(pointerIndex);
+ }
+ }
+
+ @WrapForJNI(calledFrom = "gecko")
+ public void synthesizeNativeTouchPoint(int pointerId, int eventType, int screenX,
+ int screenY, double pressure, int orientation)
+ {
+ if (pointerId == PointerInfo.RESERVED_MOUSE_POINTER_ID) {
+ throw new IllegalArgumentException("Use a different pointer ID in your test, this one is reserved for mouse");
+ }
+ synthesizeNativePointer(InputDevice.SOURCE_TOUCHSCREEN, pointerId,
+ eventType, screenX, screenY, pressure, orientation);
+ }
+
+ @WrapForJNI(calledFrom = "gecko")
+ public void synthesizeNativeMouseEvent(int eventType, int screenX, int screenY) {
+ synthesizeNativePointer(InputDevice.SOURCE_MOUSE, PointerInfo.RESERVED_MOUSE_POINTER_ID,
+ eventType, screenX, screenY, 0, 0);
+ }
+
+ @WrapForJNI
+ public LayerRenderer.Frame createFrame() {
+ // Create the shaders and textures if necessary.
+ if (!mLayerRendererInitialized) {
+ if (mLayerRenderer == null) {
+ return null;
+ }
+ mLayerRenderer.createDefaultProgram();
+ mLayerRendererInitialized = true;
+ }
+
+ try {
+ return mLayerRenderer.createFrame(mFrameMetrics);
+ } catch (Exception e) {
+ Log.w(LOGTAG, e);
+ return null;
+ }
+ }
+
+ private void geometryChanged() {
+ /* Let Gecko know if the screensize has changed */
+ sendResizeEventIfNecessary(false, null);
+ }
+
+ /** Implementation of LayerView.Listener */
+ @Override
+ public void surfaceChanged(int width, int height) {
+ IntSize viewportSize = mToolbarAnimator.getViewportSize();
+ setViewportSize(viewportSize.width, viewportSize.height, null);
+ }
+
+ ImmutableViewportMetrics getViewportMetrics() {
+ return mViewportMetrics;
+ }
+
+ /*
+ * You must hold the monitor while calling this.
+ */
+ private void setViewportMetrics(ImmutableViewportMetrics metrics, boolean notifyGecko) {
+ // This class owns the viewport size and the fixed layer margins; don't let other pieces
+ // of code clobber either of them. The only place the viewport size should ever be
+ // updated is in GeckoLayerClient.setViewportSize, and the only place the margins should
+ // ever be updated is in GeckoLayerClient.setFixedLayerMargins; both of these assign to
+ // mViewportMetrics directly.
+ metrics = metrics.setViewportSize(mViewportMetrics.viewportRectWidth, mViewportMetrics.viewportRectHeight);
+ mViewportMetrics = metrics;
+
+ viewportMetricsChanged(notifyGecko);
+ }
+
+ /*
+ * You must hold the monitor while calling this.
+ */
+ private void viewportMetricsChanged(boolean notifyGecko) {
+ mToolbarAnimator.onMetricsChanged(mViewportMetrics);
+
+ mView.requestRender();
+ if (notifyGecko && mGeckoIsReady) {
+ geometryChanged();
+ }
+ }
+
+ /*
+ * Updates the viewport metrics, overriding the viewport size and margins
+ * which are normally retained when calling setViewportMetrics.
+ * You must hold the monitor while calling this.
+ */
+ void forceViewportMetrics(ImmutableViewportMetrics metrics, boolean notifyGecko, boolean forceRedraw) {
+ if (forceRedraw) {
+ mForceRedraw = true;
+ }
+ mViewportMetrics = metrics;
+ viewportMetricsChanged(notifyGecko);
+ }
+
+ /** Implementation of PanZoomTarget */
+ @Override
+ public void panZoomStopped() {
+ mToolbarAnimator.onPanZoomStopped();
+ }
+
+ Object getLock() {
+ return this;
+ }
+
+ Matrix getMatrixForLayerRectToViewRect() {
+ if (!mGeckoIsReady) {
+ return null;
+ }
+
+ ImmutableViewportMetrics viewportMetrics = mViewportMetrics;
+ PointF origin = viewportMetrics.getOrigin();
+ float zoom = viewportMetrics.zoomFactor;
+ ImmutableViewportMetrics geckoViewport = mViewportMetrics;
+ PointF geckoOrigin = geckoViewport.getOrigin();
+ float geckoZoom = geckoViewport.zoomFactor;
+
+ Matrix matrix = new Matrix();
+ matrix.postTranslate(geckoOrigin.x / geckoZoom, geckoOrigin.y / geckoZoom);
+ matrix.postScale(zoom, zoom);
+ matrix.postTranslate(-origin.x, -origin.y);
+ return matrix;
+ }
+
+ @Override
+ public void setScrollingRootContent(boolean isRootContent) {
+ mToolbarAnimator.setScrollingRootContent(isRootContent);
+ }
+
+ public void addDrawListener(DrawListener listener) {
+ mDrawListeners.add(listener);
+ }
+
+ public void removeDrawListener(DrawListener listener) {
+ mDrawListeners.remove(listener);
+ }
+
+ public void setClearColor(int color) {
+ mClearColor = color;
+ }
+}