Commit 09ab62ef authored by Friedrich Horschig's avatar Friedrich Horschig Committed by Commit Bot

[Android] Fill accessory credential in form field

With this CL, a tapped suggestion in the passwords keyboard accessory
sheet is filled into the last selected input for field.

If that form field is not a password field but the selected credential
is, nothing will happen.

The autofill_agent has detailed knowledge of the form field and tracks
the last selected input field. The password_autofill_agent queries the
autofill_agent for that field and fills it with the selected credential.

Bug: 811747, 854152
Cq-Include-Trybots: luci.chromium.try:ios-simulator-full-configs;master.tryserver.chromium.mac:ios-simulator-cronet
Change-Id: Id8ac5330269bcc8b8326a5e050789260e119d5e8
Reviewed-on: https://chromium-review.googlesource.com/1104338
Commit-Queue: Friedrich Horschig <fhorschig@chromium.org>
Reviewed-by: default avatarChris Palmer <palmer@chromium.org>
Reviewed-by: default avatarVaclav Brozek <vabr@chromium.org>
Reviewed-by: default avatarVasilii Sukhanov <vasilii@chromium.org>
Cr-Commit-Position: refs/heads/master@{#569574}
parent 2dca1d9b
......@@ -46,12 +46,12 @@ class PasswordAccessoryBridge {
items[i] = new Item(type[i], text[i], description[i], isPassword[i] == 1, (item) -> {
assert mNativeView
!= 0 : "Controller has been destroyed but the bridge wasn't cleaned up!";
nativeOnFillingTriggered(mNativeView, item.getCaption());
nativeOnFillingTriggered(mNativeView, item.isPassword(), item.getCaption());
});
}
return items;
}
private native void nativeOnFillingTriggered(
long nativePasswordAccessoryViewAndroid, String textToFill);
long nativePasswordAccessoryViewAndroid, boolean isPassword, String textToFill);
}
\ No newline at end of file
......@@ -65,9 +65,10 @@ void PasswordAccessoryViewAndroid::OnItemsAvailable(
void PasswordAccessoryViewAndroid::OnFillingTriggered(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
jboolean isPassword,
const base::android::JavaParamRef<_jstring*>& textToFill) {
controller_->OnFillingTriggered(
base::android::ConvertJavaStringToUTF16(textToFill));
isPassword, base::android::ConvertJavaStringToUTF16(textToFill));
}
// static
......
......@@ -30,6 +30,7 @@ class PasswordAccessoryViewAndroid : public PasswordAccessoryViewInterface {
void OnFillingTriggered(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
jboolean isPassword,
const base::android::JavaParamRef<jstring>& textToFill);
private:
......
......@@ -149,6 +149,9 @@ class FakePasswordAutofillAgent
int key,
const autofill::PasswordFormFillData& form_data) override {}
void FillIntoFocusedField(bool is_password,
const base::string16& credential) override {}
void SetLoggingState(bool active) override {
called_set_logging_state_ = true;
logging_state_active_ = active;
......
......@@ -9,7 +9,11 @@
#include "chrome/browser/password_manager/password_accessory_view_interface.h"
#include "chrome/browser/ui/passwords/manage_passwords_view_utils.h"
#include "chrome/grit/generated_resources.h"
#include "components/autofill/content/browser/content_autofill_driver.h"
#include "components/autofill/content/browser/content_autofill_driver_factory.h"
#include "components/autofill/core/common/password_form.h"
#include "components/password_manager/content/browser/content_password_manager_driver.h"
#include "components/password_manager/content/browser/content_password_manager_driver_factory.h"
#include "components/strings/grit/components_strings.h"
#include "ui/base/l10n/l10n_util.h"
......@@ -20,14 +24,14 @@ DEFINE_WEB_CONTENTS_USER_DATA_KEY(PasswordAccessoryController);
PasswordAccessoryController::PasswordAccessoryController(
content::WebContents* web_contents)
: container_view_(web_contents->GetNativeView()),
: web_contents_(web_contents),
view_(PasswordAccessoryViewInterface::Create(this)) {}
// Additional creation functions in unit tests only:
PasswordAccessoryController::PasswordAccessoryController(
content::WebContents* web_contents,
std::unique_ptr<PasswordAccessoryViewInterface> view)
: container_view_(web_contents->GetNativeView()), view_(std::move(view)) {}
: web_contents_(web_contents), view_(std::move(view)) {}
PasswordAccessoryController::~PasswordAccessoryController() = default;
......@@ -45,6 +49,12 @@ void PasswordAccessoryController::CreateForWebContentsForTesting(
void PasswordAccessoryController::OnPasswordsAvailable(
const std::map<base::string16, const PasswordForm*>& best_matches,
const GURL& origin) {
const url::Origin& tab_origin =
web_contents_->GetMainFrame()->GetLastCommittedOrigin();
if (!tab_origin.IsSameOriginWith(url::Origin::Create(origin))) {
// TODO(fhorschig): Support iframes: https://crbug.com/854150.
return;
}
DCHECK(view_);
std::vector<Item> items;
base::string16 passwords_title_str = l10n_util::GetStringUTF16(
......@@ -72,10 +82,21 @@ void PasswordAccessoryController::OnPasswordsAvailable(
}
void PasswordAccessoryController::OnFillingTriggered(
bool is_password,
const base::string16& textToFill) const {
// TODO(fhorschig): Actually fill |textToFill| into focused field.
password_manager::ContentPasswordManagerDriverFactory* factory =
password_manager::ContentPasswordManagerDriverFactory::FromWebContents(
web_contents_);
DCHECK(factory);
// TODO(fhorschig): Consider allowing filling on non-main frames.
password_manager::ContentPasswordManagerDriver* driver =
factory->GetDriverForFrame(web_contents_->GetMainFrame());
if (!driver) {
return;
} // |driver| can be NULL if the tab is being closed.
driver->FillIntoFocusedField(is_password, textToFill);
}
gfx::NativeView PasswordAccessoryController::container_view() const {
return container_view_;
return web_contents_->GetNativeView();
}
......@@ -32,6 +32,9 @@ class PasswordAccessoryViewInterface;
// by calling:
// PasswordAccessoryController::FromWebContents(web_contents);
// Any further calls to |CreateForWebContents| will be a noop.
//
// TODO(fhorschig): This class currently only supports credentials originating
// from the main frame. Supporting iframes is intended: https://crbug.com/854150
class PasswordAccessoryController
: public content::WebContentsUserData<PasswordAccessoryController> {
public:
......@@ -45,7 +48,8 @@ class PasswordAccessoryController
// Called by the UI code to request that |textToFill| is to be filled into the
// currently focused field.
void OnFillingTriggered(const base::string16& textToFill) const;
void OnFillingTriggered(bool is_password,
const base::string16& textToFill) const;
// The web page view containing the focused field.
gfx::NativeView container_view() const;
......@@ -72,7 +76,7 @@ class PasswordAccessoryController
std::unique_ptr<PasswordAccessoryViewInterface> view);
// The web page view this accessory sheet and the focused field live in.
const gfx::NativeView container_view_;
content::WebContents* web_contents_;
// Hold the native instance of the view. Must be last declared and initialized
// member so the view can be created in the constructor with a fully set up
......
......@@ -22,6 +22,7 @@ using autofill::PasswordForm;
using base::ASCIIToUTF16;
using base::UTF16ToASCII;
using base::UTF16ToWide;
using testing::_;
using testing::ElementsAre;
using testing::Eq;
using testing::Mock;
......@@ -138,6 +139,7 @@ class PasswordAccessoryControllerTest : public ChromeRenderViewHostTestHarness {
PasswordAccessoryController::CreateForWebContentsForTesting(
web_contents(),
std::make_unique<StrictMock<MockPasswordAccessoryView>>());
NavigateAndCommit(GURL("https://example.com"));
}
PasswordAccessoryController* controller() {
......@@ -239,3 +241,10 @@ TEST_F(PasswordAccessoryControllerTest, ProvidesEmptySuggestionsMessage) {
controller()->OnPasswordsAvailable({}, GURL("https://example.com"));
}
TEST_F(PasswordAccessoryControllerTest, IgnoresCrossOriginCalls) {
// Don't expect any call to |OnItemsAvailable|. (https://crbug.com/854150)
EXPECT_CALL(*view(), OnItemsAvailable(_, _)).Times(0);
controller()->OnPasswordsAvailable({CreateEntry("Ben", "S3cur3").first},
GURL("https://other-domain.com"));
}
......@@ -1624,6 +1624,68 @@ TEST_F(PasswordAutofillAgentTest,
}
}
// Tests that |FillIntoFocusedField| doesn't fill read-only text fields.
TEST_F(PasswordAutofillAgentTest, FillIntoFocusedReadonlyTextField) {
// Neither field should be autocompleted.
CheckTextFieldsDOMState(std::string(), false, std::string(), false);
// If the field is readonly, it should not be affected.
SetElementReadOnly(username_element_, true);
SimulateElementClick(kUsernameName);
password_autofill_agent_->FillIntoFocusedField(/*is_password=*/false,
ASCIIToUTF16(kAliceUsername));
CheckTextFieldsDOMState(std::string(), false, std::string(), false);
}
// Tests that |FillIntoFocusedField| properly fills user-provided credentials.
TEST_F(PasswordAutofillAgentTest, FillIntoFocusedWritableTextField) {
// Neither field should be autocompleted.
CheckTextFieldsDOMState(std::string(), false, std::string(), false);
// The same field should be filled if it is writable.
SetFocused(username_element_);
SetElementReadOnly(username_element_, false);
password_autofill_agent_->FillIntoFocusedField(/*is_password=*/false,
ASCIIToUTF16(kAliceUsername));
CheckTextFieldsDOMState(kAliceUsername, true, std::string(), false);
CheckUsernameSelection(strlen(kAliceUsername), strlen(kAliceUsername));
}
// Tests that |FillIntoFocusedField| doesn't fill passwords in userfields.
TEST_F(PasswordAutofillAgentTest, FillIntoFocusedFieldOnlyIntoPasswordFields) {
// Neither field should be autocompleted.
CheckTextFieldsDOMState(std::string(), false, std::string(), false);
// Filling a password into a username field doesn't work.
SimulateElementClick(kUsernameName);
password_autofill_agent_->FillIntoFocusedField(/*is_password=*/true,
ASCIIToUTF16(kAlicePassword));
CheckTextFieldsDOMState(std::string(), false, std::string(), false);
// When a password field is focus, the filling works.
SimulateElementClick(kPasswordName);
password_autofill_agent_->FillIntoFocusedField(/*is_password=*/true,
ASCIIToUTF16(kAlicePassword));
CheckTextFieldsDOMState(std::string(), false, kAlicePassword, true);
}
// Tests that |FillIntoFocusedField| fills last focused, not last clicked field.
TEST_F(PasswordAutofillAgentTest, FillIntoFocusedFieldForNonClickFocus) {
// Neither field should be autocompleted.
CheckTextFieldsDOMState(std::string(), false, std::string(), false);
// Click the username but shift the focus without click to the password.
SimulateElementClick(kUsernameName);
SetFocused(password_element_);
// The completion should now affect ONLY the password field. Don't fill a
// password so the error on failure shows where the filling happened.
// (see FillIntoFocusedFieldOnlyIntoPasswordFields).
password_autofill_agent_->FillIntoFocusedField(/*is_password=*/false,
ASCIIToUTF16("TextToFill"));
CheckTextFieldsDOMState(std::string(), false, "TextToFill", true);
}
// Tests that |ClearPreview| properly clears previewed username and password
// with neither username nor password being previously autofilled.
TEST_F(PasswordAutofillAgentTest,
......
......@@ -84,6 +84,9 @@ interface PasswordAutofillAgent {
// ShowPasswordSuggestions messages to the browser.
FillPasswordForm(int32 key, PasswordFormFillData form_data);
// Fills the given |credential| into the last focused text input.
FillIntoFocusedField(bool is_password, mojo_base.mojom.String16 credential);
// Notification to start (|active| == true) or stop (|active| == false)
// logging the decisions made about saving the password.
SetLoggingState(bool active);
......
......@@ -238,6 +238,7 @@ void AutofillAgent::FocusedNodeChanged(const WebNode& node) {
}
HidePopup();
last_input_element_.Reset();
if (node.IsNull() || !node.IsElementNode()) {
if (!last_interacted_form_.IsNull()) {
......@@ -264,6 +265,7 @@ void AutofillAgent::FocusedNodeChanged(const WebNode& node) {
return;
element_ = *element;
last_input_element_ = *element;
FormData form;
FormFieldData field;
......@@ -814,10 +816,15 @@ void AutofillAgent::FormControlElementClicked(
last_clicked_form_control_element_was_focused_for_testing_ = was_focused;
was_last_action_fill_ = false;
last_input_element_.Reset();
const WebInputElement* input_element = ToWebInputElement(&element);
if (!input_element && !form_util::IsTextAreaElement(element))
return;
if (input_element) {
last_input_element_ = *input_element;
}
ShowSuggestionsOptions options;
options.autofill_on_empty_values = true;
// Show full suggestions when clicking on an already-focused form field.
......@@ -987,6 +994,7 @@ void AutofillAgent::ResetLastInteractedElements() {
last_clicked_form_control_element_for_testing_.Reset();
formless_elements_user_edited_.clear();
provisionally_saved_form_.reset();
last_input_element_.Reset();
}
void AutofillAgent::UpdateLastInteractedForm(blink::WebFormElement form) {
......
......@@ -95,6 +95,11 @@ class AutofillAgent : public content::RenderFrameObserver,
return weak_ptr_factory_.GetWeakPtr();
}
// Returns the input element that was last focused.
blink::WebInputElement GetLastFocusedInput() const {
return last_input_element_;
}
// FormTracker::Observer
void OnProvisionallySaveForm(const blink::WebFormElement& form,
const blink::WebFormControlElement& element,
......@@ -286,6 +291,9 @@ class AutofillAgent : public content::RenderFrameObserver,
// Last form which was interacted with by the user.
blink::WebFormElement last_interacted_form_;
// Last input element the user interacted with.
blink::WebInputElement last_input_element_;
// When dealing with forms that don't use a <form> tag, we keep track of the
// elements the user has modified so we can determine when submission occurs.
std::set<blink::WebFormControlElement> formless_elements_user_edited_;
......
......@@ -796,20 +796,10 @@ bool PasswordAutofillAgent::FillSuggestion(
if (IsUsernameAmendable(username_element,
element->IsPasswordFieldForAutofill()) &&
username_element.Value().Utf16() != username) {
username_element.SetAutofillValue(blink::WebString::FromUTF16(username));
username_element.SetAutofillState(WebAutofillState::kAutofilled);
UpdateFieldValueAndPropertiesMaskMap(username_element, &username,
FieldPropertiesFlags::AUTOFILLED,
&field_value_and_properties_map_);
FillField(&username_element, username);
}
password_element.SetAutofillValue(blink::WebString::FromUTF16(password));
password_element.SetAutofillState(WebAutofillState::kAutofilled);
UpdateFieldValueAndPropertiesMaskMap(password_element, &password,
FieldPropertiesFlags::AUTOFILLED,
&field_value_and_properties_map_);
ProvisionallySavePassword(password_element.Form(), password_element,
RESTRICTION_NONE);
FillPasswordFieldAndSave(&password_element, password);
blink::WebInputElement mutable_filled_element = *element;
mutable_filled_element.SetSelectionRange(element->Value().length(),
......@@ -818,6 +808,47 @@ bool PasswordAutofillAgent::FillSuggestion(
return true;
}
void PasswordAutofillAgent::FillIntoFocusedField(
bool is_password,
const base::string16& credential) {
if (!autofill_agent_.get()) {
return;
}
blink::WebInputElement input = autofill_agent_.get()->GetLastFocusedInput();
if (input.IsNull() || (!input.IsTextField() || !IsElementEditable(input))) {
return;
}
if (is_password) {
if (!input.IsPasswordFieldForAutofill()) {
return;
}
FillPasswordFieldAndSave(&input, credential);
} else {
FillField(&input, credential);
}
}
void PasswordAutofillAgent::FillField(blink::WebInputElement* input,
const base::string16& credential) {
DCHECK(input);
DCHECK(!input->IsNull());
input->SetAutofillValue(blink::WebString::FromUTF16(credential));
input->SetAutofillState(WebAutofillState::kAutofilled);
UpdateFieldValueAndPropertiesMaskMap(*input, &credential,
FieldPropertiesFlags::AUTOFILLED,
&field_value_and_properties_map_);
}
void PasswordAutofillAgent::FillPasswordFieldAndSave(
blink::WebInputElement* password_input,
const base::string16& credential) {
DCHECK(password_input);
DCHECK(password_input->IsPasswordFieldForAutofill());
FillField(password_input, credential);
ProvisionallySavePassword(password_input->Form(), *password_input,
RESTRICTION_NONE);
}
bool PasswordAutofillAgent::PreviewSuggestion(
const blink::WebFormControlElement& control_element,
const blink::WebString& username,
......
......@@ -66,6 +66,8 @@ class PasswordAutofillAgent : public content::RenderFrameObserver,
// mojom::PasswordAutofillAgent:
void FillPasswordForm(int key,
const PasswordFormFillData& form_data) override;
void FillIntoFocusedField(bool is_password,
const base::string16& credential) override;
void SetLoggingState(bool active) override;
void AutofillUsernameAndPasswordDataReceived(
const FormsPredictionsMap& predictions) override;
......@@ -267,6 +269,16 @@ class PasswordAutofillAgent : public content::RenderFrameObserver,
void ClearPreview(blink::WebInputElement* username,
blink::WebInputElement* password);
// Checks that a given input field is valid before filling the given |input|
// with the given |credential| and marking the field as auto-filled.
void FillField(blink::WebInputElement* input,
const base::string16& credential);
// Uses |FillField| to fill the given |credential| into the |password_input|.
// Saves the password for its associated form.
void FillPasswordFieldAndSave(blink::WebInputElement* password_input,
const base::string16& credential);
// Saves |form| and |input| in |provisionally_saved_form_|, as long as it
// satisfies |restriction|. |form| and |input| are the elements user has just
// been interacting with before the form save. |form| or |input| can be null
......
......@@ -113,6 +113,12 @@ void ContentPasswordManagerDriver::FillSuggestion(
GetAutofillAgent()->FillPasswordSuggestion(username, password);
}
void ContentPasswordManagerDriver::FillIntoFocusedField(
bool is_password,
const base::string16& credential) {
GetPasswordAutofillAgent()->FillIntoFocusedField(is_password, credential);
}
void ContentPasswordManagerDriver::PreviewSuggestion(
const base::string16& username,
const base::string16& password) {
......
......@@ -66,6 +66,8 @@ class ContentPasswordManagerDriver
void UserSelectedManualGenerationOption() override;
void FillSuggestion(const base::string16& username,
const base::string16& password) override;
void FillIntoFocusedField(bool is_password,
const base::string16& credential) override;
void PreviewSuggestion(const base::string16& username,
const base::string16& password) override;
void ShowInitialPasswordAccountSuggestions(
......
......@@ -80,6 +80,7 @@ class FakePasswordAutofillAgent
// autofill::mojom::PasswordAutofillAgent:
MOCK_METHOD2(FillPasswordForm,
void(int, const autofill::PasswordFormFillData&));
MOCK_METHOD2(FillIntoFocusedField, void(bool, const base::string16&));
MOCK_METHOD0(BlacklistedFormFound, void());
......
......@@ -69,6 +69,11 @@ class PasswordManagerDriver
virtual void FillSuggestion(const base::string16& username,
const base::string16& password) = 0;
// Tells the renderer to fill the given credential into the focused element.
virtual void FillIntoFocusedField(
bool is_password,
const base::string16& user_provided_credential) {}
// Tells the driver to preview filling form with the |username| and
// |password|.
virtual void PreviewSuggestion(const base::string16& username,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment