Bug 1532582 - Display autofill popup in correct location. r=geckoview-reviewers,snorp,kats
☠☠ backed out by 55398270c922 ☠ ☠
authorEmily Toop <etoop@mozilla.com>
Fri, 29 Mar 2019 12:49:26 +0000
changeset 466757 b6f5942c42bb26498c6b11778fceb281d0eb1a46
parent 466756 0151fbb6b9e540d5783208e9e6e2d80d25943a3e
child 466758 98452610cfcc81cba0d4478797fe1e83a51172e8
push id35780
push useropoprus@mozilla.com
push dateFri, 29 Mar 2019 21:53:01 +0000
treeherdermozilla-central@414f37afbe07 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgeckoview-reviewers, snorp, kats
bugs1532582
milestone68.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 1532582 - Display autofill popup in correct location. r=geckoview-reviewers,snorp,kats This autofill popover was being displayed in the incorrect place because the display rect we were providing to the `AutofillManager` was the rect for the `GeckoView` and not the rect for the HTML element that the autofill popover was relating to. 1. Add view dimensions to info passed to autofill in `GeckoViewAutoFill`. 2. Use those view dimensions to calculate the correct location on the screen using `pageToScreenMatrix` in `GeckoSession`. The resulting locations were incorrect, as the values used by `pageToScreenMatrix` were out of date. The `GeckoSession` was only notified about updated metrics during first composite, which meant that when the metrics changed during zoom and scroll on soft keyboard presentation, `GeckoSession` was unaware of it. 3. Update `GeckoSession` with new screen metrics when they change and not only during first composite. Despite this change ensuring that `GeckoSession` always had the correct values for the viewport size and location, the request to provide the autofill location was made before the zoom and scroll was complete, meaning that even then out of date values were used during the calculation. The intial solution was to fire an event once zoom was complete, but despite this event being fired after the new screen size had been calculcated in `AsyncCompositionManager`, `GeckoSession` did not receive the values until after the event had been processed (the calls were out by 0.024ms). 5. Call new method `onScreenMetricsUpdated` inside `SessionTextInput` after screen metrics have been updated. Call `AutofillManager#notifyViewEntered` from this function. This was not my preferred solution to this, but timing issues meant I could not find/think of an alternative way of delaying the calculation of the autofill popover location until after `GeckoSession` had been updated. This patch currently fixes things on GV apps. Occasionally, on Fennec, the autofill view is out of alignment slightly. This needs further work. Differential Revision: https://phabricator.services.mozilla.com/D24397
gfx/layers/composite/AsyncCompositionManager.cpp
gfx/layers/composite/AsyncCompositionManager.h
mobile/android/chrome/geckoview/GeckoViewContentChild.js
mobile/android/geckoview/src/androidTest/assets/www/forms.html
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionTextInput.java
mobile/android/modules/geckoview/GeckoViewAutoFill.jsm
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -1,17 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "mozilla/layers/AsyncCompositionManager.h"
 #include <stdint.h>                 // for uint32_t
-#include "FrameMetrics.h"           // for FrameMetrics
 #include "LayerManagerComposite.h"  // for LayerManagerComposite, etc
 #include "Layers.h"                 // for Layer, ContainerLayer, etc
 #include "gfxPoint.h"               // for gfxPoint, gfxSize
 #include "gfxPrefs.h"               // for gfxPrefs
 #include "mozilla/ServoBindings.h"  // for Servo_AnimationValue_GetOpacity, etc
 #include "mozilla/WidgetUtils.h"    // for ComputeTransformForRotation
 #include "mozilla/gfx/BaseRect.h"   // for BaseRect
 #include "mozilla/gfx/Point.h"      // for RoundedToInt, PointTyped
