зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1591462 - [2.1] Combine autofill mechanics in Java API. r=snorp,geckoview-reviewers,droeh
Differential Revision: https://phabricator.services.mozilla.com/D50631 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
97c8819903
Коммит
0d2c69ca71
|
@ -59,7 +59,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.geckoview.AllowOrDeny;
|
||||
import org.mozilla.geckoview.AutofillElement;
|
||||
import org.mozilla.geckoview.Autofill;
|
||||
import org.mozilla.geckoview.CompositorController;
|
||||
import org.mozilla.geckoview.ContentBlocking;
|
||||
import org.mozilla.geckoview.ContentBlockingController;
|
||||
|
@ -105,28 +105,65 @@ package org.mozilla.geckoview {
|
|||
enum_constant public static final AllowOrDeny DENY;
|
||||
}
|
||||
|
||||
public class AutofillElement {
|
||||
ctor protected AutofillElement();
|
||||
field public static final int HINT_EMAIL_ADDRESS = 0;
|
||||
field public static final int HINT_NONE = -1;
|
||||
field public static final int HINT_PASSWORD = 1;
|
||||
field public static final int HINT_URL = 2;
|
||||
field public static final int HINT_USERNAME = 3;
|
||||
field public static final int INPUT_TYPE_NONE = -1;
|
||||
field public static final int INPUT_TYPE_NUMBER = 1;
|
||||
field public static final int INPUT_TYPE_PHONE = 2;
|
||||
field public static final int INPUT_TYPE_TEXT = 0;
|
||||
field @NonNull public final Map<String,String> attributes;
|
||||
field @NonNull public final Collection<AutofillElement> children;
|
||||
field @NonNull public final Rect dimensions;
|
||||
field @NonNull public final String domain;
|
||||
field public final boolean enabled;
|
||||
field public final boolean focusable;
|
||||
field public final boolean focused;
|
||||
field public final int hint;
|
||||
field public final int id;
|
||||
field public final int inputType;
|
||||
field @NonNull public final String tag;
|
||||
public class Autofill {
|
||||
ctor public Autofill();
|
||||
}
|
||||
|
||||
public static interface Autofill.Delegate {
|
||||
method @UiThread default public void onAutofill(@NonNull GeckoSession, int, @Nullable Autofill.Node);
|
||||
}
|
||||
|
||||
public static final class Autofill.Hint {
|
||||
method @AnyThread @Nullable public static String toString(int);
|
||||
field public static final int EMAIL_ADDRESS = 0;
|
||||
field public static final int NONE = -1;
|
||||
field public static final int PASSWORD = 1;
|
||||
field public static final int URI = 2;
|
||||
field public static final int USERNAME = 3;
|
||||
}
|
||||
|
||||
public static final class Autofill.InputType {
|
||||
method @AnyThread @Nullable public static String toString(int);
|
||||
field public static final int NONE = -1;
|
||||
field public static final int NUMBER = 1;
|
||||
field public static final int PHONE = 2;
|
||||
field public static final int TEXT = 0;
|
||||
}
|
||||
|
||||
public static final class Autofill.Node {
|
||||
method @UiThread public void fillViewStructure(@NonNull View, @NonNull ViewStructure, int);
|
||||
method @AnyThread @Nullable public String getAttribute(@NonNull String);
|
||||
method @AnyThread @NonNull public Map<String,String> getAttributes();
|
||||
method @AnyThread @NonNull public Collection<Autofill.Node> getChildren();
|
||||
method @AnyThread @NonNull public Rect getDimensions();
|
||||
method @AnyThread @NonNull public String getDomain();
|
||||
method @AnyThread public boolean getEnabled();
|
||||
method @AnyThread public boolean getFocusable();
|
||||
method @AnyThread public boolean getFocused();
|
||||
method @AnyThread public int getHint();
|
||||
method @AnyThread public int getId();
|
||||
method @AnyThread public int getInputType();
|
||||
method @AnyThread @NonNull public String getTag();
|
||||
method @AnyThread @NonNull public String getValue();
|
||||
method @AnyThread public boolean getVisible();
|
||||
}
|
||||
|
||||
public static final class Autofill.Notify {
|
||||
method @AnyThread @Nullable public static String toString(int);
|
||||
field public static final int NODE_ADDED = 3;
|
||||
field public static final int NODE_BLURRED = 7;
|
||||
field public static final int NODE_FOCUSED = 6;
|
||||
field public static final int NODE_REMOVED = 4;
|
||||
field public static final int NODE_UPDATED = 5;
|
||||
field public static final int SESSION_CANCELED = 2;
|
||||
field public static final int SESSION_COMMITTED = 1;
|
||||
field public static final int SESSION_STARTED = 0;
|
||||
}
|
||||
|
||||
public static final class Autofill.Session {
|
||||
method @UiThread public void fillViewStructure(@NonNull View, @NonNull ViewStructure, int);
|
||||
method @AnyThread @NonNull public Rect getDefaultDimensions();
|
||||
method @AnyThread @NonNull public Autofill.Node getRoot();
|
||||
}
|
||||
|
||||
@UiThread public class BasicSelectionActionDelegate implements ActionMode.Callback GeckoSession.SelectionActionDelegate {
|
||||
|
@ -541,8 +578,8 @@ package org.mozilla.geckoview {
|
|||
method @AnyThread @NonNull public static String createDataUri(@NonNull String, @Nullable String);
|
||||
method @AnyThread public void exitFullScreen();
|
||||
method @UiThread @NonNull public SessionAccessibility getAccessibility();
|
||||
method @UiThread @Nullable public GeckoSession.AutofillDelegate getAutofillDelegate();
|
||||
method @UiThread @NonNull public AutofillElement getAutofillElements();
|
||||
method @UiThread @Nullable public Autofill.Delegate getAutofillDelegate();
|
||||
method @UiThread @NonNull public Autofill.Session getAutofillSession();
|
||||
method @UiThread public void getClientBounds(@NonNull RectF);
|
||||
method @UiThread public void getClientToScreenMatrix(@NonNull Matrix);
|
||||
method @UiThread public void getClientToSurfaceMatrix(@NonNull Matrix);
|
||||
|
@ -594,7 +631,7 @@ package org.mozilla.geckoview {
|
|||
method @AnyThread public void reload();
|
||||
method @AnyThread public void restoreState(@NonNull GeckoSession.SessionState);
|
||||
method @AnyThread public void setActive(boolean);
|
||||
method @UiThread public void setAutofillDelegate(@Nullable GeckoSession.AutofillDelegate);
|
||||
method @UiThread public void setAutofillDelegate(@Nullable Autofill.Delegate);
|
||||
method @AnyThread public void setContentBlockingDelegate(@Nullable ContentBlocking.Delegate);
|
||||
method @UiThread public void setContentDelegate(@Nullable GeckoSession.ContentDelegate);
|
||||
method @AnyThread public void setFocused(boolean);
|
||||
|
@ -628,18 +665,6 @@ package org.mozilla.geckoview {
|
|||
field @Nullable protected GeckoSession.Window mWindow;
|
||||
}
|
||||
|
||||
public static interface GeckoSession.AutofillDelegate {
|
||||
method @UiThread default public void onAutofill(@NonNull GeckoSession, int, int);
|
||||
field public static final int AUTOFILL_NOTIFY_CANCELED = 2;
|
||||
field public static final int AUTOFILL_NOTIFY_COMMITTED = 1;
|
||||
field public static final int AUTOFILL_NOTIFY_STARTED = 0;
|
||||
field public static final int AUTOFILL_NOTIFY_VIEW_ADDED = 3;
|
||||
field public static final int AUTOFILL_NOTIFY_VIEW_ENTERED = 6;
|
||||
field public static final int AUTOFILL_NOTIFY_VIEW_EXITED = 7;
|
||||
field public static final int AUTOFILL_NOTIFY_VIEW_REMOVED = 4;
|
||||
field public static final int AUTOFILL_NOTIFY_VIEW_UPDATED = 5;
|
||||
}
|
||||
|
||||
public static interface GeckoSession.ContentDelegate {
|
||||
method @UiThread default public void onCloseRequest(@NonNull GeckoSession);
|
||||
method @UiThread default public void onContextMenu(@NonNull GeckoSession, int, int, @NonNull GeckoSession.ContentDelegate.ContextElement);
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,242 +0,0 @@
|
|||
/* -*- 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 android.graphics.Rect;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.util.ArrayMap;
|
||||
import android.view.View;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents a single autofill element.
|
||||
*/
|
||||
public class AutofillElement {
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({HINT_NONE, HINT_EMAIL_ADDRESS, HINT_PASSWORD, HINT_URL, HINT_USERNAME})
|
||||
/* package */ @interface AutofillHint {}
|
||||
|
||||
/**
|
||||
* Hint indicating that no special handling is required.
|
||||
*/
|
||||
public static final int HINT_NONE = -1;
|
||||
|
||||
/**
|
||||
* Hint indicating that an element represents an email address.
|
||||
*/
|
||||
public static final int HINT_EMAIL_ADDRESS = 0;
|
||||
|
||||
/**
|
||||
* Hint indicating that an element represents a password.
|
||||
*/
|
||||
public static final int HINT_PASSWORD = 1;
|
||||
|
||||
/**
|
||||
* Hint indicating that an element represents an URL.
|
||||
*/
|
||||
public static final int HINT_URL = 2;
|
||||
|
||||
/**
|
||||
* Hint indicating that an element represents a username.
|
||||
*/
|
||||
public static final int HINT_USERNAME = 3;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({INPUT_TYPE_NONE, INPUT_TYPE_TEXT, INPUT_TYPE_NUMBER, INPUT_TYPE_PHONE})
|
||||
/* package */ @interface AutofillInputType {}
|
||||
|
||||
/**
|
||||
* Indicates that an element is not a known input type.
|
||||
*/
|
||||
public static final int INPUT_TYPE_NONE = -1;
|
||||
|
||||
/**
|
||||
* Indicates that an element is a text input type (e.g., {@code <input type="text">})
|
||||
*/
|
||||
public static final int INPUT_TYPE_TEXT = 0;
|
||||
|
||||
/**
|
||||
* Indicates that an element is a number input type (e.g., {@code <input type="number">})
|
||||
*/
|
||||
public static final int INPUT_TYPE_NUMBER = 1;
|
||||
|
||||
/**
|
||||
* Indicates that an element is a phone input type (e.g., {@code <input type="tel">})
|
||||
*/
|
||||
public static final int INPUT_TYPE_PHONE = 2;
|
||||
|
||||
/**
|
||||
* A unique (within this page) id for this element.
|
||||
*/
|
||||
public final int id;
|
||||
|
||||
/**
|
||||
* The dimensions of this element in CSS coordinates.
|
||||
*/
|
||||
public final @NonNull Rect dimensions;
|
||||
|
||||
/**
|
||||
* The collection of child elements for this element.
|
||||
*/
|
||||
public final @NonNull Collection<AutofillElement> children;
|
||||
|
||||
/**
|
||||
* The HTML attributes for this element.
|
||||
*/
|
||||
public final @NonNull Map<String, String> attributes;
|
||||
|
||||
/**
|
||||
* Whether or not this element is enabled.
|
||||
*/
|
||||
public final boolean enabled;
|
||||
|
||||
/**
|
||||
* Whether or not this element is focusable.
|
||||
*/
|
||||
public final boolean focusable;
|
||||
|
||||
/**
|
||||
* Whether or not this element is focused.
|
||||
*/
|
||||
public final boolean focused;
|
||||
|
||||
/**
|
||||
* A hint for the type of data contained in this element, if any.
|
||||
*/
|
||||
public final @AutofillHint int hint;
|
||||
|
||||
/**
|
||||
* The input type of this element, if any.
|
||||
*/
|
||||
public final @AutofillInputType int inputType;
|
||||
|
||||
/**
|
||||
* The HTML tag for this element.
|
||||
*/
|
||||
public final @NonNull String tag;
|
||||
|
||||
/**
|
||||
* The web domain for this element.
|
||||
*/
|
||||
public final @NonNull String domain;
|
||||
|
||||
private AutofillElement(final Builder builder) {
|
||||
id = builder.mId;
|
||||
dimensions = builder.mDimensions != null ? builder.mDimensions : new Rect(0, 0, 0, 0);
|
||||
attributes = Collections.unmodifiableMap(builder.mAttributes != null ? builder.mAttributes : new ArrayMap<>());
|
||||
enabled = builder.mEnabled;
|
||||
focusable = builder.mFocusable;
|
||||
focused = builder.mFocused;
|
||||
hint = builder.mHint;
|
||||
inputType = builder.mInputType;
|
||||
tag = builder.mTag;
|
||||
domain = builder.mDomain;
|
||||
|
||||
if (builder.mChildren != null) {
|
||||
LinkedList<AutofillElement> children = new LinkedList<>();
|
||||
for (Builder child : builder.mChildren) {
|
||||
children.add(child.build());
|
||||
}
|
||||
this.children = children;
|
||||
} else {
|
||||
this.children = new LinkedList<>();
|
||||
}
|
||||
}
|
||||
|
||||
protected AutofillElement() {
|
||||
id = View.NO_ID;
|
||||
dimensions = new Rect(0, 0, 0, 0);
|
||||
attributes = Collections.unmodifiableMap(new ArrayMap<>());
|
||||
enabled = false;
|
||||
focusable = false;
|
||||
focused = false;
|
||||
hint = HINT_NONE;
|
||||
inputType = INPUT_TYPE_NONE;
|
||||
tag = "";
|
||||
domain = "";
|
||||
children = new LinkedList<>();
|
||||
}
|
||||
|
||||
/* package */ static class Builder {
|
||||
private int mId = View.NO_ID;
|
||||
private Rect mDimensions;
|
||||
private LinkedList<Builder> mChildren;
|
||||
private ArrayMap<String, String> mAttributes;
|
||||
private boolean mEnabled;
|
||||
private boolean mFocusable;
|
||||
private boolean mFocused;
|
||||
private int mHint = HINT_NONE;
|
||||
private int mInputType = INPUT_TYPE_NONE;
|
||||
private String mTag = "";
|
||||
private String mDomain = "";
|
||||
|
||||
public void dimensions(final Rect rect) {
|
||||
mDimensions = rect;
|
||||
}
|
||||
|
||||
public AutofillElement build() {
|
||||
return new AutofillElement(this);
|
||||
}
|
||||
|
||||
public void id(final int id) {
|
||||
mId = id;
|
||||
}
|
||||
|
||||
public Builder child() {
|
||||
if (mChildren == null) {
|
||||
mChildren = new LinkedList<>();
|
||||
}
|
||||
|
||||
final Builder child = new Builder();
|
||||
mChildren.add(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
public void attribute(final String key, final String value) {
|
||||
if (mAttributes == null) {
|
||||
mAttributes = new ArrayMap<>();
|
||||
}
|
||||
|
||||
mAttributes.put(key, value);
|
||||
}
|
||||
|
||||
public void enabled(final boolean enabled) {
|
||||
mEnabled = enabled;
|
||||
}
|
||||
|
||||
public void focusable(final boolean focusable) {
|
||||
mFocusable = focusable;
|
||||
}
|
||||
|
||||
public void focused(final boolean focused) {
|
||||
mFocused = focused;
|
||||
}
|
||||
|
||||
public void hint(final int hint) {
|
||||
mHint = hint;
|
||||
}
|
||||
|
||||
public void inputType(final int inputType) {
|
||||
mInputType = inputType;
|
||||
}
|
||||
|
||||
public void tag(final String tag) {
|
||||
mTag = tag;
|
||||
}
|
||||
|
||||
public void domain(final String domain) {
|
||||
mDomain = domain;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,321 +0,0 @@
|
|||
package org.mozilla.geckoview;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
|
||||
import org.mozilla.gecko.util.BundleEventListener;
|
||||
import org.mozilla.gecko.util.EventCallback;
|
||||
import org.mozilla.gecko.util.GeckoBundle;
|
||||
|
||||
/* 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, "autofill(" + 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 @Nullable GeckoSession.AutofillDelegate delegate) {
|
||||
mDelegate = delegate;
|
||||
}
|
||||
|
||||
public @Nullable GeckoSession.AutofillDelegate getDelegate() {
|
||||
return mDelegate;
|
||||
}
|
||||
|
||||
public @NonNull AutofillElement getAutofillElements() {
|
||||
final AutofillElement.Builder builder = new AutofillElement.Builder();
|
||||
|
||||
final Rect rect = getDummyAutofillRect(mSession, false, null);
|
||||
builder.dimensions(rect);
|
||||
|
||||
if (mAutofillRoots != null) {
|
||||
final int size = mAutofillRoots.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
final int id = mAutofillRoots.keyAt(i);
|
||||
final GeckoBundle root = mAutofillNodes.get(id);
|
||||
fillAutofillElement(id, root, rect, builder.child());
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private void fillAutofillElement(final int id, final GeckoBundle bundle, final Rect rect, final AutofillElement.Builder builder) {
|
||||
builder.id(id);
|
||||
builder.domain(bundle.getString("origin"));
|
||||
|
||||
if (mAutofillFocusedRoot != View.NO_ID && mAutofillFocusedRoot == bundle.getInt("root", View.NO_ID)) {
|
||||
builder.dimensions(rect);
|
||||
}
|
||||
|
||||
final GeckoBundle[] children = bundle.getBundleArray("children");
|
||||
if (children != null) {
|
||||
for (final GeckoBundle childBundle : children) {
|
||||
final int childId = childBundle.getInt("id");
|
||||
fillAutofillElement(childId, childBundle, rect, builder.child());
|
||||
mAutofillNodes.append(childId, childBundle);
|
||||
}
|
||||
}
|
||||
|
||||
String tag = bundle.getString("tag", "");
|
||||
builder.tag(tag.toLowerCase());
|
||||
|
||||
final String type = bundle.getString("type", "text");
|
||||
|
||||
final GeckoBundle attrs = bundle.getBundle("attributes");
|
||||
for (final String key : attrs.keys()) {
|
||||
builder.attribute(key, String.valueOf(attrs.get(key)));
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
builder.enabled(!disabled);
|
||||
builder.focusable(!disabled);
|
||||
builder.focused(id == mAutofillFocusedId);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case "email":
|
||||
builder.hint(AutofillElement.HINT_EMAIL_ADDRESS);
|
||||
builder.inputType(AutofillElement.INPUT_TYPE_TEXT);
|
||||
break;
|
||||
case "number":
|
||||
builder.inputType(AutofillElement.INPUT_TYPE_NUMBER);
|
||||
break;
|
||||
case "password":
|
||||
builder.hint(AutofillElement.HINT_PASSWORD);
|
||||
builder.inputType(AutofillElement.INPUT_TYPE_TEXT);
|
||||
break;
|
||||
case "tel":
|
||||
builder.inputType(AutofillElement.INPUT_TYPE_PHONE);
|
||||
break;
|
||||
case "url":
|
||||
builder.hint(AutofillElement.HINT_URL);
|
||||
builder.inputType(AutofillElement.INPUT_TYPE_TEXT);
|
||||
break;
|
||||
case "text":
|
||||
final String autofillhint = bundle.getString("autofillhint", "");
|
||||
if (autofillhint.equals("username")) {
|
||||
builder.hint(AutofillElement.HINT_USERNAME);
|
||||
builder.inputType(AutofillElement.INPUT_TYPE_TEXT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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.AUTOFILL_NOTIFY_STARTED, id);
|
||||
} else {
|
||||
mDelegate.onAutofill(
|
||||
mSession, GeckoSession.AutofillDelegate.AUTOFILL_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.AUTOFILL_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.AUTOFILL_NOTIFY_VIEW_EXITED,
|
||||
mAutofillFocusedId);
|
||||
}
|
||||
|
||||
mAutofillFocusedId = id;
|
||||
mAutofillFocusedRoot = root;
|
||||
|
||||
if (mDelegate != null && mAutofillFocusedId != View.NO_ID) {
|
||||
mDelegate.onAutofill(
|
||||
mSession, GeckoSession.AutofillDelegate.AUTOFILL_NOTIFY_VIEW_ENTERED,
|
||||
mAutofillFocusedId);
|
||||
}
|
||||
}
|
||||
|
||||
/* 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 onActiveChanged(final boolean active) {
|
||||
if (mDelegate == null || mAutofillFocusedId == View.NO_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We blur/focus the active element (if we have one) when the document is made inactive/active.
|
||||
getDelegate().onAutofill(
|
||||
mSession,
|
||||
active ? GeckoSession.AutofillDelegate.AUTOFILL_NOTIFY_VIEW_ENTERED
|
||||
: GeckoSession.AutofillDelegate.AUTOFILL_NOTIFY_VIEW_EXITED,
|
||||
mAutofillFocusedId);
|
||||
}
|
||||
}
|
|
@ -64,7 +64,6 @@ 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";
|
||||
|
@ -124,7 +123,7 @@ public class GeckoSession implements Parcelable {
|
|||
private OverscrollEdgeEffect mOverscroll;
|
||||
private DynamicToolbarAnimator mToolbar;
|
||||
private CompositorController mController;
|
||||
private AutofillSupport mAutofillSupport;
|
||||
private Autofill.Support mAutofillSupport;
|
||||
|
||||
private boolean mAttachedCompositor;
|
||||
private boolean mCompositorReady;
|
||||
|
@ -1523,7 +1522,7 @@ public class GeckoSession implements Parcelable {
|
|||
mTextInput.onWindowChanged(mWindow);
|
||||
}
|
||||
if ((change == WINDOW_CLOSE || change == WINDOW_TRANSFER_OUT) && !inProgress) {
|
||||
getAutofillSupport().clearAutofill();
|
||||
getAutofillSupport().clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5691,59 +5690,10 @@ public class GeckoSession implements Parcelable {
|
|||
})
|
||||
/* package */ @interface VisitFlags {}
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({
|
||||
AutofillDelegate.AUTOFILL_NOTIFY_STARTED,
|
||||
AutofillDelegate.AUTOFILL_NOTIFY_COMMITTED,
|
||||
AutofillDelegate.AUTOFILL_NOTIFY_CANCELED,
|
||||
AutofillDelegate.AUTOFILL_NOTIFY_VIEW_ADDED,
|
||||
AutofillDelegate.AUTOFILL_NOTIFY_VIEW_REMOVED,
|
||||
AutofillDelegate.AUTOFILL_NOTIFY_VIEW_UPDATED,
|
||||
AutofillDelegate.AUTOFILL_NOTIFY_VIEW_ENTERED,
|
||||
AutofillDelegate.AUTOFILL_NOTIFY_VIEW_EXITED})
|
||||
/* package */ @interface AutofillNotification {}
|
||||
|
||||
public interface AutofillDelegate {
|
||||
|
||||
/** An autofill session has started, usually as a result of loading a page. */
|
||||
int AUTOFILL_NOTIFY_STARTED = 0;
|
||||
/** An autofill session has been committed, usually as a result of submitting a form. */
|
||||
int AUTOFILL_NOTIFY_COMMITTED = 1;
|
||||
/** An autofill session has been canceled, usually as a result of unloading a page. */
|
||||
int AUTOFILL_NOTIFY_CANCELED = 2;
|
||||
/** A view within the autofill session has been added. */
|
||||
int AUTOFILL_NOTIFY_VIEW_ADDED = 3;
|
||||
/** A view within the autofill session has been removed. */
|
||||
int AUTOFILL_NOTIFY_VIEW_REMOVED = 4;
|
||||
/** A view within the autofill session has been updated (e.g. change in state). */
|
||||
int AUTOFILL_NOTIFY_VIEW_UPDATED = 5;
|
||||
/** A view within the autofill session has gained focus. */
|
||||
int AUTOFILL_NOTIFY_VIEW_ENTERED = 6;
|
||||
/** A view within the autofill session has lost focus. */
|
||||
int AUTOFILL_NOTIFY_VIEW_EXITED = 7;
|
||||
|
||||
/**
|
||||
* Notify that an autofill 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 #AUTOFILL_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() {
|
||||
private Autofill.Support getAutofillSupport() {
|
||||
if (mAutofillSupport == null) {
|
||||
mAutofillSupport = new AutofillSupport(this);
|
||||
mAutofillSupport = new Autofill.Support(this);
|
||||
}
|
||||
|
||||
return mAutofillSupport;
|
||||
|
@ -5752,18 +5702,18 @@ public class GeckoSession implements Parcelable {
|
|||
/**
|
||||
* Sets the autofill delegate for this session.
|
||||
*
|
||||
* @param delegate An instance of {@link AutofillDelegate}.
|
||||
* @param delegate An instance of {@link Autofill.Delegate}.
|
||||
*/
|
||||
@UiThread
|
||||
public void setAutofillDelegate(final @Nullable AutofillDelegate delegate) {
|
||||
public void setAutofillDelegate(final @Nullable Autofill.Delegate delegate) {
|
||||
getAutofillSupport().setDelegate(delegate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The current {@link AutofillDelegate} for this session, if any.
|
||||
* @return The current {@link Autofill.Delegate} for this session, if any.
|
||||
*/
|
||||
@UiThread
|
||||
public @Nullable AutofillDelegate getAutofillDelegate() {
|
||||
public @Nullable Autofill.Delegate getAutofillDelegate() {
|
||||
return getAutofillSupport().getDelegate();
|
||||
}
|
||||
|
||||
|
@ -5786,7 +5736,7 @@ public class GeckoSession implements Parcelable {
|
|||
* @return The elements available for autofill.
|
||||
*/
|
||||
@UiThread
|
||||
public @NonNull AutofillElement getAutofillElements() {
|
||||
return getAutofillSupport().getAutofillElements();
|
||||
public @NonNull Autofill.Session getAutofillSession() {
|
||||
return getAutofillSupport().getAutofillSession();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@ import android.support.annotation.NonNull;
|
|||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.text.InputType;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.SparseArray;
|
||||
|
@ -68,7 +67,7 @@ public class GeckoView extends FrameLayout {
|
|||
private boolean mAutofillEnabled = true;
|
||||
|
||||
private GeckoSession.SelectionActionDelegate mSelectionActionDelegate;
|
||||
private GeckoSession.AutofillDelegate mAutofillDelegate;
|
||||
private Autofill.Delegate mAutofillDelegate;
|
||||
|
||||
private static class SavedState extends BaseSavedState {
|
||||
public final GeckoSession session;
|
||||
|
@ -734,96 +733,8 @@ public class GeckoView extends FrameLayout {
|
|||
return;
|
||||
}
|
||||
|
||||
final AutofillElement root = mSession.getAutofillElements();
|
||||
fillViewStructure(root, structure, flags);
|
||||
}
|
||||
|
||||
@TargetApi(23)
|
||||
private void fillViewStructure(final AutofillElement element, final ViewStructure structure, final int flags) {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
structure.setAutofillId(getAutofillId(), element.id);
|
||||
structure.setWebDomain(element.domain);
|
||||
}
|
||||
|
||||
structure.setId(element.id, null, null, null);
|
||||
structure.setDimens(0, 0, 0, 0, element.dimensions.width(), element.dimensions.height());
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
final ViewStructure.HtmlInfo.Builder htmlBuilder = structure.newHtmlInfoBuilder(element.tag);
|
||||
for (final String key : element.attributes.keySet()) {
|
||||
htmlBuilder.addAttribute(key, String.valueOf(element.attributes.get(key)));
|
||||
}
|
||||
|
||||
structure.setHtmlInfo(htmlBuilder.build());
|
||||
}
|
||||
|
||||
structure.setChildCount(element.children.size());
|
||||
int childCount = 0;
|
||||
|
||||
for (final AutofillElement child : element.children) {
|
||||
final ViewStructure childStructure = structure.newChild(childCount);
|
||||
fillViewStructure(child, childStructure, flags);
|
||||
childCount++;
|
||||
}
|
||||
|
||||
switch (element.tag) {
|
||||
case "input":
|
||||
case "textarea":
|
||||
structure.setClassName("android.widget.EditText");
|
||||
structure.setEnabled(element.enabled);
|
||||
structure.setFocusable(element.focusable);
|
||||
structure.setFocused(element.focused);
|
||||
structure.setVisibility(View.VISIBLE);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
structure.setAutofillType(View.AUTOFILL_TYPE_TEXT);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (childCount > 0) {
|
||||
structure.setClassName("android.view.ViewGroup");
|
||||
} else {
|
||||
structure.setClassName("android.view.View");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 26 && "input".equals(element.tag)) {
|
||||
// LastPass will fill password to the field that setAutofillHints is unset and setInputType is set.
|
||||
switch (element.hint) {
|
||||
case AutofillElement.HINT_EMAIL_ADDRESS:
|
||||
structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_EMAIL_ADDRESS });
|
||||
structure.setInputType(InputType.TYPE_CLASS_TEXT |
|
||||
InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
|
||||
break;
|
||||
case AutofillElement.HINT_PASSWORD:
|
||||
structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_PASSWORD });
|
||||
structure.setInputType(InputType.TYPE_CLASS_TEXT |
|
||||
InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD);
|
||||
break;
|
||||
case AutofillElement.HINT_URL:
|
||||
structure.setInputType(InputType.TYPE_CLASS_TEXT |
|
||||
InputType.TYPE_TEXT_VARIATION_URI);
|
||||
break;
|
||||
case AutofillElement.HINT_USERNAME:
|
||||
structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_USERNAME });
|
||||
structure.setInputType(InputType.TYPE_CLASS_TEXT |
|
||||
InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (element.inputType) {
|
||||
case AutofillElement.INPUT_TYPE_NUMBER:
|
||||
structure.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
break;
|
||||
case AutofillElement.INPUT_TYPE_PHONE:
|
||||
structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_PHONE });
|
||||
structure.setInputType(InputType.TYPE_CLASS_PHONE);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
final Autofill.Session autofillSession = mSession.getAutofillSession();
|
||||
autofillSession.fillViewStructure(this, structure, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -862,7 +773,7 @@ public class GeckoView extends FrameLayout {
|
|||
/**
|
||||
* Sets whether or not this View participates in Android autofill.
|
||||
*
|
||||
* When enabled, this will set an {@link GeckoSession.AutofillDelegate} on the
|
||||
* When enabled, this will set an {@link Autofill.Delegate} on the
|
||||
* {@link GeckoSession} for this instance.
|
||||
*
|
||||
* @param enabled Whether or not Android autofill is enabled for this view.
|
||||
|
@ -888,35 +799,16 @@ public class GeckoView extends FrameLayout {
|
|||
return mAutofillEnabled;
|
||||
}
|
||||
|
||||
private class AndroidAutofillDelegate implements GeckoSession.AutofillDelegate {
|
||||
|
||||
private AutofillElement findElementWithId(final AutofillElement root, final int id) {
|
||||
if (root.id == id) {
|
||||
return root;
|
||||
}
|
||||
|
||||
for (AutofillElement child : root.children) {
|
||||
final AutofillElement found = findElementWithId(child, id);
|
||||
if (found != null) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
private class AndroidAutofillDelegate implements Autofill.Delegate {
|
||||
|
||||
private Rect displayRectForId(@NonNull final GeckoSession session,
|
||||
@NonNull final int virtualId,
|
||||
@Nullable final View view) {
|
||||
final AutofillElement structure = session.getAutofillElements();
|
||||
final AutofillElement element = findElementWithId(structure, virtualId);
|
||||
|
||||
if (element == null) {
|
||||
@NonNull final Autofill.Node node) {
|
||||
if (node == null) {
|
||||
return new Rect(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
final Matrix matrix = new Matrix();
|
||||
final RectF rectF = new RectF(element.dimensions);
|
||||
final RectF rectF = new RectF(node.getDimensions());
|
||||
session.getPageToScreenMatrix(matrix);
|
||||
matrix.mapRect(rectF);
|
||||
|
||||
|
@ -927,8 +819,8 @@ public class GeckoView extends FrameLayout {
|
|||
|
||||
@Override
|
||||
public void onAutofill(@NonNull final GeckoSession session,
|
||||
@GeckoSession.AutofillNotification final int notification,
|
||||
final int virtualId) {
|
||||
final int notification,
|
||||
final Autofill.Node node) {
|
||||
ThreadUtils.assertOnUiThread();
|
||||
if (Build.VERSION.SDK_INT < 26) {
|
||||
return;
|
||||
|
@ -941,21 +833,21 @@ public class GeckoView extends FrameLayout {
|
|||
}
|
||||
|
||||
switch (notification) {
|
||||
case AUTOFILL_NOTIFY_STARTED:
|
||||
case Autofill.Notify.SESSION_STARTED:
|
||||
// This line seems necessary for auto-fill to work on the initial page.
|
||||
case Autofill.Notify.SESSION_CANCELED:
|
||||
manager.cancel();
|
||||
break;
|
||||
case AUTOFILL_NOTIFY_COMMITTED:
|
||||
case Autofill.Notify.SESSION_COMMITTED:
|
||||
manager.commit();
|
||||
break;
|
||||
case AUTOFILL_NOTIFY_CANCELED:
|
||||
manager.cancel();
|
||||
case Autofill.Notify.NODE_FOCUSED:
|
||||
manager.notifyViewEntered(
|
||||
GeckoView.this, node.getId(),
|
||||
displayRectForId(session, node));
|
||||
break;
|
||||
case AUTOFILL_NOTIFY_VIEW_ENTERED:
|
||||
manager.notifyViewEntered(GeckoView.this, virtualId, displayRectForId(session, virtualId, GeckoView.this));
|
||||
break;
|
||||
case AUTOFILL_NOTIFY_VIEW_EXITED:
|
||||
manager.notifyViewExited(GeckoView.this, virtualId);
|
||||
case Autofill.Notify.NODE_BLURRED:
|
||||
manager.notifyViewExited(GeckoView.this, node.getId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,6 +69,9 @@ exclude: true
|
|||
([bug 1402369]({{bugzilla}}1402369))
|
||||
- Added [`GeckoDisplay.screenshot`][71.23] allowing apps finer grain control over screenshots.
|
||||
([bug 1577192]({{bugzilla}}1577192))
|
||||
- ⚠️ Refactored `AutofillElement` and `AutofillSupport` into the
|
||||
[`Autofill`][71.24] API.
|
||||
([bug 1591462]({{bugzilla}}1591462))
|
||||
|
||||
[71.1]: {{javadoc_uri}}/RuntimeTelemetry.Delegate.html#onBooleanScalar-org.mozilla.geckoview.RuntimeTelemetry.Metric-
|
||||
[71.2]: {{javadoc_uri}}/RuntimeTelemetry.Delegate.html#onLongScalar-org.mozilla.geckoview.RuntimeTelemetry.Metric-
|
||||
|
@ -92,6 +95,7 @@ exclude: true
|
|||
[71.21]: {{javadoc_uri}}/GeckoView.html#setAutofillEnabled-boolean-
|
||||
[71.22]: {{javadoc_uri}}/GeckoSession.PromptDelegate.html#onSharePrompt-org.mozilla.geckoview.GeckoSession-org.mozilla.geckoview.GeckoSession.PromptDelegate.SharePrompt-
|
||||
[71.23]: {{javadoc_uri}}/GeckoDisplay.html#screenshot--
|
||||
[71.24]: {{javadoc_uri}}/Autofill.html
|
||||
|
||||
## v70
|
||||
- Added API for session context assignment
|
||||
|
@ -414,4 +418,4 @@ exclude: true
|
|||
[65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
|
||||
[65.25]: {{javadoc_uri}}/GeckoResult.html
|
||||
|
||||
[api-version]: c01c3629db0185ce919b026400328804d27985a6
|
||||
[api-version]: b8934264da833b5d36110e5d8136a0feef40623d
|
||||
|
|
|
@ -94,6 +94,7 @@ class GeckoViewAutofill {
|
|||
root,
|
||||
tag: element.tagName,
|
||||
type: element instanceof window.HTMLInputElement ? element.type : null,
|
||||
value: element.value,
|
||||
editable:
|
||||
element instanceof window.HTMLInputElement &&
|
||||
[
|
||||
|
|
Загрузка…
Ссылка в новой задаче