Bug 1009935 - Implement the @autocomplete attribute for values other than off/on. r=smaug

This commit is contained in:
Matthew Noorenberghe 2014-06-06 00:25:02 -07:00
parent c902339cc5
commit 5a86849d06
5 changed files with 343 additions and 18 deletions

View File

@ -2027,6 +2027,22 @@ public:
*/
static bool IsAutocompleteEnabled(nsIDOMHTMLInputElement* aInput);
enum AutocompleteAttrState MOZ_ENUM_TYPE(uint8_t)
{
eAutocompleteAttrState_Unknown = 1,
eAutocompleteAttrState_Invalid,
eAutocompleteAttrState_Valid,
};
/**
* Parses the value of the autocomplete attribute into aResult, ensuring it's
* composed of valid tokens, otherwise the value "" is used.
* Note that this method is used for form fields, not on a <form> itself.
*
* @return whether aAttr was valid and can be cached.
*/
static AutocompleteAttrState SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
nsAString& aResult);
/**
* This will parse aSource, to extract the value of the pseudo attribute
* with the name specified in aName. See
@ -2166,6 +2182,9 @@ private:
static void* AllocClassMatchingInfo(nsINode* aRootNode,
const nsString* aClasses);
static AutocompleteAttrState InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrVal,
nsAString& aResult);
static nsIXPConnect *sXPConnect;
static nsIScriptSecurityManager *sSecurityManager;
@ -2225,6 +2244,7 @@ private:
static uint32_t sHandlingInputTimeout;
static bool sIsPerformanceTimingEnabled;
static bool sIsResourceTimingEnabled;
static bool sIsExperimentalAutocompleteEnabled;
static nsHtml5StringParser* sHTMLFragmentParser;
static nsIParser* sXMLFragmentParser;

View File

@ -237,6 +237,7 @@ bool nsContentUtils::sTrustedFullScreenOnly = true;
bool nsContentUtils::sFullscreenApiIsContentOnly = false;
bool nsContentUtils::sIsPerformanceTimingEnabled = false;
bool nsContentUtils::sIsResourceTimingEnabled = false;
bool nsContentUtils::sIsExperimentalAutocompleteEnabled = false;
uint32_t nsContentUtils::sHandlingInputTimeout = 1000;
@ -249,6 +250,161 @@ bool nsContentUtils::sFragmentParsingActive = false;
bool nsContentUtils::sDOMWindowDumpEnabled;
#endif
// Subset of http://www.whatwg.org/specs/web-apps/current-work/#autofill-field-name
enum AutocompleteFieldName
{
eAutocompleteFieldName_OFF,
eAutocompleteFieldName_ON,
// Name types
eAutocompleteFieldName_NAME,
//eAutocompleteFieldName_HONORIFIC_PREFIX,
eAutocompleteFieldName_GIVEN_NAME,
eAutocompleteFieldName_ADDITIONAL_NAME,
eAutocompleteFieldName_FAMILY_NAME,
//eAutocompleteFieldName_HONORIFIC_SUFFIX,
//eAutocompleteFieldName_NICKNAME,
//eAutocompleteFieldName_ORGANIZATION_TITLE,
// Login types
eAutocompleteFieldName_USERNAME,
eAutocompleteFieldName_NEW_PASSWORD,
eAutocompleteFieldName_CURRENT_PASSWORD,
// Address types
eAutocompleteFieldName_ORGANIZATION,
eAutocompleteFieldName_STREET_ADDRESS,
eAutocompleteFieldName_ADDRESS_LINE1,
eAutocompleteFieldName_ADDRESS_LINE2,
eAutocompleteFieldName_ADDRESS_LINE3,
eAutocompleteFieldName_ADDRESS_LEVEL4,
eAutocompleteFieldName_ADDRESS_LEVEL3,
eAutocompleteFieldName_ADDRESS_LEVEL2,
eAutocompleteFieldName_ADDRESS_LEVEL1,
eAutocompleteFieldName_COUNTRY,
eAutocompleteFieldName_COUNTRY_NAME,
eAutocompleteFieldName_POSTAL_CODE,
// Credit card types
/*
eAutocompleteFieldName_CC_NAME,
eAutocompleteFieldName_CC_GIVEN_NAME,
eAutocompleteFieldName_CC_ADDITIONAL_NAME,
eAutocompleteFieldName_CC_FAMILY_NAME,
eAutocompleteFieldName_CC_NUMBER,
eAutocompleteFieldName_CC_EXP,
eAutocompleteFieldName_CC_EXP_MONTH,
eAutocompleteFieldName_CC_EXP_YEAR,
eAutocompleteFieldName_CC_CSC,
eAutocompleteFieldName_CC_TYPE
*/
// Additional field types
/*
eAutocompleteFieldName_LANGUAGE,
eAutocompleteFieldName_BDAY,
eAutocompleteFieldName_BDAY_DAY,
eAutocompleteFieldName_BDAY_MONTH,
eAutocompleteFieldName_BDAY_YEAR,
eAutocompleteFieldName_SEX,
eAutocompleteFieldName_URL,
eAutocompleteFieldName_PHOTO,
*/
// Contact category types
eAutocompleteFieldName_TEL,
eAutocompleteFieldName_TEL_COUNTRY_CODE,
eAutocompleteFieldName_TEL_NATIONAL,
eAutocompleteFieldName_TEL_AREA_CODE,
eAutocompleteFieldName_TEL_LOCAL,
eAutocompleteFieldName_TEL_LOCAL_PREFIX,
eAutocompleteFieldName_TEL_LOCAL_SUFFIX,
eAutocompleteFieldName_TEL_EXTENSION,
eAutocompleteFieldName_EMAIL,
//eAutocompleteFieldName_IMPP,
eAutocompleteFieldName_last, // Dummy to check table sizes
};
enum AutocompleteFieldHint
{
eAutocompleteFieldHint_SHIPPING,
eAutocompleteFieldHint_BILLING,
eAutocompleteFieldHint_last, // Dummy to check table sizes
};
enum AutocompleteFieldContactHint
{
eAutocompleteFieldContactHint_HOME,
eAutocompleteFieldContactHint_WORK,
eAutocompleteFieldContactHint_MOBILE,
eAutocompleteFieldContactHint_FAX,
//eAutocompleteFieldContactHint_PAGER,
eAutocompleteFieldContactHint_last, // Dummy to check table sizes
};
enum AutocompleteCategory
{
eAutocompleteCategory_NORMAL,
eAutocompleteCategory_CONTACT,
};
static const nsAttrValue::EnumTable kAutocompleteFieldNameTable[] = {
{ "off", eAutocompleteFieldName_OFF },
{ "on", eAutocompleteFieldName_ON },
{ "name", eAutocompleteFieldName_NAME },
{ "given-name", eAutocompleteFieldName_GIVEN_NAME },
{ "additional-name", eAutocompleteFieldName_ADDITIONAL_NAME },
{ "family-name", eAutocompleteFieldName_FAMILY_NAME },
{ "username", eAutocompleteFieldName_USERNAME },
{ "new-password", eAutocompleteFieldName_NEW_PASSWORD },
{ "current-password", eAutocompleteFieldName_CURRENT_PASSWORD },
{ "organization", eAutocompleteFieldName_ORGANIZATION },
{ "street-address", eAutocompleteFieldName_STREET_ADDRESS },
{ "address-line1", eAutocompleteFieldName_ADDRESS_LINE1 },
{ "address-line2", eAutocompleteFieldName_ADDRESS_LINE2 },
{ "address-line3", eAutocompleteFieldName_ADDRESS_LINE3 },
{ "address-level4", eAutocompleteFieldName_ADDRESS_LEVEL4 },
{ "address-level3", eAutocompleteFieldName_ADDRESS_LEVEL3 },
{ "address-level2", eAutocompleteFieldName_ADDRESS_LEVEL2 },
{ "address-level1", eAutocompleteFieldName_ADDRESS_LEVEL1 },
{ "country", eAutocompleteFieldName_COUNTRY },
{ "country-name", eAutocompleteFieldName_COUNTRY_NAME },
{ "postal-code", eAutocompleteFieldName_POSTAL_CODE },
{ 0 }
};
static const nsAttrValue::EnumTable kAutocompleteContactFieldNameTable[] = {
{ "tel", eAutocompleteFieldName_TEL },
{ "tel-country-code", eAutocompleteFieldName_TEL_COUNTRY_CODE },
{ "tel-national", eAutocompleteFieldName_TEL_NATIONAL },
{ "tel-area-code", eAutocompleteFieldName_TEL_AREA_CODE },
{ "tel-local", eAutocompleteFieldName_TEL_LOCAL },
{ "tel-local-prefix", eAutocompleteFieldName_TEL_LOCAL_PREFIX },
{ "tel-local-suffix", eAutocompleteFieldName_TEL_LOCAL_SUFFIX },
{ "tel-extension", eAutocompleteFieldName_TEL_EXTENSION },
{ "email", eAutocompleteFieldName_EMAIL },
{ 0 }
};
static const nsAttrValue::EnumTable kAutocompleteFieldHintTable[] = {
{ "shipping", eAutocompleteFieldHint_SHIPPING },
{ "billing", eAutocompleteFieldHint_BILLING },
{ 0 }
};
static const nsAttrValue::EnumTable kAutocompleteContactFieldHintTable[] = {
{ "home", eAutocompleteFieldContactHint_HOME },
{ "work", eAutocompleteFieldContactHint_WORK },
{ "mobile", eAutocompleteFieldContactHint_MOBILE },
{ "fax", eAutocompleteFieldContactHint_FAX },
{ 0 }
};
namespace {
static NS_DEFINE_CID(kParserServiceCID, NS_PARSERSERVICE_CID);
@ -368,6 +524,12 @@ nsContentUtils::Init()
return NS_OK;
}
// Check that all the entries in the autocomplete enums are handled in EnumTables
MOZ_ASSERT(eAutocompleteFieldName_last == ArrayLength(kAutocompleteFieldNameTable)
+ ArrayLength(kAutocompleteContactFieldNameTable) - 2);
MOZ_ASSERT(eAutocompleteFieldHint_last == ArrayLength(kAutocompleteFieldHintTable) - 1);
MOZ_ASSERT(eAutocompleteFieldContactHint_last == ArrayLength(kAutocompleteContactFieldHintTable) - 1);
sNameSpaceManager = nsNameSpaceManager::GetInstance();
NS_ENSURE_TRUE(sNameSpaceManager, NS_ERROR_OUT_OF_MEMORY);
@ -440,6 +602,9 @@ nsContentUtils::Init()
Preferences::AddBoolVarCache(&sIsResourceTimingEnabled,
"dom.enable_resource_timing", true);
Preferences::AddBoolVarCache(&sIsExperimentalAutocompleteEnabled,
"dom.forms.autocomplete.experimental", false);
Preferences::AddUintVarCache(&sHandlingInputTimeout,
"dom.event.handling-user-input-time-limit",
1000);
@ -685,7 +850,122 @@ nsContentUtils::IsAutocompleteEnabled(nsIDOMHTMLInputElement* aInput)
form->GetAutocomplete(autocomplete);
}
return autocomplete.EqualsLiteral("on");
return !autocomplete.EqualsLiteral("off");
}
nsContentUtils::AutocompleteAttrState
nsContentUtils::SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
nsAString& aResult)
{
AutocompleteAttrState state = InternalSerializeAutocompleteAttribute(aAttr, aResult);
if (state == eAutocompleteAttrState_Valid) {
ASCIIToLower(aResult);
} else {
aResult.Truncate();
}
return state;
}
/**
* Helper to validate the @autocomplete tokens.
*
* @return {AutocompleteAttrState} The state of the attribute (invalid/valid).
*/
nsContentUtils::AutocompleteAttrState
nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrVal,
nsAString& aResult)
{
// No sandbox attribute so we are done
if (!aAttrVal) {
return eAutocompleteAttrState_Invalid;
}
uint32_t numTokens = aAttrVal->GetAtomCount();
if (!numTokens) {
return eAutocompleteAttrState_Invalid;
}
uint32_t index = numTokens - 1;
nsString tokenString = nsDependentAtomString(aAttrVal->AtomAt(index));
AutocompleteCategory category;
nsAttrValue enumValue;
bool result = enumValue.ParseEnumValue(tokenString, kAutocompleteFieldNameTable, false);
if (result) {
// Off/Automatic/Normal categories.
if (enumValue.Equals(NS_LITERAL_STRING("off"), eIgnoreCase) ||
enumValue.Equals(NS_LITERAL_STRING("on"), eIgnoreCase)) {
if (numTokens > 1) {
return eAutocompleteAttrState_Invalid;
}
enumValue.ToString(aResult);
return eAutocompleteAttrState_Valid;
}
// Only allow on/off if experimental @autocomplete values aren't enabled.
if (!sIsExperimentalAutocompleteEnabled) {
return eAutocompleteAttrState_Invalid;
}
// Normal category
if (numTokens > 2) {
return eAutocompleteAttrState_Invalid;
}
category = eAutocompleteCategory_NORMAL;
} else { // Check if the last token is of the contact category instead.
// Only allow on/off if experimental @autocomplete values aren't enabled.
if (!sIsExperimentalAutocompleteEnabled) {
return eAutocompleteAttrState_Invalid;
}
result = enumValue.ParseEnumValue(tokenString, kAutocompleteContactFieldNameTable, false);
if (!result || numTokens > 3) {
return eAutocompleteAttrState_Invalid;
}
category = eAutocompleteCategory_CONTACT;
}
enumValue.ToString(aResult);
// We are done if this was the only token.
if (numTokens == 1) {
return eAutocompleteAttrState_Valid;
}
--index;
tokenString = nsDependentAtomString(aAttrVal->AtomAt(index));
if (category == eAutocompleteCategory_CONTACT) {
nsAttrValue contactFieldHint;
result = contactFieldHint.ParseEnumValue(tokenString, kAutocompleteContactFieldHintTable, false);
if (result) {
aResult.Insert(' ', 0);
nsAutoString contactFieldHintString;
contactFieldHint.ToString(contactFieldHintString);
aResult.Insert(contactFieldHintString, 0);
if (index == 0) {
return eAutocompleteAttrState_Valid;
}
--index;
tokenString = nsDependentAtomString(aAttrVal->AtomAt(index));
}
}
// Check for billing/shipping tokens
nsAttrValue fieldHint;
if (fieldHint.ParseEnumValue(tokenString, kAutocompleteFieldHintTable, false)) {
aResult.Insert(' ', 0);
nsString fieldHintString;
fieldHint.ToString(fieldHintString);
aResult.Insert(fieldHintString, 0);
if (index == 0) {
return eAutocompleteAttrState_Valid;
}
--index;
}
return eAutocompleteAttrState_Invalid;
}
#define SKIP_WHITESPACE(iter, end_iter, end_res) \