@@ -1036,28 +1035,35 @@ bool AsyncCompositionManager::ApplyAsync
                    i + 1 >= layer->GetScrollMetadataCount());
               if (*aOutFoundRoot) {
                 mRootScrollableId = metrics.GetScrollId();
                 Compositor* compositor = mLayerManager->GetCompositor();
                 if (CompositorBridgeParent* bridge =
                         compositor->GetCompositorBridgeParent()) {
                   AndroidDynamicToolbarAnimator* animator =
                       bridge->GetAndroidDynamicToolbarAnimator();
-                  if (mIsFirstPaint) {
+
+                  LayersId rootLayerTreeId = bridge->RootLayerTreeId();
+                  if (mIsFirstPaint || FrameMetricsHaveUpdated(metrics)) {
                     if (animator) {
                       animator->UpdateRootFrameMetrics(metrics);
+                    } else if (RefPtr<UiCompositorControllerParent> uiController =
+                              UiCompositorControllerParent::
+                                  GetFromRootLayerTreeId(rootLayerTreeId)) {
+                      uiController->NotifyUpdateScreenMetrics(metrics);
+                    }
+                    mLastMetrics = metrics;
+                  } 
+                  if (mIsFirstPaint) {
+                    if (animator) {
                       animator->FirstPaint();
                     }
-                    LayersId rootLayerTreeId = bridge->RootLayerTreeId();
                     if (RefPtr<UiCompositorControllerParent> uiController =
                             UiCompositorControllerParent::
                                 GetFromRootLayerTreeId(rootLayerTreeId)) {
-                      if (!animator) {
-                        uiController->NotifyUpdateScreenMetrics(metrics);
-                      }
                       uiController->NotifyFirstPaint();
                     }
                     mIsFirstPaint = false;
                   }
                   if (mLayersUpdated) {
                     LayersId rootLayerTreeId = bridge->RootLayerTreeId();
                     if (RefPtr<UiCompositorControllerParent> uiController =
                             UiCompositorControllerParent::
@@ -1226,16 +1232,23 @@ bool AsyncCompositionManager::ApplyAsync
             layers::ScrollbarLayerType::Thumb) {
           ApplyAsyncTransformToScrollbar(layer);
         }
       });
 
   return appliedTransform;
 }
 
+#if defined(MOZ_WIDGET_ANDROID)
+bool AsyncCompositionManager::FrameMetricsHaveUpdated(const FrameMetrics& aMetrics) {
+  return RoundedToInt(mLastMetrics.GetScrollOffset()) != RoundedToInt(aMetrics.GetScrollOffset()) 
+          || mLastMetrics.GetZoom() != aMetrics.GetZoom();;
+}
+#endif
+
 static bool LayerIsScrollbarTarget(const LayerMetricsWrapper& aTarget,
                                    Layer* aScrollbar) {
   if (!aTarget.GetApzc()) {
     return false;
   }
   const FrameMetrics& metrics = aTarget.Metrics();
   MOZ_ASSERT(metrics.IsScrollable());
   if (metrics.GetScrollId() != aScrollbar->GetScrollbarData().mTargetViewId) {
--- a/gfx/layers/composite/AsyncCompositionManager.h
+++ b/gfx/layers/composite/AsyncCompositionManager.h
@@ -3,16 +3,17 @@
 /* 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/. */
 
 #ifndef GFX_ASYNCCOMPOSITIONMANAGER_H
 #define GFX_ASYNCCOMPOSITIONMANAGER_H
 
 #include "Units.h"                                 // for ScreenPoint, etc
+#include "FrameMetrics.h"                          // for FrameMetrics
 #include "mozilla/layers/LayerManagerComposite.h"  // for LayerManagerComposite
 #include "mozilla/Attributes.h"                    // for final, etc
 #include "mozilla/RefPtr.h"                        // for RefCounted
 #include "mozilla/TimeStamp.h"                     // for TimeStamp
 #include "mozilla/gfx/BasePoint.h"                 // for BasePoint
 #include "mozilla/gfx/Matrix.h"                    // for Matrix4x4
 #include "mozilla/HalScreenConfiguration.h"        // For ScreenOrientation
 #include "mozilla/layers/FrameUniformityData.h"    // For FrameUniformityData
@@ -233,16 +234,21 @@ class AsyncCompositionManager final {
 
   MOZ_NON_OWNING_REF CompositorBridgeParent* mCompositorBridge;
 
 #ifdef MOZ_WIDGET_ANDROID
  public:
   void SetFixedLayerMargins(ScreenIntCoord aTop, ScreenIntCoord aBottom);
 
  private:
+  // This calculates whether frame metrics should be sent to Java.
+  bool FrameMetricsHaveUpdated(const FrameMetrics& aMetrics);
+  // This holds the most recent scroll/zoom metrics sent to Java, and is used
+  // to send new updates when it changes.
+  FrameMetrics mLastMetrics;
   // The following two fields are only needed on Fennec with C++ APZ, because
   // then we need to reposition the gecko scrollbar to deal with the
   // dynamic toolbar shifting content around.
   ScrollableLayerGuid::ViewID mRootScrollableId;
   ScreenMargin mFixedLayerMargins;
 #endif
 };
 
--- a/mobile/android/chrome/geckoview/GeckoViewContentChild.js
+++ b/mobile/android/chrome/geckoview/GeckoViewContentChild.js
@@ -160,17 +160,16 @@ class GeckoViewContentChild extends Geck
         break;
 
       case "GeckoView:DOMFullscreenExited":
         if (content) {
           content.windowUtils
                  .exitFullscreen();
         }
         break;
-
       case "GeckoView:ZoomToInput": {
         let dwu = content.windowUtils;
 
         let zoomToFocusedInput = function() {
           if (!dwu.flushApzRepaints()) {
             dwu.zoomToFocusedInput();
             return;
           }
--- a/mobile/android/geckoview/src/androidTest/assets/www/forms.html
+++ b/mobile/android/geckoview/src/androidTest/assets/www/forms.html
@@ -1,20 +1,20 @@
 <html>
     <head>
         <title>Forms</title>
         <meta name="viewport" content="minimum-scale=1,width=device-width">
     </head>
     <body>
         <form>
             <input type="text" id="user1" value="foo">
-            <input type="password" id="pass1", value="foo">
-            <input type="email" id="email1", value="@">
-            <input type="number" id="number1", value="0">
-            <input type="tel" id="tel1", value="0">
+            <input type="password" id="pass1" value="foo">
+            <input type="email" id="email1" value="@">
+            <input type="number" id="number1" value="0">
+            <input type="tel" id="tel1" value="0">
         </form>
         <input type="Text" id="user2" value="foo">
         <input type="PassWord" id="pass2" maxlength="8" value="foo">
         <input type="button" id="button1" value="foo"/>
         <input type="checkbox" id="checkbox1"/>
         <input type="hidden" id="hidden1" value="foo"/>
 
         <iframe id="iframe"></iframe>
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt
@@ -402,16 +402,17 @@ class ContentDelegateTest : BaseSessionT
                            equalTo(GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_EXITED))
                 assertThat("ID should be valid", virtualId, not(equalTo(View.NO_ID)))
             }
         })
         assertThat("Should not have focused field",
                    countAutoFillNodes({ it.isFocused }), equalTo(0))
     }
 
