Bug 1610353 - [1.0] Add LoginStorage onLoginUsed API. r=snorp,geckoview-reviewers,MattN,agi
authorEugen Sawin <esawin@mozilla.com>
Thu, 23 Jan 2020 20:03:40 +0000
changeset 511528 d45cb482844208fa91c7f821bfe398c2def06592
parent 511527 919eb56fa57449a5ab4325bebba11b98d71ef944
child 511529 1152d253beb2046e3d829899d79ce5f00f9dfe64
push id37049
push userrmaries@mozilla.com
push dateFri, 24 Jan 2020 03:50:24 +0000
treeherdermozilla-central@e05793f68994 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp, geckoview-reviewers, MattN, agi
bugs1610353
milestone74.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 1610353 - [1.0] Add LoginStorage onLoginUsed API. r=snorp,geckoview-reviewers,MattN,agi Differential Revision: https://phabricator.services.mozilla.com/D60440
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/LoginStorage.java
mobile/android/modules/geckoview/GeckoViewLoginStorage.jsm
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/LoginStorage.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/LoginStorage.java
@@ -1,17 +1,21 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * vim: ts=4 sw=4 expandtab:
  * 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 java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 import android.support.annotation.AnyThread;
+import android.support.annotation.IntDef;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.UiThread;
 import android.util.Log;
 
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
@@ -39,23 +43,37 @@ import org.mozilla.gecko.util.GeckoBundl
  * With the document parsed and the login input fields identified, GeckoView
  * dispatches a
  * <code>LoginStorage.Delegate.onLoginFetch(&quot;example.com&quot;)</code>
  * request to fetch logins for the given domain.
  *
  * Based on the provided login entries, GeckoView will attempt to autofill the
  * login input fields.
  *
+ * Update API
+ *
+ * When the user submits some login input fields, GeckoView dispatches another
+ * <code>LoginStorage.Delegate.onLoginFetch(&quot;example.com&quot;)</code>
+ * request to check whether the submitted login exists or whether it's a new or
+ * updated login entry.
+ * If the submitted login is already contained as-is in the collection returned
+ * by <code>onLoginFetch</code>, then GeckoView dispatches
+ * <code>LoginStorage.Delegate.onLoginUsed</code> with the submitted login
+ * entry.
+ * If the submitted login is a new or updated entry, GeckoView dispatches
+ * a sequence of requests to save/update the login entry, see the Save API
+ * example.
+ *
  * Save API
  *
- * The user enters login credentials in some login input fields and commits
- * explicitely (submit action) or by navigation.
+ * The user enters new or updated (password) login credentials in some login
+ * input fields and submits explicitely (submit action) or by navigation.
  * GeckoView identifies the entered credentials and dispatches a
  * <code>GeckoSession.PromptDelegate.onLoginStoragePrompt(session, prompt)</code>
- * with the <code>prompt</code> being of type
+ * request with the <code>prompt</code> being of type
  * <code>LoginStoragePrompt.Type.SAVE</code> and containing the entered
  * credentials.
  *
  * The app may dismiss the prompt request via
  * <code>return GeckoResult.fromValue(prompt.dismiss())</code>
  * which terminates this saving request, or confirm it via
  * <code>return GeckoResult.fromValue(prompt.confirm(login))</code>
  * where <code>login</code> either holds the credentials originally provided by
