Bug 1020496, add getAutocompleteInfo method to get components of autocomplete attribute, r=smaug

This commit is contained in:
Neil Deakin 2014-07-09 08:01:00 +01:00
parent 7f12650eaa
commit 1bfbc6d5da
10 changed files with 235 additions and 33 deletions

View File

@ -27,6 +27,7 @@
#include "nsMathUtils.h"
#include "nsTArrayForwardDeclare.h"
#include "Units.h"
#include "mozilla/dom/AutocompleteInfoBinding.h"
#if defined(XP_WIN)
// Undefine LoadImage to prevent naming conflict with Windows.
@ -2045,8 +2046,22 @@ public:
*
* @return whether aAttr was valid and can be cached.
*/
static AutocompleteAttrState SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
nsAString& aResult);
static AutocompleteAttrState
SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
nsAString& aResult,
AutocompleteAttrState aCachedState =
eAutocompleteAttrState_Unknown);
/* Variation that is used to retrieve a dictionary of the parts of the
* autocomplete attribute.
*
* @return whether aAttr was valid and can be cached.
*/
static AutocompleteAttrState
SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
mozilla::dom::AutocompleteInfo& aInfo,
AutocompleteAttrState aCachedState =
eAutocompleteAttrState_Unknown);
/**
* This will parse aSource, to extract the value of the pseudo attribute
@ -2205,8 +2220,9 @@ private:
static void* AllocClassMatchingInfo(nsINode* aRootNode,
const nsString* aClasses);
// Fills in aInfo with the tokens from the supplied autocomplete attribute.
static AutocompleteAttrState InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrVal,
nsAString& aResult);
mozilla::dom::AutocompleteInfo& aInfo);
static nsIXPConnect *sXPConnect;

View File

@ -766,17 +766,73 @@ nsContentUtils::IsAutocompleteEnabled(nsIDOMHTMLInputElement* aInput)
nsContentUtils::AutocompleteAttrState
nsContentUtils::SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
nsAString& aResult)
nsAString& aResult,
AutocompleteAttrState aCachedState)
{
AutocompleteAttrState state = InternalSerializeAutocompleteAttribute(aAttr, aResult);
if (state == eAutocompleteAttrState_Valid) {
ASCIIToLower(aResult);
} else {
aResult.Truncate();
if (!aAttr ||
aCachedState == nsContentUtils::eAutocompleteAttrState_Invalid) {
return aCachedState;
}
if (aCachedState == nsContentUtils::eAutocompleteAttrState_Valid) {
uint32_t atomCount = aAttr->GetAtomCount();
for (uint32_t i = 0; i < atomCount; i++) {
if (i != 0) {
aResult.Append(' ');
}
aResult.Append(nsDependentAtomString(aAttr->AtomAt(i)));
}
nsContentUtils::ASCIIToLower(aResult);
return aCachedState;
}
aResult.Truncate();
mozilla::dom::AutocompleteInfo info;
AutocompleteAttrState state =
InternalSerializeAutocompleteAttribute(aAttr, info);
if (state == eAutocompleteAttrState_Valid) {
// Concatenate the info fields.
aResult = info.mSection;
if (!info.mAddressType.IsEmpty()) {
if (!aResult.IsEmpty()) {
aResult += ' ';
}
aResult += info.mAddressType;
}
if (!info.mContactType.IsEmpty()) {
if (!aResult.IsEmpty()) {
aResult += ' ';
}
aResult += info.mContactType;
}
if (!info.mFieldName.IsEmpty()) {
if (!aResult.IsEmpty()) {
aResult += ' ';
}
aResult += info.mFieldName;
}
}
return state;
}
nsContentUtils::AutocompleteAttrState
nsContentUtils::SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
mozilla::dom::AutocompleteInfo& aInfo,
AutocompleteAttrState aCachedState)
{
if (!aAttr ||
aCachedState == nsContentUtils::eAutocompleteAttrState_Invalid) {
return aCachedState;
}
return InternalSerializeAutocompleteAttribute(aAttr, aInfo);
}
/**
* Helper to validate the @autocomplete tokens.
*
@ -784,7 +840,7 @@ nsContentUtils::SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
*/
nsContentUtils::AutocompleteAttrState
nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrVal,
nsAString& aResult)
mozilla::dom::AutocompleteInfo& aInfo)
{
// No sandbox attribute so we are done
if (!aAttrVal) {
@ -801,6 +857,7 @@ nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrV
AutocompleteCategory category;
nsAttrValue enumValue;
nsAutoString str;
bool result = enumValue.ParseEnumValue(tokenString, kAutocompleteFieldNameTable, false);
if (result) {
// Off/Automatic/Normal categories.
@ -809,7 +866,9 @@ nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrV
if (numTokens > 1) {
return eAutocompleteAttrState_Invalid;
}
enumValue.ToString(aResult);
enumValue.ToString(str);
ASCIIToLower(str);
aInfo.mFieldName.Assign(str);
return eAutocompleteAttrState_Valid;
}
@ -837,7 +896,9 @@ nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrV
category = eAutocompleteCategory_CONTACT;
}
enumValue.ToString(aResult);
enumValue.ToString(str);
ASCIIToLower(str);
aInfo.mFieldName.Assign(str);
// We are done if this was the only token.
if (numTokens == 1) {
@ -851,10 +912,10 @@ nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrV
nsAttrValue contactFieldHint;
result = contactFieldHint.ParseEnumValue(tokenString, kAutocompleteContactFieldHintTable, false);
if (result) {
aResult.Insert(' ', 0);
nsAutoString contactFieldHintString;
contactFieldHint.ToString(contactFieldHintString);
aResult.Insert(contactFieldHintString, 0);
ASCIIToLower(contactFieldHintString);
aInfo.mContactType.Assign(contactFieldHintString);
if (index == 0) {
return eAutocompleteAttrState_Valid;
}
@ -866,16 +927,21 @@ nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrV
// 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);
ASCIIToLower(fieldHintString);
aInfo.mAddressType.Assign(fieldHintString);
if (index == 0) {
return eAutocompleteAttrState_Valid;
}
--index;
}
// Clear the fields as the autocomplete attribute is invalid.
aInfo.mAddressType.Truncate();
aInfo.mContactType.Truncate();
aInfo.mFieldName.Truncate();
return eAutocompleteAttrState_Invalid;
}

View File

@ -1529,23 +1529,10 @@ 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);
mAutocompleteAttrState =
nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aValue,
mAutocompleteAttrState);
return NS_OK;
}
@ -1555,6 +1542,15 @@ HTMLInputElement::SetAutocomplete(const nsAString& aValue)
return SetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, nullptr, aValue, true);
}
void
HTMLInputElement::GetAutocompleteInfo(AutocompleteInfo& aInfo)
{
const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
mAutocompleteAttrState =
nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aInfo,
mAutocompleteAttrState);
}
int32_t
HTMLInputElement::TabIndexDefault()
{

View File

@ -370,6 +370,8 @@ public:
SetHTMLAttr(nsGkAtoms::autocomplete, aValue, aRv);
}
void GetAutocompleteInfo(AutocompleteInfo& aInfo);
bool Autofocus() const
{
return GetBoolAttr(nsGkAtoms::autofocus);

View File

@ -0,0 +1,2 @@
[DEFAULT]
[test_autocompleteinfo.html]

View File

@ -0,0 +1,99 @@
<!DOCTYPE html>
<html>
<!--
Test getAutocompleteInfo() on <input>
-->
<head>
<title>Test for getAutocompleteInfo()</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
<form>
<input id="input"/>
</form>
</div>
<pre id="test">
<script>
"use strict";
var values = [
// Missing or empty attribute
[undefined, {}],
["", {}],
// One token
["on", {fieldName: "on" }],
["On", {fieldName: "on" }],
["off", {fieldName: "off" } ],
["username", {fieldName: "username" }],
[" username ", {fieldName: "username" }],
["foobar", {}],
// Two tokens
["on off", {}],
["off on", {}],
["username tel", {}],
["tel username ", {}],
[" username tel ", {}],
["tel mobile", {}],
["tel shipping", {}],
["shipping tel", {addressType: "shipping", fieldName: "tel"}],
["shipPING tel", {addressType: "shipping", fieldName: "tel"}],
["mobile tel", {contactType: "mobile", fieldName: "tel"}],
[" MoBiLe TeL ", {contactType: "mobile", fieldName: "tel"}],
["XXX tel", {}],
["XXX username", {}],
// Three tokens
["billing invalid tel", {}],
["___ mobile tel", {}],
["mobile foo tel", {}],
["mobile tel foo", {}],
["tel mobile billing", {}],
["billing mobile tel", {addressType: "billing", contactType: "mobile", fieldName: "tel"}],
[" BILLing MoBiLE tEl ", {addressType: "billing", contactType: "mobile", fieldName: "tel"}],
["billing home tel", {addressType: "billing", contactType: "home", fieldName: "tel"}],
// Four tokens (invalid)
["billing billing mobile tel", {}],
// Five tokens (invalid)
["billing billing billing mobile tel", {}],
];
function start() {
const fieldid = "input";
var field = document.getElementById(fieldid);
for (var test of values) {
if (typeof(test[0]) === "undefined")
field.removeAttribute("autocomplete");
else
field.setAttribute("autocomplete", test[0]);
var info = field.getAutocompleteInfo();
is(info.section, "section" in test[1] ? test[1].section : "",
"Checking autocompleteInfo.section for " + fieldid + ": " + test[0]);
is(info.addressType, "addressType" in test[1] ? test[1].addressType : "",
"Checking autocompleteInfo.addressType for " + fieldid + ": " + test[0]);
is(info.contactType, "contactType" in test[1] ? test[1].contactType : "",
"Checking autocompleteInfo.contactType for " + fieldid + ": " + test[0]);
is(info.fieldName, "fieldName" in test[1] ? test[1].fieldName : "",
"Checking autocompleteInfo.fieldName for " + fieldid + ": " + test[0]);
}
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({"set": [["dom.forms.autocomplete.experimental", true]]}, start);
</script>
</pre>
</body>
</html>

View File

@ -6,7 +6,7 @@
MOCHITEST_MANIFESTS += ['forms/mochitest.ini', 'mochitest.ini']
MOCHITEST_CHROME_MANIFESTS += ['chrome.ini']
MOCHITEST_CHROME_MANIFESTS += ['chrome.ini', 'forms/chrome.ini']
BROWSER_CHROME_MANIFESTS += ['browser.ini']

View File

@ -0,0 +1,17 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/**
* This dictionary is used for the input, textarea and select element's
* getAutocompleteInfo method.
*/
dictionary AutocompleteInfo {
DOMString section = "";
DOMString addressType = "";
DOMString contactType = "";
DOMString fieldName = "";
};

View File

@ -167,6 +167,9 @@ partial interface HTMLInputElement {
readonly attribute HTMLInputElement? ownerNumberControl;
boolean mozIsTextField(boolean aExcludePassword);
[ChromeOnly]
AutocompleteInfo getAutocompleteInfo();
};
partial interface HTMLInputElement {

View File

@ -39,6 +39,7 @@ WEBIDL_FILES = [
'AudioStreamTrack.webidl',
'AudioTrack.webidl',
'AudioTrackList.webidl',
'AutocompleteInfo.webidl',
'BarProp.webidl',
'BatteryManager.webidl',
'BeforeUnloadEvent.webidl',