21

I recently came across an issue regarding browser autocomplete functionality. One might find it in forms on web pages.

Issue is with data, that is placed into other fields depending on selection of autocomplete for one field. In more detail it is connected to usage of AngularJS.

I did find topics regarding workarounds for angular, but what interests me, is where stored data by browser is kept and what is the logic behind gathering, that data.

  • Is there some defined list of fields, that is watched by browser?
  • Is data host(location) specific on field specific?
  • When is stored by browser? On normal form submit event or some other events as well?

Thank you in advance.

1

3 Answers 3

20
+50

The implementation of autofill capabilities is up to the browser vendor. If they implement the Web Hypertext Application Technology Working Group's "Living HTML Standard", paragraph 4.10.19.8.1 (Autofilling form controls: the autocomplete attribute) applies. There, the following autofill field names are defined:

  • "name"
  • "honorific-prefix"
  • "given-name"
  • "additional-name"
  • "family-name"
  • "honorific-suffix"
  • "nickname"
  • "username"
  • "new-password"
  • "current-password"
  • "organization-title"
  • "organization"
  • "street-address"
  • "address-line1"
  • "address-line2"
  • "address-line3"
  • "address-level4"
  • "address-level3"
  • "address-level2"
  • "address-level1"
  • "country"
  • "country-name"
  • "postal-code"
  • "cc-name"
  • "cc-given-name"
  • "cc-additional-name"
  • "cc-family-name"
  • "cc-number"
  • "cc-exp"
  • "cc-exp-month"
  • "cc-exp-year"
  • "cc-csc"
  • "cc-type"
  • "transaction-currency"
  • "transaction-amount"
  • "language"
  • "bday"
  • "bday-day"
  • "bday-month"
  • "bday-year"
  • "sex"
  • "url"
  • "photo"

Let's consider Chromium, a popular open source browser making use of this standard.

In src/components/autofill/core/browser/form_structure.cc a sub set of the aforementioned autofill field names are used to map these internally:

// Returns the Chrome Autofill-supported field type corresponding to the given
// |autocomplete_attribute_value|, if there is one, in the context of the given
// |field|.  Chrome Autofill supports a subset of the field types listed at
// http://is.gd/whatwg_autocomplete
HtmlFieldType FieldTypeFromAutocompleteAttributeValue(
    const std::string& autocomplete_attribute_value,
    const AutofillField& field) {
  if (autocomplete_attribute_value == "name")
    return HTML_TYPE_NAME;

  if (autocomplete_attribute_value == "given-name")
    return HTML_TYPE_GIVEN_NAME;

  if (autocomplete_attribute_value == "additional-name") {
    if (field.max_length == 1)
      return HTML_TYPE_ADDITIONAL_NAME_INITIAL;
    else
      return HTML_TYPE_ADDITIONAL_NAME;
  }

  if (autocomplete_attribute_value == "family-name")
    return HTML_TYPE_FAMILY_NAME;

  if (autocomplete_attribute_value == "organization")
    return HTML_TYPE_ORGANIZATION;

  if (autocomplete_attribute_value == "street-address")
    return HTML_TYPE_STREET_ADDRESS;

  if (autocomplete_attribute_value == "address-line1")
    return HTML_TYPE_ADDRESS_LINE1;

  if (autocomplete_attribute_value == "address-line2")
    return HTML_TYPE_ADDRESS_LINE2;

  if (autocomplete_attribute_value == "address-line3")
    return HTML_TYPE_ADDRESS_LINE3;

  // TODO(estade): remove support for "locality" and "region".
  if (autocomplete_attribute_value == "locality")
    return HTML_TYPE_ADDRESS_LEVEL2;

  if (autocomplete_attribute_value == "region")
    return HTML_TYPE_ADDRESS_LEVEL1;

  if (autocomplete_attribute_value == "address-level1")
    return HTML_TYPE_ADDRESS_LEVEL1;

  if (autocomplete_attribute_value == "address-level2")
    return HTML_TYPE_ADDRESS_LEVEL2;

  if (autocomplete_attribute_value == "address-level3")
    return HTML_TYPE_ADDRESS_LEVEL3;

  if (autocomplete_attribute_value == "country")
    return HTML_TYPE_COUNTRY_CODE;

  if (autocomplete_attribute_value == "country-name")
    return HTML_TYPE_COUNTRY_NAME;

  if (autocomplete_attribute_value == "postal-code")
    return HTML_TYPE_POSTAL_CODE;

  // content_switches.h isn't accessible from here, hence we have
  // to copy the string literal. This should be removed soon anyway.
  if (autocomplete_attribute_value == "address" &&
      base::CommandLine::ForCurrentProcess()->HasSwitch(
          "enable-experimental-web-platform-features")) {
    return HTML_TYPE_FULL_ADDRESS;
  }

  if (autocomplete_attribute_value == "cc-name")
    return HTML_TYPE_CREDIT_CARD_NAME;

  if (autocomplete_attribute_value == "cc-number")
    return HTML_TYPE_CREDIT_CARD_NUMBER;

  if (autocomplete_attribute_value == "cc-exp") {
    if (field.max_length == 5)
      return HTML_TYPE_CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR;
    else if (field.max_length == 7)
      return HTML_TYPE_CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR;
    else
      return HTML_TYPE_CREDIT_CARD_EXP;
  }

  if (autocomplete_attribute_value == "cc-exp-month")
    return HTML_TYPE_CREDIT_CARD_EXP_MONTH;

  if (autocomplete_attribute_value == "cc-exp-year") {
    if (field.max_length == 2)
      return HTML_TYPE_CREDIT_CARD_EXP_2_DIGIT_YEAR;
    else if (field.max_length == 4)
      return HTML_TYPE_CREDIT_CARD_EXP_4_DIGIT_YEAR;
    else
      return HTML_TYPE_CREDIT_CARD_EXP_YEAR;
  }

  if (autocomplete_attribute_value == "cc-csc")
    return HTML_TYPE_CREDIT_CARD_VERIFICATION_CODE;

  if (autocomplete_attribute_value == "cc-type")
    return HTML_TYPE_CREDIT_CARD_TYPE;

  if (autocomplete_attribute_value == "transaction-amount")
    return HTML_TYPE_TRANSACTION_AMOUNT;

  if (autocomplete_attribute_value == "transaction-currency")
    return HTML_TYPE_TRANSACTION_CURRENCY;

  if (autocomplete_attribute_value == "tel")
    return HTML_TYPE_TEL;

  if (autocomplete_attribute_value == "tel-country-code")
    return HTML_TYPE_TEL_COUNTRY_CODE;

  if (autocomplete_attribute_value == "tel-national")
    return HTML_TYPE_TEL_NATIONAL;

  if (autocomplete_attribute_value == "tel-area-code")
    return HTML_TYPE_TEL_AREA_CODE;

  if (autocomplete_attribute_value == "tel-local")
    return HTML_TYPE_TEL_LOCAL;

  if (autocomplete_attribute_value == "tel-local-prefix")
    return HTML_TYPE_TEL_LOCAL_PREFIX;

  if (autocomplete_attribute_value == "tel-local-suffix")
    return HTML_TYPE_TEL_LOCAL_SUFFIX;

  if (autocomplete_attribute_value == "email")
    return HTML_TYPE_EMAIL;

  return HTML_TYPE_UNKNOWN;
}