@@ -264,16 +282,32 @@ public class LoginStorage {
             @AnyThread
             public @NonNull Builder password(final @NonNull String password) {
                 mBundle.putString(PASSWORD_KEY, password);
                 return this;
             }
         }
     }
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true,
+            value = { UsedField.PASSWORD })
+    /* package */ @interface LSUsedField {}
+
+    // Sync with UsedField in GeckoViewLoginStorage.jsm.
+    /**
+     * Possible login entry field types for {@link onLoginUsed}.
+     */
+    public static class UsedField {
+        /**
+         * The password field of a login entry.
+         */
+        public static final int PASSWORD = 1;
+    }
+
     /**
      * Implement this interface to handle runtime login storage requests.
      * Login storage events include login entry requests for autofill and
      * autocompletion of login input fields.
      * This delegate is attached to the runtime via
      * {@link GeckoRuntime#setLoginStorageDelegate}.
      */
     public interface Delegate {
@@ -302,40 +336,58 @@ public class LoginStorage {
          * request of type
          * {@link GeckoSession.PromptDelegate.LoginStoragePrompt.Type#SAVE Type.SAVE}.
          *
          * @param login The {@link LoginEntry} as confirmed by the prompt
          *              request.
          */
         @UiThread
         default void onLoginSave(@NonNull LoginEntry login) {}
+
+        /**
+         * Notify that the given login was used to autofill login input fields.
+         * This is triggered by autofilling elements with unmodified login
+         * entries as provided via {@link #onLoginFetch}.
+         *
+         * @param login The {@link LoginEntry} that was used for the
+         *              autofilling.
+         * @param usedFields The login entry fields used for autofilling.
+         *                   A combination of {@link UsedField}.
+         */
+        @UiThread
+        default void onLoginUsed(
+                @NonNull LoginEntry login,
+                @LSUsedField int usedFields) {}
     }
 
     /* package */ final static class Proxy implements BundleEventListener {
         private static final String LOGTAG = "LoginStorageProxy";
 
         private static final String FETCH_EVENT = "GeckoView:LoginStorage:Fetch";
         private static final String SAVE_EVENT = "GeckoView:LoginStorage:Save";
+        private static final String USED_EVENT = "GeckoView:LoginStorage:Used";
 
         private @Nullable Delegate mDelegate;
 
         public Proxy() {}
 
         private void registerListener() {
             EventDispatcher.getInstance().registerUiThreadListener(
                     this,
                     FETCH_EVENT,
-                    SAVE_EVENT);
+                    SAVE_EVENT,
+                    USED_EVENT);
         }
 
         private void unregisterListener() {
             EventDispatcher.getInstance().unregisterUiThreadListener(
                     this,
                     FETCH_EVENT,
-                    SAVE_EVENT);
+                    SAVE_EVENT,
+                    USED_EVENT);
         }
 
         public synchronized void setDelegate(final @Nullable Delegate delegate) {
             if (mDelegate == null && delegate != null) {
                 registerListener();
             } else if (mDelegate != null && delegate == null) {
                 unregisterListener();
             }
@@ -389,12 +441,18 @@ public class LoginStorage {
                         callback.sendSuccess(loginBundles);
                     },
                     exception -> callback.sendError(exception.getMessage()));
             } else if (SAVE_EVENT.equals(event)) {
                 final GeckoBundle loginBundle = message.getBundle("login");
                 final LoginEntry login = new LoginEntry(loginBundle);
 
                 mDelegate.onLoginSave(login);
+            } else if (USED_EVENT.equals(event)) {
+                final GeckoBundle loginBundle = message.getBundle("login");
+                final LoginEntry login = new LoginEntry(loginBundle);
+                final int fields = message.getInt("usedFields");
+
+                mDelegate.onLoginUsed(login, fields);
             }
         }
     }
 }
--- a/mobile/android/modules/geckoview/GeckoViewLoginStorage.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewLoginStorage.jsm
@@ -85,16 +85,19 @@ class LoginEntry {
     entry.timeLastUsed = aInfo.timeLastUsed;
     entry.timePasswordChanged = aInfo.timePasswordChanged;
     entry.timesUsed = aInfo.timesUsed;
 
     return entry;
   }
 }
 
+// Sync with LoginStorage.Delegate.UsedField in LoginStorage.java.
+const UsedField = { PASSWORD: 1 };
+
 const GeckoViewLoginStorage = {
   /**
    * Delegates login entry fetching for the given domain to the attached
    * LoginStorage GeckoView delegate.
    *
    * @param aDomain
    *        The domain string to fetch login entries for.
    * @return {Promise}
@@ -115,11 +118,21 @@ const GeckoViewLoginStorage = {
   onLoginSave(aLogin) {
     debug`onLoginSave ${aLogin}`;
 
     EventDispatcher.instance.sendRequest({
       type: "GeckoView:LoginStorage:Save",
       login: aLogin,
     });
   },
+
+  onLoginPasswordUsed(aLogin) {
+    debug`onLoginUsed ${aLogin}`;
+
+    EventDispatcher.instance.sendRequest({
+      type: "GeckoView:LoginStorage:Used",
+      usedFields: UsedField.PASSWORD,
+      login: aLogin,
+    });
+  },
 };
 
 const { debug } = GeckoViewUtils.initLogging("GeckoViewLoginStorage");