Bug 1364823 - Populate select elements with form autofill profile data. r=lchang

MozReview-Commit-ID: 21K5mC2tYQn

--HG--
extra : rebase_source : a0697ca4a383e0fb387fb51bad7dab4ce407fd31
This commit is contained in:
Scott Wu 2017-05-16 16:53:01 +08:00
parent aa7d5e7676
commit 49107d9741
3 changed files with 183 additions and 48 deletions

View File

@ -84,16 +84,35 @@ FormAutofillHandler.prototype = {
// 1. the focused input which is filled in FormFillController.
// 2. a non-empty input field
// 3. the invalid value set
// 4. value already chosen in select element
let element = fieldDetail.elementWeakRef.get();
if (!element || element === focusedInput || element.value) {
if (!element || element === focusedInput) {
continue;
}
let value = profile[fieldDetail.fieldName];
// TODO: Bug 1364823 is implemeting the value filling of select element.
if (element instanceof Ci.nsIDOMHTMLInputElement && value) {
if (element.value) {
continue;
}
element.setUserInput(value);
} else if (element instanceof Ci.nsIDOMHTMLSelectElement) {
for (let option of element.options) {
if (value === option.textContent || value === option.value) {
// Do not change value if the option is already selected.
// Use case for multiple select is not considered here.
if (option.selected) {
break;
}
// TODO: Using dispatchEvent does not 100% simulate select change.
// Should investigate further in Bug 1365895.
option.selected = true;
element.dispatchEvent(new Event("input", {"bubbles": true}));
element.dispatchEvent(new Event("change", {"bubbles": true}));
break;
}
}
}
}
},

View File

