Bug 1009935 - Implement the @autocomplete attribute for values other than off/on. r=smaug
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Fri, 06 Jun 2014 00:25:02 -0700
changeset 207352 21cf9d1e6f485da8dfceb8b3b3f4c4e5e95a2965
parent 207351 87fc79cf3fe18bd9b6975eeec849cf350a8846e1
child 207353 da60b8bc18a32ca050ab8543c222a4443c34f768
push id494
push userraliiev@mozilla.com
push dateMon, 25 Aug 2014 18:42:16 +0000
treeherdermozilla-release@a3cc3e46b571 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1009935
milestone32.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1009935 - Implement the @autocomplete attribute for values other than off/on. r=smaug
content/base/public/nsContentUtils.h
content/base/src/nsContentUtils.cpp
content/html/content/src/HTMLInputElement.cpp
content/html/content/src/HTMLInputElement.h
modules/libpref/src/init/all.js
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -2022,16 +2022,32 @@ public:
    * NOTE: the caller has to make sure autocomplete makes sense for the
    * element's type.
    *
    * @param aInput the input element to check. NOTE: aInput can't be null.
    * @return whether the input element has autocomplete enabled.
    */
   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
    * http://www.w3.org/TR/xml-stylesheet/#NT-StyleSheetPI for the specification
    * which is used to parse aSource.
    *
    * @param aSource the string to parse
    * @param aName the name of the attribute to get the value for
@@ -2161,16 +2177,19 @@ private:
   static void DropFragmentParsers();
 
   static bool MatchClassNames(nsIContent* aContent, int32_t aNamespaceID,
                               nsIAtom* aAtom, void* aData);
   static void DestroyClassNameArray(void* aData);
   static void* AllocClassMatchingInfo(nsINode* aRootNode,
                                       const nsString* aClasses);
 
+  static AutocompleteAttrState InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrVal,
+                                                                  nsAString& aResult);
+
   static nsIXPConnect *sXPConnect;
 
   static nsIScriptSecurityManager *sSecurityManager;
   static nsIPrincipal *sSystemPrincipal;
   static nsIPrincipal *sNullSubjectPrincipal;
 
   static nsIParserService *sParserService;
 
@@ -2220,16 +2239,17 @@ private:
   static bool sIsHandlingKeyBoardEvent;
   static bool sAllowXULXBL_for_file;
   static bool sIsFullScreenApiEnabled;
   static bool sTrustedFullScreenOnly;
   static bool sFullscreenApiIsContentOnly;
   static uint32_t sHandlingInputTimeout;
   static bool sIsPerformanceTimingEnabled;
   static bool sIsResourceTimingEnabled;
+  static bool sIsExperimentalAutocompleteEnabled;
 
   static nsHtml5StringParser* sHTMLFragmentParser;
   static nsIParser* sXMLFragmentParser;
   static nsIFragmentContentSink* sXMLFragmentSink;
 
   /**
    * True if there's a fragment parser activation on the stack.
    */
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -232,28 +232,184 @@ nsString* nsContentUtils::sAltText = nul
 nsString* nsContentUtils::sModifierSeparator = nullptr;
 
 bool nsContentUtils::sInitialized = false;
 bool nsContentUtils::sIsFullScreenApiEnabled = false;
 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;
 
 nsHtml5StringParser* nsContentUtils::sHTMLFragmentParser = nullptr;
 nsIParser* nsContentUtils::sXMLFragmentParser = nullptr;
 nsIFragmentContentSink* nsContentUtils::sXMLFragmentSink = nullptr;
 bool nsContentUtils::sFragmentParsingActive = false;
 
 #if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP))
 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);
 static NS_DEFINE_CID(kCParserCID, NS_PARSER_CID);
 
 static PLDHashTable sEventListenerManagersHash;
 
 class DOMEventListenerManagersHashReporter MOZ_FINAL : public nsIMemoryReporter
@@ -363,16 +519,22 @@ nsresult
 nsContentUtils::Init()
 {
   if (sInitialized) {
     NS_WARNING("Init() called twice");
 
     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);
 
   sXPConnect = nsXPConnect::XPConnect();
 
   sSecurityManager = nsScriptSecurityManager::GetScriptSecurityManager();
   if(!sSecurityManager)
     return NS_ERROR_FAILURE;
@@ -435,16 +597,19 @@ nsContentUtils::Init()
                                "full-screen-api.allow-trusted-requests-only");
 
   Preferences::AddBoolVarCache(&sIsPerformanceTimingEnabled,
                                "dom.enable_performance", true);
 
   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);
 
 #if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP))
   Preferences::AddBoolVarCache(&sDOMWindowDumpEnabled,
                                "browser.dom.window.dump.enabled");
 #endif
@@ -680,17 +845,132 @@ nsContentUtils::IsAutocompleteEnabled(ns
     aInput->GetForm(getter_AddRefs(form));
     if (!form) {
       return true;
     }
 
     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)                 \
   while ((iter) != (end_iter) && nsCRT::IsAsciiSpace(*(iter))) { \
     ++(iter);                                                    \
   }                                                              \
   if ((iter) == (end_iter)) {                                    \
     return (end_res);                                            \
--- a/content/html/content/src/HTMLInputElement.cpp
+++ b/content/html/content/src/HTMLInputElement.cpp
@@ -163,30 +163,16 @@ static const nsAttrValue::EnumTable kInp
   { "time", NS_FORM_INPUT_TIME },
   { "url", NS_FORM_INPUT_URL },
   { 0 }
 };
 
 // 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;
 static const uint8_t NS_INPUT_INPUTMODE_UPPERCASE         = 3;
 static const uint8_t NS_INPUT_INPUTMODE_LOWERCASE         = 4;
 static const uint8_t NS_INPUT_INPUTMODE_TITLECASE         = 5;
 static const uint8_t NS_INPUT_INPUTMODE_AUTOCAPITALIZED   = 6;
 
