Bug 1415994 - 6b. Track GeckoDisplay origin changes; r=snorp draft
authorJim Chen <nchen@mozilla.com>
Tue, 14 Nov 2017 18:18:35 -0500
changeset 697881 2626743107245b7a3f928b2e4febba25c567b9a4
parent 697880 ef470fb95aa6ad17cb53ea65079116e93451ba74
child 697882 e018cd461c93e7a4461f1b46ebd880ba637b1bc7
push id89133
push userbmo:nchen@mozilla.com
push dateTue, 14 Nov 2017 23:19:10 +0000
reviewerssnorp
bugs1415994
milestone59.0a1
Bug 1415994 - 6b. Track GeckoDisplay origin changes; r=snorp Add a `screenOriginChanged` callback to GeckoDisplay.Listener, which informs Gecko of changes in the origin of the display. The origin translates to coordinates for web APIs like screenX/screenY and certain other calculations. Also, make GeckoDisplay listen to layout changes in the view tree (by overriding gatherTransparentRegion as an optimization), and call `screenOriginChanged` accordingly. MozReview-Commit-ID: C72EHCkbV3T
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoView.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoDisplay.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerSession.java
widget/android/nsWindow.cpp
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoView.java
@@ -7,16 +7,17 @@
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.gfx.GeckoDisplay;
 import org.mozilla.gecko.gfx.LayerView;
 
 import android.content.Context;
 import android.graphics.Color;
 import android.graphics.Rect;
+import android.graphics.Region;
 import android.os.Handler;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.ViewGroup;
@@ -66,16 +67,18 @@ public class GeckoView extends LayerView
             public SavedState[] newArray(final int size) {
                 return new SavedState[size];
             }
         };
     }
 
     private class Display implements GeckoDisplay,
                                      SurfaceHolder.Callback {
+        private final int[] mOrigin = new int[2];
+
         private Listener mListener;
         private boolean mValid;
 
         @Override // GeckoDisplay
         public Listener getListener() {
             return mListener;
         }
 
@@ -88,16 +91,17 @@ public class GeckoView extends LayerView
 
             mListener = listener;
 
             if (!mValid || listener == null) {
                 return;
             }
 
             // Tell new listener there is already a surface.
+            onGlobalLayout();
             if (GeckoView.this.mSurfaceView != null) {
                 final SurfaceHolder holder = GeckoView.this.mSurfaceView.getHolder();
                 final Rect frame = holder.getSurfaceFrame();
                 listener.surfaceChanged(holder.getSurface(), frame.right, frame.bottom);
             }
         }
 
         @Override // SurfaceHolder.Callback
@@ -115,16 +119,26 @@ public class GeckoView extends LayerView
 
         @Override // SurfaceHolder.Callback
         public void surfaceDestroyed(final SurfaceHolder holder) {
             if (mListener != null) {
                 mListener.surfaceDestroyed();
             }
             mValid = false;
         }
+
+        public void onGlobalLayout() {
+            if (mListener == null) {
+                return;
+            }
+            if (GeckoView.this.mSurfaceView != null) {
+                GeckoView.this.mSurfaceView.getLocationOnScreen(mOrigin);
+                mListener.screenOriginChanged(mOrigin[0], mOrigin[1]);
+            }
+        }
     }
 
     public GeckoView(final Context context) {
         super(context);
         init();
     }
 
     public GeckoView(final Context context, final AttributeSet attrs) {
@@ -216,16 +230,27 @@ public class GeckoView extends LayerView
         }
 
         if (mSession != null && mSession.isOpen()) {
             mSession.closeWindow();
         }
     }
 
     @Override
+    public boolean gatherTransparentRegion(final Region region) {
+        // For detecting changes in SurfaceView layout, we take a shortcut here and
+        // override gatherTransparentRegion, instead of registering a layout listener,
+        // which is more expensive.
+        if (mSurfaceView != null) {
+            mDisplay.onGlobalLayout();
+        }
+        return super.gatherTransparentRegion(region);
+    }
+
+    @Override
     protected Parcelable onSaveInstanceState() {
         mStateSaved = true;
         return new SavedState(super.onSaveInstanceState(), mSession);
     }
 
     @Override
     protected void onRestoreInstanceState(final Parcelable state) {
         mStateSaved = false;
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoDisplay.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoDisplay.java
@@ -33,16 +33,25 @@ public interface GeckoDisplay {
         void surfaceChanged(Surface surface, int width, int height);
 
         /**
          * 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.
          */
         void surfaceDestroyed();
+
+        /**
+         * 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.
+         */
+        void screenOriginChanged(int left, int top);
     }
 
     /**
      * Get the current listener attached to this display. Must be called on the
      * application main thread.
      *
      * @return Current listener or null if there is no listener.
      */
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerSession.java
@@ -12,16 +12,22 @@ import org.mozilla.gecko.util.ThreadUtil
 import android.content.Context;
 import android.util.Log;
 import android.view.Surface;
 
 public class LayerSession {
     private static final String LOGTAG = "GeckoLayerSession";
     private static final boolean DEBUG = false;
 
+    // Sent from compositor when the static toolbar image has been updated and
+    // is ready to animate.
+    /* package */ final static int STATIC_TOOLBAR_READY = 1;
+    // Sent from compositor when the static toolbar has been made visible so
+    // the real toolbar should be shown.
+    /* package */ final static int TOOLBAR_SHOW = 4;
     // Special message sent from UiCompositorControllerChild once it is open.
     /* package */ final static int COMPOSITOR_CONTROLLER_OPEN = 20;
     // Special message sent from controller to query if the compositor controller is open.
     /* package */ final static int IS_COMPOSITOR_CONTROLLER_OPEN = 21;
 
     protected class Compositor extends JNIObject {
         public LayerView layerView;
 
@@ -48,17 +54,17 @@ public class LayerSession {
         @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
         @Override protected native void disposeNative();
 
         @WrapForJNI(calledFrom = "any", dispatchTo = "gecko")
         public native void attachToJava(GeckoLayerClient layerClient,
                                         NativePanZoomController npzc);
 
         @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
-        public native void onSizeChanged(int windowWidth, int windowHeight);
+        public native void onBoundsChanged(int left, int top, int width, int height);
 
         // Gecko thread creates compositor; blocks UI thread.
         @WrapForJNI(calledFrom = "ui", dispatchTo = "proxy")
         public native void createCompositor(int width, int height, Object surface);
 
         // Gecko thread pauses compositor; blocks UI thread.
         @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
         public native void syncPauseCompositor();
@@ -91,16 +97,20 @@ public class LayerSession {
                         LayerSession.this.onCompositorReady();
                     }
                 });
             }
 
             if (layerView != null) {
                 layerView.handleToolbarAnimatorMessage(message);
             }
+
+            if (message == STATIC_TOOLBAR_READY || message == TOOLBAR_SHOW) {
+                LayerSession.this.onWindowBoundsChanged();
+            }
         }
 
         @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
         public native void setDefaultClearColor(int color);
 
         @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
         public native void requestScreenPixels();
 
