Bug 1599873 - [2.2] Implement Login Storage Prompt API for save requests. r=snorp,geckoview-reviewers,agi

Differential Revision: https://phabricator.services.mozilla.com/D57012

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Eugen Sawin 2019-12-20 16:26:56 +00:00
Родитель 0127aa97c4
Коммит 85d70f0acf
4 изменённых файлов: 226 добавлений и 5 удалений

Просмотреть файл

@ -1233,6 +1233,8 @@ class LoginStorageDelegate {
const loginInfo = LoginEntry.fromBundle(result.login).toLoginInfo();
Services.obs.notifyObservers(loginInfo, "passwordmgr-prompt-save");
GeckoViewLoginStorage.onLoginSave(result.login);
}
);
}

Просмотреть файл

@ -1048,7 +1048,7 @@ public class GeckoSession implements Parcelable {
}
/**
* Get the current prompt delegate for this GeckoSession.
* Get the current permission delegate for this GeckoSession.
* @return PermissionDelegate instance or null if using default delegate.
*/
@UiThread
@ -2593,7 +2593,8 @@ public class GeckoSession implements Parcelable {
final String mode = message.getString("mode");
final String title = message.getString("title");
final String msg = message.getString("msg");
GeckoResult<PromptDelegate.PromptResponse> res;
GeckoResult<PromptDelegate.PromptResponse> res = null;
switch (type) {
case "alert": {
final PromptDelegate.AlertPrompt prompt =
@ -2716,6 +2717,29 @@ public class GeckoSession implements Parcelable {
res = delegate.onSharePrompt(session, prompt);
break;
}
case "loginStorage": {
final int lsType = message.getInt("lsType");
final int hint = message.getInt("hint");
final GeckoBundle[] loginBundles =
message.getBundleArray("logins");
if (loginBundles == null) {
break;
}
final LoginStorage.LoginEntry[] logins =
new LoginStorage.LoginEntry[loginBundles.length];
for (int i = 0; i < logins.length; ++i) {
logins[i] = new LoginStorage.LoginEntry(loginBundles[i]);
}
final PromptDelegate.LoginStoragePrompt prompt =
new PromptDelegate.LoginStoragePrompt(lsType, hint, logins);
res = delegate.onLoginStoragePrompt(session, prompt);
break;
}
default: {
callback.sendError("Invalid type");
return;
@ -4612,6 +4636,101 @@ public class GeckoSession implements Parcelable {
}
}
/**
* LoginStoragePrompt contains the information necessary to handle a
* login storage request.
*/
public class LoginStoragePrompt extends BasePrompt {
@Retention(RetentionPolicy.SOURCE)
@IntDef({ Type.SAVE })
/* package */ @interface LoginStorageType {}
// Sync with LoginStorageDelegate.Type in GeckoViewPrompt.js
/**
* Possible type of a {@link LoginStoragePrompt}.
*/
public static class Type {
public static final int SAVE = 1;
protected Type() {}
}
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true,
value = { Hint.NONE })
/* package */ @interface LoginStorageHint {}
public static class Hint {
public static final int NONE = 0;
protected Hint() {}
}
/**
* The type of the prompt request, one of {@link #Type}.
*/
public final @LoginStorageType int type;
/**
* The hint may provide some additional information on the nature or
* confidence level of the prompt request to support appropriate
* prompting styles. A flag combination of {@link #Hint}.
*/
public final @LoginStorageHint int hint;
/**
* The logins that are subject to the prompt request.
* For {@link Type#SAVE}, it holds one
* {@link LoginStorage.LoginEntry} depicting the entry to be saved.
*/
public final @NonNull LoginStorage.LoginEntry[] logins;
protected LoginStoragePrompt(
final @LoginStorageType int type,
final @LoginStorageHint int hint,
final @NonNull LoginStorage.LoginEntry[] logins) {
super(null);
this.type = type;
this.hint = hint;
this.logins = logins;
}
/**
* Confirm the prompt by responding with a
* {@link LoginStorage.LoginEntry}.
* For {@link Type#SAVE}, confirm with the entry to be saved. This
* can be the original entry as provided by logins[0] or a modified
* entry based on that.
* Confirming a {@link Type#SAVE} prompt request triggers a
* {@link LoginStorage.Delegate#onLoginSave} request in the runtime
* delegate with the confirmed login entry.
*
* @param login A {@link LoginStorage.LoginEntry} specifying the
* login entry saved.
*
* @return A {@link PromptResponse} which can be used to complete the
* {@link GeckoResult} associated with this prompt.
*/
@UiThread
public @NonNull PromptResponse confirm(
final @NonNull LoginStorage.LoginEntry login) {
ensureResult().putBundle("login", login.toBundle());
return super.confirm();
}
/**
* Dismisses the prompt.
*
* @return A {@link PromptResponse} which can be used to complete the
* {@link GeckoResult} associated with this prompt.
*/
@UiThread
public @NonNull PromptResponse dismiss() {
return super.dismiss();
}
}
// Delegate functions.
/**
* Display an alert prompt.
@ -4765,6 +4884,24 @@ public class GeckoSession implements Parcelable {
@NonNull final SharePrompt prompt) {
return null;
}
/**
* Handle a login storage prompt.
* This is triggered by the user entering new or modified login
* credentials into a login form.
*
* @param session GeckoSession that triggered the prompt.
* @param prompt The {@link LoginStoragePrompt} that describes the prompt.
*
* @return A {@link GeckoResult} resolving to a {@link PromptResponse}
* which includes all necessary information to resolve the prompt.
*/
@UiThread
default @Nullable GeckoResult<PromptResponse> onLoginStoragePrompt(
@NonNull final GeckoSession session,
@NonNull final LoginStoragePrompt prompt) {
return null;
}
}
/**

Просмотреть файл

@ -21,6 +21,56 @@ import org.mozilla.gecko.util.GeckoBundle;
* The Login Storage API provides a storage-level delegate to leverage Gecko's
* complete range of heuristics for login forms, autofill and autocomplete
* scenarios.
*
* Examples
*
* Autofill/Fetch API
*
* GeckoView loads <code>https://example.com</code> which contains (for the
* purpose of this example) elements resembling a login form, e.g.,
* <pre><code>
* &lt;form&gt;
* &lt;input type=&quot;text&quot; placeholder=&quot;username&quot;&gt;
* &lt;input type=&quot;password&quot; placeholder=&quot;password&quot;&gt;
* &lt;input type=&quot;submit&quot; value=&quot;submit&quot;&gt;
* &lt;/form&gt;
* </code></pre>
*
* 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.
*
* Save API
*
* The user enters login credentials in some login input fields and commits
* 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
* <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
* the prompt request (<code>prompt.logins[0]</code>) or a new or modified login
* entry.
*
* The login entry returned in a confirmed save prompt is used to request for
* saving in the runtime delegate via
* <code>LoginStorage.Delegate.onLoginSave(login)</code>.
* If the app has already stored the entry during the prompt request handling,
* it may ignore this storage saving request.
*
* @see GeckoRuntime#setLoginStorageDelegate
* @see GeckoSession#setPromptDelegate
* @see GeckoSession.PromptDelegate#onLoginStoragePrompt
*/
public class LoginStorage {
private static final String LOGTAG = "LoginStorage";
@ -244,12 +294,26 @@ public class LoginStorage {
@NonNull String domain) {
return null;
}
/**
* Request saving or updating of the given login entry.
* This is triggered by confirming a
* {@link GeckoSession.PromptDelegate.onLoginStoragePrompt onLoginStoragePrompt}
* 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) {}
}
/* 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 @Nullable Delegate mDelegate;
@ -258,13 +322,15 @@ public class LoginStorage {
private void registerListener() {
EventDispatcher.getInstance().registerUiThreadListener(
this,
FETCH_EVENT);
FETCH_EVENT,
SAVE_EVENT);
}
private void unregisterListener() {
EventDispatcher.getInstance().unregisterUiThreadListener(
this,
FETCH_EVENT);
FETCH_EVENT,
SAVE_EVENT);
}
public synchronized void setDelegate(final @Nullable Delegate delegate) {
@ -291,7 +357,9 @@ public class LoginStorage {
}
if (mDelegate == null) {
callback.sendError("No LoginStorage delegate attached");
if (callback != null) {
callback.sendError("No LoginStorage delegate attached");
}
return;
}
@ -321,6 +389,11 @@ 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);
}
}
}

Просмотреть файл

@ -111,6 +111,15 @@ const GeckoViewLoginStorage = {
domain: aDomain,
});
},
onLoginSave(aLogin) {
debug`onLoginSave ${aLogin}`;
EventDispatcher.instance.sendRequest({
type: "GeckoView:LoginStorage:Save",
login: aLogin,
});
},
};
const { debug } = GeckoViewUtils.initLogging("GeckoViewLoginStorage");