@@ -1108,16 +1094,17 @@ static nsresult FireEventForAccessibilit
 //
 // construction, destruction
 //
 
 HTMLInputElement::HTMLInputElement(already_AddRefed<nsINodeInfo>& aNodeInfo,
                                    FromParser aFromParser)
   : nsGenericHTMLFormElementWithState(aNodeInfo)
   , mType(kInputDefaultType->value)
+  , mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown)
   , mDisabledChanged(false)
   , mValueChanged(false)
   , mCheckedChanged(false)
   , mChecked(false)
   , mHandlingSelectEvent(false)
   , mShouldInitChecked(false)
   , mParserCreating(aFromParser != NOT_FROM_PARSER)
   , mInInternalActivate(false)
@@ -1469,16 +1456,19 @@ HTMLInputElement::AfterSetAttr(int32_t a
         nsAutoString value;
         GetValueInternal(value);
         nsNumberControlFrame* numberControlFrame =
           do_QueryFrame(GetPrimaryFrame());
         if (numberControlFrame) {
           numberControlFrame->SetValueOfAnonTextControl(value);
         }
       }
+    } else if (aName == nsGkAtoms::autocomplete) {
+      // Clear the cached @autocomplete attribute state.
+      mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
     }
 
     UpdateState(aNotify);
   }
 
   return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName,
                                                          aValue, aNotify);
 }
@@ -1491,18 +1481,16 @@ HTMLInputElement::GetForm(nsIDOMHTMLForm
   return nsGenericHTMLFormElementWithState::GetForm(aForm);
 }
 
 NS_IMPL_STRING_ATTR(HTMLInputElement, DefaultValue, value)
 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)
 NS_IMPL_STRING_ATTR(HTMLInputElement, Max, max)
 NS_IMPL_STRING_ATTR(HTMLInputElement, Min, min)
 NS_IMPL_ACTION_ATTR(HTMLInputElement, FormAction, formaction)
 NS_IMPL_ENUM_ATTR_DEFAULT_MISSING_INVALID_VALUES(HTMLInputElement, FormEnctype, formenctype,
                                                  "", kFormDefaultEnctype->tag)
@@ -1522,16 +1510,47 @@ NS_IMPL_STRING_ATTR(HTMLInputElement, St
 NS_IMPL_STRING_ATTR(HTMLInputElement, UseMap, usemap)
 //NS_IMPL_STRING_ATTR(HTMLInputElement, Value, value)
 NS_IMPL_UINT_ATTR_NON_ZERO_DEFAULT_VALUE(HTMLInputElement, Size, size, DEFAULT_COLS)
 NS_IMPL_STRING_ATTR(HTMLInputElement, Pattern, pattern)
 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()
 {
   return 0;
 }
 
 uint32_t
 HTMLInputElement::Height()
@@ -4887,17 +4906,18 @@ HTMLInputElement::ParseAttribute(int32_t
     }
     if (aAttribute == nsGkAtoms::formmethod) {
       return aResult.ParseEnumValue(aValue, kFormMethodTable, false);
     }
     if (aAttribute == nsGkAtoms::formenctype) {
       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);
     }
     if (ParseImageAttribute(aAttribute, aValue, aResult)) {
       // We have to call |ParseImageAttribute| unconditionally since we
       // don't know if we're going to have a type="image" attribute yet,
       // (or could have it set dynamically in the future).  See bug
--- a/content/html/content/src/HTMLInputElement.h
+++ b/content/html/content/src/HTMLInputElement.h
@@ -16,16 +16,17 @@
 #include "nsIDOMNSEditableElement.h"
 #include "nsCOMPtr.h"
 #include "nsIConstraintValidation.h"
 #include "mozilla/dom/HTMLFormElement.h" // for HasEverTriedInvalidSubmit()
 #include "mozilla/dom/HTMLInputElementBinding.h"
 #include "nsIFilePicker.h"
 #include "nsIContentPrefService2.h"
 #include "mozilla/Decimal.h"
+#include "nsContentUtils.h"
 
 class nsDOMFileList;
 class nsIRadioGroupContainer;
 class nsIRadioGroupVisitor;
 class nsIRadioVisitor;
 class nsTextEditorState;
 
 namespace mozilla {
@@ -1263,16 +1264,17 @@ protected:
   // Float value returned by GetStep() when the step attribute is set to 'any'.
   static const Decimal kStepAny;
 
   /**
    * The type of this input (<input type=...>) as an integer.
    * @see nsIFormControl.h (specifically NS_FORM_INPUT_*)
    */
   uint8_t                  mType;
+  nsContentUtils::AutocompleteAttrState mAutocompleteAttrState;
   bool                     mDisabledChanged     : 1;
   bool                     mValueChanged        : 1;
   bool                     mCheckedChanged      : 1;
   bool                     mChecked             : 1;
   bool                     mHandlingSelectEvent : 1;
   bool                     mShouldInitChecked   : 1;
   bool                     mParserCreating      : 1;
   bool                     mInInternalActivate  : 1;
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -764,16 +764,19 @@ pref("dom.experimental_forms", false);
 
 // Enable <input type=number>:
 pref("dom.forms.number", true);
 
 // Enable <input type=color> by default. It will be turned off for remaining
 // 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);
 
 // Enable pre-installed applications.
 pref("dom.webapps.useCurrentProfile", false);
 
 pref("dom.cycle_collector.incremental", true);