@ -24,10 +24,12 @@ let MOCK_STORAGE = [{
organization: "Sesame Street",
"street-address": "123 Sesame Street.",
tel: "1-345-345-3456",
country: "US",
}, {
organization: "Mozilla",
"street-address": "331 E. Evelyn Avenue",
tel: "1-650-903-0800",
country: "US",
}];
function expectPopup() {
@ -85,7 +87,7 @@ async function setupAddressStorage() {
async function setupFormHistory() {
await updateFormHistory([
{op: "add", fieldname: "tel", value: "1-234-567-890"},
{op: "add", fieldname: "country", value: "US"},
{op: "add", fieldname: "email", value: "foo@mozilla.com"},
]);
}
@ -127,10 +129,10 @@ add_task(async function check_menu_when_both_existed() {
// Display history search result if no matched data in addresses.
add_task(async function check_fallback_for_mismatched_field() {
setInput("#country", "");
setInput("#email", "");
doKey("down");
await expectPopup();
checkMenuEntries(["US"]);
checkMenuEntries(["foo@mozilla.com"]);
});
// Autofill the address from dropdown menu.
@ -166,7 +168,11 @@ registerPopupShownListener(popupShownListener);
<p><label>organization: <input id="organization" name="organization" autocomplete="organization" type="text"></label></p>
<p><label>streetAddress: <input id="street-address" name="street-address" autocomplete="street-address" type="text"></label></p>
<p><label>tel: <input id="tel" name="tel" autocomplete="tel" type="text"></label></p>
<p><label>country: <input id="country" name="country" autocomplete="country" type="text"></label></p>
<p><label>email: <input id="email" name="email" autocomplete="email" type="text"></label></p>
<p><label>country: <select id="country" name="country" autocomplete="country">
<option/>
<option value="US">United States</option>
</label></p>
</form>
</div>

View File

@ -28,7 +28,10 @@ const TESTCASES = [
<input id="family-name" autocomplete="family-name">
<input id="street-addr" autocomplete="street-address">
<input id="city" autocomplete="address-level2">
<select id="country" autocomplete="country"></select>
<select id="country" autocomplete="country">
<option/>
<option value="US">United States</option>
</select>
<input id="email" autocomplete="email">
<input id="tel" autocomplete="tel"></form>`,
fieldDetails: [
@ -62,7 +65,10 @@ const TESTCASES = [
<input id="family-name" autocomplete="shipping family-name">
<input id="street-addr" autocomplete="shipping street-address">
<input id="city" autocomplete="shipping address-level2">
<select id="country" autocomplete="shipping country"></select>
<select id="country" autocomplete="shipping country">
<option/>
<option value="US">United States</option>
</select>
<input id='email' autocomplete="shipping email">
<input id="tel" autocomplete="shipping tel"></form>`,
fieldDetails: [
@ -159,49 +165,153 @@ const TESTCASES = [
"tel": "1234567",
},
},
{
description: "Form with autocomplete select elements and matching option values",
document: `<form>
<select id="country" autocomplete="shipping country">
<option value=""></option>
<option value="US">United States</option>
</select>
<select id="state" autocomplete="shipping address-level1">
<option value=""></option>
<option value="CA">California</option>
<option value="WA">Washington</option>
</select>
</form>`,
fieldDetails: [
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "element": {}},
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level1", "element": {}},
],
profileData: {
"guid": "123",
"country": "US",
"address-level1": "CA",
},
expectedResult: {
"country": "US",
"state": "CA",
},
},
{
description: "Form with autocomplete select elements and matching option texts",
document: `<form>
<select id="country" autocomplete="shipping country">
<option value=""></option>
<option value="US">United States</option>
</select>
<select id="state" autocomplete="shipping address-level1">
<option value=""></option>
<option value="CA">California</option>
<option value="WA">Washington</option>
</select>
</form>`,
fieldDetails: [
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "element": {}},
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level1", "element": {}},
],
profileData: {
"guid": "123",
"country": "United States",
"address-level1": "California",
},
expectedResult: {
"country": "US",
"state": "CA",
},
},
];
for (let tc of TESTCASES) {
(function() {
let testcase = tc;
add_task(async function() {
do_print("Starting testcase: " + testcase.description);
const TESTCASES_INPUT_UNCHANGED = [
{
description: "Form with autocomplete select elements; with default and no matching options",
document: `<form>
<select id="country" autocomplete="shipping country">
<option value="US">United States</option>
</select>
<select id="state" autocomplete="shipping address-level1">
<option value=""></option>
<option value="CA">California</option>
<option value="WA">Washington</option>
</select>
</form>`,
fieldDetails: [
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "element": {}},
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level1", "element": {}},
],
profileData: {
"guid": "123",
"country": "US",
"address-level1": "unknown state",
},
expectedResult: {
"country": "US",
"state": "",
},
},
];
let doc = MockDocument.createTestDocument("http://localhost:8080/test/",
testcase.document);
let form = doc.querySelector("form");
let handler = new FormAutofillHandler(form);
let onChangePromises = [];
function do_test(testcases, testFn) {
for (let tc of testcases) {
(function() {
let testcase = tc;
add_task(async function() {
do_print("Starting testcase: " + testcase.description);
handler.fieldDetails = testcase.fieldDetails;
handler.fieldDetails.forEach((field, index) => {
let element = doc.querySelectorAll("input, select")[index];
field.elementWeakRef = Cu.getWeakReference(element);
if (element instanceof Ci.nsIDOMHTMLSelectElement) {
// TODO: Bug 1364823 should remove the condition and handle filling
// value in <select>
return;
}
if (!testcase.profileData[field.fieldName]) {
// Avoid waiting for `change` event of a input with a blank value to
// be filled.
return;
}
onChangePromises.push(new Promise(resolve => {
element.addEventListener("change", () => {
let id = element.id;
Assert.equal(element.value, testcase.expectedResult[id],
"Check the " + id + " fields were filled with correct data");
resolve();
}, {once: true});
}));
let doc = MockDocument.createTestDocument("http://localhost:8080/test/",
testcase.document);
let form = doc.querySelector("form");
let handler = new FormAutofillHandler(form);
let promises = [];
handler.fieldDetails = testcase.fieldDetails;
handler.fieldDetails.forEach((field, index) => {
let element = doc.querySelectorAll("input, select")[index];
field.elementWeakRef = Cu.getWeakReference(element);
if (!testcase.profileData[field.fieldName]) {
// Avoid waiting for `change` event of a input with a blank value to
// be filled.
return;
}
promises.push(testFn(testcase, element));
});
handler.autofillFormFields(testcase.profileData);
Assert.equal(handler.filledProfileGUID, testcase.profileData.guid,
"Check if filledProfileGUID is set correctly");
await Promise.all(promises);
});
handler.autofillFormFields(testcase.profileData);
Assert.equal(handler.filledProfileGUID, testcase.profileData.guid,
"Check if filledProfileGUID is set correctly");
await Promise.all(onChangePromises);
});
})();
})();
}
}
do_test(TESTCASES, (testcase, element) => {
return new Promise(resolve => {
element.addEventListener("change", () => {
let id = element.id;
Assert.equal(element.value, testcase.expectedResult[id],
"Check the " + id + " field was filled with correct data");
resolve();
}, {once: true});
});
});
do_test(TESTCASES_INPUT_UNCHANGED, (testcase, element) => {
return new Promise((resolve, reject) => {
// Make sure no change or input event is fired when no change occurs.
let cleaner;
let timer = setTimeout(() => {
let id = element.id;
element.removeEventListener("change", cleaner);
element.removeEventListener("input", cleaner);
Assert.equal(element.value, testcase.expectedResult[id],
"Check no value is changed on the " + id + " field");
resolve();
}, 1000);
cleaner = event => {
clearTimeout(timer);
reject(`${event.type} event should not fire`);
};
element.addEventListener("change", cleaner);
element.addEventListener("input", cleaner);
});
});