#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 HashMap<AtomicString, AutofillInfo>& fieldNameMap()
{
static NeverDestroyed<HashMap<AtomicString, AutofillInfo>> map;
if (map.get().isEmpty()) {
map.get().add(AtomicString("off", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::None, AutofillCategory::Off });
map.get().add(AtomicString("on", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::None, AutofillCategory::Automatic });
map.get().add(AtomicString("name", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Name, AutofillCategory::Normal });
map.get().add(AtomicString("honorific-prefix", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::HonorificPrefix, AutofillCategory::Normal });
map.get().add(AtomicString("given-name", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::GivenName, AutofillCategory::Normal });
map.get().add(AtomicString("additional-name", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::AdditionalName, AutofillCategory::Normal });
map.get().add(AtomicString("family-name", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::FamilyName, AutofillCategory::Normal });
map.get().add(AtomicString("honorific-suffix", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::HonorificSuffix, AutofillCategory::Normal });
map.get().add(AtomicString("nickname", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Nickname, AutofillCategory::Normal });
map.get().add(AtomicString("username", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Username, AutofillCategory::Normal });
map.get().add(AtomicString("new-password", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::NewPassword, AutofillCategory::Normal });
map.get().add(AtomicString("current-password", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CurrentPassword, AutofillCategory::Normal });
map.get().add(AtomicString("organization-title", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::OrganizationTitle, AutofillCategory::Normal });
map.get().add(AtomicString("organization", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Organization, AutofillCategory::Normal });
map.get().add(AtomicString("street-address", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::StreetAddress, AutofillCategory::Normal });
map.get().add(AtomicString("address-line1", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::AddressLine1, AutofillCategory::Normal });
map.get().add(AtomicString("address-line2", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::AddressLine2, AutofillCategory::Normal });
map.get().add(AtomicString("address-line3", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::AddressLine3, AutofillCategory::Normal });
map.get().add(AtomicString("address-level4", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::AddressLevel4, AutofillCategory::Normal });
map.get().add(AtomicString("address-level3", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::AddressLevel3, AutofillCategory::Normal });
map.get().add(AtomicString("address-level2", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::AddressLevel2, AutofillCategory::Normal });
map.get().add(AtomicString("address-level1", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::AddressLevel1, AutofillCategory::Normal });
map.get().add(AtomicString("country", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Country, AutofillCategory::Normal });
map.get().add(AtomicString("country-name", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CountryName, AutofillCategory::Normal });
map.get().add(AtomicString("postal-code", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::PostalCode, AutofillCategory::Normal });
map.get().add(AtomicString("cc-name", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CcName, AutofillCategory::Normal });
map.get().add(AtomicString("cc-given-name", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CcGivenName, AutofillCategory::Normal });
map.get().add(AtomicString("cc-additional-name", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CcAdditionalName, AutofillCategory::Normal });
map.get().add(AtomicString("cc-family-name", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CcFamilyName, AutofillCategory::Normal });
map.get().add(AtomicString("cc-number", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CcNumber, AutofillCategory::Normal });
map.get().add(AtomicString("cc-exp", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CcExp, AutofillCategory::Normal });
map.get().add(AtomicString("cc-exp-month", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CcExpMonth, AutofillCategory::Normal });
map.get().add(AtomicString("cc-exp-year", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CcExpYear, AutofillCategory::Normal });
map.get().add(AtomicString("cc-csc", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CcCsc, AutofillCategory::Normal });
map.get().add(AtomicString("cc-type", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CcType, AutofillCategory::Normal });
map.get().add(AtomicString("transaction-currency", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::TransactionCurrency, AutofillCategory::Normal });
map.get().add(AtomicString("transaction-amount", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::TransactionAmount, AutofillCategory::Normal });
map.get().add(AtomicString("language", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Language, AutofillCategory::Normal });
map.get().add(AtomicString("bday", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Bday, AutofillCategory::Normal });
map.get().add(AtomicString("bday-day", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::BdayDay, AutofillCategory::Normal });
map.get().add(AtomicString("bday-month", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::BdayMonth, AutofillCategory::Normal });
map.get().add(AtomicString("bday-year", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::BdayYear, AutofillCategory::Normal });
map.get().add(AtomicString("sex", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Sex, AutofillCategory::Normal });
map.get().add(AtomicString("url", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::URL, AutofillCategory::Normal });
map.get().add(AtomicString("photo", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Photo, AutofillCategory::Normal });
map.get().add(AtomicString("tel", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Tel, AutofillCategory::Contact });
map.get().add(AtomicString("tel-country-code", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::TelCountryCode, AutofillCategory::Contact });
map.get().add(AtomicString("tel-national", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::TelNational, AutofillCategory::Contact });
map.get().add(AtomicString("tel-area-code", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::TelAreaCode, AutofillCategory::Contact });
map.get().add(AtomicString("tel-local", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::TelLocal, AutofillCategory::Contact });
map.get().add(AtomicString("tel-local-prefix", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::TelLocalPrefix, AutofillCategory::Contact });
map.get().add(AtomicString("tel-local-suffix", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::TelLocalSuffix, AutofillCategory::Contact });
map.get().add(AtomicString("tel-extension", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::TelExtension, AutofillCategory::Contact });
map.get().add(AtomicString("email", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Email, AutofillCategory::Contact });
map.get().add(AtomicString("impp", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Impp, AutofillCategory::Contact });
}
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 == nullAtom)
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 (!sectionToken.startsWithIgnoringASCIICase("section-"))
return defaultLabel();
const auto& section = sectionToken;
idlValue = WTF::makeString(section, " ", idlValue);
return { field, idlValue };
}
}