summaryrefslogtreecommitdiff
path: root/chromium/components/autofill/content/renderer/password_form_conversion_utils.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/autofill/content/renderer/password_form_conversion_utils.cc')
-rw-r--r--chromium/components/autofill/content/renderer/password_form_conversion_utils.cc647
1 files changed, 257 insertions, 390 deletions
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 1cd2c4e812e..68e02e38c21 100644
--- a/chromium/components/autofill/content/renderer/password_form_conversion_utils.cc
+++ b/chromium/components/autofill/content/renderer/password_form_conversion_utils.cc
@@ -10,7 +10,6 @@
#include <set>
#include <string>
-#include "base/containers/flat_set.h"
#include "base/i18n/case_conversion.h"
#include "base/lazy_instance.h"
#include "base/macros.h"
@@ -82,7 +81,7 @@ AutocompleteFlag ExtractAutocompleteFlag(const std::string& attribute) {
return cc_seen ? AutocompleteFlag::CREDIT_CARD : AutocompleteFlag::NONE;
}
-// Helper to spare map::find boilerplate when caching element's autocomplete
+// Helper to spare map::find boilerplate when caching field's autocomplete
// attributes.
class AutocompleteCache {
public:
@@ -90,18 +89,18 @@ class AutocompleteCache {
~AutocompleteCache();
- // Computes and stores the AutocompleteFlag for |element| based on its
+ // Computes and stores the AutocompleteFlag for |field| based on its
// autocomplete attribute. Note that this cannot be done on-demand during
// RetrieveFor, because the cache spares space and look-up time by not storing
// AutocompleteFlag::NONE values, hence for all elements without an
// autocomplete attribute, every retrieval would result in a new computation.
- void Store(const WebInputElement& element);
+ void Store(const FormFieldData* field);
- // Retrieves the value previously stored for |element|.
- AutocompleteFlag RetrieveFor(const WebInputElement& element) const;
+ // Retrieves the value previously stored for |field|.
+ AutocompleteFlag RetrieveFor(const FormFieldData* field) const;
private:
- std::map<WebInputElement, AutocompleteFlag> cache_;
+ std::map<const FormFieldData*, AutocompleteFlag> cache_;
DISALLOW_COPY_AND_ASSIGN(AutocompleteCache);
};
@@ -110,18 +109,19 @@ AutocompleteCache::AutocompleteCache() = default;
AutocompleteCache::~AutocompleteCache() = default;
-void AutocompleteCache::Store(const WebInputElement& element) {
- const AutocompleteFlag flag = AutocompleteFlagForElement(element);
+void AutocompleteCache::Store(const FormFieldData* field) {
+ const AutocompleteFlag flag =
+ ExtractAutocompleteFlag(field->autocomplete_attribute);
// Only store non-trivial flags. Most of the elements will have the NONE
// value, so spare storage and lookup time by assuming anything not stored in
// |cache_| has the NONE flag.
if (flag != AutocompleteFlag::NONE)
- cache_[element] = flag;
+ cache_[field] = flag;
}
AutocompleteFlag AutocompleteCache::RetrieveFor(
- const WebInputElement& element) const {
- auto it = cache_.find(element);
+ const FormFieldData* field) const {
+ auto it = cache_.find(field);
if (it == cache_.end())
return AutocompleteFlag::NONE;
return it->second;
@@ -139,29 +139,6 @@ enum class FieldFilteringLevel {
USER_INPUT = 2
};
-// PasswordForms can be constructed for both WebFormElements and for collections
-// of WebInputElements that are not in a WebFormElement. This intermediate
-// aggregating structure is provided so GetPasswordForm() only has one
-// view of the underlying data, regardless of its origin.
-struct SyntheticForm {
- SyntheticForm();
- SyntheticForm(SyntheticForm&& other);
- ~SyntheticForm();
-
- // Contains control elements of the represented form, including not fillable
- // ones.
- std::vector<blink::WebFormControlElement> control_elements;
- // The origin of the containing document.
- GURL origin;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(SyntheticForm);
-};
-
-SyntheticForm::SyntheticForm() = default;
-SyntheticForm::SyntheticForm(SyntheticForm&& other) = default;
-SyntheticForm::~SyntheticForm() = default;
-
// Layout classification of password forms
// A layout sequence of a form is the sequence of it's non-password and password
// input fields, represented by "N" and "P", respectively. A form like this
@@ -200,18 +177,6 @@ struct LoginAndSignupLazyInstanceTraits
base::LazyInstance<re2::RE2, LoginAndSignupLazyInstanceTraits>
g_login_and_signup_matcher = LAZY_INSTANCE_INITIALIZER;
-// Return a pointer to WebInputElement iff |control_element| is an enabled text
-// input element. Otherwise, returns nullptr.
-const WebInputElement* GetEnabledTextInputFieldOrNull(
- const WebFormControlElement& control_element) {
- const WebInputElement* input_element = ToWebInputElement(&control_element);
- if (input_element && input_element->IsEnabled() &&
- input_element->IsTextField()) {
- return input_element;
- }
- return nullptr;
-}
-
// Given the sequence of non-password and password text input fields of a form,
// represented as a string of Ns (non-password) and Ps (password), computes the
// layout type of that form.
@@ -225,48 +190,30 @@ PasswordForm::Layout SequenceToLayout(base::StringPiece layout_sequence) {
return PasswordForm::Layout::LAYOUT_OTHER;
}
-void PopulateSyntheticFormFromWebForm(const WebFormElement& web_form,
- SyntheticForm* synthetic_form) {
- // TODO(vabr): The fact that we are actually passing all form fields, not just
- // autofillable ones (cause of http://crbug.com/537396, see also
- // http://crbug.com/543006) is not tested yet, due to difficulties to fake
- // test frame origin to match GAIA login page. Once this code gets moved to
- // browser, we need to add tests for this as well.
- blink::WebVector<blink::WebFormControlElement> web_control_elements;
- web_form.GetFormControlElements(web_control_elements);
- synthetic_form->control_elements.assign(web_control_elements.begin(),
- web_control_elements.end());
- synthetic_form->origin =
- form_util::GetCanonicalOriginForDocument(web_form.GetDocument());
-}
-
// Helper to determine which password is the main (current) one, and which is
// the new password (e.g., on a sign-up or change password form), if any. If the
// new password is found and there is another password field with the same user
// input, the function also sets |confirmation_password| to this field.
-void LocateSpecificPasswords(std::vector<WebInputElement> passwords,
- WebInputElement* current_password,
- WebInputElement* new_password,
- WebInputElement* confirmation_password,
+void LocateSpecificPasswords(std::vector<const FormFieldData*> passwords,
+ const FormFieldData** current_password,
+ const FormFieldData** new_password,
+ const FormFieldData** confirmation_password,
const AutocompleteCache& autocomplete_cache) {
DCHECK(!passwords.empty());
- DCHECK(current_password && current_password->IsNull());
- DCHECK(new_password && new_password->IsNull());
- DCHECK(confirmation_password && confirmation_password->IsNull());
+ DCHECK(current_password && !*current_password);
+ DCHECK(new_password && !*new_password);
+ DCHECK(confirmation_password && !*confirmation_password);
// First, look for elements marked with either autocomplete='current-password'
// or 'new-password' -- if we find any, take the hint, and treat the first of
// each kind as the element we are looking for.
- for (const WebInputElement& password : passwords) {
+ for (const FormFieldData* password : passwords) {
const AutocompleteFlag flag = autocomplete_cache.RetrieveFor(password);
- if (flag == AutocompleteFlag::CURRENT_PASSWORD &&
- current_password->IsNull()) {
+ if (flag == AutocompleteFlag::CURRENT_PASSWORD && !*current_password) {
*current_password = password;
- } else if (flag == AutocompleteFlag::NEW_PASSWORD &&
- new_password->IsNull()) {
+ } else if (flag == AutocompleteFlag::NEW_PASSWORD && !*new_password) {
*new_password = password;
- } else if (!new_password->IsNull() &&
- (new_password->Value() == password.Value())) {
+ } else if (*new_password && ((*new_password)->value == password->value)) {
*confirmation_password = password;
}
}
@@ -276,7 +223,7 @@ void LocateSpecificPasswords(std::vector<WebInputElement> passwords,
// rest of the password fields unmarked. Perhaps they are used for other
// purposes, e.g., PINs, OTPs, and the like. So we skip all the heuristics we
// normally do, and ignore the rest of the password fields.
- if (!current_password->IsNull() || !new_password->IsNull())
+ if (*current_password || *new_password)
return;
switch (passwords.size()) {
@@ -285,8 +232,8 @@ void LocateSpecificPasswords(std::vector<WebInputElement> passwords,
*current_password = passwords[0];
break;
case 2:
- if (!passwords[0].Value().IsEmpty() &&
- passwords[0].Value() == passwords[1].Value()) {
+ if (!passwords[0]->value.empty() &&
+ passwords[0]->value == passwords[1]->value) {
// Two identical non-empty passwords: assume we are seeing a new
// password with a confirmation. This can be either a sign-up form or a
// password change form that does not ask for the old password.
@@ -302,20 +249,20 @@ void LocateSpecificPasswords(std::vector<WebInputElement> passwords,
}
break;
default:
- if (!passwords[0].Value().IsEmpty() &&
- passwords[0].Value() == passwords[1].Value() &&
- passwords[0].Value() == passwords[2].Value()) {
+ if (!passwords[0]->value.empty() &&
+ passwords[0]->value == passwords[1]->value &&
+ passwords[0]->value == passwords[2]->value) {
// All three passwords are the same and non-empty? It may be a change
// password form where old and new passwords are the same. It doesn't
// matter what field is correct, let's save the value.
*current_password = passwords[0];
- } else if (passwords[1].Value() == passwords[2].Value()) {
+ } else if (passwords[1]->value == passwords[2]->value) {
// New password is the duplicated one, and comes second; or empty form
// with 3 password fields, in which case we will assume this layout.
*current_password = passwords[0];
*new_password = passwords[1];
*confirmation_password = passwords[2];
- } else if (passwords[0].Value() == passwords[1].Value()) {
+ } else if (passwords[0]->value == passwords[1]->value) {
// It is strange that the new password comes first, but trust more which
// fields are duplicated than the ordering of fields. Assume that
// any password fields after the new password contain sensitive
@@ -332,11 +279,10 @@ void LocateSpecificPasswords(std::vector<WebInputElement> passwords,
}
void FindPredictedElements(
- const std::vector<blink::WebFormControlElement>& control_elements,
const FormData& form_data,
const FormsPredictionsMap& form_predictions,
- std::map<WebInputElement, PasswordFormFieldPredictionType>*
- predicted_elements) {
+ std::map<const FormFieldData*, PasswordFormFieldPredictionType>*
+ predicted_fields) {
// Matching only requires that action and name of the form match to allow
// the username to be updated even if the form is changed after page load.
// See https://crbug.com/476092 for more details.
@@ -352,21 +298,12 @@ void FindPredictedElements(
if (!field_predictions)
return;
- std::vector<blink::WebFormControlElement> autofillable_elements =
- form_util::ExtractAutofillableElementsFromSet(control_elements);
for (const auto& prediction : *field_predictions) {
const FormFieldData& target_field = prediction.first;
const PasswordFormFieldPredictionType& type = prediction.second;
- for (const auto& control_element : autofillable_elements) {
- if (control_element.NameForAutofill().Utf16() == target_field.name) {
- const WebInputElement* input_element =
- ToWebInputElement(&control_element);
-
- // TODO(sebsg): Investigate why this guard is necessary, see
- // https://crbug.com/517490 for more details.
- if (input_element) {
- (*predicted_elements)[*input_element] = type;
- }
+ for (const FormFieldData& field : form_data.fields) {
+ if (field.name == target_field.name) {
+ (*predicted_fields)[&field] = type;
break;
}
}
@@ -387,50 +324,33 @@ base::LazyInstance<re2::RE2, PasswordSiteUrlLazyInstanceTraits>
g_password_site_matcher = LAZY_INSTANCE_INITIALIZER;
// Returns the |input_field| name if its non-empty; otherwise a |dummy_name|.
-base::string16 FieldName(const WebInputElement& input_field,
- const char dummy_name[]) {
- base::string16 field_name = input_field.NameForAutofill().Utf16();
- return field_name.empty() ? base::ASCIIToUTF16(dummy_name) : field_name;
-}
-
-// Returns true iff the properties mask of |element| intersects with |mask|.
-bool FieldHasPropertiesMask(const FieldValueAndPropertiesMaskMap* field_map,
- const blink::WebFormControlElement& element,
- FieldPropertiesMask mask) {
- if (!field_map)
- return false;
- FieldValueAndPropertiesMaskMap::const_iterator it = field_map->find(element);
- return it != field_map->end() && (it->second.second & mask);
+base::string16 FieldName(const FormFieldData* input_field,
+ const char* dummy_name) {
+ return input_field->name.empty() ? base::ASCIIToUTF16(dummy_name)
+ : input_field->name;
}
-// Return the maximal filtering criteria that |element| passes.
+// Return the maximal filtering criteria that |field| passes.
// If |ignore_autofilled_values|, autofilled value isn't considered user input.
-FieldFilteringLevel GetFiltertingLevelForField(
- const blink::WebFormControlElement& element,
- const FieldValueAndPropertiesMaskMap* field_value_and_properties_map,
- bool ignore_autofilled_values) {
+FieldFilteringLevel GetFiltertingLevelForField(const FormFieldData& field,
+ bool ignore_autofilled_values) {
FieldPropertiesMask user_input_mask =
ignore_autofilled_values
? FieldPropertiesFlags::USER_TYPED
: FieldPropertiesFlags::USER_TYPED | FieldPropertiesFlags::AUTOFILLED;
- if (FieldHasPropertiesMask(field_value_and_properties_map, element,
- user_input_mask)) {
+ if (field.properties_mask & user_input_mask)
return FieldFilteringLevel::USER_INPUT;
- }
- return form_util::IsWebElementVisible(element)
- ? FieldFilteringLevel::VISIBILITY
- : FieldFilteringLevel::NO_FILTER;
+ return field.is_focusable ? FieldFilteringLevel::VISIBILITY
+ : FieldFilteringLevel::NO_FILTER;
}
// Calculates the maximal filtering levels for password and username fields and
// saves them to |username_fields_level| and |password_fields_level|. The
// criteria for username fields considers only the fields before the first
// password field that has the maximal filtering level.
-void GetFieldFilteringLevels(
- const std::vector<blink::WebFormControlElement>& control_elements,
- const FieldValueAndPropertiesMaskMap* field_value_and_properties_map,
- FieldFilteringLevel* username_fields_level,
- FieldFilteringLevel* password_fields_level) {
+void GetFieldFilteringLevels(const std::vector<FormFieldData>& fields,
+ FieldFilteringLevel* username_fields_level,
+ FieldFilteringLevel* password_fields_level) {
DCHECK(password_fields_level);
DCHECK(username_fields_level);
*username_fields_level = FieldFilteringLevel::NO_FILTER;
@@ -438,12 +358,9 @@ void GetFieldFilteringLevels(
FieldFilteringLevel max_level_found_for_username_fields =
FieldFilteringLevel::NO_FILTER;
- for (auto& control_element : control_elements) {
- const WebInputElement* input_element = ToWebInputElement(&control_element);
- if (!input_element || !input_element->IsEnabled() ||
- !input_element->IsTextField()) {
+ for (const FormFieldData& field : fields) {
+ if (!field.is_enabled || !field.IsTextInputElement())
continue;
- }
// TODO(crbug.com/789917): Ignore autofilled values here because if there
// are only autofilled values then a form may not be filled completely (i.e.
@@ -451,11 +368,9 @@ void GetFieldFilteringLevels(
// fields filtering. Once the bug is resolved, autofilled values will not be
// ignored.
FieldFilteringLevel current_field_filtering_level =
- GetFiltertingLevelForField(control_element,
- field_value_and_properties_map,
- true /* ignore_autofilled_values */);
+ GetFiltertingLevelForField(field, true /* ignore_autofilled_values */);
- if (input_element->IsPasswordFieldForAutofill()) {
+ if (field.form_control_type == "password") {
if (*password_fields_level < current_field_filtering_level) {
*password_fields_level = current_field_filtering_level;
*username_fields_level = max_level_found_for_username_fields;
@@ -467,51 +382,10 @@ void GetFieldFilteringLevels(
}
}
-ValueElementPair MakePossibleUsernamePair(const blink::WebInputElement& input) {
- base::string16 trimmed_input_value, trimmed_input_autofill;
- base::TrimString(input.Value().Utf16(), base::ASCIIToUTF16(" "),
- &trimmed_input_value);
- return {trimmed_input_value, input.NameForAutofill().Utf16()};
-}
-
-// Check if a script modified username is suitable for Password Manager to
-// remember.
-bool ScriptModifiedUsernameAcceptable(
- const base::string16& username_value,
- const base::string16& typed_username_value,
- const PasswordForm* password_form,
- const FieldValueAndPropertiesMaskMap* field_value_and_properties_map) {
- DCHECK(password_form);
- DCHECK(field_value_and_properties_map);
- // The minimal size of a field value that will be matched.
- const size_t kMinMatchSize = 3u;
- const auto username = base::i18n::ToLower(username_value);
- const auto typed_username = base::i18n::ToLower(typed_username_value);
- if (base::StartsWith(username, typed_username, base::CompareCase::SENSITIVE))
- return true;
-
- // Check if the username was generated by javascript based on user typed name.
- if (typed_username.size() >= kMinMatchSize &&
- username_value.find(typed_username) != base::string16::npos)
- return true;
-
- // Check if the username was generated by javascript based on user typed or
- // autofilled field values.
- for (const auto& field_prop : *field_value_and_properties_map) {
- if (!field_prop.second.first)
- continue;
- const auto& field_value = *field_prop.second.first;
- const WebInputElement* input_element = ToWebInputElement(&field_prop.first);
- if (input_element && input_element->IsTextField() &&
- !input_element->IsPasswordFieldForAutofill() &&
- field_value.size() >= kMinMatchSize &&
- username_value.find(base::i18n::ToLower(field_value)) !=
- base::string16::npos) {
- return true;
- }
- }
-
- return false;
+ValueElementPair MakePossibleUsernamePair(const FormFieldData* input) {
+ base::string16 trimmed_input_value;
+ base::TrimString(input->value, base::ASCIIToUTF16(" "), &trimmed_input_value);
+ return {trimmed_input_value, input->name};
}
bool StringMatchesCVC(const base::string16& str) {
@@ -529,29 +403,46 @@ bool IsEnabledPasswordFieldPresent(const std::vector<FormFieldData>& fields) {
}
// Find the first element in |username_predictions| (i.e. the most reliable
-// prediction) that occurs in |possible_usernames|. If the element is found, the
-// method saves it to |username_element| and returns true.
-bool FindUsernameInPredictions(
- const std::vector<blink::WebInputElement>& username_predictions,
- const std::vector<blink::WebInputElement>& possible_usernames,
- WebInputElement* username_element) {
- // To speed-up the matching for-loop below, convert |possible_usernames| to a
- // set. Creating is O(N log N) for N=possible_usernames.size(). Retrieval is
- // O(log N), so the whole for-loop is O(M log N) for
- // M=username_predictions.size(). Use flat_set, because of cache locality (the
- // M and N are likely small, so this can make a difference) and less heap
- // allocations.
- const base::flat_set<blink::WebInputElement> usernames(
- possible_usernames.begin(), possible_usernames.end());
-
- for (const blink::WebInputElement& prediction : username_predictions) {
- auto iter = usernames.find(prediction);
- if (iter != usernames.end()) {
- *username_element = *iter;
- return true;
+// prediction) that occurs in |possible_usernames|.
+const FormFieldData* FindUsernameInPredictions(
+ const std::vector<uint32_t>& username_predictions,
+ const std::vector<const FormFieldData*>& possible_usernames) {
+ for (uint32_t predicted_id : username_predictions) {
+ auto iter =
+ std::find_if(possible_usernames.begin(), possible_usernames.end(),
+ [predicted_id](const FormFieldData* field) {
+ return field->unique_renderer_id == predicted_id;
+ });
+ if (iter != possible_usernames.end()) {
+ return *iter;
}
}
- return false;
+ return nullptr;
+}
+
+// Extracts the username predictions. |control_elements| should be all the DOM
+// elements of the form, |form_data| should be the already extracted FormData
+// representation of that form. |username_detector_cache| is optional, and can
+// be used to spare recomputation if called multiple times for the same form.
+std::vector<uint32_t> GetUsernamePredictions(
+ const std::vector<blink::WebFormControlElement>& control_elements,
+ const FormData& form_data,
+ UsernameDetectorCache* username_detector_cache) {
+ std::vector<uint32_t> username_predictions;
+ // Dummy cache stores the predictions in case no real cache was passed to
+ // here.
+ UsernameDetectorCache dummy_cache;
+ if (!username_detector_cache)
+ username_detector_cache = &dummy_cache;
+
+ const std::vector<WebInputElement>& username_predictions_dom =
+ GetPredictionsFieldBasedOnHtmlAttributes(control_elements, form_data,
+ username_detector_cache);
+ username_predictions.reserve(username_predictions_dom.size());
+ for (const WebInputElement& element : username_predictions_dom) {
+ username_predictions.push_back(element.UniqueRendererFormControlId());
+ }
+ return username_predictions;
}
// Get information about a login form encapsulated in a PasswordForm struct.
@@ -559,37 +450,44 @@ bool FindUsernameInPredictions(
// associated string is used instead of the element's value to create
// the PasswordForm.
bool GetPasswordForm(
- SyntheticForm form,
+ const GURL& form_origin,
+ const std::vector<blink::WebFormControlElement>& control_elements,
PasswordForm* password_form,
- const FieldValueAndPropertiesMaskMap* field_value_and_properties_map,
const FormsPredictionsMap* form_predictions,
UsernameDetectorCache* username_detector_cache) {
- DCHECK(!form.control_elements.empty());
+ DCHECK(!control_elements.empty());
+
+ const FormData& form_data = password_form->form_data;
// Early exit if no passwords to be typed into.
- if (!IsEnabledPasswordFieldPresent(password_form->form_data.fields))
+ if (!IsEnabledPasswordFieldPresent(form_data.fields))
return false;
- // Narrow the scope to enabled inputs.
- std::vector<WebInputElement> enabled_inputs;
- enabled_inputs.reserve(form.control_elements.size());
- for (const WebFormControlElement& control_element : form.control_elements) {
- const WebInputElement* input_element =
- GetEnabledTextInputFieldOrNull(control_element);
- if (input_element)
- enabled_inputs.push_back(*input_element);
+ // Evaluate the context of the fields.
+ if (base::FeatureList::IsEnabled(
+ password_manager::features::kHtmlBasedUsernameDetector)) {
+ password_form->form_data.username_predictions = GetUsernamePredictions(
+ control_elements, form_data, username_detector_cache);
+ }
+
+ // Narrow the scope to enabled text inputs.
+ std::vector<const FormFieldData*> enabled_fields;
+ enabled_fields.reserve(form_data.fields.size());
+ for (const FormFieldData& field : form_data.fields) {
+ if (field.is_enabled && field.IsTextInputElement())
+ enabled_fields.push_back(&field);
}
// Remember the list of password fields without any heuristics applied in case
// the heuristics fail and a fall-back is needed:
// All password fields.
- std::vector<WebInputElement> passwords_without_heuristics;
+ std::vector<const FormFieldData*> passwords_without_heuristics;
// Map from all password fields to the most recent non-password text input.
- std::map<WebInputElement, WebInputElement>
+ std::map<const FormFieldData*, const FormFieldData*>
preceding_text_input_for_password_without_heuristics;
- WebInputElement most_recent_text_input; // Just a temporary.
- for (const WebInputElement& input : enabled_inputs) {
- if (input.IsPasswordFieldForAutofill()) {
+ const FormFieldData* most_recent_text_input = nullptr; // Just a temporary.
+ for (const FormFieldData* input : enabled_fields) {
+ if (input->form_control_type == "password") {
passwords_without_heuristics.push_back(input);
preceding_text_input_for_password_without_heuristics[input] =
most_recent_text_input;
@@ -600,25 +498,29 @@ bool GetPasswordForm(
// Fill the cache with autocomplete flags.
AutocompleteCache autocomplete_cache;
- for (const WebInputElement& input : enabled_inputs) {
+ for (const FormFieldData* input : enabled_fields) {
autocomplete_cache.Store(input);
}
// Narrow the scope further: drop credit-card fields.
- std::vector<WebInputElement> plausible_inputs;
- plausible_inputs.reserve(enabled_inputs.size());
- for (const WebInputElement& input : enabled_inputs) {
+ std::vector<const FormFieldData*> plausible_inputs;
+ plausible_inputs.reserve(enabled_fields.size());
+ for (const FormFieldData* input : enabled_fields) {
const AutocompleteFlag flag = autocomplete_cache.RetrieveFor(input);
if (flag == AutocompleteFlag::CURRENT_PASSWORD ||
flag == AutocompleteFlag::NEW_PASSWORD) {
// A field marked as a password is considered not a credit-card field, no
// matter what.
plausible_inputs.push_back(input);
- } else if (flag != AutocompleteFlag::CREDIT_CARD &&
- !IsCreditCardVerificationPasswordField(input)) {
- // Otherwise ensure that nothing hints that |input| is a credit-card
- // field.
- plausible_inputs.push_back(input);
+ } else if (flag != AutocompleteFlag::CREDIT_CARD) {
+ const bool is_credit_card_verification =
+ input->form_control_type == "password" &&
+ (StringMatchesCVC(input->name) || StringMatchesCVC(input->id));
+ if (!is_credit_card_verification) {
+ // Otherwise ensure that nothing hints that |input| is a credit-card
+ // field.
+ plausible_inputs.push_back(input);
+ }
}
}
@@ -627,19 +529,17 @@ bool GetPasswordForm(
// Compute the best filtering levels for usernames and for passwords.
FieldFilteringLevel username_fields_level = FieldFilteringLevel::NO_FILTER;
FieldFilteringLevel password_fields_level = FieldFilteringLevel::NO_FILTER;
- GetFieldFilteringLevels(form.control_elements, field_value_and_properties_map,
- &username_fields_level, &password_fields_level);
+ GetFieldFilteringLevels(form_data.fields, &username_fields_level,
+ &password_fields_level);
// Remove all fields with filtering level below the best.
base::EraseIf(
- plausible_inputs, [&field_value_and_properties_map, password_fields_level,
- username_fields_level](const WebInputElement& input) {
- FieldFilteringLevel current_field_level =
- GetFiltertingLevelForField(input, field_value_and_properties_map,
- false /* ignore_autofilled_values */);
- if (input.IsPasswordFieldForAutofill())
- return (current_field_level < password_fields_level);
- else
- return (current_field_level < username_fields_level);
+ plausible_inputs, [password_fields_level,
+ username_fields_level](const FormFieldData* input) {
+ FieldFilteringLevel current_field_level = GetFiltertingLevelForField(
+ *input, false /* ignore_autofilled_values */);
+ if (input->form_control_type == "password")
+ return current_field_level < password_fields_level;
+ return current_field_level < username_fields_level;
});
// Further, remove all readonly passwords. If the password field is readonly,
@@ -649,17 +549,15 @@ bool GetPasswordForm(
// made readonly by JavaScript before submission, it remains interesting. If
// the password was marked via the autocomplete attribute, it also remains
// interesting.
- base::EraseIf(plausible_inputs, [&field_value_and_properties_map,
- &autocomplete_cache](
- const WebInputElement& input) {
- if (!input.IsPasswordFieldForAutofill())
+ base::EraseIf(plausible_inputs, [&autocomplete_cache](
+ const FormFieldData* input) {
+ if (!input->is_readonly)
return false;
- if (!input.IsReadOnly())
+ if (input->form_control_type != "password")
return false;
// Check if the field was only made readonly before submission.
- if (FieldHasPropertiesMask(field_value_and_properties_map, input,
- FieldPropertiesFlags::USER_TYPED |
- FieldPropertiesFlags::AUTOFILLED)) {
+ if (input->properties_mask &
+ (FieldPropertiesFlags::USER_TYPED | FieldPropertiesFlags::AUTOFILLED)) {
return false;
}
// Check whether the field was explicitly marked as password.
@@ -672,15 +570,16 @@ bool GetPasswordForm(
});
// Evaluate available server-side predictions.
- std::map<WebInputElement, PasswordFormFieldPredictionType> predicted_elements;
- WebInputElement predicted_username_element;
+ std::map<const FormFieldData*, PasswordFormFieldPredictionType>
+ predicted_fields;
+ const FormFieldData* predicted_username_field = nullptr;
if (form_predictions) {
- FindPredictedElements(form.control_elements, password_form->form_data,
- *form_predictions, &predicted_elements);
+ FindPredictedElements(password_form->form_data, *form_predictions,
+ &predicted_fields);
- for (const auto& predicted_pair : predicted_elements) {
+ for (const auto& predicted_pair : predicted_fields) {
if (predicted_pair.second == PREDICTION_USERNAME) {
- predicted_username_element = predicted_pair.first;
+ predicted_username_field = predicted_pair.first;
break;
}
}
@@ -689,31 +588,30 @@ bool GetPasswordForm(
// Finally, remove all password fields for which we have a negative
// prediction, unless they are explicitly marked by the autocomplete attribute
// as a password.
- base::EraseIf(plausible_inputs, [&predicted_elements, &autocomplete_cache](
- const WebInputElement& input) {
- if (!input.IsPasswordFieldForAutofill())
+ base::EraseIf(plausible_inputs, [&predicted_fields, &autocomplete_cache](
+ const FormFieldData* input) {
+ if (input->form_control_type != "password")
return false;
const AutocompleteFlag flag = autocomplete_cache.RetrieveFor(input);
if (flag == AutocompleteFlag::CURRENT_PASSWORD ||
flag == AutocompleteFlag::NEW_PASSWORD) {
return false;
}
- auto possible_password_element_iterator = predicted_elements.find(input);
- return possible_password_element_iterator != predicted_elements.end() &&
- possible_password_element_iterator->second ==
- PREDICTION_NOT_PASSWORD;
+ auto possible_password_field_iterator = predicted_fields.find(input);
+ return possible_password_field_iterator != predicted_fields.end() &&
+ possible_password_field_iterator->second == PREDICTION_NOT_PASSWORD;
});
// Derive the list of all plausible passwords, usernames and the non-password
// inputs preceding the plausible passwords.
- std::vector<WebInputElement> plausible_passwords;
- std::vector<WebInputElement> plausible_usernames;
- std::map<WebInputElement, WebInputElement>
+ std::vector<const FormFieldData*> plausible_passwords;
+ std::vector<const FormFieldData*> plausible_usernames;
+ std::map<const FormFieldData*, const FormFieldData*>
preceding_text_input_for_plausible_password;
- most_recent_text_input.Reset();
+ most_recent_text_input = nullptr;
plausible_usernames.reserve(plausible_inputs.size());
- for (const WebInputElement& input : plausible_inputs) {
- if (input.IsPasswordFieldForAutofill()) {
+ for (const FormFieldData* input : plausible_inputs) {
+ if (input->form_control_type == "password") {
plausible_passwords.push_back(input);
preceding_text_input_for_plausible_password[input] =
most_recent_text_input;
@@ -724,42 +622,30 @@ bool GetPasswordForm(
}
// Evaluate autocomplete attributes for username.
- WebInputElement username_by_attribute;
- for (const WebInputElement& input : plausible_inputs) {
- if (!input.IsPasswordFieldForAutofill()) {
+ const FormFieldData* username_by_attribute = nullptr;
+ for (const FormFieldData* input : plausible_inputs) {
+ if (input->form_control_type != "password") {
if (autocomplete_cache.RetrieveFor(input) == AutocompleteFlag::USERNAME) {
// Only consider the first occurrence of autocomplete='username'.
// Multiple occurences hint at the attribute being used incorrectly, in
// which case sticking to the first one is just a bet.
- if (username_by_attribute.IsNull()) {
+ if (!username_by_attribute) {
username_by_attribute = input;
+ break;
}
}
}
}
// Evaluate the context of the fields.
- WebInputElement username_element_by_context;
+ const FormFieldData* username_field_by_context = nullptr;
if (base::FeatureList::IsEnabled(
password_manager::features::kHtmlBasedUsernameDetector)) {
- // Call HTML based username detector only if neither server predictions nor
+ // Use HTML based username detector only if neither server predictions nor
// autocomplete attributes were useful to detect the username.
- if (predicted_username_element.IsNull() && username_by_attribute.IsNull()) {
- // Dummy cache stores the predictions in case no real cache was passed to
- // here.
- UsernameDetectorCache dummy_cache;
- if (!username_detector_cache)
- username_detector_cache = &dummy_cache;
-
- const std::vector<blink::WebInputElement>& username_predictions =
- GetPredictionsFieldBasedOnHtmlAttributes(form.control_elements,
- password_form->form_data,
- username_detector_cache);
-
- if (!FindUsernameInPredictions(username_predictions, plausible_usernames,
- &username_element_by_context)) {
- username_element_by_context.Reset();
- }
+ if (!predicted_username_field && !username_by_attribute) {
+ username_field_by_context = FindUsernameInPredictions(
+ form_data.username_predictions, plausible_usernames);
}
}
@@ -767,8 +653,9 @@ bool GetPasswordForm(
// sign-up, sign-in, etc.).
std::string layout_sequence;
layout_sequence.reserve(plausible_inputs.size());
- for (const WebInputElement& input : plausible_inputs) {
- layout_sequence.push_back((input.IsPasswordFieldForAutofill()) ? 'P' : 'N');
+ for (const FormFieldData* input : plausible_inputs) {
+ layout_sequence.push_back((input->form_control_type == "password") ? 'P'
+ : 'N');
}
// Populate all_possible_passwords and form_has_autofilled_value in
@@ -782,22 +669,20 @@ bool GetPasswordForm(
// Pretend that an empty value has been already seen, so that empty-valued
// password elements won't get added to |all_possible_passwords|.
seen_values.insert(base::StringPiece16());
- for (const WebInputElement& password_element : passwords_without_heuristics) {
- const base::string16 value = password_element.Value().Utf16();
- if (seen_values.count(value) > 0)
+ for (const FormFieldData* password_field : passwords_without_heuristics) {
+ if (seen_values.count(password_field->value) > 0)
continue;
all_possible_passwords.push_back(
- {std::move(value), password_element.NameForAutofill().Utf16()});
+ {password_field->value, password_field->name});
seen_values.insert(
base::StringPiece16(all_possible_passwords.back().first));
}
bool form_has_autofilled_value = false;
- for (const WebInputElement& password_element : passwords_without_heuristics) {
- bool element_has_autofilled_value =
- FieldHasPropertiesMask(field_value_and_properties_map, password_element,
- FieldPropertiesFlags::AUTOFILLED);
- form_has_autofilled_value |= element_has_autofilled_value;
+ for (const FormFieldData* password_field : passwords_without_heuristics) {
+ bool field_has_autofilled_value =
+ password_field->properties_mask & FieldPropertiesFlags::AUTOFILLED;
+ form_has_autofilled_value |= field_has_autofilled_value;
}
if (!all_possible_passwords.empty()) {
@@ -818,66 +703,66 @@ bool GetPasswordForm(
}
// Find the password fields.
- WebInputElement password;
- WebInputElement new_password;
- WebInputElement confirmation_password;
+ const FormFieldData* password = nullptr;
+ const FormFieldData* new_password = nullptr;
+ const FormFieldData* confirmation_password = nullptr;
LocateSpecificPasswords(std::move(plausible_passwords), &password,
&new_password, &confirmation_password,
autocomplete_cache);
// Choose the username element.
- WebInputElement username_element;
+ const FormFieldData* username_field = nullptr;
UsernameDetectionMethod username_detection_method =
UsernameDetectionMethod::NO_USERNAME_DETECTED;
password_form->username_marked_by_site = false;
- if (!predicted_username_element.IsNull()) {
+ if (predicted_username_field) {
// Server predictions are most trusted, so try them first. Only if the form
// already has user input and the predicted username field has an empty
// value, then don't trust the prediction (can be caused by, e.g., a <form>
// actually contains several forms).
- if ((password_fields_level < FieldFilteringLevel::USER_INPUT ||
- !predicted_username_element.Value().IsEmpty())) {
- username_element = predicted_username_element;
+ if (password_fields_level < FieldFilteringLevel::USER_INPUT ||
+ !predicted_username_field->value.empty()) {
+ username_field = predicted_username_field;
password_form->was_parsed_using_autofill_predictions = true;
username_detection_method =
UsernameDetectionMethod::SERVER_SIDE_PREDICTION;
}
}
- if (username_element.IsNull() && !username_by_attribute.IsNull()) {
+ if (!username_field && username_by_attribute) {
// Next in the trusted queue: autocomplete attributes.
- username_element = username_by_attribute;
+ username_field = username_by_attribute;
username_detection_method = UsernameDetectionMethod::AUTOCOMPLETE_ATTRIBUTE;
}
- if (username_element.IsNull() && !username_element_by_context.IsNull()) {
+ if (!username_field && username_field_by_context) {
// Last step before base heuristics: HTML-based classifier.
- username_element = username_element_by_context;
+ username_field = username_field_by_context;
username_detection_method = UsernameDetectionMethod::HTML_BASED_CLASSIFIER;
}
// Compute base heuristic for username detection.
- WebInputElement base_heuristic_username;
- if (!password.IsNull()) {
+ const FormFieldData* base_heuristic_username = nullptr;
+ if (password) {
base_heuristic_username =
preceding_text_input_for_plausible_password[password];
}
- if (base_heuristic_username.IsNull() && !new_password.IsNull()) {
+ if (!base_heuristic_username && new_password) {
base_heuristic_username =
preceding_text_input_for_plausible_password[new_password];
}
// Apply base heuristic for username detection.
- if (username_element.IsNull()) {
- username_element = base_heuristic_username;
- if (!username_element.IsNull())
+ if (!username_field) {
+ username_field = base_heuristic_username;
+ if (username_field)
username_detection_method = UsernameDetectionMethod::BASE_HEURISTIC;
- } else if (base_heuristic_username == username_element &&
+ } else if (base_heuristic_username == username_field &&
username_detection_method !=
UsernameDetectionMethod::AUTOCOMPLETE_ATTRIBUTE) {
// TODO(crbug.com/786404): when the bug is fixed, remove this block and
- // calculate |base_heuristic_username| only if |username_element.IsNull()|
+ // calculate |base_heuristic_username| only if |username_field| is null.
// This block was added to measure the impact of server-side predictions and
// HTML based classifier compared to "old classifiers" (the based heuristic
// and 'autocomplete' attribute).
@@ -888,51 +773,38 @@ bool GetPasswordForm(
UsernameDetectionMethod::USERNAME_DETECTION_METHOD_COUNT);
// Populate the username fields in |password_form|.
- if (!username_element.IsNull()) {
+ if (username_field) {
password_form->username_element =
- FieldName(username_element, "anonymous_username");
- base::string16 username_value = username_element.Value().Utf16();
- if (FieldHasPropertiesMask(field_value_and_properties_map, username_element,
- FieldPropertiesFlags::USER_TYPED |
- FieldPropertiesFlags::AUTOFILLED)) {
- base::string16 typed_username_value =
- *field_value_and_properties_map->at(username_element).first;
-
- if (!ScriptModifiedUsernameAcceptable(username_value,
- typed_username_value, password_form,
- field_value_and_properties_map)) {
- // If |username_value| was obtained by autofilling
- // |typed_username_value|, |typed_username_value| might be incomplete,
- // so we should leave autofilled value.
- username_value = typed_username_value;
- }
+ FieldName(username_field, "anonymous_username");
+ password_form->username_value = username_field->value;
+ if ((username_field->properties_mask &
+ (FieldPropertiesFlags::USER_TYPED |
+ FieldPropertiesFlags::AUTOFILLED)) &&
+ !username_field->typed_value.empty()) {
+ password_form->username_value = username_field->typed_value;
}
- password_form->username_value = username_value;
}
// Populate the password fields in |password_form|.
- if (!password.IsNull()) {
+ if (password) {
password_form->password_element = FieldName(password, "anonymous_password");
- blink::WebString password_value = password.Value();
- if (FieldHasPropertiesMask(field_value_and_properties_map, password,
- FieldPropertiesFlags::USER_TYPED |
- FieldPropertiesFlags::AUTOFILLED)) {
- password_value = blink::WebString::FromUTF16(
- *field_value_and_properties_map->at(password).first);
+ password_form->password_value = password->value;
+ if ((password->properties_mask & (FieldPropertiesFlags::USER_TYPED |
+ FieldPropertiesFlags::AUTOFILLED)) &&
+ !password->typed_value.empty()) {
+ password_form->password_value = password->typed_value;
}
- password_form->password_value = password_value.Utf16();
}
- if (!new_password.IsNull()) {
+ if (new_password) {
password_form->new_password_element =
FieldName(new_password, "anonymous_new_password");
- password_form->new_password_value = new_password.Value().Utf16();
- password_form->new_password_value_is_default =
- new_password.GetAttribute("value") == new_password.Value();
+ password_form->new_password_value = new_password->value;
+ password_form->new_password_value_is_default = new_password->is_default;
if (autocomplete_cache.RetrieveFor(new_password) ==
AutocompleteFlag::NEW_PASSWORD) {
password_form->new_password_marked_by_site = true;
}
- if (!confirmation_password.IsNull()) {
+ if (confirmation_password) {
password_form->confirmation_password_element =
FieldName(confirmation_password, "anonymous_confirmation_password");
}
@@ -940,8 +812,8 @@ bool GetPasswordForm(
// Populate |other_possible_usernames| in |password_form|.
ValueElementVector other_possible_usernames;
- for (const WebInputElement& plausible_username : plausible_usernames) {
- if (plausible_username == username_element)
+ for (const FormFieldData* plausible_username : plausible_usernames) {
+ if (plausible_username == username_field)
continue;
auto pair = MakePossibleUsernamePair(plausible_username);
if (!pair.first.empty())
@@ -950,7 +822,7 @@ bool GetPasswordForm(
password_form->other_possible_usernames = std::move(other_possible_usernames);
// Report metrics.
- if (username_element.IsNull()) {
+ if (!username_field) {
// To get a better idea on how password forms without a username field
// look like, report the total number of text and password fields.
UMA_HISTOGRAM_COUNTS_100(
@@ -962,7 +834,7 @@ bool GetPasswordForm(
std::count(layout_sequence.begin(), layout_sequence.end(), 'P'));
}
- password_form->origin = std::move(form.origin);
+ password_form->origin = std::move(form_origin);
password_form->signon_realm = GetSignOnRealm(password_form->origin);
password_form->scheme = PasswordForm::SCHEME_HTML;
password_form->preferred = false;
@@ -973,6 +845,13 @@ bool GetPasswordForm(
return true;
}
+bool HasGaiaSchemeAndHost(const WebFormElement& form) {
+ GURL form_url = form.GetDocument().Url();
+ GURL gaia_url = GaiaUrls::GetInstance()->gaia_url();
+ return form_url.scheme() == gaia_url.scheme() &&
+ form_url.host() == gaia_url.host();
+}
+
} // namespace
AutocompleteFlag AutocompleteFlagForElement(const WebInputElement& element) {
@@ -993,10 +872,8 @@ re2::RE2* CreateMatcher(void* instance, const char* pattern) {
}
bool IsGaiaReauthenticationForm(const blink::WebFormElement& form) {
- if (GURL(form.GetDocument().Url()).GetOrigin() !=
- GaiaUrls::GetInstance()->gaia_url().GetOrigin()) {
+ if (!HasGaiaSchemeAndHost(form))
return false;
- }
bool has_rart_field = false;
bool has_continue_field = false;
@@ -1024,16 +901,14 @@ bool IsGaiaReauthenticationForm(const blink::WebFormElement& form) {
has_continue_field = true;
}
}
-
return has_rart_field && has_continue_field;
}
bool IsGaiaWithSkipSavePasswordForm(const blink::WebFormElement& form) {
- GURL url(form.GetDocument().Url());
- if (url.GetOrigin() != GaiaUrls::GetInstance()->gaia_url().GetOrigin()) {
+ if (!HasGaiaSchemeAndHost(form))
return false;
- }
+ GURL url(form.GetDocument().Url());
std::string should_skip_password;
if (!net::GetValueForKeyInQuery(url, "ssp", &should_skip_password))
return false;
@@ -1056,8 +931,10 @@ std::unique_ptr<PasswordForm> CreatePasswordFormFromWebForm(
IsGaiaWithSkipSavePasswordForm(web_form) ||
IsGaiaReauthenticationForm(web_form);
- SyntheticForm synthetic_form;
- PopulateSyntheticFormFromWebForm(web_form, &synthetic_form);
+ blink::WebVector<blink::WebFormControlElement> control_elements;
+ web_form.GetFormControlElements(control_elements);
+ if (control_elements.empty())
+ return nullptr;
if (!WebFormElementToFormData(
web_form, blink::WebFormControlElement(),
@@ -1066,9 +943,10 @@ std::unique_ptr<PasswordForm> CreatePasswordFormFromWebForm(
return nullptr;
}
- if (!GetPasswordForm(std::move(synthetic_form), password_form.get(),
- field_value_and_properties_map, form_predictions,
- username_detector_cache)) {
+ if (!GetPasswordForm(
+ form_util::GetCanonicalOriginForDocument(web_form.GetDocument()),
+ control_elements.ReleaseVector(), password_form.get(),
+ form_predictions, username_detector_cache)) {
return nullptr;
}
return password_form;
@@ -1079,28 +957,25 @@ std::unique_ptr<PasswordForm> CreatePasswordFormFromUnownedInputElements(
const FieldValueAndPropertiesMaskMap* field_value_and_properties_map,
const FormsPredictionsMap* form_predictions,
UsernameDetectorCache* username_detector_cache) {
- SyntheticForm synthetic_form;
std::vector<blink::WebElement> fieldsets;
- synthetic_form.control_elements = form_util::GetUnownedFormFieldElements(
- frame.GetDocument().All(), &fieldsets);
- synthetic_form.origin =
- form_util::GetCanonicalOriginForDocument(frame.GetDocument());
-
- if (synthetic_form.control_elements.empty())
+ std::vector<blink::WebFormControlElement> control_elements =
+ form_util::GetUnownedFormFieldElements(frame.GetDocument().All(),
+ &fieldsets);
+ if (control_elements.empty())
return nullptr;
auto password_form = std::make_unique<PasswordForm>();
if (!UnownedPasswordFormElementsAndFieldSetsToFormData(
- fieldsets, synthetic_form.control_elements, nullptr,
- frame.GetDocument(), field_value_and_properties_map,
- form_util::EXTRACT_VALUE, &password_form->form_data,
- nullptr /* FormFieldData */)) {
+ fieldsets, control_elements, nullptr, frame.GetDocument(),
+ field_value_and_properties_map, form_util::EXTRACT_VALUE,
+ &password_form->form_data, nullptr /* FormFieldData */)) {
return nullptr;
}
- if (!GetPasswordForm(std::move(synthetic_form), password_form.get(),
- field_value_and_properties_map, form_predictions,
- username_detector_cache)) {
+ if (!GetPasswordForm(
+ form_util::GetCanonicalOriginForDocument(frame.GetDocument()),
+ control_elements, password_form.get(), form_predictions,
+ username_detector_cache)) {
return nullptr;
}
@@ -1109,14 +984,6 @@ std::unique_ptr<PasswordForm> CreatePasswordFormFromUnownedInputElements(
return password_form;
}
-bool IsCreditCardVerificationPasswordField(
- const blink::WebInputElement& field) {
- if (!field.IsPasswordFieldForAutofill())
- return false;
- return StringMatchesCVC(field.GetAttribute("id").Utf16()) ||
- StringMatchesCVC(field.GetAttribute("name").Utf16());
-}
-
std::string GetSignOnRealm(const GURL& origin) {
GURL::Replacements rep;
rep.SetPathStr("");