This method is then used to assist during parsing of the form fields:

void FormStructure::ParseFieldTypesFromAutocompleteAttributes() {
  const std::string kDefaultSection = "-default";

  has_author_specified_types_ = false;
  has_author_specified_sections_ = false;
  for (AutofillField* field : fields_) {
    // To prevent potential section name collisions, add a default suffix for
    // other fields.  Without this, 'autocomplete' attribute values
    // "section--shipping street-address" and "shipping street-address" would be
    // parsed identically, given the section handling code below.  We do this
    // before any validation so that fields with invalid attributes still end up
    // in the default section.  These default section names will be overridden
    // by subsequent heuristic parsing steps if there are no author-specified
    // section names.
    field->set_section(kDefaultSection);

    // Canonicalize the attribute value by trimming whitespace, collapsing
    // non-space characters (e.g. tab) to spaces, and converting to lowercase.
    std::string autocomplete_attribute =
        base::CollapseWhitespaceASCII(field->autocomplete_attribute, false);
    autocomplete_attribute = base::ToLowerASCII(autocomplete_attribute);

    // The autocomplete attribute is overloaded: it can specify either a field
    // type hint or whether autocomplete should be enabled at all.  Ignore the
    // latter type of attribute value.
    if (autocomplete_attribute.empty() ||
        autocomplete_attribute == "on" ||
        autocomplete_attribute == "off") {
      continue;
    }

    // Any other value, even it is invalid, is considered to be a type hint.
    // This allows a website's author to specify an attribute like
    // autocomplete="other" on a field to disable all Autofill heuristics for
    // the form.
    has_author_specified_types_ = true;

    // Tokenize the attribute value.  Per the spec, the tokens are parsed in
    // reverse order.
    std::vector<std::string> tokens = base::SplitString(
        autocomplete_attribute, " ", base::KEEP_WHITESPACE,
        base::SPLIT_WANT_NONEMPTY);

    // The final token must be the field type.
    // If it is not one of the known types, abort.
    DCHECK(!tokens.empty());
    std::string field_type_token = tokens.back();
    tokens.pop_back();
    HtmlFieldType field_type =
        FieldTypeFromAutocompleteAttributeValue(field_type_token, *field);
    if (field_type == HTML_TYPE_UNKNOWN)
      continue;

    // The preceding token, if any, may be a type hint.
    if (!tokens.empty() && IsContactTypeHint(tokens.back())) {
      // If it is, it must match the field type; otherwise, abort.
      // Note that an invalid token invalidates the entire attribute value, even
      // if the other tokens are valid.
      if (!ContactTypeHintMatchesFieldType(tokens.back(), field_type))
        continue;

      // Chrome Autofill ignores these type hints.
      tokens.pop_back();
    }

    // The preceding token, if any, may be a fixed string that is either
    // "shipping" or "billing".  Chrome Autofill treats these as implicit
    // section name suffixes.
    DCHECK_EQ(kDefaultSection, field->section());
    std::string section = field->section();
    HtmlFieldMode mode = HTML_MODE_NONE;
    if (!tokens.empty()) {
      if (tokens.back() == kShippingMode)
        mode = HTML_MODE_SHIPPING;
      else if (tokens.back() == kBillingMode)
        mode = HTML_MODE_BILLING;
    }

    if (mode != HTML_MODE_NONE) {
      section = "-" + tokens.back();
      tokens.pop_back();
    }

    // The preceding token, if any, may be a named section.
    const std::string kSectionPrefix = "section-";
    if (!tokens.empty() &&
        base::StartsWith(tokens.back(), kSectionPrefix,
                         base::CompareCase::SENSITIVE)) {
      // Prepend this section name to the suffix set in the preceding block.
      section = tokens.back().substr(kSectionPrefix.size()) + section;
      tokens.pop_back();
    }

    // No other tokens are allowed.  If there are any remaining, abort.
    if (!tokens.empty())
      continue;

    if (section != kDefaultSection) {
      has_author_specified_sections_ = true;
      field->set_section(section);
    }

    // No errors encountered while parsing!
    // Update the |field|'s type based on what was parsed from the attribute.
    field->SetHtmlType(field_type, mode);
  }

  was_parsed_for_autocomplete_attributes_ = true;
}
4
  • In my case, Chrome autofill is suggested for such fields like zip, email, phone in one form at least. I haven't found such field names in list above. Is it some other case of autofill?
    – Eugene
    Commented Jan 17, 2016 at 8:43
  • 1
    AFAIK these field names are not part of any standard and thus subject to implementation decisions made by the vendor. Answering that for all browsers is a little broad. If you're interested how Chromium does it, take a look at the sources linked. src/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.h is also quite interesting in this regard.
    – mritz_p
    Commented Jan 19, 2016 at 14:37
  • Excellent! Exactly what I was looking for. It deserves way more upvotes!
    – Aidin
    Commented Nov 13, 2019 at 20:43
  • You didn't answer where the data is stored. On my website we don't allow browser history. I'd like to know if this could cause the auto-complete to not work.
    – MrFox
    Commented Jan 3, 2020 at 11:10
