Bug 1020607 - Populate pending elements with values given by requestAutocomplete UI. r=MattN

This commit is contained in:
Brian Nicholson 2014-06-25 17:22:00 +01:00
parent 100c420152
commit 27209f0b98
5 changed files with 180 additions and 22 deletions

View File

@ -27,6 +27,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "Task",
function FormHandler(aForm, aWindow) {
this.form = aForm;
this.window = aWindow;
this.fieldDetails = [];
}
FormHandler.prototype = {
@ -40,6 +42,21 @@ FormHandler.prototype = {
*/
window: null,
/**
* Array of collected data about relevant form fields. Each item is an object
* storing the identifying details of the field and a reference to the
* originally associated element from the form.
*
* The "section", "addressType", "contactType", and "fieldName" values are
* used to identify the exact field when the serializable data is received
* from the requestAutocomplete user interface. There cannot be multiple
* fields which have the same exact combination of these values.
*
* A direct reference to the associated element cannot be sent to the user
* interface because processing may be done in the parent process.
*/
fieldDetails: null,
/**
* Handles requestAutocomplete and generates the DOM events when finished.
*/
@ -49,14 +66,7 @@ FormHandler.prototype = {
// string indicates that an unexpected exception occurred.
let reason = "";
try {
let data = this.collectFormElements();
let ui = yield FormAutofill.integration.createRequestAutocompleteUI(data);
let result = yield ui.show();
// At present, we only have cancellation and success cases, since we
// don't do any validation or precondition check.
reason = result.canceled ? "cancel" : "success";
reason = yield this.promiseRequestAutocomplete();
} catch (ex) {
Cu.reportError(ex);
}
@ -72,10 +82,39 @@ FormHandler.prototype = {
}),
/**
* Collects information from the form about fields that can be autofilled, and
* returns an object that can be used to build RequestAutocompleteUI.
* Handles requestAutocomplete and returns the outcome when finished.
*
* @return {Promise}
* @resolves The "reason" value indicating the outcome of the
* requestAutocomplete operation, including "success" if the
* operation completed successfully.
*/
collectFormElements: function () {
promiseRequestAutocomplete: Task.async(function* () {
let data = this.collectFormFields();
if (!data) {
return "disabled";
}
let ui = yield FormAutofill.integration.createRequestAutocompleteUI(data);
let result = yield ui.show();
if (result.canceled) {
return "cancel";
}
this.autofillFormFields(result);
return "success";
}),
/**
* Returns information from the form about fields that can be autofilled, and
* populates the fieldDetails array on this object accordingly.
*
* @returns Serializable data structure that can be sent to the user
* interface, or null if the operation failed because the constraints
* on the allowed fields were not honored.
*/
collectFormFields: function () {
let autofillData = {
sections: [],
};
@ -92,6 +131,22 @@ FormHandler.prototype = {
continue;
}
// Store the association between the field metadata and the element.
if (this.fieldDetails.some(f => f.section == info.section &&
f.addressType == info.addressType &&
f.contactType == info.contactType &&
f.fieldName == info.fieldName)) {
// A field with the same identifier already exists.
return null;
}
this.fieldDetails.push({
section: info.section,
addressType: info.addressType,
contactType: info.contactType,
fieldName: info.fieldName,
element: element,
});
// The first level is the custom section.
let section = autofillData.sections
.find(s => s.name == info.section);
@ -125,6 +180,38 @@ FormHandler.prototype = {
return autofillData;
},
/**
* Processes form fields that can be autofilled, and populates them with the
* data provided by RequestAutocompleteUI.
*
* @param aAutofillResult
* Data returned by the user interface.
* {
* fields: [
* section: Value originally provided to the user interface.
* addressType: Value originally provided to the user interface.
* contactType: Value originally provided to the user interface.
* fieldName: Value originally provided to the user interface.
* value: String with which the field should be updated.
* ],
* }
*/
autofillFormFields: function (aAutofillResult) {
for (let field of aAutofillResult.fields) {
// Get the field details, if it was processed by the user interface.
let fieldDetail = this.fieldDetails
.find(f => f.section == field.section &&
f.addressType == field.addressType &&
f.contactType == field.contactType &&
f.fieldName == field.fieldName);
if (!fieldDetail) {
continue;
}
fieldDetail.element.value = field.value;
}
},
/**
* Waits for one tick of the event loop before resolving the returned promise.
*/

View File

@ -26,11 +26,12 @@ XPCOMUtils.defineLazyModuleGetter(this, "Task",
* Handles the requestAutocomplete user interface.
*/
this.RequestAutocompleteUI = function (aAutofillData) {
Services.console.logStringMessage("rAc UI request: " +
JSON.stringify(aAutofillData));
this._autofillData = aAutofillData;
}
this.RequestAutocompleteUI.prototype = {
_autofillData: null,
show: Task.async(function* () {
// Create a new promise and store the function that will resolve it. This
// will be called by the UI once the selection has been made.
@ -38,7 +39,10 @@ this.RequestAutocompleteUI.prototype = {
let uiPromise = new Promise(resolve => resolveFn = resolve);
// Wrap the callback function so that it survives XPCOM.
let args = { resolveFn: resolveFn };
let args = {
resolveFn: resolveFn,
autofillData: this._autofillData,
};
args.wrappedJSObject = args;
// Open the window providing the function to call when it closes.

View File

@ -20,10 +20,13 @@ XPCOMUtils.defineLazyModuleGetter(this, "Task",
const RequestAutocompleteDialog = {
resolveFn: null,
autofillData: null,
onLoad: function () {
Task.spawn(function* () {
this.resolveFn = window.arguments[0].wrappedJSObject.resolveFn;
let args = window.arguments[0].wrappedJSObject;
this.resolveFn = args.resolveFn;
this.autofillData = args.autofillData;
window.sizeToContent();
@ -33,8 +36,46 @@ const RequestAutocompleteDialog = {
},
onAccept: function () {
// TODO: Replace with autofill storage module (bug 1018304).
const dummyDB = {
"": {
"name": "Mozzy La",
"street-address": "331 E Evelyn Ave",
"address-level2": "Mountain View",
"address-level1": "CA",
"country": "US",
"postal-code": "94041",
"email": "email@example.org",
}
};
let result = { fields: [] };
for (let section of this.autofillData.sections) {
for (let addressSection of section.addressSections) {
let addressType = addressSection.addressType;
if (!(addressType in dummyDB)) {
continue;
}
for (let field of addressSection.fields) {
let fieldName = field.fieldName;
if (!(fieldName in dummyDB[addressType])) {
continue;
}
result.fields.push({
section: section.name,
addressType: addressType,
contactType: field.contactType,
fieldName: field.fieldName,
value: dummyDB[addressType][fieldName],
});
}
}
}
window.close();
this.resolveFn({ email: "email@example.org" });
this.resolveFn(result);
},
onCancel: function () {

View File

@ -13,15 +13,20 @@
*/
add_task(function* test_select_profile() {
// Request an e-mail address.
let data = { "": { "": { "email": null } } };
let { uiWindow, promiseResult } = yield FormAutofillTest.showUI(data);
let { uiWindow, promiseResult } = yield FormAutofillTest.showUI(
TestData.requestEmailOnly);
// Accept the dialog.
let acceptButton = uiWindow.document.getElementById("accept");
EventUtils.synthesizeMouseAtCenter(acceptButton, {}, uiWindow);
let result = yield promiseResult;
Assert.equal(result.email, "email@example.org");
Assert.equal(result.fields.length, 1);
Assert.equal(result.fields[0].section, "");
Assert.equal(result.fields[0].addressType, "");
Assert.equal(result.fields[0].contactType, "");
Assert.equal(result.fields[0].fieldName, "email");
Assert.equal(result.fields[0].value, "email@example.org");
});
/**
@ -29,8 +34,8 @@ add_task(function* test_select_profile() {
*/
add_task(function* test_cancel() {
// Request an e-mail address.
let data = { "": { "": { "email": null } } };
let { uiWindow, promiseResult } = yield FormAutofillTest.showUI(data);
let { uiWindow, promiseResult } = yield FormAutofillTest.showUI(
TestData.requestEmailOnly);
// Cancel the dialog.
let acceptButton = uiWindow.document.getElementById("cancel");

View File

@ -193,7 +193,8 @@ let FormAutofillTest = {
// Wait for the initialization event before opening the window.
let promiseUIWindow =
TestUtils.waitForNotification("formautofill-window-initialized");
let ui = yield FormAutofill.integration.createRequestAutocompleteUI({});
let ui = yield FormAutofill.integration.createRequestAutocompleteUI(
aFormAutofillData);
let promiseResult = ui.show();
// The window is the subject of the observer notification.
@ -204,6 +205,26 @@ let FormAutofillTest = {
}),
};
let TestData = {
/**
* Autofill UI request for the e-mail field only.
*/
get requestEmailOnly() {
return {
sections: [{
name: "",
addressSections: [{
addressType: "",
fields: [{
fieldName: "email",
contactType: "",
}],
}],
}],
};
},
};
/* --- Initialization and termination functions common to all tests --- */
add_task(function* test_common_initialize() {