#include "config.h"
#include "Autofill.h"
#include "HTMLFormControlElement.h"
#include "HTMLFormElement.h"
#include "HTMLNames.h"
#include <wtf/HashMap.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/text/AtomicString.h>
#include <wtf/text/AtomicStringHash.h>
namespace WebCore {
enum class AutofillCategory {
Off,
Automatic,
Normal,
Contact,
};
struct AutofillInfo {
AutofillFieldName fieldName;
AutofillCategory category;
};
static const HashMap<AtomicString, AutofillInfo>& fieldNameMap()
{
static const auto map = makeNeverDestroyed([] {
struct MapEntry {
const char* name;
AutofillInfo value;
};
static const MapEntry entries[] = {
{ "off", { AutofillFieldName::None, AutofillCategory::Off } },
{ "on", { AutofillFieldName::None, AutofillCategory::Automatic } },
{ "name", { AutofillFieldName::Name, AutofillCategory::Normal } },
{ "honorific-prefix", { AutofillFieldName::HonorificPrefix, AutofillCategory::Normal } },
{ "given-name", { AutofillFieldName::GivenName, AutofillCategory::Normal } },
{ "additional-name", { AutofillFieldName::AdditionalName, AutofillCategory::Normal } },
{ "family-name", { AutofillFieldName::FamilyName, AutofillCategory::Normal } },
{ "honorific-suffix", { AutofillFieldName::HonorificSuffix, AutofillCategory::Normal } },
{ "nickname", { AutofillFieldName::Nickname, AutofillCategory::Normal } },
{ "username", { AutofillFieldName::Username, AutofillCategory::Normal } },
{ "new-password", { AutofillFieldName::NewPassword, AutofillCategory::Normal } },
{ "current-password", { AutofillFieldName::CurrentPassword, AutofillCategory::Normal } },
{ "organization-title", { AutofillFieldName::OrganizationTitle, AutofillCategory::Normal } },
{ "organization", { AutofillFieldName::Organization, AutofillCategory::Normal } },
{ "street-address", { AutofillFieldName::StreetAddress, AutofillCategory::Normal } },
{ "address-line1", { AutofillFieldName::AddressLine1, AutofillCategory::Normal } },
{ "address-line2", { AutofillFieldName::AddressLine2, AutofillCategory::Normal } },
{ "address-line3", { AutofillFieldName::AddressLine3, AutofillCategory::Normal } },
{ "address-level4", { AutofillFieldName::AddressLevel4, AutofillCategory::Normal } },
{ "address-level3", { AutofillFieldName::AddressLevel3, AutofillCategory::Normal } },
{ "address-level2", { AutofillFieldName::AddressLevel2, AutofillCategory::Normal } },
{ "address-level1", { AutofillFieldName::AddressLevel1, AutofillCategory::Normal } },
{ "country", { AutofillFieldName::Country, AutofillCategory::Normal } },
{ "country-name", { AutofillFieldName::CountryName, AutofillCategory::Normal } },
{ "postal-code", { AutofillFieldName::PostalCode, AutofillCategory::Normal } },
{ "cc-name", { AutofillFieldName::CcName, AutofillCategory::Normal } },
{ "cc-given-name", { AutofillFieldName::CcGivenName, AutofillCategory::Normal } },
{ "cc-additional-name", { AutofillFieldName::CcAdditionalName, AutofillCategory::Normal } },
{ "cc-family-name", { AutofillFieldName::CcFamilyName, AutofillCategory::Normal } },
{ "cc-number", { AutofillFieldName::CcNumber, AutofillCategory::Normal } },
{ "cc-exp", { AutofillFieldName::CcExp, AutofillCategory::Normal } },
{ "cc-exp-month", { AutofillFieldName::CcExpMonth, AutofillCategory::Normal } },
{ "cc-exp-year", { AutofillFieldName::CcExpYear, AutofillCategory::Normal } },
{ "cc-csc", { AutofillFieldName::CcCsc, AutofillCategory::Normal } },
{ "cc-type", { AutofillFieldName::CcType, AutofillCategory::Normal } },
{ "transaction-currency", { AutofillFieldName::TransactionCurrency, AutofillCategory::Normal } },
{ "transaction-amount", { AutofillFieldName::TransactionAmount, AutofillCategory::Normal } },
{ "language", { AutofillFieldName::Language, AutofillCategory::Normal } },
{ "bday", { AutofillFieldName::Bday, AutofillCategory::Normal } },
{ "bday-day", { AutofillFieldName::BdayDay, AutofillCategory::Normal } },
{ "bday-month", { AutofillFieldName::BdayMonth, AutofillCategory::Normal } },
{ "bday-year", { AutofillFieldName::BdayYear, AutofillCategory::Normal } },
{ "sex", { AutofillFieldName::Sex, AutofillCategory::Normal } },
{ "url", { AutofillFieldName::URL, AutofillCategory::Normal } },
{ "photo", { AutofillFieldName::Photo, AutofillCategory::Normal } },
{ "tel", { AutofillFieldName::Tel, AutofillCategory::Contact } },
{ "tel-country-code", { AutofillFieldName::TelCountryCode, AutofillCategory::Contact } },
{ "tel-national", { AutofillFieldName::TelNational, AutofillCategory::Contact } },
{ "tel-area-code", { AutofillFieldName::TelAreaCode, AutofillCategory::Contact } },
{ "tel-local", { AutofillFieldName::TelLocal, AutofillCategory::Contact } },
{ "tel-local-prefix", { AutofillFieldName::TelLocalPrefix, AutofillCategory::Contact } },
{ "tel-local-suffix", { AutofillFieldName::TelLocalSuffix, AutofillCategory::Contact } },
{ "tel-extension", { AutofillFieldName::TelExtension, AutofillCategory::Contact } },
{ "email", { AutofillFieldName::Email, AutofillCategory::Contact } },
{ "impp", { AutofillFieldName::Impp, AutofillCategory::Contact } },
};
HashMap<AtomicString, AutofillInfo> map;
for (auto& entry : entries)
map.add(entry.name, entry.value);
return map;
}());
return map;
}
AutofillFieldName toAutofillFieldName(const AtomicString& value)
{
auto map = fieldNameMap();
auto it = map.find(value);
if (it == map.end())
return AutofillFieldName::None;
return it->value.fieldName;
}
static inline bool isContactToken(const AtomicString& token)
{
static NeverDestroyed<AtomicString> home("home", AtomicString::ConstructFromLiteral);
static NeverDestroyed<AtomicString> work("work", AtomicString::ConstructFromLiteral);
static NeverDestroyed<AtomicString> mobile("mobile", AtomicString::ConstructFromLiteral);
static NeverDestroyed<AtomicString> fax("fax", AtomicString::ConstructFromLiteral);
static NeverDestroyed<AtomicString> pager("pager", AtomicString::ConstructFromLiteral);
return token == home || token == work || token == mobile || token == fax || token == pager;
}
static unsigned maxTokensForAutofillFieldCategory(AutofillCategory category)
{
switch (category) {
case AutofillCategory::Automatic:
case AutofillCategory::Off:
return 1;
case AutofillCategory::Normal:
return 3;
case AutofillCategory::Contact:
return 4;
}
ASSERT_NOT_REACHED();
return 0;
}
AutofillData AutofillData::createFromHTMLFormControlElement(const HTMLFormControlElement& element)
{
static NeverDestroyed<AtomicString> on("on", AtomicString::ConstructFromLiteral);
static NeverDestroyed<AtomicString> off("off", AtomicString::ConstructFromLiteral);
auto defaultLabel = [&] () -> AutofillData {
if (element.autofillMantle() == AutofillMantle::Anchor)
return { emptyString(), emptyString() };
auto form = element.form();
if (form && form->autocomplete() == off)
return { off, emptyString() };
return { on, emptyString() };
};
const AtomicString& attributeValue = element.attributeWithoutSynchronization(HTMLNames::autocompleteAttr);
if (attributeValue.isNull())
return defaultLabel();
SpaceSplitString tokens(attributeValue, true);
if (tokens.isEmpty())
return defaultLabel();
unsigned index = tokens.size() - 1;
auto& map = fieldNameMap();
auto it = map.find(tokens[index]);
if (it == map.end())
return defaultLabel();
auto category = it->value.category;
if (tokens.size() > maxTokensForAutofillFieldCategory(category))
return defaultLabel();
const auto& field = tokens[index];
auto mantle = element.autofillMantle();
if ((category == AutofillCategory::Off || category == AutofillCategory::Automatic) && mantle == AutofillMantle::Anchor)
return defaultLabel();
if (category == AutofillCategory::Off)
return { off, off.get().string() };
if (category == AutofillCategory::Automatic)
return { on, on.get().string() };
String idlValue = field;
if (index == 0)
return { field, idlValue };
index--;
const auto& contactToken = tokens[index];
if (category == AutofillCategory::Contact && isContactToken(contactToken)) {
const auto& contact = contactToken;
idlValue = WTF::makeString(contact, " ", idlValue);
if (index == 0)
return { field, idlValue };
index--;
}
const auto& modeToken = tokens[index];
if (equalIgnoringASCIICase(modeToken, "shipping") || equalIgnoringASCIICase(modeToken, "billing")) {
const auto& mode = modeToken;
idlValue = WTF::makeString(mode, " ", idlValue);
if (index == 0)
return { field, idlValue };
index--;
}
if (index != 0)
return defaultLabel();
const auto& sectionToken = tokens[index];
if (!startsWithLettersIgnoringASCIICase(sectionToken, "section-"))
return defaultLabel();
const auto& section = sectionToken;
idlValue = WTF::makeString(section, " ", idlValue);
return { field, idlValue };
}
}