зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
0127aa97c4
Коммит
85d70f0acf
|
@ -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>
|
||||
* <form>
|
||||
* <input type="text" placeholder="username">
|
||||
* <input type="password" placeholder="password">
|
||||
* <input type="submit" value="submit">
|
||||
* </form>
|
||||
* </code></pre>
|
||||
*
|
||||
* With the document parsed and the login input fields identified, GeckoView
|
||||
* dispatches a
|
||||
* <code>LoginStorage.Delegate.onLoginFetch("example.com")</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");
|
||||
|
|
Загрузка…
Ссылка в новой задаче