+    @WithDisplay(height = 100, width = 100)
     @WithDevToolsAPI
     @Test fun autofill_userpass() {
         if (Build.VERSION.SDK_INT < 26) {
             return
         }
 
         mainSession.loadTestPath(FORMS2_HTML_PATH)
         // Wait for the auto-fill nodes to populate.
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java
@@ -1442,16 +1442,17 @@ import android.view.inputmethod.EditorIn
         }
 
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 if (DEBUG) {
                     Log.d(LOGTAG, "restartInput(" + reason + ", " + toggleSoftInput + ')');
                 }
+
                 if (toggleSoftInput) {
                     mSoftInputReentrancyGuard.incrementAndGet();
                 }
 
                 final GeckoSession session = mSession.get();
                 if (session != null) {
                     session.getTextInput().getDelegate().restartInput(session, reason);
                 }
@@ -1590,16 +1591,17 @@ import android.view.inputmethod.EditorIn
                     isReentrant = reentrancyGuard > 0;
                 }
 
                 // When using Find In Page, we can still receive notifyIMEContext calls due to the
                 // selection changing when highlighting. However in this case we don't want to
                 // show/hide the keyboard because the find box has the focus and is taking input from
                 // the keyboard.
                 final GeckoSession session = mSession.get();
+
                 if (session == null) {
                     return;
                 }
 
                 final View view = session.getTextInput().getView();
                 final boolean isFocused = (view == null) || view.hasFocus();
 
                 final boolean isUserAction = ((flags &
@@ -1608,25 +1610,19 @@ import android.view.inputmethod.EditorIn
                 if (!force && (isReentrant || !isFocused || !isUserAction)) {
                     if (DEBUG) {
                         Log.d(LOGTAG, "toggleSoftInput: no-op, reentrant=" + isReentrant +
                                 ", focused=" + isFocused + ", user=" + isUserAction);
                     }
                     return;
                 }
                 if (state == SessionTextInput.EditableListener.IME_STATE_DISABLED) {
-                    if (DEBUG) {
-                        Log.d(LOGTAG, "hideSoftInput");
-                    }
                     session.getTextInput().getDelegate().hideSoftInput(session);
                     return;
                 }
-                if (DEBUG) {
-                    Log.d(LOGTAG, "showSoftInput");
-                }
                 session.getEventDispatcher().dispatch("GeckoView:ZoomToInput", null);
                 session.getTextInput().getDelegate().showSoftInput(session);
             }
         });
     }
 
     @Override // IGeckoEditableParent
     public void onSelectionChange(final IBinder token,
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -4593,16 +4593,18 @@ public class GeckoSession implements Par
                                         final float zoom) {
         if (DEBUG) {
             ThreadUtils.assertOnUiThread();
         }
 
         mViewportLeft = scrollX;
         mViewportTop = scrollY;
         mViewportZoom = zoom;
+
+        mTextInput.onScreenMetricsUpdated();
     }
 
     /* protected */ void onWindowBoundsChanged() {
         if (DEBUG) {
             ThreadUtils.assertOnUiThread();
         }
 
         final int toolbarHeight;
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionTextInput.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionTextInput.java
@@ -1,28 +1,30 @@
 /* -*- 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.geckoview;
 
+import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.InputMethods;
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.IGeckoEditableParent;
 import org.mozilla.gecko.NativeQueue;
 import org.mozilla.gecko.util.ActivityUtils;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.annotation.TargetApi;
 import android.app.Activity;
 import android.content.Context;
+import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Build;
 import android.os.Handler;
 import android.support.annotation.AnyThread;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.UiThread;
@@ -240,16 +242,51 @@ public final class SessionTextInput {
             ThreadUtils.assertOnUiThread();
             final View view = session.getTextInput().getView();
             final InputMethodManager imm = getInputMethodManager(view);
             if (imm != null) {
                 imm.updateCursorAnchorInfo(view, info);
             }
         }
 
+        private Rect displayRectForId(@NonNull final GeckoSession session,
+                                      @NonNull final int virtualId,
+                                      @Nullable final View view) {
+            final SessionTextInput textInput = session.getTextInput();
+            Rect contentRect;
+            if (textInput.mAutoFillNodes.indexOfKey(virtualId) >= 0) {
+                GeckoBundle element = textInput.mAutoFillNodes
+                        .get(virtualId);
+                GeckoBundle bounds = element.getBundle("bounds");
+                contentRect = new Rect(bounds.getInt("left"),
+                        bounds.getInt("top"),
+                        bounds.getInt("right"),
+                        bounds.getInt("bottom"));
+
+                final Matrix matrix = new Matrix();
+                final RectF rectF = new RectF(contentRect);
+                if (GeckoAppShell.isFennec()) {
+                    session.getClientToScreenMatrix(matrix);
+                } else {
+                    session.getPageToScreenMatrix(matrix);
+                }
+                matrix.mapRect(rectF);
+                rectF.roundOut(contentRect);
+                if (DEBUG) {
+                    Log.d(LOGTAG, "Displaying autofill rect at (" + contentRect.left + ", " +
+                            contentRect.top + "), (" + contentRect.right + ", " +
+                            contentRect.bottom + ")");
+                }
+            } else {
+                contentRect = getDummyAutoFillRect(session, true, view);
+            }
+
+            return contentRect;
+        }
+
         @Override
         public void notifyAutoFill(@NonNull final GeckoSession session,
                                    @AutoFillNotification final int notification,
                                    final int virtualId) {
             ThreadUtils.assertOnUiThread();
             final View view = session.getTextInput().getView();
             if (Build.VERSION.SDK_INT < 26 || view == null) {
                 return;
@@ -268,19 +305,17 @@ public final class SessionTextInput {
                     break;
                 case AUTO_FILL_NOTIFY_COMMITTED:
                     manager.commit();
                     break;
                 case AUTO_FILL_NOTIFY_CANCELED:
                     manager.cancel();
                     break;
                 case AUTO_FILL_NOTIFY_VIEW_ENTERED:
-                    // Use a dummy rect for the View.
-                    manager.notifyViewEntered(view, virtualId, getDummyAutoFillRect(
-                            session, /* screen */ true, view));
+                    manager.notifyViewEntered(view, virtualId, displayRectForId(session, virtualId, view));
                     break;
                 case AUTO_FILL_NOTIFY_VIEW_EXITED:
                     manager.notifyViewExited(view, virtualId);
                     break;
             }
         }
     }
 