View File

@ -168,20 +168,6 @@ static const nsAttrValue::EnumTable kInputTypeTable[] = {
// Default type is 'text'.
static const nsAttrValue::EnumTable* kInputDefaultType = &kInputTypeTable[16];
static const uint8_t NS_INPUT_AUTOCOMPLETE_OFF = 0;
static const uint8_t NS_INPUT_AUTOCOMPLETE_ON = 1;
static const uint8_t NS_INPUT_AUTOCOMPLETE_DEFAULT = 2;
static const nsAttrValue::EnumTable kInputAutocompleteTable[] = {
{ "", NS_INPUT_AUTOCOMPLETE_DEFAULT },
{ "on", NS_INPUT_AUTOCOMPLETE_ON },
{ "off", NS_INPUT_AUTOCOMPLETE_OFF },
{ 0 }
};
// Default autocomplete value is "".
static const nsAttrValue::EnumTable* kInputDefaultAutocomplete = &kInputAutocompleteTable[0];
static const uint8_t NS_INPUT_INPUTMODE_AUTO = 0;
static const uint8_t NS_INPUT_INPUTMODE_NUMERIC = 1;
static const uint8_t NS_INPUT_INPUTMODE_DIGIT = 2;
@ -1113,6 +1099,7 @@ HTMLInputElement::HTMLInputElement(already_AddRefed<nsINodeInfo>& aNodeInfo,
FromParser aFromParser)
: nsGenericHTMLFormElementWithState(aNodeInfo)
, mType(kInputDefaultType->value)
, mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown)
, mDisabledChanged(false)
, mValueChanged(false)
, mCheckedChanged(false)
@ -1474,6 +1461,9 @@ HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
numberControlFrame->SetValueOfAnonTextControl(value);
}
}
} else if (aName == nsGkAtoms::autocomplete) {
// Clear the cached @autocomplete attribute state.
mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
}
UpdateState(aNotify);
@ -1496,8 +1486,6 @@ NS_IMPL_BOOL_ATTR(HTMLInputElement, DefaultChecked, checked)
NS_IMPL_STRING_ATTR(HTMLInputElement, Accept, accept)
NS_IMPL_STRING_ATTR(HTMLInputElement, Align, align)
NS_IMPL_STRING_ATTR(HTMLInputElement, Alt, alt)
NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLInputElement, Autocomplete, autocomplete,
kInputDefaultAutocomplete->tag)
NS_IMPL_BOOL_ATTR(HTMLInputElement, Autofocus, autofocus)
//NS_IMPL_BOOL_ATTR(HTMLInputElement, Checked, checked)
NS_IMPL_BOOL_ATTR(HTMLInputElement, Disabled, disabled)
@ -1527,6 +1515,37 @@ NS_IMPL_STRING_ATTR(HTMLInputElement, Placeholder, placeholder)
NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLInputElement, Type, type,
kInputDefaultType->tag)
NS_IMETHODIMP
HTMLInputElement::GetAutocomplete(nsAString& aValue)
{
aValue.Truncate(0);
const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
if (!attributeVal ||
mAutocompleteAttrState == nsContentUtils::eAutocompleteAttrState_Invalid) {
return NS_OK;
}
if (mAutocompleteAttrState == nsContentUtils::eAutocompleteAttrState_Valid) {
uint32_t atomCount = attributeVal->GetAtomCount();
for (uint32_t i = 0; i < atomCount; i++) {
if (i != 0) {
aValue.Append(' ');
}
aValue.Append(nsDependentAtomString(attributeVal->AtomAt(i)));
}
nsContentUtils::ASCIIToLower(aValue);
return NS_OK;
}
mAutocompleteAttrState = nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aValue);
return NS_OK;
}
NS_IMETHODIMP
HTMLInputElement::SetAutocomplete(const nsAString& aValue)
{
return SetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, nullptr, aValue, true);
}
int32_t
HTMLInputElement::TabIndexDefault()
{
@ -4892,7 +4911,8 @@ HTMLInputElement::ParseAttribute(int32_t aNamespaceID,
return aResult.ParseEnumValue(aValue, kFormEnctypeTable, false);
}
if (aAttribute == nsGkAtoms::autocomplete) {
return aResult.ParseEnumValue(aValue, kInputAutocompleteTable, false);
aResult.ParseAtomArray(aValue);
return true;
}
if (aAttribute == nsGkAtoms::inputmode) {
return aResult.ParseEnumValue(aValue, kInputInputmodeTable, false);

View File

@ -21,6 +21,7 @@
#include "nsIFilePicker.h"
#include "nsIContentPrefService2.h"
#include "mozilla/Decimal.h"
#include "nsContentUtils.h"
class nsDOMFileList;
class nsIRadioGroupContainer;
@ -1268,6 +1269,7 @@ protected:
* @see nsIFormControl.h (specifically NS_FORM_INPUT_*)
*/
uint8_t mType;
nsContentUtils::AutocompleteAttrState mAutocompleteAttrState;
bool mDisabledChanged : 1;
bool mValueChanged : 1;
bool mCheckedChanged : 1;

View File

@ -769,6 +769,9 @@ pref("dom.forms.number", true);
// platforms which don't have a color picker implemented yet.
pref("dom.forms.color", true);
// Support for new @autocomplete values
pref("dom.forms.autocomplete.experimental", false);
// Enables system messages and activities
pref("dom.sysmsg.enabled", false);