diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-12 14:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:35:20 +0000 |
commit | c30a6232df03e1efbd9f3b226777b07e087a1122 (patch) | |
tree | e992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/components/autofill/content | |
parent | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff) | |
download | qtwebengine-chromium-85-based.tar.gz |
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/components/autofill/content')
25 files changed, 795 insertions, 256 deletions
diff --git a/chromium/components/autofill/content/browser/content_autofill_driver.cc b/chromium/components/autofill/content/browser/content_autofill_driver.cc index 7cf16fc27ea..ed248d98c85 100644 --- a/chromium/components/autofill/content/browser/content_autofill_driver.cc +++ b/chromium/components/autofill/content/browser/content_autofill_driver.cc @@ -288,17 +288,11 @@ void ContentAutofillDriver::DidEndTextFieldEditing() { autofill_handler_->OnDidEndTextFieldEditing(); } -void ContentAutofillDriver::SetDataList( - const std::vector<base::string16>& values, - const std::vector<base::string16>& labels) { - autofill_handler_->OnSetDataList(values, labels); -} - void ContentAutofillDriver::SelectFieldOptionsDidChange(const FormData& form) { autofill_handler_->SelectFieldOptionsDidChange(form); } -void ContentAutofillDriver::DidNavigateMainFrame( +void ContentAutofillDriver::DidNavigateFrame( content::NavigationHandle* navigation_handle) { if (navigation_handle->IsSameDocument()) return; diff --git a/chromium/components/autofill/content/browser/content_autofill_driver.h b/chromium/components/autofill/content/browser/content_autofill_driver.h index 587b09f7e65..7d201fe0deb 100644 --- a/chromium/components/autofill/content/browser/content_autofill_driver.h +++ b/chromium/components/autofill/content/browser/content_autofill_driver.h @@ -115,13 +115,11 @@ class ContentAutofillDriver : public AutofillDriver, base::TimeTicks timestamp) override; void DidPreviewAutofillFormData() override; void DidEndTextFieldEditing() override; - void SetDataList(const std::vector<base::string16>& values, - const std::vector<base::string16>& labels) override; void SelectFieldOptionsDidChange(const FormData& form) override; - // Called when the main frame has navigated. Explicitely will not trigger for - // subframe navigations. See navigation_handle.h for details. - void DidNavigateMainFrame(content::NavigationHandle* navigation_handle); + // DidNavigateFrame() is called on the frame's driver, respectively, when a + // navigation occurs in that specific frame. + void DidNavigateFrame(content::NavigationHandle* navigation_handle); AutofillManager* autofill_manager() { return autofill_manager_; } AutofillHandler* autofill_handler() { return autofill_handler_.get(); } diff --git a/chromium/components/autofill/content/browser/content_autofill_driver_factory.cc b/chromium/components/autofill/content/browser/content_autofill_driver_factory.cc index 35e691af47c..2738209f51c 100644 --- a/chromium/components/autofill/content/browser/content_autofill_driver_factory.cc +++ b/chromium/components/autofill/content/browser/content_autofill_driver_factory.cc @@ -132,18 +132,13 @@ void ContentAutofillDriverFactory::RenderFrameDeleted( void ContentAutofillDriverFactory::DidFinishNavigation( content::NavigationHandle* navigation_handle) { - // For the purposes of this code, a navigation is not important if it has not - // committed yet or if it's in a subframe. - if (!navigation_handle->HasCommitted() || - !navigation_handle->IsInMainFrame()) { - return; + if (navigation_handle->HasCommitted() && + (navigation_handle->IsInMainFrame() || + navigation_handle->HasSubframeNavigationEntryCommitted())) { + NavigationFinished(); + DriverForFrame(navigation_handle->GetRenderFrameHost()) + ->DidNavigateFrame(navigation_handle); } - - // A main frame navigation has occured. We suppress the autofill popup and - // tell the autofill driver. - NavigationFinished(); - DriverForFrame(navigation_handle->GetRenderFrameHost()) - ->DidNavigateMainFrame(navigation_handle); } void ContentAutofillDriverFactory::OnVisibilityChanged( diff --git a/chromium/components/autofill/content/browser/content_autofill_driver_unittest.cc b/chromium/components/autofill/content/browser/content_autofill_driver_unittest.cc index cc75354a844..335cde6bf11 100644 --- a/chromium/components/autofill/content/browser/content_autofill_driver_unittest.cc +++ b/chromium/components/autofill/content/browser/content_autofill_driver_unittest.cc @@ -282,7 +282,7 @@ class TestContentAutofillDriver : public ContentAutofillDriver { return static_cast<MockAutofillManager*>(autofill_manager()); } - using ContentAutofillDriver::DidNavigateMainFrame; + using ContentAutofillDriver::DidNavigateFrame; }; class ContentAutofillDriverTest : public content::RenderViewHostTestHarness { @@ -316,7 +316,7 @@ class ContentAutofillDriverTest : public content::RenderViewHostTestHarness { content::MockNavigationHandle navigation_handle(GURL(), main_rfh()); navigation_handle.set_has_committed(true); navigation_handle.set_is_same_document(same_document); - driver_->DidNavigateMainFrame(&navigation_handle); + driver_->DidNavigateFrame(&navigation_handle); } protected: diff --git a/chromium/components/autofill/content/browser/risk/fingerprint.cc b/chromium/components/autofill/content/browser/risk/fingerprint.cc index 35475b3d8a0..b7aa9f45bb9 100644 --- a/chromium/components/autofill/content/browser/risk/fingerprint.cc +++ b/chromium/components/autofill/content/browser/risk/fingerprint.cc @@ -51,6 +51,7 @@ #include "ui/display/display.h" #include "ui/display/screen.h" #include "ui/gfx/geometry/rect.h" +#include "url/gurl.h" #if BUILDFLAG(ENABLE_PLUGINS) #include "content/public/browser/plugin_service.h" @@ -316,7 +317,7 @@ FingerprintDataLoader::FingerprintDataLoader( content::GetDeviceService().BindGeolocationContext( geolocation_context_.BindNewPipeAndPassReceiver()); geolocation_context_->BindGeolocation( - geolocation_.BindNewPipeAndPassReceiver()); + geolocation_.BindNewPipeAndPassReceiver(), GURL::EmptyGURL()); geolocation_->SetHighAccuracy(false); geolocation_->QueryNextPosition( base::BindOnce(&FingerprintDataLoader::OnGotGeoposition, diff --git a/chromium/components/autofill/content/common/mojom/autofill_agent.mojom b/chromium/components/autofill/content/common/mojom/autofill_agent.mojom index 3e7e9c944a5..28b90f991f2 100644 --- a/chromium/components/autofill/content/common/mojom/autofill_agent.mojom +++ b/chromium/components/autofill/content/common/mojom/autofill_agent.mojom @@ -90,7 +90,7 @@ interface PasswordAutofillAgent { // Lets the renderer know that there are no saved credentials for filling. // This is the "no results" equivalent of FillPasswordForm. - InformNoSavedCredentials(); + InformNoSavedCredentials(bool should_show_popup_without_passwords); // Fills the given |credential| into the last focused text input. FillIntoFocusedField(bool is_password, mojo_base.mojom.String16 credential); diff --git a/chromium/components/autofill/content/common/mojom/autofill_driver.mojom b/chromium/components/autofill/content/common/mojom/autofill_driver.mojom index 96fcfbf363b..1c9d27910e0 100644 --- a/chromium/components/autofill/content/common/mojom/autofill_driver.mojom +++ b/chromium/components/autofill/content/common/mojom/autofill_driver.mojom @@ -71,10 +71,6 @@ interface AutofillDriver { // Sent when a text field is done editing. DidEndTextFieldEditing(); - - // Informs browser of data list values for the current field. - SetDataList(array<mojo_base.mojom.String16> values, - array<mojo_base.mojom.String16> labels); }; // There is one instance of this interface per web contents in the browser diff --git a/chromium/components/autofill/content/renderer/BUILD.gn b/chromium/components/autofill/content/renderer/BUILD.gn index 82ce0f34d5a..6ee2bee980b 100644 --- a/chromium/components/autofill/content/renderer/BUILD.gn +++ b/chromium/components/autofill/content/renderer/BUILD.gn @@ -80,6 +80,7 @@ jumbo_static_library("test_support") { "//components/autofill/content/renderer", "//services/service_manager/public/cpp", "//skia", + "//testing/gmock", "//testing/gtest", "//third_party/blink/public:blink", ] diff --git a/chromium/components/autofill/content/renderer/autofill_agent.cc b/chromium/components/autofill/content/renderer/autofill_agent.cc index 307fa136447..39e65c63370 100644 --- a/chromium/components/autofill/content/renderer/autofill_agent.cc +++ b/chromium/components/autofill/content/renderer/autofill_agent.cc @@ -86,6 +86,7 @@ using blink::WebVector; namespace autofill { +using form_util::ExtractMask; using form_util::FindFormAndFieldForFormControlElement; using form_util::UnownedCheckoutFormElementsAndFieldSetsToFormData; using mojom::SubmissionSource; @@ -98,32 +99,12 @@ namespace { // upon, instead of multiple in close succession (debounce time). size_t kWaitTimeForSelectOptionsChangesMs = 50; -// Gets all the data list values (with corresponding label) for the given -// element. -void GetDataListSuggestions(const WebInputElement& element, - std::vector<base::string16>* values, - std::vector<base::string16>* labels) { - for (const auto& option : element.FilteredDataListOptions()) { - values->push_back(option.Value().Utf16()); - if (option.Value() != option.Label()) - labels->push_back(option.Label().Utf16()); - else - labels->push_back(base::string16()); - } -} - -// Trim the vector before sending it to the browser process to ensure we -// don't send too much data through the IPC. -void TrimStringVectorForIPC(std::vector<base::string16>* strings) { - // Limit the size of the vector. - if (strings->size() > kMaxListSize) - strings->resize(kMaxListSize); - - // Limit the size of the strings in the vector. - for (size_t i = 0; i < strings->size(); ++i) { - if ((*strings)[i].length() > kMaxDataLength) - (*strings)[i].resize(kMaxDataLength); - } +// Helper function to return EXTRACT_DATALIST if kAutofillExtractAllDatalist is +// enabled, otherwise EXTRACT_NONE is returned. +ExtractMask GetExtractDatalistMask() { + return base::FeatureList::IsEnabled(features::kAutofillExtractAllDatalists) + ? form_util::EXTRACT_DATALIST + : form_util::EXTRACT_NONE; } } // namespace @@ -160,6 +141,9 @@ AutofillAgent::AutofillAgent(content::RenderFrame* render_frame, &AutofillAgent::BindPendingReceiver, base::Unretained(this))); } +// The destructor is not guaranteed to be called. Destruction happens (only) +// through the OnDestruct() event, which posts a task to delete this object. +// The process may be killed before this deletion can happen. AutofillAgent::~AutofillAgent() { RemoveFormObserver(this); } @@ -175,17 +159,13 @@ bool AutofillAgent::FormDataCompare::operator()(const FormData& lhs, std::tie(rhs.name, rhs.url, rhs.action, rhs.is_form_tag); } -void AutofillAgent::DidCommitProvisionalLoad(bool is_same_document_navigation, - ui::PageTransition transition) { +void AutofillAgent::DidCommitProvisionalLoad(ui::PageTransition transition) { blink::WebFrame* frame = render_frame()->GetWebFrame(); // TODO(dvadym): check if we need to check if it is main frame navigation // http://crbug.com/443155 if (frame->Parent()) return; // Not a top-level navigation. - if (is_same_document_navigation) - return; - // Navigation to a new page or a page refresh. element_.Reset(); @@ -225,9 +205,11 @@ void AutofillAgent::DidChangeScrollOffsetImpl( FormData form; FormFieldData field; - if (FindFormAndFieldForFormControlElement(element_, field_data_manager_.get(), - form_util::EXTRACT_BOUNDS, &form, - &field)) { + if (FindFormAndFieldForFormControlElement( + element_, field_data_manager_.get(), + static_cast<ExtractMask>(form_util::EXTRACT_BOUNDS | + GetExtractDatalistMask()), + &form, &field)) { GetAutofillDriver()->TextFieldDidScroll(form, field, field.bounds); } @@ -237,14 +219,6 @@ void AutofillAgent::DidChangeScrollOffsetImpl( void AutofillAgent::FocusedElementChanged(const WebElement& element) { was_focused_before_now_ = false; - - if ((IsKeyboardAccessoryEnabled() || !focus_requires_scroll_) && - !element.IsNull() && - element.GetDocument().GetFrame()->HasTransientUserActivation()) { - focused_node_was_last_clicked_ = true; - HandleFocusChangeComplete(); - } - HidePopup(); if (element.IsNull()) { @@ -258,14 +232,30 @@ void AutofillAgent::FocusedElementChanged(const WebElement& element) { const WebInputElement* input = ToWebInputElement(&element); + bool focus_moved_to_new_form = false; if (!last_interacted_form_.IsNull() && (!input || last_interacted_form_ != input->Form())) { // The focused element is not part of the last interacted form (could be // in a different form). GetAutofillDriver()->FocusNoLongerOnForm(); - return; + focus_moved_to_new_form = true; + } + + // Calls HandleFocusChangeComplete() after notifying the focus is no longer on + // the previous form, then early return. No need to notify the newly focused + // element because that will be done by HandleFocusChangeComplete() which + // triggers FormControlElementClicked(). + // Refer to http://crbug.com/1105254 + if ((IsKeyboardAccessoryEnabled() || !focus_requires_scroll_) && + !element.IsNull() && + element.GetDocument().GetFrame()->HasTransientUserActivation()) { + focused_node_was_last_clicked_ = true; + HandleFocusChangeComplete(); } + if (focus_moved_to_new_form) + return; + if (!input || !input->IsEnabled() || input->IsReadOnly() || !input->IsTextField()) return; @@ -274,9 +264,11 @@ void AutofillAgent::FocusedElementChanged(const WebElement& element) { FormData form; FormFieldData field; - if (FindFormAndFieldForFormControlElement(element_, field_data_manager_.get(), - form_util::EXTRACT_BOUNDS, &form, - &field)) { + if (FindFormAndFieldForFormControlElement( + element_, field_data_manager_.get(), + static_cast<ExtractMask>(form_util::EXTRACT_BOUNDS | + GetExtractDatalistMask()), + &form, &field)) { GetAutofillDriver()->FocusOnFormField(form, field, field.bounds); } } @@ -355,9 +347,11 @@ void AutofillAgent::OnTextFieldDidChange(const WebInputElement& element) { FormData form; FormFieldData field; - if (FindFormAndFieldForFormControlElement(element, field_data_manager_.get(), - form_util::EXTRACT_BOUNDS, &form, - &field)) { + if (FindFormAndFieldForFormControlElement( + element, field_data_manager_.get(), + static_cast<ExtractMask>(form_util::EXTRACT_BOUNDS | + GetExtractDatalistMask()), + &form, &field)) { GetAutofillDriver()->TextFieldDidChange(form, field, field.bounds, AutofillTickClock::NowTicks()); } @@ -616,9 +610,8 @@ bool AutofillAgent::CollectFormlessElements(FormData* output) { if (control_elements.size() > kMaxParseableFields) return false; - const form_util::ExtractMask extract_mask = - static_cast<form_util::ExtractMask>(form_util::EXTRACT_VALUE | - form_util::EXTRACT_OPTIONS); + const ExtractMask extract_mask = static_cast<ExtractMask>( + form_util::EXTRACT_VALUE | form_util::EXTRACT_OPTIONS); return UnownedCheckoutFormElementsAndFieldSetsToFormData( fieldsets, control_elements, nullptr, document, field_data_manager_.get(), @@ -784,15 +777,18 @@ void AutofillAgent::QueryAutofillSuggestions( FormData form; FormFieldData field; - if (!FindFormAndFieldForFormControlElement(element, field_data_manager_.get(), - form_util::EXTRACT_BOUNDS, &form, - &field)) { + if (!FindFormAndFieldForFormControlElement( + element, field_data_manager_.get(), + static_cast<ExtractMask>(form_util::EXTRACT_BOUNDS | + GetExtractDatalistMask()), + &form, &field)) { // If we didn't find the cached form, at least let autocomplete have a shot // at providing suggestions. WebFormControlElementToFormField( element, nullptr, - static_cast<form_util::ExtractMask>(form_util::EXTRACT_VALUE | - form_util::EXTRACT_BOUNDS), + static_cast<ExtractMask>(form_util::EXTRACT_VALUE | + form_util::EXTRACT_BOUNDS | + GetExtractDatalistMask()), &field); } @@ -803,20 +799,15 @@ void AutofillAgent::QueryAutofillSuggestions( return; } - std::vector<base::string16> data_list_values; - std::vector<base::string16> data_list_labels; - const WebInputElement* input_element = ToWebInputElement(&element); - if (input_element) { - // Find the datalist values and send them to the browser process. - GetDataListSuggestions(*input_element, &data_list_values, - &data_list_labels); - TrimStringVectorForIPC(&data_list_values); - TrimStringVectorForIPC(&data_list_labels); + if (!base::FeatureList::IsEnabled(features::kAutofillExtractAllDatalists)) { + if (const WebInputElement* input_element = ToWebInputElement(&element)) { + // Find the datalist values and send them to the browser process. + form_util::GetDataListSuggestions(*input_element, &field.datalist_values, + &field.datalist_labels); + } } is_popup_possibly_visible_ = true; - - GetAutofillDriver()->SetDataList(data_list_values, data_list_labels); GetAutofillDriver()->QueryFormFieldAutofill(autofill_query_id_, form, field, field.bounds, autoselect_first_suggestion); @@ -1041,7 +1032,9 @@ void AutofillAgent::OnProvisionallySaveForm( FormData form; FormFieldData field; if (FindFormAndFieldForFormControlElement( - element, field_data_manager_.get(), form_util::EXTRACT_BOUNDS, + element, field_data_manager_.get(), + static_cast<ExtractMask>(form_util::EXTRACT_BOUNDS | + GetExtractDatalistMask()), &form, &field)) { GetAutofillDriver()->SelectControlDidChange(form, field, field.bounds); } diff --git a/chromium/components/autofill/content/renderer/autofill_agent.h b/chromium/components/autofill/content/renderer/autofill_agent.h index b0e7943afb2..be2b158814f 100644 --- a/chromium/components/autofill/content/renderer/autofill_agent.h +++ b/chromium/components/autofill/content/renderer/autofill_agent.h @@ -168,8 +168,7 @@ class AutofillAgent : public content::RenderFrameObserver, }; // content::RenderFrameObserver: - void DidCommitProvisionalLoad(bool is_same_document_navigation, - ui::PageTransition transition) override; + void DidCommitProvisionalLoad(ui::PageTransition transition) override; void DidFinishDocumentLoad() override; void DidChangeScrollOffset() override; void FocusedElementChanged(const blink::WebElement& element) override; diff --git a/chromium/components/autofill/content/renderer/focus_test_utils.h b/chromium/components/autofill/content/renderer/focus_test_utils.h index a20563153b8..038e8fcedb0 100644 --- a/chromium/components/autofill/content/renderer/focus_test_utils.h +++ b/chromium/components/autofill/content/renderer/focus_test_utils.h @@ -5,9 +5,9 @@ #ifndef COMPONENTS_AUTOFILL_CONTENT_RENDERER_FOCUS_TEST_UTILS_H_ #define COMPONENTS_AUTOFILL_CONTENT_RENDERER_FOCUS_TEST_UTILS_H_ -#include "base/callback.h" -#include "string" +#include <string> +#include "base/callback.h" #include "third_party/blink/public/web/web_document.h" namespace autofill { diff --git a/chromium/components/autofill/content/renderer/form_autofill_util.cc b/chromium/components/autofill/content/renderer/form_autofill_util.cc index c1f47cb9575..35f247f215c 100644 --- a/chromium/components/autofill/content/renderer/form_autofill_util.cc +++ b/chromium/components/autofill/content/renderer/form_autofill_util.cc @@ -1470,11 +1470,6 @@ bool UnownedFormElementsAndFieldSetsToFormData( FormData* form, FormFieldData* field) { form->url = GetCanonicalOriginForDocument(document); - if (IsAutofillFieldMetadataEnabled() && !document.Body().IsNull()) { - SCOPED_UMA_HISTOGRAM_TIMER( - "PasswordManager.ButtonTitlePerformance.NoFormTag"); - form->button_titles = InferButtonTitlesForForm(document.Body()); - } if (document.GetFrame() && document.GetFrame()->Top()) { form->main_frame_origin = document.GetFrame()->Top()->GetSecurityOrigin(); } else { @@ -1514,6 +1509,20 @@ bool ScriptModifiedUsernameAcceptable( return field_data_manager->FindMachedValue(value); } +// Trim the vector before sending it to the browser process to ensure we +// don't send too much data through the IPC. +void TrimStringVectorForIPC(std::vector<base::string16>* strings) { + // Limit the size of the vector. + if (strings->size() > kMaxListSize) + strings->resize(kMaxListSize); + + // Limit the size of the strings in the vector. + for (auto& string : *strings) { + if (string.length() > kMaxDataLength) + string.resize(kMaxDataLength); + } +} + // Helper function that strips any authentication data, as well as query and // ref portions of URL. GURL StripAuthAndParams(const GURL& gurl) { @@ -1527,6 +1536,20 @@ GURL StripAuthAndParams(const GURL& gurl) { } // namespace +void GetDataListSuggestions(const WebInputElement& element, + std::vector<base::string16>* values, + std::vector<base::string16>* labels) { + for (const auto& option : element.FilteredDataListOptions()) { + values->push_back(option.Value().Utf16()); + if (option.Value() != option.Label()) + labels->push_back(option.Label().Utf16()); + else + labels->push_back(base::string16()); + } + TrimStringVectorForIPC(values); + TrimStringVectorForIPC(labels); +} + bool ExtractFormData(const WebFormElement& form_element, const FieldDataManager& field_data_manager, FormData* data) { @@ -1583,7 +1606,7 @@ GURL GetCanonicalOriginForDocument(const WebDocument& document) { return StripAuthAndParams(full_origin); } -GURL GetOriginWithoutAuthForDocument(const WebDocument& document) { +GURL GetDocumentUrlWithoutAuth(const WebDocument& document) { GURL::Replacements rep; rep.ClearUsername(); rep.ClearPassword(); @@ -1647,6 +1670,11 @@ base::string16 GetFormIdentifier(const WebFormElement& form) { return identifier; } +FormRendererId GetFormRendererId(const blink::WebFormElement& form) { + return form.IsNull() ? FormRendererId() + : FormRendererId(form.UniqueRendererFormId()); +} + base::i18n::TextDirection GetTextDirectionForElement( const blink::WebFormControlElement& element) { // Use 'text-align: left|right' if set or 'direction' otherwise. @@ -1766,6 +1794,12 @@ void WebFormControlElementToFormField( } } } + if (extract_mask & EXTRACT_DATALIST) { + if (auto* input = blink::ToWebInputElement(&element)) { + GetDataListSuggestions(*input, &field->datalist_values, + &field->datalist_labels); + } + } if (!(extract_mask & EXTRACT_VALUE)) return; @@ -1834,11 +1868,6 @@ bool WebFormElementToFormData( form->action = GetCanonicalActionForForm(form_element); form->is_action_empty = form_element.Action().IsNull() || form_element.Action().IsEmpty(); - if (IsAutofillFieldMetadataEnabled()) { - SCOPED_UMA_HISTOGRAM_TIMER( - "PasswordManager.ButtonTitlePerformance.HasFormTag"); - form->button_titles = InferButtonTitlesForForm(form_element); - } if (frame->Top()) { form->main_frame_origin = frame->Top()->GetSecurityOrigin(); } else { @@ -2179,6 +2208,41 @@ base::string16 FindChildText(const WebNode& node) { return FindChildTextWithIgnoreList(node, std::set<WebNode>()); } +ButtonTitleList GetButtonTitles(const WebFormElement& web_form, + const WebDocument& document, + ButtonTitlesCache* button_titles_cache) { + DCHECK(button_titles_cache); + if (!IsAutofillFieldMetadataEnabled() && web_form.IsNull()) + return ButtonTitleList(); + + // True if the cache has no entry for |web_form|. + bool cache_miss = true; + // Iterator pointing to the entry for |web_form| if the entry for |web_form| + // is found. + ButtonTitlesCache::iterator form_position; + std::tie(form_position, cache_miss) = button_titles_cache->emplace( + GetFormRendererId(web_form), ButtonTitleList()); + if (!cache_miss) + return form_position->second; + + ButtonTitleList button_titles; + DCHECK(!web_form.IsNull() || !document.IsNull()); + if (web_form.IsNull()) { + const WebElement& body = document.Body(); + if (!body.IsNull()) { + SCOPED_UMA_HISTOGRAM_TIMER( + "PasswordManager.ButtonTitlePerformance.NoFormTag"); + button_titles = InferButtonTitlesForForm(body); + } + } else { + SCOPED_UMA_HISTOGRAM_TIMER( + "PasswordManager.ButtonTitlePerformance.HasFormTag"); + button_titles = InferButtonTitlesForForm(web_form); + } + form_position->second = std::move(button_titles); + return form_position->second; +} + base::string16 FindChildTextWithIgnoreListForTesting( const WebNode& node, const std::set<WebNode>& divs_to_skip) { @@ -2192,10 +2256,6 @@ bool InferLabelForElementForTesting(const WebFormControlElement& element, return InferLabelForElement(element, stop_words, label, label_source); } -ButtonTitleList InferButtonTitlesForTesting(const WebElement& form_element) { - return InferButtonTitlesForForm(form_element); -} - WebFormElement FindFormByUniqueRendererId(WebDocument doc, FormRendererId form_renderer_id) { for (const auto& form : doc.Forms()) { diff --git a/chromium/components/autofill/content/renderer/form_autofill_util.h b/chromium/components/autofill/content/renderer/form_autofill_util.h index 877d8aa285b..1a33e640255 100644 --- a/chromium/components/autofill/content/renderer/form_autofill_util.h +++ b/chromium/components/autofill/content/renderer/form_autofill_util.h @@ -10,6 +10,7 @@ #include <set> #include <vector> +#include "base/containers/flat_map.h" #include "base/i18n/rtl.h" #include "base/macros.h" #include "base/strings/string16.h" @@ -45,6 +46,10 @@ class FieldDataManager; namespace form_util { +// Mapping from a form element's render id to results of button titles +// heuristics for a given form element. +using ButtonTitlesCache = base::flat_map<FormRendererId, ButtonTitleList>; + // A bit field mask to extract data from WebFormControlElement. // Copied to components/autofill/ios/browser/resources/autofill_controller.js. enum ExtractMask { @@ -59,8 +64,18 @@ enum ExtractMask { // WebFormControlElement. EXTRACT_BOUNDS = 1 << 3, // Extract bounds from WebFormControlElement, // could trigger layout if needed. + EXTRACT_DATALIST = 1 << 4, // Extract datalist from WebFormControlElement, + // the total number of options is up to + // kMaxListSize and each option has as far as + // kMaxDataLength. }; +// Gets up to kMaxListSize data list values (with corresponding label) for the +// given element, each value and label have as far as kMaxDataLength. +void GetDataListSuggestions(const blink::WebInputElement& element, + std::vector<base::string16>* values, + std::vector<base::string16>* labels); + // Extract FormData from the form element and return whether the operation was // successful. bool ExtractFormData(const blink::WebFormElement& form_element, @@ -89,7 +104,7 @@ bool AreFormContentsVisible(const blink::WebFormElement& form); // strip unnecessary data (e.g. query params and HTTP credentials). GURL GetCanonicalActionForForm(const blink::WebFormElement& form); GURL GetCanonicalOriginForDocument(const blink::WebDocument& document); -GURL GetOriginWithoutAuthForDocument(const blink::WebDocument& document); +GURL GetDocumentUrlWithoutAuth(const blink::WebDocument& document); // Returns true if |element| is a month input element. bool IsMonthInput(const blink::WebInputElement* element); @@ -123,6 +138,10 @@ bool IsWebElementVisible(const blink::WebElement& element); // attribute. base::string16 GetFormIdentifier(const blink::WebFormElement& form); +// Returns the |unique_renderer_id| of a given |WebFormElement|. If +// |WebFormElement::IsNull()|, returns a null renderer ID. +FormRendererId GetFormRendererId(const blink::WebFormElement& form); + // Returns text alignment for |element|. base::i18n::TextDirection GetTextDirectionForElement( const blink::WebFormControlElement& element); @@ -273,6 +292,15 @@ void PreviewSuggestion(const base::string16& suggestion, // Whitespace is trimmed from text accumulated at descendant nodes. base::string16 FindChildText(const blink::WebNode& node); +// Returns the button titles for |web_form| (or unowned buttons in |document| if +// |web_form| is null). |button_titles_cache| can be used to spare recomputation +// if called multiple times for the same form. Button titles computation for +// unowned buttons is enabled only in Dev and Canary (crbug.com/1086446), +// otherwise the method returns an empty list. +ButtonTitleList GetButtonTitles(const blink::WebFormElement& web_form, + const blink::WebDocument& document, + ButtonTitlesCache* button_titles_cache); + // Exposed for testing purpose base::string16 FindChildTextWithIgnoreListForTesting( const blink::WebNode& node, @@ -281,8 +309,6 @@ bool InferLabelForElementForTesting(const blink::WebFormControlElement& element, const std::vector<base::char16>& stop_words, base::string16* label, FormFieldData::LabelSource* label_source); -ButtonTitleList InferButtonTitlesForTesting( - const blink::WebElement& form_element); // Returns form by unique renderer id. Return null element if there is no form // with given form renderer id. diff --git a/chromium/components/autofill/content/renderer/form_autofill_util_browsertest.cc b/chromium/components/autofill/content/renderer/form_autofill_util_browsertest.cc index 9693e877d2f..34575e77b47 100644 --- a/chromium/components/autofill/content/renderer/form_autofill_util_browsertest.cc +++ b/chromium/components/autofill/content/renderer/form_autofill_util_browsertest.cc @@ -4,12 +4,15 @@ #include "components/autofill/content/renderer/form_autofill_util.h" +#include "base/metrics/field_trial.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" +#include "base/test/scoped_feature_list.h" #include "components/autofill/core/common/mojom/autofill_types.mojom-shared.h" #include "components/autofill/core/common/renderer_id.h" #include "content/public/test/render_view_test.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/public/platform/web_string.h" #include "third_party/blink/public/platform/web_vector.h" @@ -138,6 +141,14 @@ const char kDivTableExample6[] = // TODO(crbug.com/796918): Should be "label" or "label-" const char kDivTableExample6Expected[] = ""; +void VerifyButtonTitleCache(const WebFormElement& form_target, + const ButtonTitleList& expected_button_titles, + const ButtonTitlesCache& actual_cache) { + EXPECT_THAT(actual_cache, + testing::ElementsAre(testing::Pair(GetFormRendererId(form_target), + expected_button_titles))); +} + class FormAutofillUtilsTest : public content::RenderViewTest { public: FormAutofillUtilsTest() {} @@ -272,8 +283,8 @@ TEST_F(FormAutofillUtilsTest, InferLabelSourceTest) { } } -TEST_F(FormAutofillUtilsTest, InferButtonTitleForFormTest) { - const char kHtml[] = +TEST_F(FormAutofillUtilsTest, GetButtonTitles) { + constexpr char kHtml[] = "<form id='target'>" " <input type='button' value='Clear field'>" " <input type='button' value='Clear field'>" @@ -294,8 +305,11 @@ TEST_F(FormAutofillUtilsTest, InferButtonTitleForFormTest) { ASSERT_FALSE(target.IsNull()); const WebFormElement& form_target = target.ToConst<WebFormElement>(); ASSERT_FALSE(form_target.IsNull()); + ButtonTitlesCache cache; + + autofill::ButtonTitleList actual = + GetButtonTitles(form_target, web_frame->GetDocument(), &cache); - autofill::ButtonTitleList actual = InferButtonTitlesForTesting(form_target); autofill::ButtonTitleList expected = { {base::UTF8ToUTF16("Clear field"), ButtonTitleType::INPUT_ELEMENT_BUTTON_TYPE}, @@ -309,9 +323,11 @@ TEST_F(FormAutofillUtilsTest, InferButtonTitleForFormTest) { {base::UTF8ToUTF16("Join"), ButtonTitleType::DIV}, {base::UTF8ToUTF16("Start"), ButtonTitleType::SPAN}}; EXPECT_EQ(expected, actual); + + VerifyButtonTitleCache(form_target, expected, cache); } -TEST_F(FormAutofillUtilsTest, InferButtonTitleForFormTest_TooLongTitle) { +TEST_F(FormAutofillUtilsTest, GetButtonTitles_TooLongTitle) { std::string title; for (int i = 0; i < 300; ++i) title += "a"; @@ -330,8 +346,10 @@ TEST_F(FormAutofillUtilsTest, InferButtonTitleForFormTest_TooLongTitle) { ASSERT_FALSE(target.IsNull()); const WebFormElement& form_target = target.ToConst<WebFormElement>(); ASSERT_FALSE(form_target.IsNull()); + ButtonTitlesCache cache; - autofill::ButtonTitleList actual = InferButtonTitlesForTesting(form_target); + autofill::ButtonTitleList actual = + GetButtonTitles(form_target, web_frame->GetDocument(), &cache); int total_length = 0; for (auto title : actual) { @@ -341,8 +359,14 @@ TEST_F(FormAutofillUtilsTest, InferButtonTitleForFormTest_TooLongTitle) { EXPECT_EQ(200, total_length); } -TEST_F(FormAutofillUtilsTest, InferButtonTitle_Formless) { - const char kNoFormHtml[] = +TEST_F(FormAutofillUtilsTest, GetButtonTitles_Formless) { + // Button titles computation and crowdsourcing for <form>less forms are + // enabled only if |AutofillFieldMetadata| (Dev and Canary) is enabled. + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.Init(); + base::FieldTrialList::CreateFieldTrial("AutofillFieldMetadata", "Enabled"); + + constexpr char kNoFormHtml[] = "<div class='reg-form'>" " <input type='button' value='\n Show\t password '>" " <button>Sign Up</button>" @@ -358,10 +382,12 @@ TEST_F(FormAutofillUtilsTest, InferButtonTitle_Formless) { LoadHTML(kNoFormHtml); WebLocalFrame* web_frame = GetMainFrame(); ASSERT_NE(nullptr, web_frame); - const WebElement& body = web_frame->GetDocument().Body(); - ASSERT_FALSE(body.IsNull()); + WebFormElement form_target; + ASSERT_FALSE(web_frame->GetDocument().Body().IsNull()); + ButtonTitlesCache cache; - autofill::ButtonTitleList actual = InferButtonTitlesForTesting(body); + autofill::ButtonTitleList actual = + GetButtonTitles(form_target, web_frame->GetDocument(), &cache); autofill::ButtonTitleList expected = { {base::UTF8ToUTF16("Show password"), ButtonTitleType::INPUT_ELEMENT_BUTTON_TYPE}, @@ -370,6 +396,42 @@ TEST_F(FormAutofillUtilsTest, InferButtonTitle_Formless) { {base::UTF8ToUTF16("Register"), ButtonTitleType::BUTTON_ELEMENT_BUTTON_TYPE}}; EXPECT_EQ(expected, actual); + + VerifyButtonTitleCache(form_target, expected, cache); +} + +TEST_F(FormAutofillUtilsTest, GetButtonTitles_Formless_DisabledByDefault) { + // Button titles computation and crowdsourcing for <form>less forms should be + // disabled if |AutofillFieldMetadata| is disabled. + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.Init(); + base::FieldTrialList::CreateFieldTrial("AutofillFieldMetadata", "Disabled"); + + constexpr char kNoFormHtml[] = + "<div class='reg-form'>" + " <input type='button' value='\n Show\t password '>" + " <button>Sign Up</button>" + " <button type='button'>Register</button>" + "</div>" + "<form id='ignored-form'>" + " <input type='button' value='Ignore this'>" + " <button>Ignore this</button>" + " <a id='Submit' value='Ignore this'>" + " <div name='BTN'>Ignore this</div>" + "</form>"; + + LoadHTML(kNoFormHtml); + WebLocalFrame* web_frame = GetMainFrame(); + ASSERT_NE(nullptr, web_frame); + WebFormElement form_target; + ASSERT_FALSE(web_frame->GetDocument().Body().IsNull()); + ButtonTitlesCache cache; + + autofill::ButtonTitleList actual = + GetButtonTitles(form_target, web_frame->GetDocument(), &cache); + + EXPECT_TRUE(actual.empty()); + EXPECT_TRUE(cache.empty()); } TEST_F(FormAutofillUtilsTest, IsEnabled) { @@ -788,6 +850,84 @@ TEST_F(FormAutofillUtilsTest, ExtractUnownedBounds) { EXPECT_FALSE(form_data.fields.back().bounds.IsEmpty()); } +TEST_F(FormAutofillUtilsTest, GetDataListSuggestions) { + LoadHTML( + "<body><input list='datalist_id' name='count' id='i1'><datalist " + "id='datalist_id'><option value='1'><option " + "value='2'></datalist></body>"); + WebDocument doc = GetMainFrame()->GetDocument(); + auto web_control = doc.GetElementById("i1").To<WebInputElement>(); + std::vector<base::string16> values; + std::vector<base::string16> labels; + GetDataListSuggestions(web_control, &values, &labels); + ASSERT_EQ(values.size(), 2u); + ASSERT_EQ(labels.size(), 2u); + EXPECT_EQ(values[0], base::UTF8ToUTF16("1")); + EXPECT_EQ(values[1], base::UTF8ToUTF16("2")); + EXPECT_EQ(labels[0], base::UTF8ToUTF16("")); + EXPECT_EQ(labels[1], base::UTF8ToUTF16("")); +} + +TEST_F(FormAutofillUtilsTest, GetDataListSuggestionsWithLabels) { + LoadHTML( + "<body><input list='datalist_id' name='count' id='i1'><datalist " + "id='datalist_id'><option value='1'>one</option><option " + "value='2'>two</option></datalist></body>"); + WebDocument doc = GetMainFrame()->GetDocument(); + auto web_control = doc.GetElementById("i1").To<WebInputElement>(); + std::vector<base::string16> values; + std::vector<base::string16> labels; + GetDataListSuggestions(web_control, &values, &labels); + ASSERT_EQ(values.size(), 2u); + ASSERT_EQ(labels.size(), 2u); + EXPECT_EQ(values[0], base::UTF8ToUTF16("1")); + EXPECT_EQ(values[1], base::UTF8ToUTF16("2")); + EXPECT_EQ(labels[0], base::UTF8ToUTF16("one")); + EXPECT_EQ(labels[1], base::UTF8ToUTF16("two")); +} + +TEST_F(FormAutofillUtilsTest, ExtractDataList) { + LoadHTML( + "<body><input list='datalist_id' name='count' id='i1'><datalist " + "id='datalist_id'><option value='1'>one</option><option " + "value='2'>two</option></datalist></body>"); + WebDocument doc = GetMainFrame()->GetDocument(); + auto web_control = doc.GetElementById("i1").To<WebInputElement>(); + FormData form_data; + FormFieldData form_field_data; + ASSERT_TRUE(FindFormAndFieldForFormControlElement( + web_control, nullptr /*field_data_manager*/, EXTRACT_DATALIST, &form_data, + &form_field_data)); + + auto& values = form_data.fields.back().datalist_values; + auto& labels = form_data.fields.back().datalist_labels; + ASSERT_EQ(values.size(), 2u); + ASSERT_EQ(labels.size(), 2u); + EXPECT_EQ(values[0], base::UTF8ToUTF16("1")); + EXPECT_EQ(values[1], base::UTF8ToUTF16("2")); + EXPECT_EQ(labels[0], base::UTF8ToUTF16("one")); + EXPECT_EQ(labels[1], base::UTF8ToUTF16("two")); + EXPECT_EQ(form_field_data.datalist_values, values); + EXPECT_EQ(form_field_data.datalist_labels, labels); +} + +TEST_F(FormAutofillUtilsTest, NotExtractDataList) { + LoadHTML( + "<body><input list='datalist_id' name='count' id='i1'><datalist " + "id='datalist_id'><option value='1'>one</option><option " + "value='2'>two</option></datalist></body>"); + WebDocument doc = GetMainFrame()->GetDocument(); + auto web_control = doc.GetElementById("i1").To<WebInputElement>(); + FormData form_data; + FormFieldData form_field_data; + ASSERT_TRUE(FindFormAndFieldForFormControlElement( + web_control, nullptr /*field_data_manager*/, &form_data, + &form_field_data)); + + EXPECT_TRUE(form_data.fields.back().datalist_values.empty()); + EXPECT_TRUE(form_data.fields.back().datalist_labels.empty()); +} + } // namespace } // namespace form_util } // namespace autofill diff --git a/chromium/components/autofill/content/renderer/form_cache.cc b/chromium/components/autofill/content/renderer/form_cache.cc index 1bde0197a15..fe638091f41 100644 --- a/chromium/components/autofill/content/renderer/form_cache.cc +++ b/chromium/components/autofill/content/renderer/form_cache.cc @@ -18,6 +18,7 @@ #include "base/strings/string_split.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" +#include "base/strings/string_number_conversions.h" #include "components/autofill/content/renderer/form_autofill_util.h" #include "components/autofill/content/renderer/page_form_analyser_logger.h" #include "components/autofill/core/common/autofill_constants.h" @@ -420,37 +421,19 @@ bool FormCache::ShowPredictions(const FormDataPredictions& form, std::vector<WebFormControlElement> control_elements; - // First check the synthetic form. - bool found_synthetic_form = false; - if (form.data.SameFormAs(synthetic_form_)) { - found_synthetic_form = true; + if (form.data.unique_renderer_id.is_null()) { // Form is synthetic. WebDocument document = frame_->GetDocument(); control_elements = form_util::GetUnownedAutofillableFormFieldElements( document.All(), nullptr); - } - - if (!found_synthetic_form) { - // Find the real form by searching through the WebDocuments. - bool found_form = false; - + } else { for (const WebFormElement& form_element : frame_->GetDocument().Forms()) { - // To match two forms, we look for the form's name and the number of - // fields on that form. (Form names may not be unique.) - // Note: WebString() == WebString(string16()) does not evaluate to |true| - // -- WebKit distinguishes between a "null" string (lhs) and an "empty" - // string (rhs). We don't want that distinction, so forcing to string16. - base::string16 element_name = form_util::GetFormIdentifier(form_element); - if (element_name == form.data.name) { - found_form = true; + FormRendererId form_id(form_element.UniqueRendererFormId()); + if (form_id == form.data.unique_renderer_id) { control_elements = form_util::ExtractAutofillableElementsInForm(form_element); - if (control_elements.size() == form.fields.size()) - break; + break; } } - - if (!found_form) - return false; } if (control_elements.size() != form.fields.size()) { @@ -464,11 +447,9 @@ bool FormCache::ShowPredictions(const FormDataPredictions& form, WebFormControlElement& element = control_elements[i]; const FormFieldData& field_data = form.data.fields[i]; - if (element.NameForAutofill().Utf16() != field_data.name) { - // Keep things simple. Don't show predictions for elements whose names - // were modified between page load and the server's response to our query. + FieldRendererId field_id(element.UniqueRendererFormControlId()); + if (field_id != field_data.unique_renderer_id) continue; - } const FormFieldDataPredictions& field = form.fields[i]; // Possibly add a console warning for this field regarding the usage of @@ -492,6 +473,11 @@ bool FormCache::ShowPredictions(const FormDataPredictions& form, const base::string16 truncated_label = field_data.label.substr( 0, std::min(field_data.label.length(), kMaxLabelSize)); + std::string form_id = + base::NumberToString(form.data.unique_renderer_id.value()); + std::string field_id = + base::NumberToString(field.field.unique_renderer_id.value()); + std::string title = base::StrCat({"overall type: ", field.overall_type, // "\nserver type: ", field.server_type, // @@ -500,7 +486,9 @@ bool FormCache::ShowPredictions(const FormDataPredictions& form, "\nparseable name: ", field.parseable_name, // "\nsection: ", field.section, // "\nfield signature: ", field.signature, // - "\nform signature: ", form.signature}); + "\nform signature: ", form.signature, // + "\nform renderer id: ", form_id, // + "\nfield renderer id: ", field_id}); // Set this debug string to the title so that a developer can easily debug // by hovering the mouse over the input field. diff --git a/chromium/components/autofill/content/renderer/form_tracker.cc b/chromium/components/autofill/content/renderer/form_tracker.cc index e077f40a8b7..8613e8d3938 100644 --- a/chromium/components/autofill/content/renderer/form_tracker.cc +++ b/chromium/components/autofill/content/renderer/form_tracker.cc @@ -126,14 +126,13 @@ void FormTracker::FormControlDidChangeImpl( } } -void FormTracker::DidCommitProvisionalLoad(bool is_same_document_navigation, - ui::PageTransition transition) { +void FormTracker::DidCommitProvisionalLoad(ui::PageTransition transition) { DCHECK_CALLED_ON_VALID_SEQUENCE(form_tracker_sequence_checker_); - if (!is_same_document_navigation) { - ResetLastInteractedElements(); - return; - } + ResetLastInteractedElements(); +} +void FormTracker::DidFinishSameDocumentNavigation() { + DCHECK_CALLED_ON_VALID_SEQUENCE(form_tracker_sequence_checker_); FireSubmissionIfFormDisappear(SubmissionSource::SAME_DOCUMENT_NAVIGATION); } diff --git a/chromium/components/autofill/content/renderer/form_tracker.h b/chromium/components/autofill/content/renderer/form_tracker.h index ed4e403ff26..1ac3a8a1272 100644 --- a/chromium/components/autofill/content/renderer/form_tracker.h +++ b/chromium/components/autofill/content/renderer/form_tracker.h @@ -87,8 +87,8 @@ class FormTracker : public content::RenderFrameObserver { FormSubmittedBySameDocumentNavigation); // content::RenderFrameObserver: - void DidCommitProvisionalLoad(bool is_same_document_navigation, - ui::PageTransition transition) override; + void DidCommitProvisionalLoad(ui::PageTransition transition) override; + void DidFinishSameDocumentNavigation() override; void DidStartNavigation( const GURL& url, base::Optional<blink::WebNavigationType> navigation_type) override; diff --git a/chromium/components/autofill/content/renderer/html_based_username_detector.cc b/chromium/components/autofill/content/renderer/html_based_username_detector.cc index 9929ea02fd1..1006ccd24ab 100644 --- a/chromium/components/autofill/content/renderer/html_based_username_detector.cc +++ b/chromium/components/autofill/content/renderer/html_based_username_detector.cc @@ -284,13 +284,6 @@ void FindUsernameFieldInternal( } } -// Returns the |unique_renderer_id| of a given |WebFormElement|. If -// |WebFormElement::IsNull()| return a null renderer ID. -FormRendererId GetFormRendererId(WebFormElement form) { - return form.IsNull() ? FormRendererId() - : FormRendererId(form.UniqueRendererFormId()); -} - } // namespace const std::vector<FieldRendererId>& GetPredictionsFieldBasedOnHtmlAttributes( @@ -311,8 +304,8 @@ const std::vector<FieldRendererId>& GetPredictionsFieldBasedOnHtmlAttributes( bool cache_miss = true; // Iterator pointing to the entry for |form| if the entry for |form| is found. UsernameDetectorCache::iterator form_position; - std::tie(form_position, cache_miss) = username_detector_cache->insert( - std::make_pair(GetFormRendererId(form), std::vector<FieldRendererId>())); + std::tie(form_position, cache_miss) = username_detector_cache->emplace( + form_util::GetFormRendererId(form), std::vector<FieldRendererId>()); if (cache_miss) { std::vector<FieldRendererId> username_predictions; diff --git a/chromium/components/autofill/content/renderer/html_based_username_detector_browsertest.cc b/chromium/components/autofill/content/renderer/html_based_username_detector_browsertest.cc new file mode 100644 index 00000000000..d39bca5d8cd --- /dev/null +++ b/chromium/components/autofill/content/renderer/html_based_username_detector_browsertest.cc @@ -0,0 +1,345 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/stringprintf.h" +#include "components/autofill/content/renderer/form_autofill_util.h" +#include "components/autofill/content/renderer/html_based_username_detector.h" +#include "content/public/test/render_view_test.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/web/web_document.h" +#include "third_party/blink/public/web/web_local_frame.h" + +using blink::WebElement; +using blink::WebFormControlElement; +using blink::WebFormElement; +using blink::WebLocalFrame; +using blink::WebString; + +namespace autofill { + +namespace { + +struct TextField { + const char* name; + const char* id; + const char* value; + const char* label; +}; + +constexpr char kTestForm[] = R"( + <FORM name="Test" id="userform"> + <label for="%s">%s</label> + <input type="text" name="%s" id="%s" value="%s" /> + + <label for="%s">%s</label> + <input type="text" name="%s" id="%s" value="%s" /> + + <input type="password" id="password" value="v" /> + <input type="submit" value="submit" /> + </FORM> +)"; + +std::string GetFormHTML(const TextField& first_field, + const TextField& second_field) { + return base::StringPrintf( + kTestForm, first_field.id, first_field.label, first_field.name, + first_field.id, first_field.value, second_field.id, second_field.label, + second_field.name, second_field.id, second_field.value); +} + +class HtmlBasedUsernameDetectorTest : public content::RenderViewTest { + protected: + struct TestCase { + const TextField first_text_field_parameter; + const TextField second_text_field_parameter; + const WebString expected_username_id; + }; + + FormData LoadFormDataFromHtml(const std::string& html) { + LoadHTML(html.data()); + const WebFormElement& form = GetFormElement(); + return GetFormDataFromForm(form); + } + + FormData GetFormDataFromForm(const WebFormElement& form) { + FormData form_data; + EXPECT_TRUE(form_util::WebFormElementToFormData( + form, WebFormControlElement(), nullptr, form_util::EXTRACT_NONE, + &form_data, nullptr)); + + return form_data; + } + + FieldRendererId GetRendererIdFromWebElementId(const WebString& id) { + const WebLocalFrame* frame = GetMainFrame(); + const WebElement& element = frame->GetDocument().GetElementById(id); + EXPECT_FALSE(element.IsNull()); + return FieldRendererId(element.ToConst<blink::WebInputElement>() + .UniqueRendererFormControlId()); + } + + WebFormElement GetFormElement() { + const WebLocalFrame* frame = GetMainFrame(); + const blink::WebVector<WebFormElement>& forms = + frame->GetDocument().Forms(); + EXPECT_EQ(1U, forms.size()); + EXPECT_FALSE(forms[0].IsNull()); + + return forms[0]; + } + + std::vector<WebFormControlElement> GetFormControlElements() { + const WebFormElement& form = GetFormElement(); + blink::WebVector<WebFormControlElement> control_elements = + form.GetFormControlElements(); + return control_elements.ReleaseVector(); + } + + void PredictAndCheckUsernameId(const std::string& html, + const WebString& expected_username_id) { + const FormData& form_data = LoadFormDataFromHtml(html); + const std::vector<WebFormControlElement>& control_elements = + GetFormControlElements(); + + // Get the expected renderer id from the expected username id. + const FieldRendererId expected_renderer_id = + GetRendererIdFromWebElementId(expected_username_id); + + // Run predictions and test the result. + UsernameDetectorCache cache; + const std::vector<FieldRendererId>& renderer_ids = + GetPredictionsFieldBasedOnHtmlAttributes(control_elements, form_data, + &cache); + + ASSERT_EQ(1u, cache.size()); + ASSERT_FALSE(cache.begin()->second.empty()); + EXPECT_EQ(expected_renderer_id, cache.begin()->second[0]); + ASSERT_FALSE(renderer_ids.empty()); + EXPECT_EQ(expected_renderer_id, renderer_ids[0]); + } +}; + +} // namespace + +TEST_F(HtmlBasedUsernameDetectorTest, DeveloperGroupAttributes) { + // Each test case consists of a set of parameters to be plugged into + // the TestCase struct, plus the corresponding expectations. The test data + // contains cases that are identified by HTML detector, and not by + // base heuristic. Thus, username field does not necessarely have to + // be right before password field. These tests basically check + // searching in developer group (i.e. name and id attribute, + // concatenated, with "$" guard in between). + const TestCase test_cases[] = { + // There are both field name and id. + {{"username", "x1d", "johnsmith"}, + {"email", "y1d", "js@google.com"}, + "x1d"}, + // there is no field id. + {{"username", "x1d", "johnsmith"}, + {"email", "y1d", "js@google.com"}, + "x1d"}, + // Upper or mixed case shouldn't matter. + {{"uSeRnAmE", "x1d", "johnsmith"}, + {"email", "y1d", "js@google.com"}, + "x1d"}, + // Check removal of special characters. + {{"u1_s2-e3~r4/n5(a)6m#e", "x1d", "johnsmith"}, + {"email", "y1d", "js@google.com"}, + "x1d"}, + // Check guard between field name and field id. + {{"us", "ername", "johnsmith"}, {"email", "id", "js@google.com"}, "id"}, + // Check removal of fields with latin negative words in developer group. + {{"email", "x", "js@google.com"}, + {"fake_username", "y", "johnsmith"}, + "x"}, + {{"email", "mail", "js@google.com"}, + {"user_name", "fullname", "johnsmith"}, + "mail"}, + // Identify latin translations of "username". + {{"benutzername", "x", "johnsmith"}, + {"email", "y", "js@google.com"}, + "x"}, + // Identify latin translations of "user". + {{"utilizator", "x1d", "johnsmith"}, + {"email", "y1d", "js@google.com"}, + "x1d"}, + // Identify technical words. + {{"loginid", "x1d", "johnsmith"}, + {"email", "y1d", "js@google.com"}, + "x1d"}, + // Identify weak words. + {{"usrname", "x1d", "johnsmith"}, + {"email", "y1d", "js@google.com"}, + "y1d"}, + // If a word matches in maximum 2 fields, it is accepted. + // First encounter is selected as username. + {{"username", "x1d", "johnsmith"}, + {"repeat_username", "y1d", "johnsmith"}, + "x1d"}, + // A short word should be enclosed between delimiters. Otherwise, an + // Occurrence doesn't count. + {{"identity_name", "idn", "johnsmith"}, {"id", "xid", "123"}, "xid"}}; + + for (size_t i = 0; i < base::size(test_cases); ++i) { + SCOPED_TRACE(testing::Message() << "Iteration " << i); + + const std::string& form_html = + GetFormHTML(test_cases[i].first_text_field_parameter, + test_cases[i].second_text_field_parameter); + + PredictAndCheckUsernameId(form_html, test_cases[i].expected_username_id); + } +} + +TEST_F(HtmlBasedUsernameDetectorTest, UserGroupAttributes) { + // Each test case consists of a set of parameters to be plugged into + // the TestCase struct, plus the corresponding expectations. The test data + // contains cases that are identified by HTML detector, and not by + // base heuristic. Thus, username field does not necessarely have to + // be right before password field. These tests basically check + // searching in user group + const TestCase test_cases[] = { + // Label information will decide username. + {{"name1", "id1", "johnsmith", "Username:"}, + {"name2", "id2", "js@google.com", "Email:"}, + "id1"}, + // Placeholder information will decide username. + {{"name1", "id1", "js@google.com", "Email:"}, + {"name2", "id2", "johnsmith", "Username:"}, + "id2"}, + // Check removal of special characters. + {{"name1", "id1", "johnsmith", "U s er n a m e:"}, + {"name2", "id2", "js@google.com", "Email:"}, + "id1"}, + // Check removal of fields with latin negative words in user group. + {{"name1", "id1", "johnsmith", "Username password:"}, + {"name2", "id2", "js@google.com", "Email:"}, + "id2"}, + // Check removal of fields with non-latin negative words in user group. + {{"name1", "id1", "js@google.com", "Email:"}, + {"name2", "id2", "johnsmith", "የይለፍቃልየይለፍቃል:"}, + "id1"}, + // Identify latin translations of "username". + {{"name1", "id1", "johnsmith", "Username:"}, + {"name2", "id2", "js@google.com", "Email:"}, + "id1"}, + // Identify non-latin translations of "username". + {{"name1", "id1", "johnsmith", "用户名:"}, + {"name2", "id2", "js@google.com", "Email:"}, + "id1"}, + // Identify latin translations of "user". + {{"name1", "id1", "johnsmith", "Wosuta:"}, + {"name2", "id2", "js@google.com", "Email:"}, + "id1"}, + // Identify non-latin translations of "user". + {{"name1", "id1", "johnsmith", "истифода:"}, + {"name2", "id2", "js@google.com", "Email:"}, + "id1"}, + // Identify weak words. + {{"name1", "id1", "johnsmith", "Insert your login details:"}, + {"name2", "id2", "js@google.com", "Insert your email:"}, + "id1"}, + // Check user group priority, compared to developer group. + // User group should have higher priority than developer group. + {{"email", "id1", "js@google.com", "Username:"}, + {"username", "id2", "johnsmith", "Email:"}, + "id1"}, + // Check treatment for short dictionary words. "uid" has higher priority, + // but its occurrence is ignored because it is a part of another word. + { + {"name1", "noword", "johnsmith", "Insert your id:"}, + {"name2", "uidentical", "js@google.com", "Insert something:"}, + "noword", + }}; + + for (size_t i = 0; i < base::size(test_cases); ++i) { + SCOPED_TRACE(testing::Message() << "Iteration " << i); + + const std::string& form_html = + GetFormHTML(test_cases[i].first_text_field_parameter, + test_cases[i].second_text_field_parameter); + + PredictAndCheckUsernameId(form_html, test_cases[i].expected_username_id); + } +} + +TEST_F(HtmlBasedUsernameDetectorTest, SeveralDetections) { + // If word matches in more than 2 fields, we don't match on it. + // We search for match with another word. + const std::string& test_form = R"( + <form> + <input type="text" name="address" id="xuser" value="addr" /> + <input type="text" name="loginid" id="yuser" value="johnsmith" /> + <input type="text" name="tel" id="zuser" value="sometel" /> + <input type="password" id="password" value="v" /> + <input type="submit" value="submit" /> + </form> + )"; + PredictAndCheckUsernameId(test_form, "yuser"); +} + +TEST_F(HtmlBasedUsernameDetectorTest, HTMLDetectorCache) { + const TextField text_fields[] = { + {"unknown", "12345"}, + {"something", "smith"}, + }; + + const std::string& form_html = GetFormHTML(text_fields[0], text_fields[1]); + + FormData form_data = LoadFormDataFromHtml(form_html); + std::vector<WebFormControlElement> control_elements = + GetFormControlElements(); + + UsernameDetectorCache cache; + std::vector<FieldRendererId> field_ids = + GetPredictionsFieldBasedOnHtmlAttributes(control_elements, form_data, + &cache); + + // No signals from HTML attributes. The classifier found nothing and cached + // it. + ASSERT_EQ(1u, cache.size()); + EXPECT_TRUE(field_ids.empty()); + const WebFormElement& form = GetFormElement(); + EXPECT_EQ(FormRendererId(form.UniqueRendererFormId()), cache.begin()->first); + EXPECT_TRUE(cache.begin()->second.empty()); + + // Changing attributes would change the classifier's output. But the output + // will be the same because it was cached in |username_detector_cache|. + control_elements[0].SetAttribute("name", "id"); + form_data = GetFormDataFromForm(GetFormElement()); + field_ids = GetPredictionsFieldBasedOnHtmlAttributes(control_elements, + form_data, &cache); + ASSERT_EQ(1u, cache.size()); + EXPECT_TRUE(field_ids.empty()); + EXPECT_EQ(FormRendererId(form.UniqueRendererFormId()), cache.begin()->first); + EXPECT_TRUE(cache.begin()->second.empty()); + + // Clear the cache. The classifier will find username field and cache it. + cache.clear(); + ASSERT_EQ(4u, control_elements.size()); + field_ids = GetPredictionsFieldBasedOnHtmlAttributes(control_elements, + form_data, &cache); + ASSERT_EQ(1u, cache.size()); + EXPECT_EQ(1u, field_ids.size()); + EXPECT_EQ(FormRendererId(form.UniqueRendererFormId()), cache.begin()->first); + ASSERT_EQ(1u, cache.begin()->second.size()); + EXPECT_EQ(FieldRendererId(control_elements[0].UniqueRendererFormControlId()), + cache.begin()->second[0]); + + // Change the attributes again ("username" is stronger signal than "id"), + // but keep the cache. The classifier's output should be the same. + control_elements[1].SetAttribute("name", "username"); + form_data = GetFormDataFromForm(GetFormElement()); + field_ids = GetPredictionsFieldBasedOnHtmlAttributes(control_elements, + form_data, &cache); + + ASSERT_EQ(1u, cache.size()); + EXPECT_EQ(1u, field_ids.size()); + EXPECT_EQ(FormRendererId(form.UniqueRendererFormId()), cache.begin()->first); + ASSERT_EQ(1u, cache.begin()->second.size()); + EXPECT_EQ(FieldRendererId(control_elements[0].UniqueRendererFormControlId()), + cache.begin()->second[0]); +} + +} // namespace autofill diff --git a/chromium/components/autofill/content/renderer/password_autofill_agent.cc b/chromium/components/autofill/content/renderer/password_autofill_agent.cc index d85705681a9..4d283f72a0f 100644 --- a/chromium/components/autofill/content/renderer/password_autofill_agent.cc +++ b/chromium/components/autofill/content/renderer/password_autofill_agent.cc @@ -24,7 +24,6 @@ #include "base/strings/utf_string_conversions.h" #include "base/threading/thread_task_runner_handle.h" #include "build/build_config.h" -#include "components/autofill/content/renderer/form_autofill_util.h" #include "components/autofill/content/renderer/password_form_conversion_utils.h" #include "components/autofill/content/renderer/password_generation_agent.h" #include "components/autofill/content/renderer/prefilled_values_detector.h" @@ -36,7 +35,6 @@ #include "components/autofill/core/common/mojom/autofill_types.mojom.h" #include "components/autofill/core/common/password_form_fill_data.h" #include "components/autofill/core/common/signatures.h" -#include "components/password_manager/core/common/password_manager_features.h" #include "components/safe_browsing/buildflags.h" #include "content/public/renderer/document_state.h" #include "content/public/renderer/render_frame.h" @@ -257,9 +255,9 @@ WebString GetFormSignatureAsWebString(const FormData& form_data) { // Annotate |fields| with field signatures and form signature as HTML // attributes. void AnnotateFieldsWithSignatures( - std::vector<blink::WebFormControlElement>* fields, + std::vector<blink::WebFormControlElement>& fields, const blink::WebString& form_signature) { - for (blink::WebFormControlElement& control_element : *fields) { + for (blink::WebFormControlElement& control_element : fields) { FieldSignature field_signature = CalculateFieldSignatureByNameAndType( control_element.NameForAutofill().Utf16(), control_element.FormControlTypeForAutofill().Utf8()); @@ -272,37 +270,6 @@ void AnnotateFieldsWithSignatures( } } -// Annotate |forms| and all fields in the |frame| with form and field signatures -// as HTML attributes. -void AnnotateFormsAndFieldsWithSignatures(WebLocalFrame* frame, - WebVector<WebFormElement>* forms) { - for (WebFormElement& form : *forms) { - std::unique_ptr<FormData> form_data( - CreateFormDataFromWebForm(form, /*field_data_manager=*/nullptr, - /*username_detector_cache=*/nullptr)); - WebString form_signature; - if (form_data) { - form_signature = GetFormSignatureAsWebString(*form_data); - form.SetAttribute(WebString::FromASCII(kDebugAttributeForFormSignature), - form_signature); - } - std::vector<WebFormControlElement> form_fields = - form_util::ExtractAutofillableElementsInForm(form); - AnnotateFieldsWithSignatures(&form_fields, form_signature); - } - - std::vector<WebFormControlElement> unowned_elements = - form_util::GetUnownedAutofillableFormFieldElements( - frame->GetDocument().All(), nullptr); - std::unique_ptr<FormData> form_data(CreateFormDataFromUnownedInputElements( - *frame, /*field_data_manager=*/nullptr, - /*username_detector_cache=*/nullptr)); - WebString form_signature; - if (form_data) - form_signature = GetFormSignatureAsWebString(*form_data); - AnnotateFieldsWithSignatures(&unowned_elements, form_signature); -} - // Returns true iff there is a password field in |frame|. bool HasPasswordField(const WebLocalFrame& frame) { static base::NoDestructor<WebString> kPassword("password"); @@ -416,14 +383,6 @@ bool HasDocumentWithValidFrame(const WebInputElement& element) { return frame && frame->View(); } -bool ShowPopupWithoutPasswords(const WebInputElement& password_element) { - if (!base::FeatureList::IsEnabled( - password_manager::features::kEnablePasswordsAccountStorage)) { - return false; - } - return !password_element.IsNull() && IsElementEditable(password_element); -} - // This method tries to fix `fields` with empty typed or filled properties by // matching them against previously filled or typed in fields with the same // value and copying their filled or typed mask. @@ -905,9 +864,10 @@ bool PasswordAutofillAgent::ShowSuggestions( FindPasswordInfoForElement(element, UseFallbackData(true), &username_element, &password_element, &password_info); - if (!password_info && !ShowPopupWithoutPasswords(password_element)) { + if (!password_info) { MaybeCheckSafeBrowsingReputation(element); - return false; + if (!CanShowPopupWithoutPasswords(password_element)) + return false; } // Check that all fillable elements are editable. @@ -1015,6 +975,31 @@ void PasswordAutofillAgent::UserGestureObserved() { gatekeeper_.OnUserGesture(); } +void PasswordAutofillAgent::AnnotateFormsAndFieldsWithSignatures( + WebVector<WebFormElement>& forms) { + for (WebFormElement& form : forms) { + std::unique_ptr<FormData> form_data = GetFormDataFromWebForm(form); + WebString form_signature; + if (form_data) { + form_signature = GetFormSignatureAsWebString(*form_data); + form.SetAttribute(WebString::FromASCII(kDebugAttributeForFormSignature), + form_signature); + } + std::vector<WebFormControlElement> form_fields = + form_util::ExtractAutofillableElementsInForm(form); + AnnotateFieldsWithSignatures(form_fields, form_signature); + } + + std::vector<WebFormControlElement> unowned_elements = + form_util::GetUnownedAutofillableFormFieldElements( + render_frame()->GetWebFrame()->GetDocument().All(), nullptr); + std::unique_ptr<FormData> form_data = GetFormDataFromUnownedInputElements(); + WebString form_signature; + if (form_data) + form_signature = GetFormSignatureAsWebString(*form_data); + AnnotateFieldsWithSignatures(unowned_elements, form_signature); +} + void PasswordAutofillAgent::SendPasswordForms(bool only_visible) { std::unique_ptr<RendererSavePasswordProgressLogger> logger; if (logging_state_active_) { @@ -1046,7 +1031,7 @@ void PasswordAutofillAgent::SendPasswordForms(bool only_visible) { WebVector<WebFormElement> forms = frame->GetDocument().Forms(); if (IsShowAutofillSignaturesEnabled()) - AnnotateFormsAndFieldsWithSignatures(frame, &forms); + AnnotateFormsAndFieldsWithSignatures(forms); if (logger) logger->LogNumber(Logger::STRING_NUMBER_OF_ALL_FORMS, forms.size()); @@ -1131,7 +1116,7 @@ void PasswordAutofillAgent::SendPasswordForms(bool only_visible) { password_forms_data.back().url = form_util::GetCanonicalOriginForDocument(frame->GetDocument()); password_forms_data.back().full_url = - form_util::GetOriginWithoutAuthForDocument(frame->GetDocument()); + form_util::GetDocumentUrlWithoutAuth(frame->GetDocument()); } if (!password_forms_data.empty()) { sent_request_to_store_ = true; @@ -1161,12 +1146,9 @@ void PasswordAutofillAgent::DidFinishLoad() { } void PasswordAutofillAgent::DidCommitProvisionalLoad( - bool is_same_document_navigation, ui::PageTransition transition) { - if (!is_same_document_navigation) { - checked_safe_browsing_reputation_ = false; - recorded_first_filling_result_ = false; - } + checked_safe_browsing_reputation_ = false; + recorded_first_filling_result_ = false; } void PasswordAutofillAgent::OnFrameDetached() { @@ -1312,7 +1294,10 @@ void PasswordAutofillAgent::AnnotateFieldsWithParsingResult( "confirmation_password_element"); } -void PasswordAutofillAgent::InformNoSavedCredentials() { +void PasswordAutofillAgent::InformNoSavedCredentials( + bool should_show_popup_without_passwords) { + should_show_popup_without_passwords_ = should_show_popup_without_passwords; + autofilled_elements_cache_.clear(); // Clear the actual field values. @@ -1382,7 +1367,8 @@ void PasswordAutofillAgent::FocusedNodeHasChanged(const blink::WebNode& node) { std::unique_ptr<FormData> PasswordAutofillAgent::GetFormDataFromWebForm( const WebFormElement& web_form) { return CreateFormDataFromWebForm(web_form, field_data_manager_.get(), - &username_detector_cache_); + &username_detector_cache_, + &button_titles_cache_); } std::unique_ptr<FormData> @@ -1398,7 +1384,8 @@ PasswordAutofillAgent::GetFormDataFromUnownedInputElements() { if (!web_frame) return nullptr; return CreateFormDataFromUnownedInputElements( - *web_frame, field_data_manager_.get(), &username_detector_cache_); + *web_frame, field_data_manager_.get(), &username_detector_cache_, + &button_titles_cache_); } //////////////////////////////////////////////////////////////////////////////// @@ -1430,6 +1417,7 @@ void PasswordAutofillAgent::CleanupOnDocumentShutdown() { web_input_to_password_info_.clear(); password_to_username_.clear(); last_supplied_password_info_iter_ = web_input_to_password_info_.end(); + should_show_popup_without_passwords_ = false; browser_has_form_to_process_ = false; field_data_manager_.get()->ClearData(); username_autofill_state_ = WebAutofillState::kNotFilled; @@ -1535,7 +1523,7 @@ bool PasswordAutofillAgent::FillUserNameAndPassword( // This is a heuristic guess. If the credential is stored for // www.example.com, the username may be prefilled with "@example.com". std::string possible_email_domain = - GetRegistryControlledDomain(fill_data.origin); + GetRegistryControlledDomain(fill_data.url); prefilled_placeholder_username = !username_element.Value().IsEmpty() && @@ -1886,4 +1874,10 @@ void PasswordAutofillAgent::SetLastUpdatedFormAndField( : FieldRendererId(input.UniqueRendererFormControlId()); } +bool PasswordAutofillAgent::CanShowPopupWithoutPasswords( + const WebInputElement& password_element) const { + return should_show_popup_without_passwords_ && !password_element.IsNull() && + IsElementEditable(password_element); +} + } // namespace autofill diff --git a/chromium/components/autofill/content/renderer/password_autofill_agent.h b/chromium/components/autofill/content/renderer/password_autofill_agent.h index 011425f9186..4852ec2a3b8 100644 --- a/chromium/components/autofill/content/renderer/password_autofill_agent.h +++ b/chromium/components/autofill/content/renderer/password_autofill_agent.h @@ -20,6 +20,7 @@ #include "components/autofill/content/common/mojom/autofill_driver.mojom.h" #include "components/autofill/content/renderer/autofill_agent.h" #include "components/autofill/content/renderer/field_data_manager.h" +#include "components/autofill/content/renderer/form_autofill_util.h" #include "components/autofill/content/renderer/form_tracker.h" #include "components/autofill/content/renderer/html_based_username_detector.h" #include "components/autofill/core/common/mojom/autofill_types.mojom.h" @@ -99,10 +100,6 @@ enum class FillingResult { kMaxValue = kNoFillableElementsFound, }; -// Names of HTML attributes to show form and field signatures for debugging. -extern const char kDebugAttributeForFormSignature[]; -extern const char kDebugAttributeForFieldSignature[]; - class FieldDataManager; class RendererSavePasswordProgressLogger; class PasswordGenerationAgent; @@ -133,7 +130,8 @@ class PasswordAutofillAgent : public content::RenderFrameObserver, // mojom::PasswordAutofillAgent: void FillPasswordForm(const PasswordFormFillData& form_data) override; - void InformNoSavedCredentials() override; + void InformNoSavedCredentials( + bool should_show_popup_without_passwords) override; void FillIntoFocusedField(bool is_password, const base::string16& credential) override; void SetLoggingState(bool active) override; @@ -235,8 +233,7 @@ class PasswordAutofillAgent : public content::RenderFrameObserver, void DidFinishLoad() override; void ReadyToCommitNavigation( blink::WebDocumentLoader* document_loader) override; - void DidCommitProvisionalLoad(bool is_same_document_navigation, - ui::PageTransition transition) override; + void DidCommitProvisionalLoad(ui::PageTransition transition) override; void OnDestruct() override; const scoped_refptr<FieldDataManager> GetFieldDataManager() { @@ -345,6 +342,12 @@ class PasswordAutofillAgent : public content::RenderFrameObserver, DISALLOW_COPY_AND_ASSIGN(PasswordValueGatekeeper); }; + // Annotate |forms| and all fields in the current frame with form and field + // signatures as HTML attributes. Used by + // chrome://flags/#enable-show-autofill-signatures only. + void AnnotateFormsAndFieldsWithSignatures( + blink::WebVector<blink::WebFormElement>& forms); + // Scans the given frame for password forms and sends them up to the browser. // If |only_visible| is true, only forms visible in the layout are sent. void SendPasswordForms(bool only_visible); @@ -467,6 +470,9 @@ class PasswordAutofillAgent : public content::RenderFrameObserver, void SetLastUpdatedFormAndField(const blink::WebFormElement& form, const blink::WebFormControlElement& input); + bool CanShowPopupWithoutPasswords( + const blink::WebInputElement& password_element) const; + // The logins we have filled so far with their associated info. WebInputToPasswordInfoMap web_input_to_password_info_; // A (sort-of) reverse map to |web_input_to_password_info_|. @@ -474,6 +480,8 @@ class PasswordAutofillAgent : public content::RenderFrameObserver, // The chronologically last insertion into |web_input_to_password_info_|. WebInputToPasswordInfoMap::iterator last_supplied_password_info_iter_; + bool should_show_popup_without_passwords_ = false; + // Map WebFormControlElement to the pair of: // 1) The most recent text that user typed or PasswordManager autofilled in // input elements. Used for storing username/password before JavaScript @@ -513,10 +521,6 @@ class PasswordAutofillAgent : public content::RenderFrameObserver, // Records the username typed before suggestions preview. base::string16 username_query_prefix_; - // The HTML based username detector's cache which maps form elements to - // username predictions. - UsernameDetectorCache username_detector_cache_; - // This notifier is used to avoid sending redundant messages to the password // manager driver mojo interface. FocusStateNotifier focus_state_notifier_; @@ -543,6 +547,14 @@ class PasswordAutofillAgent : public content::RenderFrameObserver, // structure. Replace FormData with a smaller structure. std::map<FormRendererId, FormStructureInfo> forms_structure_cache_; + // The HTML based username detector's cache which maps form elements to + // username predictions. + UsernameDetectorCache username_detector_cache_; + + // Stores the mapping from a form element's ID to results of button titles + // heuristics for that form. + form_util::ButtonTitlesCache button_titles_cache_; + // Flag to prevent that multiple PasswordManager.FirstRendererFillingResult // UMA metrics are recorded per page load. This is reset on // DidCommitProvisionalLoad() but only for non-same-document-navigations. diff --git a/chromium/components/autofill/content/renderer/password_form_conversion_utils.cc b/chromium/components/autofill/content/renderer/password_form_conversion_utils.cc index a216162bcfd..93824cae0bd 100644 --- a/chromium/components/autofill/content/renderer/password_form_conversion_utils.cc +++ b/chromium/components/autofill/content/renderer/password_form_conversion_utils.cc @@ -9,7 +9,6 @@ #include "base/no_destructor.h" #include "base/strings/string_piece.h" #include "base/strings/string_split.h" -#include "components/autofill/content/renderer/form_autofill_util.h" #include "components/autofill/content/renderer/html_based_username_detector.h" #include "components/autofill/core/common/password_form.h" #include "components/autofill/core/common/renderer_id.h" @@ -128,7 +127,8 @@ bool IsGaiaWithSkipSavePasswordForm(const blink::WebFormElement& form) { std::unique_ptr<FormData> CreateFormDataFromWebForm( const WebFormElement& web_form, const FieldDataManager* field_data_manager, - UsernameDetectorCache* username_detector_cache) { + UsernameDetectorCache* username_detector_cache, + form_util::ButtonTitlesCache* button_titles_cache) { if (web_form.IsNull()) return nullptr; @@ -136,7 +136,7 @@ std::unique_ptr<FormData> CreateFormDataFromWebForm( form_data->url = form_util::GetCanonicalOriginForDocument(web_form.GetDocument()); form_data->full_url = - form_util::GetOriginWithoutAuthForDocument(web_form.GetDocument()); + form_util::GetDocumentUrlWithoutAuth(web_form.GetDocument()); form_data->is_gaia_with_skip_save_password_form = IsGaiaWithSkipSavePasswordForm(web_form) || IsGaiaReauthenticationForm(web_form); @@ -153,6 +153,8 @@ std::unique_ptr<FormData> CreateFormDataFromWebForm( } form_data->username_predictions = GetUsernamePredictions( control_elements.ReleaseVector(), *form_data, username_detector_cache); + form_data->button_titles = form_util::GetButtonTitles( + web_form, web_form.GetDocument(), button_titles_cache); return form_data; } @@ -160,7 +162,8 @@ std::unique_ptr<FormData> CreateFormDataFromWebForm( std::unique_ptr<FormData> CreateFormDataFromUnownedInputElements( const WebLocalFrame& frame, const FieldDataManager* field_data_manager, - UsernameDetectorCache* username_detector_cache) { + UsernameDetectorCache* username_detector_cache, + form_util::ButtonTitlesCache* button_titles_cache) { std::vector<WebElement> fieldsets; std::vector<WebFormControlElement> control_elements = form_util::GetUnownedFormFieldElements(frame.GetDocument().All(), @@ -179,9 +182,12 @@ std::unique_ptr<FormData> CreateFormDataFromUnownedInputElements( form_data->url = form_util::GetCanonicalOriginForDocument(frame.GetDocument()); form_data->full_url = - form_util::GetOriginWithoutAuthForDocument(frame.GetDocument()); + form_util::GetDocumentUrlWithoutAuth(frame.GetDocument()); form_data->username_predictions = GetUsernamePredictions( control_elements, *form_data, username_detector_cache); + form_data->button_titles = form_util::GetButtonTitles( + WebFormElement(), frame.GetDocument(), button_titles_cache); + return form_data; } diff --git a/chromium/components/autofill/content/renderer/password_form_conversion_utils.h b/chromium/components/autofill/content/renderer/password_form_conversion_utils.h index 8701d51e57e..07a2ad05095 100644 --- a/chromium/components/autofill/content/renderer/password_form_conversion_utils.h +++ b/chromium/components/autofill/content/renderer/password_form_conversion_utils.h @@ -12,6 +12,7 @@ #include <vector> #include "base/strings/string_piece.h" +#include "components/autofill/content/renderer/form_autofill_util.h" #include "components/autofill/content/renderer/html_based_username_detector.h" #include "components/autofill/core/common/password_form.h" #include "third_party/blink/public/platform/web_string.h" @@ -42,14 +43,16 @@ bool IsGaiaWithSkipSavePasswordForm(const blink::WebFormElement& form); std::unique_ptr<FormData> CreateFormDataFromWebForm( const blink::WebFormElement& web_form, const FieldDataManager* field_data_manager, - UsernameDetectorCache* username_detector_cache); + UsernameDetectorCache* username_detector_cache, + form_util::ButtonTitlesCache* button_titles_cache); // Same as CreateFormDataFromWebForm() but for input elements that are // not enclosed in <form> element. std::unique_ptr<FormData> CreateFormDataFromUnownedInputElements( const blink::WebLocalFrame& frame, const FieldDataManager* field_data_manager, - UsernameDetectorCache* username_detector_cache); + UsernameDetectorCache* username_detector_cache, + form_util::ButtonTitlesCache* button_titles_cache); // The "Realm" for the sign-on. This is scheme, host, port. std::string GetSignOnRealm(const GURL& origin); diff --git a/chromium/components/autofill/content/renderer/password_generation_agent.cc b/chromium/components/autofill/content/renderer/password_generation_agent.cc index 959fa2d7388..a87d298ca38 100644 --- a/chromium/components/autofill/content/renderer/password_generation_agent.cc +++ b/chromium/components/autofill/content/renderer/password_generation_agent.cc @@ -160,10 +160,7 @@ void PasswordGenerationAgent::BindPendingReceiver( } void PasswordGenerationAgent::DidCommitProvisionalLoad( - bool is_same_document_navigation, ui::PageTransition transition) { - if (is_same_document_navigation) - return; // Update stats for main frame navigation. if (!render_frame()->GetWebFrame()->Parent()) { if (current_generation_item_) { diff --git a/chromium/components/autofill/content/renderer/password_generation_agent.h b/chromium/components/autofill/content/renderer/password_generation_agent.h index 6baf4393658..2eeb0e3044e 100644 --- a/chromium/components/autofill/content/renderer/password_generation_agent.h +++ b/chromium/components/autofill/content/renderer/password_generation_agent.h @@ -97,8 +97,7 @@ class PasswordGenerationAgent : public content::RenderFrameObserver, struct GenerationItemInfo; // RenderFrameObserver: - void DidCommitProvisionalLoad(bool is_same_document_navigation, - ui::PageTransition transition) override; + void DidCommitProvisionalLoad(ui::PageTransition transition) override; void DidChangeScrollOffset() override; void OnDestruct() override; |