@@ -122,16 +132,18 @@ public class LayerSession {
     protected final Compositor mCompositor = new Compositor();
 
     // Following fields are accessed on UI thread.
     private GeckoDisplay mDisplay;
     private boolean mAttachedCompositor;
     private boolean mCalledCreateCompositor;
     private boolean mCompositorReady;
     private Surface mSurface;
+    private int mLeft;
+    private int mTop;
     private int mWidth;
     private int mHeight;
 
     /* package */ GeckoDisplay getDisplay() {
         if (DEBUG) {
             ThreadUtils.assertOnUiThread();
         }
         return mDisplay;
@@ -175,46 +187,47 @@ public class LayerSession {
         if (mSurface != null) {
             // If we have a valid surface, resume the
             // compositor now that the compositor is ready.
             onSurfaceChanged(mSurface, mWidth, mHeight);
             mSurface = null;
         }
     }
 
-    private void onWindowResize() {
+    /* protected */ void onWindowBoundsChanged() {
         if (DEBUG) {
             ThreadUtils.assertOnUiThread();
         }
 
         if (mAttachedCompositor) {
-            final int viewportHeight;
+            final int toolbarHeight;
             if (mCompositor.layerView != null) {
-                viewportHeight = mHeight - mCompositor.layerView.getCurrentToolbarHeight();
+                toolbarHeight = mCompositor.layerView.getCurrentToolbarHeight();
             } else {
-                viewportHeight = mHeight;
+                toolbarHeight = 0;
             }
-            mCompositor.onSizeChanged(mWidth, viewportHeight);
+            mCompositor.onBoundsChanged(mLeft, mTop + toolbarHeight,
+                                        mWidth, mHeight - toolbarHeight);
 
             if (mCompositor.layerView != null) {
                 mCompositor.layerView.onSizeChanged(mWidth, mHeight);
             }
         }
     }
 
     /* package */ void onSurfaceChanged(final Surface surface, final int width,
                                         final int height) {
         ThreadUtils.assertOnUiThread();
 
         mWidth = width;
         mHeight = height;
-        onWindowResize();
 
         if (mCompositorReady) {
             mCompositor.syncResumeResizeCompositor(width, height, surface);
+            onWindowBoundsChanged();
             return;
         }
 
         if (mAttachedCompositor && !mCalledCreateCompositor) {
             mCompositor.createCompositor(width, height, surface);
             mCompositor.sendToolbarAnimatorMessage(IS_COMPOSITOR_CONTROLLER_OPEN);
             mCalledCreateCompositor = true;
         }
@@ -232,16 +245,28 @@ public class LayerSession {
             return;
         }
 
         // While the surface was valid, we never became attached or the
         // compositor never became ready; clear the saved surface.
         mSurface = null;
     }
 
+    /* package */ void onScreenOriginChanged(final int left, final int top) {
+        ThreadUtils.assertOnUiThread();
+
+        if (mLeft == left && mTop == top) {
+            return;
+        }
+
+        mLeft = left;
+        mTop = top;
+        onWindowBoundsChanged();
+    }
+
     public void addDisplay(final GeckoDisplay display) {
         ThreadUtils.assertOnUiThread();
 
         if (display.getListener() != null) {
             throw new IllegalArgumentException("Display already attached");
         } else if (mDisplay != null) {
             throw new IllegalArgumentException("Only one display supported");
         }
@@ -253,16 +278,21 @@ public class LayerSession {
                                        final int height) {
                 onSurfaceChanged(surface, width, height);
             }
 
             @Override
             public void surfaceDestroyed() {
                 onSurfaceDestroyed();
             }
+
+            @Override
+            public void screenOriginChanged(final int left, final int top) {
+                onScreenOriginChanged(left, top);
+            }
         });
     }
 
     public void removeDisplay(final GeckoDisplay display) {
         ThreadUtils.assertOnUiThread();
 
         if (mDisplay != display) {
             throw new IllegalArgumentException("Display not attached");
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -925,28 +925,25 @@ public:
 
         // Set the first-paint flag so that we (re-)link any new Java objects
         // to Gecko, co-ordinate viewports, etc.
         if (RefPtr<CompositorBridgeChild> bridge = mWindow->GetCompositorBridgeChild()) {
             bridge->SendForceIsFirstPaint();
         }
     }
 
-    void OnSizeChanged(int32_t aWindowWidth, int32_t aWindowHeight)
+    void OnBoundsChanged(int32_t aLeft, int32_t aTop,
+                         int32_t aWidth, int32_t aHeight)
     {
         MOZ_ASSERT(NS_IsMainThread());
         if (!mWindow) {
             return; // Already shut down.
         }
 
-        if (aWindowWidth != mWindow->mBounds.width ||
-            aWindowHeight != mWindow->mBounds.height) {
-
-            mWindow->Resize(aWindowWidth, aWindowHeight, /* repaint */ false);
-        }
+        mWindow->Resize(aLeft, aTop, aWidth, aHeight, /* repaint */ false);
     }
 
     void CreateCompositor(int32_t aWidth, int32_t aHeight,
                           jni::Object::Param aSurface)
     {
         MOZ_ASSERT(NS_IsMainThread());
         if (!mWindow) {
             return; // Already shut down.
@@ -1874,25 +1871,25 @@ nsWindow::GetScreenBounds()
 {
     return LayoutDeviceIntRect(WidgetToScreenOffset(), mBounds.Size());
 }
 
 LayoutDeviceIntPoint
 nsWindow::WidgetToScreenOffset()
 {
     LayoutDeviceIntPoint p(0, 0);
-    nsWindow *w = this;
 
-    while (w && !w->IsTopLevel()) {
+    for (nsWindow *w = this; !!w; w = w->mParent) {
         p.x += w->mBounds.x;
         p.y += w->mBounds.y;
 
-        w = w->mParent;
+        if (w->IsTopLevel()) {
+            break;
+        }
     }
-
     return p;
 }
 
 nsresult
 nsWindow::DispatchEvent(WidgetGUIEvent* aEvent,
                         nsEventStatus& aStatus)
 {
     aStatus = DispatchEvent(aEvent);