Bug 1577003 - Move autofill stuff from TextInputDelegate into AutofillDelegate r=geckoview-reviewers,agi,esawin
authorJames Willcox <snorp@snorp.net>
Fri, 04 Oct 2019 17:54:36 +0000
changeset 496363 6967ce1ef236215386d51f8a84d985a6cb9abb8f
parent 496362 64abb85617adb11b7f4634aab4e50b8c408f5dea
child 496364 8be3fae908128e6aac63b72a795ffb19836b741c
push id36652
push userbtara@mozilla.com
push dateSat, 05 Oct 2019 09:47:12 +0000
treeherdermozilla-central@30154d163aca [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgeckoview-reviewers, agi, esawin
bugs1577003
milestone71.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 1577003 - Move autofill stuff from TextInputDelegate into AutofillDelegate r=geckoview-reviewers,agi,esawin This also moves `autofill()` and `provideAutofillVirtualStructure()` into `GeckoSession`. Differential Revision: https://phabricator.services.mozilla.com/D47481
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/Callbacks.kt
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/AutofillSupport.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionTextInput.java
--- 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
@@ -249,21 +249,21 @@ class ContentDelegateTest : BaseSessionT
 
     // TextInputDelegateTest is parameterized, so we put this test under ContentDelegateTest.
     @SdkSuppress(minSdkVersion = 23)
     @Test fun autofill() {
         // Test parts of the Oreo auto-fill API; there is another autofill test in
         // SessionAccessibility for a11y auto-fill support.
         mainSession.loadTestPath(FORMS_HTML_PATH)
         // Wait for the auto-fill nodes to populate.
-        sessionRule.waitUntilCalled(object : Callbacks.TextInputDelegate {
+        sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
             // For the root document and the iframe document, each has a form group and
             // a group for inputs outside of forms, so the total count is 4.
             @AssertCalled(count = 4)
-            override fun notifyAutoFill(session: GeckoSession, notification: Int, virtualId: Int) {
+            override fun onAutofill(session: GeckoSession, notification: Int, virtualId: Int) {
             }
         })
 
         val autoFills = mapOf(
                 "#user1" to "bar", "#user2" to "bar") +
                 if (Build.VERSION.SDK_INT >= 26) mapOf(
                         "#pass1" to "baz", "#pass2" to "baz", "#email1" to "a@b.c",
                         "#number1" to "24", "#tel1" to "42")
@@ -356,20 +356,20 @@ class ContentDelegateTest : BaseSessionT
                     InputType.TYPE_CLASS_PHONE -> "42"
                     else -> "bar"
                 })
             }
         }
 
         val rootStructure = MockViewNode()
 
-        mainSession.textInput.onProvideAutofillVirtualStructure(rootStructure, 0)
+        mainSession.provideAutofillVirtualStructure(null, rootStructure, 0)
         checkAutoFillChild(rootStructure)
 
-        mainSession.textInput.autofill(autoFillValues)
+        mainSession.autofill(autoFillValues)
 
         // Wait on the promises and check for correct values.
         for ((key, actual, expected, eventInterface) in promises.map { it.value.asJSList<String>() }) {
             assertThat("Auto-filled value must match", actual, equalTo(expected))
 
             // <input type=number> elements don't get InputEvent events.
             if (key == "#number1") {
                 assertThat("input type=number event should be dispatched with Event interface", eventInterface, equalTo("Event"))
@@ -385,111 +385,111 @@ class ContentDelegateTest : BaseSessionT
     // and firing the autofill event.
     // XXX(agi): This shouldn't be necessary though? What if the page doesn't scroll?
     @WithDisplay(width = 10, height = 10)
     @Test fun autoFill_navigation() {
         fun countAutoFillNodes(cond: (MockViewNode) -> Boolean =
                                        { it.className == "android.widget.EditText" },
                                root: MockViewNode? = null): Int {
             val node = if (root !== null) root else MockViewNode().also {
-                mainSession.textInput.onProvideAutofillVirtualStructure(it, 0)
+                mainSession.provideAutofillVirtualStructure(null, it, 0)
             }
 
             return (if (cond(node)) 1 else 0) +
                     (if (node.childCount > 0) node.children.sumBy {
                         countAutoFillNodes(cond, it) } else 0)
         }
 
         // Wait for the accessibility nodes to populate.
         mainSession.loadTestPath(FORMS_HTML_PATH)
-        sessionRule.waitUntilCalled(object : Callbacks.TextInputDelegate {
+        sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
             @AssertCalled(count = 4)
-            override fun notifyAutoFill(session: GeckoSession, notification: Int, virtualId: Int) {
+            override fun onAutofill(session: GeckoSession, notification: Int, virtualId: Int) {
                 assertThat("Should be starting auto-fill", notification, equalTo(forEachCall(
-                        GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_STARTED,
-                        GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_ADDED)))
+                        GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_STARTED,
+                        GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_ADDED)))
                 assertThat("ID should be valid", virtualId, not(equalTo(View.NO_ID)))
             }
         })
         assertThat("Initial auto-fill count should match",
                    countAutoFillNodes(), equalTo(14))
 
         // Now wait for the nodes to clear.
         mainSession.loadTestPath(HELLO_HTML_PATH)
-        sessionRule.waitUntilCalled(object : Callbacks.TextInputDelegate {
+        sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
             @AssertCalled(count = 1)
-            override fun notifyAutoFill(session: GeckoSession, notification: Int, virtualId: Int) {
+            override fun onAutofill(session: GeckoSession, notification: Int, virtualId: Int) {
                 assertThat("Should be canceling auto-fill",
                            notification,
-                           equalTo(GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_CANCELED))
+                           equalTo(GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_CANCELED))
                 assertThat("ID should be valid", virtualId, equalTo(View.NO_ID))
             }
         })
         assertThat("Should not have auto-fill fields",
                    countAutoFillNodes(), equalTo(0))
 
         // Now wait for the nodes to reappear.
         mainSession.waitForPageStop()
         mainSession.goBack()
-        sessionRule.waitUntilCalled(object : Callbacks.TextInputDelegate {
+        sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
             @AssertCalled(count = 4)
-            override fun notifyAutoFill(session: GeckoSession, notification: Int, virtualId: Int) {
+            override fun onAutofill(session: GeckoSession, notification: Int, virtualId: Int) {
                 assertThat("Should be starting auto-fill", notification, equalTo(forEachCall(
-                        GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_STARTED,
-                        GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_ADDED)))
+                        GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_STARTED,
+                        GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_ADDED)))
                 assertThat("ID should be valid", virtualId, not(equalTo(View.NO_ID)))
             }
         })
         assertThat("Should have auto-fill fields again",
                    countAutoFillNodes(), equalTo(14))
         assertThat("Should not have focused field",
                    countAutoFillNodes({ it.isFocused }), equalTo(0))
 
         mainSession.evaluateJS("document.querySelector('#pass2').focus()")