@@ -498,16 +533,23 @@ public final class SessionTextInput {
     public @NonNull GeckoSession.TextInputDelegate getDelegate() {
         ThreadUtils.assertOnUiThread();
         if (mDelegate == null) {
             mDelegate = DefaultDelegate.INSTANCE;
         }
         return mDelegate;
     }
 
+    /*package*/ void onScreenMetricsUpdated() {
+        if (mAutoFillFocusedId != View.NO_ID) {
+            getDelegate().notifyAutoFill(
+                    mSession, GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_ENTERED, mAutoFillFocusedId);
+        }
+    }
+
     /**
      * Fill the specified {@link ViewStructure} with auto-fill fields from the current page.
      *
      * @param structure Structure to be filled.
      * @param flags Zero or a combination of {@link View#AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
      *              AUTOFILL_FLAG_*} constants.
      */
     @TargetApi(23)
@@ -516,18 +558,17 @@ public final class SessionTextInput {
                                                   final int flags) {
         final View view = getView();
         if (view != null) {
             structure.setClassName(view.getClass().getName());
         }
         structure.setEnabled(true);
         structure.setVisibility(View.VISIBLE);
 
-        final Rect rect = getDummyAutoFillRect(mSession, /* screen */ false,
-                                               /* view */ null);
+        final Rect rect = getDummyAutoFillRect(mSession, false, null);
         structure.setDimens(rect.left, rect.top, 0, 0, rect.width(), rect.height());
 
         if (mAutoFillRoots == null) {
             structure.setChildCount(0);
             return;
         }
 
         final int size = mAutoFillRoots.size();
@@ -715,29 +756,41 @@ public final class SessionTextInput {
         } else {
             initializing = false;
         }
 
         final int id = message.getInt("id");
         if (DEBUG) {
             Log.d(LOGTAG, "addAutoFill(" + id + ')');
         }
-
         mAutoFillRoots.append(id, callback);
-        mAutoFillNodes.append(id, message);
+        populateAutofillNodes(message);
 
         if (initializing) {
             getDelegate().notifyAutoFill(
                     mSession, GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_STARTED, id);
         } else {
             getDelegate().notifyAutoFill(
                     mSession, GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_ADDED, id);
         }
     }
 
+    private void populateAutofillNodes(final GeckoBundle bundle) {
+        final int id = bundle.getInt("id");
+
+        mAutoFillNodes.append(id, bundle);
+
+        final GeckoBundle[] children = bundle.getBundleArray("children");
+        if (children != null) {
+            for (GeckoBundle child : children) {
+                populateAutofillNodes(child);
+            }
+        }
+    }
+
     /* package */ void clearAutoFill() {
         if (mAutoFillRoots == null) {
             return;
         }
         if (DEBUG) {
             Log.d(LOGTAG, "clearAutoFill()");
         }
         mAutoFillRoots = null;
@@ -772,20 +825,16 @@ public final class SessionTextInput {
         if (mAutoFillFocusedId != View.NO_ID) {
             getDelegate().notifyAutoFill(
                     mSession, GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_EXITED,
                     mAutoFillFocusedId);
         }
 
         mAutoFillFocusedId = id;
         mAutoFillFocusedRoot = root;
-        if (id != View.NO_ID) {
-            getDelegate().notifyAutoFill(
-                    mSession, GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_ENTERED, id);
-        }
     }
 
     /* package */ static Rect getDummyAutoFillRect(@NonNull final GeckoSession session,
                                                    final boolean screen,
                                                    @Nullable final View view) {
         final Rect rect = new Rect();
         session.getSurfaceBounds(rect);
         if (screen) {
--- a/mobile/android/modules/geckoview/GeckoViewAutoFill.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewAutoFill.jsm
@@ -79,16 +79,17 @@ class GeckoViewAutoFill {
 
     let sendFocusEvent = false;
     const window = aFormLike.rootElement.ownerGlobal;
     const getInfo = (element, parent, root, usernameField) => {
       let info = this._autoFillInfos.get(element);
       if (info) {
         return info;
       }
+      let bounds = element.getBoundingClientRect();
       info = {
         id: ++this._autoFillId,
         parent,
         root,
         tag: element.tagName,
         type: element instanceof window.HTMLInputElement ? element.type : null,
         editable: (element instanceof window.HTMLInputElement) &&
                   ["color", "date", "datetime-local", "email", "month",
@@ -96,16 +97,22 @@ class GeckoViewAutoFill {
                    "time", "url", "week"].includes(element.type),
         disabled: element instanceof window.HTMLInputElement ? element.disabled
                                                              : null,
         attributes: Object.assign({}, ...Array.from(element.attributes)
             .filter(attr => attr.localName !== "value")
             .map(attr => ({[attr.localName]: attr.value}))),
         origin: element.ownerDocument.location.origin,
         autofillhint: "",
+        bounds: {
+          left: bounds.left,
+          top: bounds.top,
+          right: bounds.right,
+          bottom: bounds.bottom,
+        },
       };
 
       if (element === usernameField) {
         info.autofillhint = "username"; // AUTOFILL_HINT_USERNAME
       }
 
       this._autoFillInfos.set(element, info);
       this._autoFillElements.set(info.id, Cu.getWeakReference(element));