Bug 775722 - Reposition the text selection handles during pan/zoom operations. r=blassey
authorKartikaya Gupta <kgupta@mozilla.com>
Thu, 26 Jul 2012 10:13:48 -0400
changeset 100572 2b0e4574d8d016735fb256a0aea2c93c20363420
parent 100571 873bd2f652b7f14a8b726148fcae0ca53e0d5a66
child 100573 33b40911b9bad70c0ae8359ea30120ed54a00a8e
push id23185
push usermbrubeck@mozilla.com
push dateThu, 26 Jul 2012 20:58:28 +0000
treeherdermozilla-central@8a7ad0adcccf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersblassey
bugs775722
milestone17.0a1
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
Bug 775722 - Reposition the text selection handles during pan/zoom operations. r=blassey
mobile/android/base/TextSelection.java
mobile/android/base/TextSelectionHandle.java
mobile/android/base/gfx/LayerController.java
mobile/android/chrome/content/browser.js
--- a/mobile/android/base/TextSelection.java
+++ b/mobile/android/base/TextSelection.java
@@ -1,24 +1,31 @@
 /* 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;
 
 import android.util.Log;
 import android.view.View;
+import org.mozilla.gecko.gfx.Layer;
+import org.mozilla.gecko.gfx.Layer.RenderContext;
+import org.mozilla.gecko.gfx.LayerController;
 import org.json.JSONObject;
 
-class TextSelection implements GeckoEventListener {
+class TextSelection extends Layer implements GeckoEventListener {
     private static final String LOGTAG = "GeckoTextSelection";
 
     private final TextSelectionHandle mStartHandle;
     private final TextSelectionHandle mEndHandle;
 
+    private float mViewLeft;
+    private float mViewTop;
+    private float mViewZoom;
+
     TextSelection(TextSelectionHandle startHandle, TextSelectionHandle endHandle) {
         mStartHandle = startHandle;
         mEndHandle = endHandle;
 
         // Only register listeners if we have valid start/end handles
         if (mStartHandle == null || mEndHandle == null) {
             Log.e(LOGTAG, "Failed to initialize text selection because at least one handle is null");
         } else {
@@ -36,21 +43,34 @@ class TextSelection implements GeckoEven
 
     public void handleMessage(String event, JSONObject message) {
         try {
             if (event.equals("TextSelection:ShowHandles")) {
                 GeckoApp.mAppContext.mMainHandler.post(new Runnable() {
                     public void run() {
                         mStartHandle.setVisibility(View.VISIBLE);
                         mEndHandle.setVisibility(View.VISIBLE);
+
+                        mViewLeft = 0.0f;
+                        mViewTop = 0.0f;
+                        mViewZoom = 0.0f;
+                        LayerController layerController = GeckoApp.mAppContext.getLayerController();
+                        if (layerController != null) {
+                            layerController.getView().addLayer(TextSelection.this);
+                        }
                     }
                 });
             } else if (event.equals("TextSelection:HideHandles")) {
                 GeckoApp.mAppContext.mMainHandler.post(new Runnable() {
                     public void run() {
+                        LayerController layerController = GeckoApp.mAppContext.getLayerController();
+                        if (layerController != null) {
+                            layerController.getView().removeLayer(TextSelection.this);
+                        }
+
                         mStartHandle.setVisibility(View.GONE);
                         mEndHandle.setVisibility(View.GONE);
                     }
                 });
             } else if (event.equals("TextSelection:PositionHandles")) {
                 final int startLeft = message.getInt("startLeft");
                 final int startTop = message.getInt("startTop");
                 final int endLeft = message.getInt("endLeft");
@@ -62,9 +82,31 @@ class TextSelection implements GeckoEven
                         mEndHandle.positionFromGecko(endLeft, endTop);
                     }
                 });
             }
         } catch (Exception e) {
             Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
         }
     }
+
+    @Override
+    public void draw(final RenderContext context) {
+        // cache the relevant values from the context and bail out if they are the same. we do this
+        // because this draw function gets called a lot (once per compositor frame) and we want to
+        // avoid doing a lot of extra work in cases where it's not needed.
+        if (FloatUtils.fuzzyEquals(mViewLeft, context.viewport.left)
+                && FloatUtils.fuzzyEquals(mViewTop, context.viewport.top)
+                && FloatUtils.fuzzyEquals(mViewZoom, context.zoomFactor)) {
+            return;
+        }
+        mViewLeft = context.viewport.left;
+        mViewTop = context.viewport.top;
+        mViewZoom = context.zoomFactor;
+
+        GeckoApp.mAppContext.mMainHandler.post(new Runnable() {
+            public void run() {
+                mStartHandle.repositionWithViewport(context.viewport.left, context.viewport.top, context.zoomFactor);
+                mEndHandle.repositionWithViewport(context.viewport.left, context.viewport.top, context.zoomFactor);
+            }
+        });
+    }
 }
--- a/mobile/android/base/TextSelectionHandle.java
+++ b/mobile/android/base/TextSelectionHandle.java
@@ -8,31 +8,33 @@ import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.PointF;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.widget.RelativeLayout;
 import android.widget.ImageView;
+import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.gfx.LayerController;
 import org.json.JSONObject;
 
 class TextSelectionHandle extends ImageView implements View.OnTouchListener {
     private static final String LOGTAG = "GeckoTextSelectionHandle";
 
     private enum HandleType { START, END }; 
 
     private final HandleType mHandleType;
     private final int mWidth;
     private final int mHeight;
     private final int mShadow;
 
     private int mLeft;
     private int mTop;
+    private PointF mGeckoPoint;
     private int mTouchStartX;
     private int mTouchStartY;
 
     private RelativeLayout.LayoutParams mLayoutParams;
 
     TextSelectionHandle(Context context, AttributeSet attrs) {
         super(context, attrs);
         setOnTouchListener(this);
@@ -103,21 +105,28 @@ class TextSelectionHandle extends ImageV
     }
 
     void positionFromGecko(int left, int top) {
         LayerController layerController = GeckoApp.mAppContext.getLayerController();
         if (layerController == null) {
             Log.e(LOGTAG, "Can't position handle because layerController is null");
             return;
         }
-        PointF geckoPoint = new PointF((float) left, (float) top);
-        geckoPoint = layerController.convertLayerPointToViewPoint(geckoPoint);
+
+        mGeckoPoint = new PointF((float) left, (float) top);
+        ImmutableViewportMetrics metrics = layerController.getViewportMetrics();
+        repositionWithViewport(metrics.viewportRectLeft, metrics.viewportRectTop, metrics.zoomFactor);
+    }
 
-        mLeft = Math.round(geckoPoint.x) - (mHandleType.equals(HandleType.START) ? mWidth - mShadow : mShadow);
-        mTop = Math.round(geckoPoint.y);
+    void repositionWithViewport(float x, float y, float zoom) {
+        PointF viewPoint = new PointF((mGeckoPoint.x * zoom) - x,
+                                      (mGeckoPoint.y * zoom) - y);
+
+        mLeft = Math.round(viewPoint.x) - (mHandleType.equals(HandleType.START) ? mWidth - mShadow : mShadow);
+        mTop = Math.round(viewPoint.y);
 
         setLayoutPosition();
     }
 
     private void setLayoutPosition() {
         if (mLayoutParams == null) {
             mLayoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
             // Set negative right/bottom margins so that the handles can be dragged outside of
--- a/mobile/android/base/gfx/LayerController.java
+++ b/mobile/android/base/gfx/LayerController.java
@@ -295,38 +295,16 @@ public class LayerController {
         // the current Gecko coordinate in CSS pixels.
         PointF layerPoint = new PointF(
                 ((viewPoint.x + origin.x) / zoom) - (geckoOrigin.x / geckoZoom),
                 ((viewPoint.y + origin.y) / zoom) - (geckoOrigin.y / geckoZoom));
 
         return layerPoint;
     }
 
-    /**
-     * Does the opposite of convertViewPointToLayerPoint.
-     */
-    public PointF convertLayerPointToViewPoint(PointF layerPoint) {
-        if (mLayerClient == null) {
-            return null;
-        }
-
-        ImmutableViewportMetrics viewportMetrics = mViewportMetrics;
-        PointF origin = viewportMetrics.getOrigin();
-        float zoom = viewportMetrics.zoomFactor;
-        ViewportMetrics geckoViewport = mLayerClient.getGeckoViewportMetrics();
-        PointF geckoOrigin = geckoViewport.getOrigin();
-        float geckoZoom = geckoViewport.getZoomFactor();
-
-        PointF viewPoint = new PointF(
-                ((layerPoint.x + (geckoOrigin.x / geckoZoom)) * zoom - origin.x),
-                ((layerPoint.y + (geckoOrigin.y / geckoZoom)) * zoom - origin.y));
-
-        return viewPoint;
-    }
-
     /** Retrieves whether we should show checkerboard checks or not. */
     public boolean checkerboardShouldShowChecks() {
         return mCheckerboardShouldShowChecks;
     }
 
     /** Retrieves the color that the checkerboard should be. */
     public int getCheckerboardColor() {
         return mCheckerboardColor;
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -1560,20 +1560,18 @@ var SelectionHandler = {
       case "Tab:Selected":
       case "Window:Resize": {
         // Knowing when the page is done drawing is hard, so let's just cancel
         // the selection when the window changes. We should fix this later.
         this.endSelection();
         break;
       }
       case "after-viewport-change": {
-        // Update the cache and reposition the handles after the viewport
-        // changes (e.g. panning, zooming).
+        // Update the cache after the viewport changes (e.g. panning, zooming).
         this.updateCacheForSelection();
-        this.positionHandles();
         break;
       }
       case "TextSelection:Move": {
         let data = JSON.parse(aData);
         this.moveSelection(data.handleType == this.HANDLE_TYPE_START, data.x, data.y);
         break;
       }
       case "TextSelection:Position": {
@@ -1866,23 +1864,25 @@ var SelectionHandler = {
 
     return selectionReversed;
   },
 
   positionHandles: function sh_positionHandles() {
     // Translate coordinates to account for selections in sub-frames. We can't cache
     // this because the top-level page may have scrolled since selection started.
     let offset = this._getViewOffset();
+    let scrollX = {}, scrollY = {};
+    this._view.top.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).getScrollXY(false, scrollX, scrollY);
     sendMessageToJava({
       gecko: {
         type: "TextSelection:PositionHandles",
-        startLeft: this.cache.start.x + offset.x,
-        startTop: this.cache.start.y + offset.y,
-        endLeft: this.cache.end.x + offset.x,
-        endTop: this.cache.end.y + offset.y
+        startLeft: this.cache.start.x + offset.x + scrollX.value,
+        startTop: this.cache.start.y + offset.y + scrollY.value,
+        endLeft: this.cache.end.x + offset.x + scrollX.value,
+        endTop: this.cache.end.y + offset.y + scrollY.value
       }
     });
   },
 
   showHandles: function sh_showHandles() {
     this.positionHandles();
 
     sendMessageToJava({