-        sessionRule.waitUntilCalled(object : Callbacks.TextInputDelegate {
+        sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
             @AssertCalled(count = 1)
-            override fun notifyAutoFill(session: GeckoSession, notification: Int, virtualId: Int) {
+            override fun onAutofill(session: GeckoSession, notification: Int, virtualId: Int) {
                 assertThat("Should be entering auto-fill view",
                            notification,
-                           equalTo(GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_ENTERED))
+                           equalTo(GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_ENTERED))
                 assertThat("ID should be valid", virtualId, not(equalTo(View.NO_ID)))
             }
         })
         assertThat("Should have one focused field",
                    countAutoFillNodes({ it.isFocused }), equalTo(1))
         // The focused field, its siblings, its parent, and the root node should be visible.
         assertThat("Should have seven visible nodes",
                    countAutoFillNodes({ node -> node.width > 0 && node.height > 0 }),
                    equalTo(7))
 
         mainSession.evaluateJS("document.querySelector('#pass2').blur()")
-        sessionRule.waitUntilCalled(object : Callbacks.TextInputDelegate {
+        sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
             @AssertCalled(count = 1)
-            override fun notifyAutoFill(session: GeckoSession, notification: Int, virtualId: Int) {
+            override fun onAutofill(session: GeckoSession, notification: Int, virtualId: Int) {
                 assertThat("Should be exiting auto-fill view",
                            notification,
-                           equalTo(GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_EXITED))
+                           equalTo(GeckoSession.AutofillDelegate.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)
     @Test fun autofill_userpass() {
         if (Build.VERSION.SDK_INT < 26) {
             return
         }
 
         mainSession.loadTestPath(FORMS2_HTML_PATH)
         // Wait for the auto-fill nodes to populate.
-        sessionRule.waitUntilCalled(object : Callbacks.TextInputDelegate {
+        sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
             @AssertCalled(count = 1)
-            override fun notifyAutoFill(session: GeckoSession, notification: Int, virtualId: Int) {
+            override fun onAutofill(session: GeckoSession, notification: Int, virtualId: Int) {
             }
         })
 
         // Perform auto-fill and return number of auto-fills performed.
         fun checkAutoFillChild(child: MockViewNode): Int {
             var sum = 0
             // Seal the node info instance so we can perform actions on it.
             if (child.children.size > 0) {
@@ -519,17 +519,17 @@ class ContentDelegateTest : BaseSessionT
                     }
                 }
             }
             return sum
         }
 
         val rootStructure = MockViewNode()
 
-        mainSession.textInput.onProvideAutofillVirtualStructure(rootStructure, 0)
+        mainSession.provideAutofillVirtualStructure(null, rootStructure, 0)
         // form and iframe have each 2 hints.
         assertThat("autofill hint count",
                    checkAutoFillChild(rootStructure), equalTo(4))
     }
 
     class MockViewNode : ViewStructure() {
         private var mClassName: String? = null
         private var mEnabled = false
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/Callbacks.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/Callbacks.kt
@@ -18,22 +18,23 @@ import org.mozilla.geckoview.WebRequestE
 import android.view.inputmethod.CursorAnchorInfo
 import android.view.inputmethod.ExtractedText
 import android.view.inputmethod.ExtractedTextRequest
 import org.json.JSONObject
 
 class Callbacks private constructor() {
     object Default : All
 
-    interface All : ContentBlockingDelegate, ContentDelegate,
+    interface All : AutofillDelegate, ContentBlockingDelegate, ContentDelegate,
                     HistoryDelegate, MediaDelegate,
                     NavigationDelegate, PermissionDelegate, ProgressDelegate,
                     PromptDelegate, ScrollDelegate, SelectionActionDelegate,
                     TextInputDelegate
 
+    interface AutofillDelegate : GeckoSession.AutofillDelegate {}
     interface ContentDelegate : GeckoSession.ContentDelegate {}
     interface NavigationDelegate : GeckoSession.NavigationDelegate {}
     interface PermissionDelegate : GeckoSession.PermissionDelegate {}
     interface ProgressDelegate : GeckoSession.ProgressDelegate {}
     interface PromptDelegate : GeckoSession.PromptDelegate {}
     interface ScrollDelegate : GeckoSession.ScrollDelegate {}
     interface ContentBlockingDelegate : ContentBlocking.Delegate {}
     interface SelectionActionDelegate : GeckoSession.SelectionActionDelegate {}
@@ -53,13 +54,10 @@ class Callbacks private constructor() {
         override fun updateSelection(session: GeckoSession, selStart: Int, selEnd: Int, compositionStart: Int, compositionEnd: Int) {
         }
 
         override fun updateExtractedText(session: GeckoSession, request: ExtractedTextRequest, text: ExtractedText) {
         }
 
         override fun updateCursorAnchorInfo(session: GeckoSession, info: CursorAnchorInfo) {
         }
-
-        override fun notifyAutoFill(session: GeckoSession, notification: Int, virtualId: Int) {
-        }
     }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/AutofillSupport.java
@@ -0,0 +1,383 @@
+package org.mozilla.geckoview;
+
+import android.annotation.TargetApi;
+import android.graphics.Rect;
+import android.os.Build;
+import android.support.annotation.AnyThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.UiThread;
+import android.text.InputType;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewStructure;
+
+import org.mozilla.gecko.util.BundleEventListener;
+import org.mozilla.gecko.util.EventCallback;
+import org.mozilla.gecko.util.GeckoBundle;
+
+import java.util.Locale;
+
+/* package */ class AutofillSupport {
+    private static final String LOGTAG = "AutofillSupport";
+    private static final boolean DEBUG = false;
+
+    private final GeckoSession mSession;
+    private GeckoSession.AutofillDelegate mDelegate;
+    private SparseArray<GeckoBundle> mAutoFillNodes;
+    private SparseArray<EventCallback> mAutoFillRoots;
+    private int mAutoFillFocusedId = View.NO_ID;
+    private int mAutoFillFocusedRoot = View.NO_ID;
+
+    public AutofillSupport(final GeckoSession session) {
+        mSession = session;
+
+        session.getEventDispatcher().registerUiThreadListener(
+                new BundleEventListener() {
+                    @Override
+                    public void handleMessage(final String event, final GeckoBundle message,
+                                              final EventCallback callback) {
+                        if ("GeckoView:AddAutoFill".equals(event)) {
+                            addAutoFill(message, callback);
+                        } else if ("GeckoView:ClearAutoFill".equals(event)) {
+                            clearAutoFill();
+                        } else if ("GeckoView:OnAutoFillFocus".equals(event)) {
+                            onAutoFillFocus(message);
+                        }
+                    }
+                },
+                "GeckoView:AddAutoFill",
+                "GeckoView:ClearAutoFill",
+                "GeckoView:OnAutoFillFocus",
+                null);
+    }
+
+    /**
+     * Perform auto-fill using the specified values.
+     *
+     * @param values Map of auto-fill IDs to values.
+     */
+    public void autofill(final SparseArray<CharSequence> values) {
+        if (mAutoFillRoots == null) {
+            return;
+        }
+
+        GeckoBundle response = null;
+        EventCallback callback = null;
+
+        for (int i = 0; i < values.size(); i++) {
+            final int id = values.keyAt(i);
+            final CharSequence value = values.valueAt(i);
+
+            if (DEBUG) {
+                Log.d(LOGTAG, "performAutoFill(" + id + ')');
+            }
+            int rootId = id;
+            for (int currentId = id; currentId != View.NO_ID; ) {
+                final GeckoBundle bundle = mAutoFillNodes.get(currentId);
+                if (bundle == null) {
+                    return;
+                }
+                rootId = currentId;
+                currentId = bundle.getInt("parent", View.NO_ID);
+            }
+
+            final EventCallback newCallback = mAutoFillRoots.get(rootId);
+            if (callback == null || newCallback != callback) {
+                if (callback != null) {
+                    callback.sendSuccess(response);
+                }
+                response = new GeckoBundle(values.size() - i);
+                callback = newCallback;
+            }
+            response.putString(String.valueOf(id), String.valueOf(value));
+        }
+
+        if (callback != null) {
+            callback.sendSuccess(response);
+        }
+    }
+
+    public void setDelegate(final GeckoSession.AutofillDelegate delegate) {
+        mDelegate = delegate;
+    }
+
+    public GeckoSession.AutofillDelegate getDelegate() {
+        return mDelegate;
+    }
+
+
+    /* package */ void addAutoFill(@NonNull final GeckoBundle message,
+                                   @NonNull final EventCallback callback) {
+        final boolean initializing;
+        if (mAutoFillRoots == null) {
+            mAutoFillRoots = new SparseArray<>();
+            mAutoFillNodes = new SparseArray<>();
+            initializing = true;
+        } else {
+            initializing = false;
+        }
+
+        final int id = message.getInt("id");
+        if (DEBUG) {
+            Log.d(LOGTAG, "addAutoFill(" + id + ')');
+        }
+        mAutoFillRoots.append(id, callback);
+        populateAutofillNodes(message);
+
+        if (mDelegate == null) {
+            return;
+        }
+
+        if (initializing) {
+            mDelegate.onAutofill(
+                    mSession, GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_STARTED, id);
+        } else {
+            mDelegate.onAutofill(
+                    mSession, GeckoSession.AutofillDelegate.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;
+        mAutoFillNodes = null;
+        mAutoFillFocusedId = View.NO_ID;
+        mAutoFillFocusedRoot = View.NO_ID;
+
+        if (mDelegate != null) {
+            mDelegate.onAutofill(
+                    mSession, GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_CANCELED, View.NO_ID);
+        }
+    }
+
+    /* package */ void onAutoFillFocus(@Nullable final GeckoBundle message) {
+        if (mAutoFillRoots == null) {
+            return;
+        }
+
+        final int id;
+        final int root;
+        if (message != null) {
+            id = message.getInt("id");
+            root = message.getInt("root");
+        } else {
+            id = root = View.NO_ID;
+        }
+
+        if (DEBUG) {
+            Log.d(LOGTAG, "onAutoFillFocus(" + id + ')');
+        }
+        if (mAutoFillFocusedId == id) {
+            return;
+        }
+
+        if (mDelegate != null && mAutoFillFocusedId != View.NO_ID) {
+            mDelegate.onAutofill(
+                    mSession, GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_EXITED,
+                    mAutoFillFocusedId);
+        }
+
+        mAutoFillFocusedId = id;
+        mAutoFillFocusedRoot = root;
+
+        if (mDelegate != null && mAutoFillFocusedId != View.NO_ID) {
+            mDelegate.onAutofill(
+                    mSession, GeckoSession.AutofillDelegate.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)
+    @UiThread
+    public void provideAutofillVirtualStructure(@Nullable final View view,
+                                                @NonNull final ViewStructure structure,
+                                                final int flags) {
+        if (view != null) {
+            structure.setClassName(view.getClass().getName());
+        }
+        structure.setEnabled(true);
+        structure.setVisibility(View.VISIBLE);
+
+        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();
+        structure.setChildCount(size);
+
+        for (int i = 0; i < size; i++) {
+            final int id = mAutoFillRoots.keyAt(i);
+            final GeckoBundle root = mAutoFillNodes.get(id);
+            fillAutoFillStructure(view, id, root, structure.newChild(i), rect);
+        }
+    }
+
+    @TargetApi(23)
+    private void fillAutoFillStructure(@Nullable final View view, final int id,
+                                       @NonNull final GeckoBundle bundle,
+                                       @NonNull final ViewStructure structure,
+                                       @NonNull final Rect rect) {
+        if (mAutoFillRoots == null) {
+            return;
+        }
+
+        if (DEBUG) {
+            Log.d(LOGTAG, "fillAutoFillStructure(" + id + ')');
+        }
+
+        if (Build.VERSION.SDK_INT >= 26) {
+            if (view != null) {
+                structure.setAutofillId(view.getAutofillId(), id);
+            }
+            structure.setWebDomain(bundle.getString("origin"));
+        }
+        structure.setId(id, null, null, null);
+
+        if (mAutoFillFocusedRoot != View.NO_ID &&
+                mAutoFillFocusedRoot == bundle.getInt("root", View.NO_ID)) {
+            structure.setDimens(0, 0, 0, 0, rect.width(), rect.height());
+        }
+
+        final GeckoBundle[] children = bundle.getBundleArray("children");
+        if (children != null) {
+            structure.setChildCount(children.length);
+            for (int i = 0; i < children.length; i++) {
+                final GeckoBundle childBundle = children[i];
+                final int childId = childBundle.getInt("id");
+                final ViewStructure childStructure = structure.newChild(i);
+                fillAutoFillStructure(view, childId, childBundle, childStructure, rect);
+                mAutoFillNodes.append(childId, childBundle);
+            }
+        }
+
+        String tag = bundle.getString("tag", "");
+        final String type = bundle.getString("type", "text");
+
+        if (Build.VERSION.SDK_INT >= 26) {
+            final GeckoBundle attrs = bundle.getBundle("attributes");
+            final ViewStructure.HtmlInfo.Builder builder =
+                    structure.newHtmlInfoBuilder(tag.toLowerCase(Locale.US));
+            for (final String key : attrs.keys()) {
+                builder.addAttribute(key, String.valueOf(attrs.get(key)));
+            }
+            structure.setHtmlInfo(builder.build());
+        }
+
+        if ("INPUT".equals(tag) && !bundle.getBoolean("editable", false)) {
+            tag = ""; // Don't process non-editable inputs (e.g. type="button").
+        }
+        switch (tag) {
+            case "INPUT":
+            case "TEXTAREA": {
+                final boolean disabled = bundle.getBoolean("disabled");
+                structure.setClassName("android.widget.EditText");
+                structure.setEnabled(!disabled);
+                structure.setFocusable(!disabled);
+                structure.setFocused(id == mAutoFillFocusedId);
+                structure.setVisibility(View.VISIBLE);
+
+                if (Build.VERSION.SDK_INT >= 26) {
+                    structure.setAutofillType(View.AUTOFILL_TYPE_TEXT);
+                }
+                break;
+            }
+            default:
+                if (children != null) {
+                    structure.setClassName("android.view.ViewGroup");
+                } else {
+                    structure.setClassName("android.view.View");
+                }
+                break;
+        }
+
+        if (Build.VERSION.SDK_INT >= 26 && "INPUT".equals(tag)) {
+            // LastPass will fill password to the feild that setAutofillHints is unset and setInputType is set.
+            switch (type) {
+                case "email":
+                    structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_EMAIL_ADDRESS });
+                    structure.setInputType(InputType.TYPE_CLASS_TEXT |
+                                           InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS);
+                    break;
+                case "number":
+                    structure.setInputType(InputType.TYPE_CLASS_NUMBER);
+                    break;
+                case "password":
+                    structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_PASSWORD });
+                    structure.setInputType(InputType.TYPE_CLASS_TEXT |
+                                           InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD);
+                    break;
+                case "tel":
+                    structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_PHONE });
+                    structure.setInputType(InputType.TYPE_CLASS_PHONE);
+                    break;
+                case "url":
+                    structure.setInputType(InputType.TYPE_CLASS_TEXT |
+                                           InputType.TYPE_TEXT_VARIATION_URI);
+                    break;
+                case "text":
+                    final String autofillhint = bundle.getString("autofillhint", "");
+                    if (autofillhint.equals("username")) {
+                        structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_USERNAME });
+                        structure.setInputType(InputType.TYPE_CLASS_TEXT |
+                                               InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
+                    }
+                    break;
+            }
+        }
+    }
+
+    /* 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) {
+            if (view == null) {
+                throw new IllegalArgumentException();
+            }
+            final int[] offset = new int[2];
+            view.getLocationOnScreen(offset);
+            rect.offset(offset[0], offset[1]);
+        }
+        return rect;
+    }
+
+    public void onScreenMetricsUpdated() {
+        if (mDelegate != null && mAutoFillFocusedId != View.NO_ID) {
+            getDelegate().onAutofill(
+                    mSession, GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_ENTERED, mAutoFillFocusedId);
+        }
+    }
+}
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -29,16 +29,17 @@ import org.mozilla.gecko.IGeckoEditableP
 import org.mozilla.gecko.mozglue.JNIObject;
 import org.mozilla.gecko.NativeQueue;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.IntentUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
+import android.annotation.TargetApi;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.net.Uri;
@@ -53,20 +54,24 @@ import android.support.annotation.IntDef
 import android.support.annotation.LongDef;
 import android.support.annotation.Nullable;
 import android.support.annotation.NonNull;
 import android.support.annotation.StringDef;
 import android.support.annotation.UiThread;
 import android.util.Base64;
 import android.util.Log;
 import android.util.LongSparseArray;
+import android.util.SparseArray;
 import android.view.Surface;
 import android.view.inputmethod.CursorAnchorInfo;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
+import android.view.View;
+import android.view.ViewStructure;
+import android.view.autofill.AutofillManager;
 
 public class GeckoSession implements Parcelable {
     private static final String LOGTAG = "GeckoSession";
     private static final boolean DEBUG = false;
 
     // Type of changes given to onWindowChanged.
     // Window has been cleared due to the session being closed.
     private static final int WINDOW_CLOSE = 0;
@@ -116,16 +121,17 @@ public class GeckoSession implements Par
 
     private boolean mShouldPinOnScreen;
 
     // All fields are accessed on UI thread only.
     private PanZoomController mPanZoomController = new PanZoomController(this);
     private OverscrollEdgeEffect mOverscroll;
     private DynamicToolbarAnimator mToolbar;
     private CompositorController mController;
+    private AutofillSupport mAutofillSupport;
 
     private boolean mAttachedCompositor;
     private boolean mCompositorReady;
     private Surface mSurface;
 
     // All fields of coordinates are in screen units.
     private int mLeft;
     private int mTop; // Top of the surface (including toolbar);
@@ -1518,17 +1524,17 @@ public class GeckoSession implements Par
         onWindowChanged(WINDOW_CLOSE, /* inProgress */ false);
     }
 
     private void onWindowChanged(final int change, final boolean inProgress) {
         if ((change == WINDOW_OPEN || change == WINDOW_TRANSFER_IN) && !inProgress) {
             mTextInput.onWindowChanged(mWindow);
         }
         if ((change == WINDOW_CLOSE || change == WINDOW_TRANSFER_OUT) && !inProgress) {
-            mTextInput.clearAutoFill();
+            getAutofillSupport().clearAutoFill();
         }
     }
 
     /**
      * Get the SessionTextInput instance for this session. May be called on any thread.
      *
      * @return SessionTextInput instance.
      */
@@ -5098,70 +5104,23 @@ public class GeckoSession implements Par
          * Consequently, this method is <i>not</i> called in viewless mode.
          *
          * @param session Session instance.
          * @param info Cursor-anchor information.
          */
         @UiThread
         default void updateCursorAnchorInfo(@NonNull GeckoSession session,
                                             @NonNull CursorAnchorInfo info) {}
-
-        /** An auto-fill session has started, usually as a result of loading a page. */
-        int AUTO_FILL_NOTIFY_STARTED = 0;
-        /** An auto-fill session has been committed, usually as a result of submitting a form. */
-        int AUTO_FILL_NOTIFY_COMMITTED = 1;
-        /** An auto-fill session has been canceled, usually as a result of unloading a page. */
-        int AUTO_FILL_NOTIFY_CANCELED = 2;
-        /** A view within the auto-fill session has been added. */
-        int AUTO_FILL_NOTIFY_VIEW_ADDED = 3;
-        /** A view within the auto-fill session has been removed. */
-        int AUTO_FILL_NOTIFY_VIEW_REMOVED = 4;
-        /** A view within the auto-fill session has been updated (e.g. change in state). */
-        int AUTO_FILL_NOTIFY_VIEW_UPDATED = 5;
-        /** A view within the auto-fill session has gained focus. */
-        int AUTO_FILL_NOTIFY_VIEW_ENTERED = 6;
-        /** A view within the auto-fill session has lost focus. */
-        int AUTO_FILL_NOTIFY_VIEW_EXITED = 7;
-
-        /**
-         * Notify that an auto-fill event has occurred. The default implementation forwards the
-         * notification to the system {@link android.view.autofill.AutofillManager}. This method is
-         * only called on Android 6.0 and above, and it is called in viewless mode as well.
-         *
-         * @param session Session instance.
-         * @param notification Notification type as one of the {@link #AUTO_FILL_NOTIFY_STARTED
-         *                     AUTO_FILL_NOTIFY_*} constants.
-         * @param virtualId Virtual ID of the target, or {@link android.view.View#NO_ID} if not
-         *                  applicable. The ID matches one of the virtual IDs provided by {@link
-         *                  SessionTextInput#onProvideAutofillVirtualStructure} and can be used
-         *                  with {@link SessionTextInput#autofill}.
-         */
-        @UiThread
-        default void notifyAutoFill(@NonNull GeckoSession session,
-                                    @AutoFillNotification int notification,
-                                    int virtualId) {}
     }
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({TextInputDelegate.RESTART_REASON_FOCUS, TextInputDelegate.RESTART_REASON_BLUR,
             TextInputDelegate.RESTART_REASON_CONTENT_CHANGE})
             /* package */ @interface RestartReason {}
 
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({
-            TextInputDelegate.AUTO_FILL_NOTIFY_STARTED,
-            TextInputDelegate.AUTO_FILL_NOTIFY_COMMITTED,
-            TextInputDelegate.AUTO_FILL_NOTIFY_CANCELED,
-            TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_ADDED,
-            TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_REMOVED,
-            TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_UPDATED,
-            TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_ENTERED,
-            TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_EXITED})
-    /* package */ @interface AutoFillNotification {}
-
     /* package */ void onSurfaceChanged(final Surface surface, final int x, final int y, final int width,
                                         final int height) {
         ThreadUtils.assertOnUiThread();
 
         mOffsetX = x;
         mOffsetY = y;
         mWidth = width;
         mHeight = height;
@@ -5370,17 +5329,17 @@ public class GeckoSession implements Par
         if (DEBUG) {
             ThreadUtils.assertOnUiThread();
         }
 
         mViewportLeft = scrollX;
         mViewportTop = scrollY;
         mViewportZoom = zoom;
 
-        mTextInput.onScreenMetricsUpdated();
+        getAutofillSupport().onScreenMetricsUpdated();
     }
 
     /* protected */ void onWindowBoundsChanged() {
         if (DEBUG) {
             ThreadUtils.assertOnUiThread();
         }
 
         final int toolbarHeight;
@@ -5623,9 +5582,103 @@ public class GeckoSession implements Par
                 HistoryDelegate.VISIT_TOP_LEVEL,
                 HistoryDelegate.VISIT_REDIRECT_TEMPORARY,
                 HistoryDelegate.VISIT_REDIRECT_PERMANENT,
                 HistoryDelegate.VISIT_REDIRECT_SOURCE,
                 HistoryDelegate.VISIT_REDIRECT_SOURCE_PERMANENT,
                 HistoryDelegate.VISIT_UNRECOVERABLE_ERROR
             })
     /* package */ @interface VisitFlags {}
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            AutofillDelegate.AUTO_FILL_NOTIFY_STARTED,
+            AutofillDelegate.AUTO_FILL_NOTIFY_COMMITTED,
+            AutofillDelegate.AUTO_FILL_NOTIFY_CANCELED,
+            AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_ADDED,
+            AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_REMOVED,
+            AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_UPDATED,
+            AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_ENTERED,
+            AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_EXITED})
+    /* package */ @interface AutofillNotification {}
+
+    public interface AutofillDelegate {
+
+        /** An auto-fill session has started, usually as a result of loading a page. */
+        int AUTO_FILL_NOTIFY_STARTED = 0;
+        /** An auto-fill session has been committed, usually as a result of submitting a form. */
+        int AUTO_FILL_NOTIFY_COMMITTED = 1;
+        /** An auto-fill session has been canceled, usually as a result of unloading a page. */
+        int AUTO_FILL_NOTIFY_CANCELED = 2;
+        /** A view within the auto-fill session has been added. */
+        int AUTO_FILL_NOTIFY_VIEW_ADDED = 3;
+        /** A view within the auto-fill session has been removed. */
+        int AUTO_FILL_NOTIFY_VIEW_REMOVED = 4;
+        /** A view within the auto-fill session has been updated (e.g. change in state). */
+        int AUTO_FILL_NOTIFY_VIEW_UPDATED = 5;
+        /** A view within the auto-fill session has gained focus. */
+        int AUTO_FILL_NOTIFY_VIEW_ENTERED = 6;
+        /** A view within the auto-fill session has lost focus. */
+        int AUTO_FILL_NOTIFY_VIEW_EXITED = 7;
+
+        /**
+         * Notify that an auto-fill event has occurred. The default implementation forwards the
+         * notification to the system {@link AutofillManager}. This method is
+         * only called on Android 6.0 and above, and it is called in viewless mode as well.
+         *
+         * @param session Session instance.
+         * @param notification Notification type as one of the {@link #AUTO_FILL_NOTIFY_STARTED
+         *                     AUTO_FILL_NOTIFY_*} constants.
+         * @param virtualId Virtual ID of the target, or {@link View#NO_ID} if not
+         *                  applicable. The ID matches one of the virtual IDs provided by {@link
+         *                  GeckoSession#getAutofillElements()} and can be used
+         *                  with {@link GeckoSession#autofill}.
+         */
+        @UiThread
+        default void onAutofill(@NonNull GeckoSession session,
+                                @GeckoSession.AutofillNotification int notification,
+                                int virtualId) {}
+    }
+
+    private AutofillSupport getAutofillSupport() {
+        if (mAutofillSupport == null) {
+            mAutofillSupport = new AutofillSupport(this);
+        }
+
+        return mAutofillSupport;
+    }
+
+    /**
+     * Sets the autofill delegate for this session.
+     *
+     * @param delegate An instance of {@link AutofillDelegate}.
+     */
+    @UiThread
+    public void setAutofillDelegate(final @Nullable AutofillDelegate delegate) {
+        getAutofillSupport().setDelegate(delegate);
+    }
+
+    /**
+     * @return The current {@link AutofillDelegate} for this session, if any.
+     */
+    @UiThread
+    public @Nullable AutofillDelegate getAutofillDelegate() {
+        return getAutofillSupport().getDelegate();
+    }
+
+    /**
+     * Perform auto-fill using the specified values.
+     *
+     * @param values Map of auto-fill IDs to values.
+     */
+    @UiThread
+    public void autofill(final @NonNull SparseArray<CharSequence> values) {
+        getAutofillSupport().autofill(values);
+    }
+
+    @TargetApi(23)
+    @UiThread
+    public void provideAutofillVirtualStructure(@Nullable final View view,
+                                                @NonNull final ViewStructure structure,
+                                                final int flags) {
+        getAutofillSupport().provideAutofillVirtualStructure(view, structure, flags);
+    }
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
@@ -709,17 +709,17 @@ public class GeckoView extends FrameLayo
     }
 
     @Override
     public void onProvideAutofillVirtualStructure(final ViewStructure structure,
                                                   final int flags) {
         super.onProvideAutofillVirtualStructure(structure, flags);
 
         if (mSession != null) {
-            mSession.getTextInput().onProvideAutofillVirtualStructure(structure, flags);
+            mSession.provideAutofillVirtualStructure(this, structure, flags);
         }
     }
 
     @Override
     @TargetApi(26)
     public void autofill(@NonNull final SparseArray<AutofillValue> values) {
         super.autofill(values);
 
@@ -729,17 +729,17 @@ public class GeckoView extends FrameLayo
         final SparseArray<CharSequence> strValues = new SparseArray<>(values.size());
         for (int i = 0; i < values.size(); i++) {
             final AutofillValue value = values.valueAt(i);
             if (value.isText()) {
                 // Only text is currently supported.
                 strValues.put(values.keyAt(i), value.getTextValue());
             }
         }
-        mSession.getTextInput().autofill(strValues);
+        mSession.autofill(strValues);
     }
 
     /**
      * Request a {@link Bitmap} of the visible portion of the web page currently being
      * rendered.
      *
      * See {@link GeckoDisplay#capturePixels} for more details.
      *
--- 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,54 +1,41 @@
 /* -*- 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;
 import android.text.Editable;
-import android.text.InputType;
 import android.util.Log;
-import android.util.SparseArray;
 import android.view.KeyEvent;
 import android.view.View;
-import android.view.ViewStructure;
-import android.view.autofill.AutofillManager;
 import android.view.inputmethod.CursorAnchorInfo;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
 
-import java.util.Locale;
+import org.mozilla.gecko.IGeckoEditableParent;
+import org.mozilla.gecko.InputMethods;
+import org.mozilla.gecko.NativeQueue;
+import org.mozilla.gecko.annotation.WrapForJNI;
+import org.mozilla.gecko.util.ActivityUtils;
+import org.mozilla.gecko.util.ThreadUtils;
 
 /**
  * {@code SessionTextInput} handles text input for {@code GeckoSession} through key events or input
  * methods. It is typically used to implement certain methods in {@link android.view.View}
  * such as {@link android.view.View#onCreateInputConnection}, by forwarding such calls to
  * corresponding methods in {@code SessionTextInput}.
  * <p>
  * For full functionality, {@code SessionTextInput} requires a {@link android.view.View} to be set
@@ -233,124 +220,29 @@ public final class SessionTextInput {
                                            @NonNull final CursorAnchorInfo info) {
             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,
-                                   @GeckoSession.AutoFillNotification final int notification,
-                                   final int virtualId) {
-            ThreadUtils.assertOnUiThread();
-            final View view = session.getTextInput().getView();
-            if (Build.VERSION.SDK_INT < 26 || view == null) {
-                return;
-            }
-
-            final AutofillManager manager =
-                    view.getContext().getSystemService(AutofillManager.class);
-            if (manager == null) {
-                return;
-            }
-
-            switch (notification) {
-                case AUTO_FILL_NOTIFY_STARTED:
-                    // This line seems necessary for auto-fill to work on the initial page.
-                    manager.cancel();
-                    break;
-                case AUTO_FILL_NOTIFY_COMMITTED:
-                    manager.commit();
-                    break;
-                case AUTO_FILL_NOTIFY_CANCELED:
-                    manager.cancel();
-                    break;
-                case AUTO_FILL_NOTIFY_VIEW_ENTERED:
-                    manager.notifyViewEntered(view, virtualId, displayRectForId(session, virtualId, view));
-                    break;
-                case AUTO_FILL_NOTIFY_VIEW_EXITED:
-                    manager.notifyViewExited(view, virtualId);
-                    break;
-            }
-        }
     }
 
     private final GeckoSession mSession;
     private final NativeQueue mQueue;
     private final GeckoEditable mEditable;
     private InputConnectionClient mInputConnection;
     private GeckoSession.TextInputDelegate mDelegate;
-    // Auto-fill nodes.
-    private SparseArray<GeckoBundle> mAutoFillNodes;
-    private SparseArray<EventCallback> mAutoFillRoots;
-    private int mAutoFillFocusedId = View.NO_ID;
-    private int mAutoFillFocusedRoot = View.NO_ID;
 
     /* package */ SessionTextInput(final @NonNull GeckoSession session,
                                    final @NonNull NativeQueue queue) {
         mSession = session;
         mQueue = queue;
         mEditable = new GeckoEditable(session);
-
-        session.getEventDispatcher().registerUiThreadListener(
-                new BundleEventListener() {
-                    @Override
-                    public void handleMessage(final String event, final GeckoBundle message,
-                                              final EventCallback callback) {
-                        if ("GeckoView:AddAutoFill".equals(event)) {
-                            addAutoFill(message, callback);
-                        } else if ("GeckoView:ClearAutoFill".equals(event)) {
-                            clearAutoFill();
-                        } else if ("GeckoView:OnAutoFillFocus".equals(event)) {
-                            onAutoFillFocus(message);
-                        }
-                    }
-                },
-                "GeckoView:AddAutoFill",
-                "GeckoView:ClearAutoFill",
-                "GeckoView:OnAutoFillFocus",
-                null);
     }
 
     /* package */ void onWindowChanged(final GeckoSession.Window window) {
         if (mQueue.isReady()) {
             window.attachEditable(mEditable);
         } else {
             mQueue.queueUntilReady(window, "attachEditable",
                                    IGeckoEditableParent.class, mEditable);
@@ -524,319 +416,9 @@ public final class SessionTextInput {
     @UiThread
     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)
-    @UiThread
-    public void onProvideAutofillVirtualStructure(@NonNull final ViewStructure structure,
-                                                  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, 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();
-        structure.setChildCount(size);
-
-        for (int i = 0; i < size; i++) {
-            final int id = mAutoFillRoots.keyAt(i);
-            final GeckoBundle root = mAutoFillNodes.get(id);
-            fillAutoFillStructure(view, id, root, structure.newChild(i), rect);
-        }
-    }
-
-    /**
-     * Perform auto-fill using the specified values.
-     *
-     * @param values Map of auto-fill IDs to values.
-     */
-    @UiThread
-    public void autofill(final @NonNull SparseArray<CharSequence> values) {
-        if (mAutoFillRoots == null) {
-            return;
-        }
-
-        GeckoBundle response = null;
-        EventCallback callback = null;
-
-        for (int i = 0; i < values.size(); i++) {
-            final int id = values.keyAt(i);
-            final CharSequence value = values.valueAt(i);
-
-            if (DEBUG) {
-                Log.d(LOGTAG, "performAutoFill(" + id + ')');
-            }
-            int rootId = id;
-            for (int currentId = id; currentId != View.NO_ID; ) {
-                final GeckoBundle bundle = mAutoFillNodes.get(currentId);
-                if (bundle == null) {
-                    return;
-                }
-                rootId = currentId;
-                currentId = bundle.getInt("parent", View.NO_ID);
-            }
-
-            final EventCallback newCallback = mAutoFillRoots.get(rootId);
-            if (callback == null || newCallback != callback) {
-                if (callback != null) {
-                    callback.sendSuccess(response);
-                }
-                response = new GeckoBundle(values.size() - i);
-                callback = newCallback;
-            }
-            response.putString(String.valueOf(id), String.valueOf(value));
-        }
-
-        if (callback != null) {
-            callback.sendSuccess(response);
-        }
-    }
-
-    @TargetApi(23)
-    private void fillAutoFillStructure(@Nullable final View view, final int id,
-                                       @NonNull final GeckoBundle bundle,
-                                       @NonNull final ViewStructure structure,
-                                       @NonNull final Rect rect) {
-        if (mAutoFillRoots == null) {
-            return;
-        }
-
-        if (DEBUG) {
-            Log.d(LOGTAG, "fillAutoFillStructure(" + id + ')');
-        }
-
-        if (Build.VERSION.SDK_INT >= 26) {
-            if (view != null) {
-                structure.setAutofillId(view.getAutofillId(), id);
-            }
-            structure.setWebDomain(bundle.getString("origin"));
-        }
-        structure.setId(id, null, null, null);
-
-        if (mAutoFillFocusedRoot != View.NO_ID &&
-                mAutoFillFocusedRoot == bundle.getInt("root", View.NO_ID)) {
-            structure.setDimens(0, 0, 0, 0, rect.width(), rect.height());
-        }
-
-        final GeckoBundle[] children = bundle.getBundleArray("children");
-        if (children != null) {
-            structure.setChildCount(children.length);
-            for (int i = 0; i < children.length; i++) {
-                final GeckoBundle childBundle = children[i];
-                final int childId = childBundle.getInt("id");
-                final ViewStructure childStructure = structure.newChild(i);
-                fillAutoFillStructure(view, childId, childBundle, childStructure, rect);
-                mAutoFillNodes.append(childId, childBundle);
-            }
-        }
-
-        String tag = bundle.getString("tag", "");
-        final String type = bundle.getString("type", "text");
-
-        if (Build.VERSION.SDK_INT >= 26) {
-            final GeckoBundle attrs = bundle.getBundle("attributes");
-            final ViewStructure.HtmlInfo.Builder builder =
-                    structure.newHtmlInfoBuilder(tag.toLowerCase(Locale.US));
-            for (final String key : attrs.keys()) {
-                builder.addAttribute(key, String.valueOf(attrs.get(key)));
-            }
-            structure.setHtmlInfo(builder.build());
-        }
-
-        if ("INPUT".equals(tag) && !bundle.getBoolean("editable", false)) {
-            tag = ""; // Don't process non-editable inputs (e.g. type="button").
-        }
-        switch (tag) {
-            case "INPUT":
-            case "TEXTAREA": {
-                final boolean disabled = bundle.getBoolean("disabled");
-                structure.setClassName("android.widget.EditText");
-                structure.setEnabled(!disabled);
-                structure.setFocusable(!disabled);
-                structure.setFocused(id == mAutoFillFocusedId);
-                structure.setVisibility(View.VISIBLE);
-
-                if (Build.VERSION.SDK_INT >= 26) {
-                    structure.setAutofillType(View.AUTOFILL_TYPE_TEXT);
-                }
-                break;
-            }
-            default:
-                if (children != null) {
-                    structure.setClassName("android.view.ViewGroup");
-                } else {
-                    structure.setClassName("android.view.View");
-                }
-                break;
-        }
-
-        if (Build.VERSION.SDK_INT >= 26 && "INPUT".equals(tag)) {
-            // LastPass will fill password to the feild that setAutofillHints is unset and setInputType is set.
-            switch (type) {
-                case "email":
-                    structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_EMAIL_ADDRESS });
-                    structure.setInputType(InputType.TYPE_CLASS_TEXT |
-                                              InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS);
-                    break;
-                case "number":
-                    structure.setInputType(InputType.TYPE_CLASS_NUMBER);
-                    break;
-                case "password":
-                    structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_PASSWORD });
-                    structure.setInputType(InputType.TYPE_CLASS_TEXT |
-                                           InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD);
-                    break;
-                case "tel":
-                    structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_PHONE });
-                    structure.setInputType(InputType.TYPE_CLASS_PHONE);
-                    break;
-                case "url":
-                    structure.setInputType(InputType.TYPE_CLASS_TEXT |
-                                           InputType.TYPE_TEXT_VARIATION_URI);
-                    break;
-                case "text":
-                    final String autofillhint = bundle.getString("autofillhint", "");
-                    if (autofillhint.equals("username")) {
-                        structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_USERNAME });
-                        structure.setInputType(InputType.TYPE_CLASS_TEXT |
-                                               InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
-                    }
-                    break;
-            }
-        }
-    }
-
-    /* package */ void addAutoFill(@NonNull final GeckoBundle message,
-                                   @NonNull final EventCallback callback) {
-        if (Build.VERSION.SDK_INT < 23) {
-            return;
-        }
-
-        final boolean initializing;
-        if (mAutoFillRoots == null) {
-            mAutoFillRoots = new SparseArray<>();
-            mAutoFillNodes = new SparseArray<>();
-            initializing = true;
-        } else {
-            initializing = false;
-        }
-
-        final int id = message.getInt("id");
-        if (DEBUG) {
-            Log.d(LOGTAG, "addAutoFill(" + id + ')');
-        }
-        mAutoFillRoots.append(id, callback);
-        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;
-        mAutoFillNodes = null;
-        mAutoFillFocusedId = View.NO_ID;
-        mAutoFillFocusedRoot = View.NO_ID;
-
-        getDelegate().notifyAutoFill(
-                mSession, GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_CANCELED, View.NO_ID);
-    }
-
-    /* package */ void onAutoFillFocus(@Nullable final GeckoBundle message) {
-        if (mAutoFillRoots == null) {
-            return;
-        }
-
-        final int id;
-        final int root;
-        if (message != null) {
-            id = message.getInt("id");
-            root = message.getInt("root");
-        } else {
-            id = root = View.NO_ID;
-        }
-
-        if (DEBUG) {
-            Log.d(LOGTAG, "onAutoFillFocus(" + id + ')');
-        }
-        if (mAutoFillFocusedId == id) {
-            return;
-        }
-        if (mAutoFillFocusedId != View.NO_ID) {
-            getDelegate().notifyAutoFill(
-                    mSession, GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_EXITED,
-                    mAutoFillFocusedId);
-        }
-
-        mAutoFillFocusedId = id;
-        mAutoFillFocusedRoot = root;
-    }
-
-    /* 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) {
-            if (view == null) {
-                throw new IllegalArgumentException();
-            }
-            final int[] offset = new int[2];
-            view.getLocationOnScreen(offset);
-            rect.offset(offset[0], offset[1]);
-        }
-        return rect;
-    }
 }