-1

I was trying to find where the Form Entry data was stored too, and this website helped me (for Chrome):

https://www.foxtonforensics.com/browser-history-examiner/chrome-history-location#:~:text=favicons%27%20table.-,Form%20History,-Chrome%20Form%20History

"Windows XP"  
C:\Documents and Settings\<username>\Local Settings\Application Data\Google\Chrome\User Data\Default  
C:\Documents and Settings\<username>\Local Settings\Application Data\Google\Chrome\User Data\Default\Cache  

"Windows Vista, 7, 8, 10"  
C:\Users\<username>\AppData\Local\Google\Chrome\User Data\Default  
C:\Users\<username>\AppData\Local\Google\Chrome\User Data\Default\Cache  

"Mac OS X"  
/Users/<username>/Library/Application Support/Google/Chrome/Default  
/Users/<username>/Library/Caches/Google/Chrome/Default/Cache  

"Linux/Unix"  
/home/<username>/.config/google-chrome/Default  
/home/<username>/.cache/google-chrome/Default/Cache 

The autofill data is in the 'Web Data' SQLite database file. Which you can view by uploading it to this website: https://inloop.github.io/sqlite-viewer/

-2

In my experience, autocomplete usually is field specific and makes an ajax call when they keyup after the third char is input.

if (input.length === 3)
  //call server on keyup

Then it caches the result. After that, you can just filter that result on the client side. If they backspace and enter a new 3 char beginning, it queries again and stores it locally. I hope this answers your question.

2
  • Are you sure, that you are talking about browsers form autocomplete functionality?
    – Eugene
    Commented Jan 11, 2016 at 1:50
  • Disregard my answer.. I thought you were talking about custom dynamic autocomplete fields, i.e. when adding one of your contacts into a textbox querying your own database and offering completion options.
    – gegillam
    Commented Jan 11, 2016 at 1:54

Not the answer you're looking for? Browse other questions tagged or ask your own question.