Bug 1380279 - Add an ability to retrieve multiple collection type in startSearch to support credit cards autofilling. r=seanlee
MozReview-Commit-ID: EeV3W8fZylh
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -100,68 +100,73 @@ AutofillProfileAutoCompleteSearch.protot
onSearchResult: (search, result) => {
listener.onSearchResult(this, result);
ProfileAutocomplete.setProfileAutoCompleteResult(result);
},
});
return;
}
- this._getAddresses({info, searchString}).then((addresses) => {
+ let collectionName = FormAutofillUtils.isAddressField(info.fieldName) ?
+ "addresses" : "creditCards";
+
+ this._getRecords({collectionName, info, searchString}).then((records) => {
if (this.forceStop) {
return;
}
// Sort addresses by timeLastUsed for showing the lastest used address at top.
- addresses.sort((a, b) => b.timeLastUsed - a.timeLastUsed);
+ records.sort((a, b) => b.timeLastUsed - a.timeLastUsed);
let handler = FormAutofillContent.getFormHandler(focusedInput);
- let adaptedAddresses = handler.getAdaptedProfiles(addresses);
+ let adaptedRecords = handler.getAdaptedProfiles(records);
let allFieldNames = FormAutofillContent.getAllFieldNames(focusedInput);
let result = new ProfileAutoCompleteResult(searchString,
info.fieldName,
allFieldNames,
- adaptedAddresses,
+ adaptedRecords,
{});
listener.onSearchResult(this, result);
ProfileAutocomplete.setProfileAutoCompleteResult(result);
});
},
/**
* Stops an asynchronous search that is in progress
*/
stopSearch() {
ProfileAutocomplete.setProfileAutoCompleteResult(null);
this.forceStop = true;
},
/**
- * Get the address data from parent process for AutoComplete result.
+ * Get the records from parent process for AutoComplete result.
*
* @private
* @param {Object} data
* Parameters for querying the corresponding result.
+ * @param {string} data.collectionName
+ * The name used to specify which collection to retrieve records.
* @param {string} data.searchString
- * The typed string for filtering out the matched address.
+ * The typed string for filtering out the matched records.
* @param {string} data.info
* The input autocomplete property's information.
* @returns {Promise}
* Promise that resolves when addresses returned from parent process.
*/
- _getAddresses(data) {
- this.log.debug("_getAddresses with data:", data);
+ _getRecords(data) {
+ this.log.debug("_getRecords with data:", data);
return new Promise((resolve) => {
- Services.cpmm.addMessageListener("FormAutofill:Addresses", function getResult(result) {
- Services.cpmm.removeMessageListener("FormAutofill:Addresses", getResult);
+ Services.cpmm.addMessageListener("FormAutofill:Records", function getResult(result) {
+ Services.cpmm.removeMessageListener("FormAutofill:Records", getResult);
resolve(result.data);
});
- Services.cpmm.sendAsyncMessage("FormAutofill:GetAddresses", data);
+ Services.cpmm.sendAsyncMessage("FormAutofill:GetRecords", data);
});
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AutofillProfileAutoCompleteSearch]);
let ProfileAutocomplete = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -75,17 +75,17 @@ FormAutofillParent.prototype = {
_active: null,
/**
* Initializes ProfileStorage and registers the message handler.
*/
async init() {
Services.obs.addObserver(this, "advanced-pane-loaded");
Services.ppmm.addMessageListener("FormAutofill:InitStorage", this);
- Services.ppmm.addMessageListener("FormAutofill:GetAddresses", this);
+ Services.ppmm.addMessageListener("FormAutofill:GetRecords", this);
Services.ppmm.addMessageListener("FormAutofill:SaveAddress", this);
Services.ppmm.addMessageListener("FormAutofill:RemoveAddresses", this);
Services.ppmm.addMessageListener("FormAutofill:OpenPreferences", this);
Services.mm.addMessageListener("FormAutofill:OnFormSubmit", this);
// Observing the pref and storage changes
Services.prefs.addObserver(ENABLED_PREF, this);
Services.obs.addObserver(this, "formautofill-storage-changed");
@@ -176,18 +176,18 @@ FormAutofillParent.prototype = {
* @param {nsIFrameMessageManager} message.target Caller's message manager.
*/
receiveMessage({name, data, target}) {
switch (name) {
case "FormAutofill:InitStorage": {
this.profileStorage.initialize();
break;
}
- case "FormAutofill:GetAddresses": {
- this._getAddresses(data, target);
+ case "FormAutofill:GetRecords": {
+ this._getRecords(data, target);
break;
}
case "FormAutofill:SaveAddress": {
if (data.guid) {
this.profileStorage.addresses.update(data.guid, data.address);
} else {
this.profileStorage.addresses.add(data.address);
}
@@ -212,61 +212,68 @@ FormAutofillParent.prototype = {
* Uninitializes FormAutofillParent. This is for testing only.
*
* @private
*/
_uninit() {
this.profileStorage._saveImmediately();
Services.ppmm.removeMessageListener("FormAutofill:InitStorage", this);
- Services.ppmm.removeMessageListener("FormAutofill:GetAddresses", this);
+ Services.ppmm.removeMessageListener("FormAutofill:GetRecords", this);
Services.ppmm.removeMessageListener("FormAutofill:SaveAddress", this);
Services.ppmm.removeMessageListener("FormAutofill:RemoveAddresses", this);
Services.obs.removeObserver(this, "advanced-pane-loaded");
Services.prefs.removeObserver(ENABLED_PREF, this);
},
/**
- * Get the address data from profile store and return addresses back to content
+ * Get the records from profile store and return results back to content
* process.
*
* @private
+ * @param {string} data.collectionName
+ * The name used to specify which collection to retrieve records.
* @param {string} data.searchString
- * The typed string for filtering out the matched address.
+ * The typed string for filtering out the matched records.
* @param {string} data.info
* The input autocomplete property's information.
* @param {nsIFrameMessageManager} target
* Content's message manager.
*/
- _getAddresses({searchString, info}, target) {
- let addresses = [];
+ _getRecords({collectionName, searchString, info}, target) {
+ let records;
+ let collection = this.profileStorage[collectionName];
- if (info && info.fieldName) {
- addresses = this.profileStorage.addresses.getByFilter({searchString, info});
+ if (!collection) {
+ records = [];
+ } else if (info && info.fieldName) {
+ records = collection.getByFilter({searchString, info});
} else {
- addresses = this.profileStorage.addresses.getAll();
+ records = collection.getAll();
}
- target.sendAsyncMessage("FormAutofill:Addresses", addresses);
+ target.sendAsyncMessage("FormAutofill:Records", records);
},
_updateSavedFieldNames() {
log.debug("_updateSavedFieldNames");
if (!Services.ppmm.initialProcessData.autofillSavedFieldNames) {
Services.ppmm.initialProcessData.autofillSavedFieldNames = new Set();
} else {
Services.ppmm.initialProcessData.autofillSavedFieldNames.clear();
}
- this.profileStorage.addresses.getAll().forEach((address) => {
- Object.keys(address).forEach((fieldName) => {
- if (!address[fieldName]) {
- return;
- }
- Services.ppmm.initialProcessData.autofillSavedFieldNames.add(fieldName);
+ ["addresses", "creditCards"].forEach(c => {
+ this.profileStorage[c].getAll().forEach((record) => {
+ Object.keys(record).forEach((fieldName) => {
+ if (!record[fieldName]) {
+ return;
+ }
+ Services.ppmm.initialProcessData.autofillSavedFieldNames.add(fieldName);
+ });
});
});
// Remove the internal guid and metadata fields.
this.profileStorage.INTERNAL_FIELDS.forEach((fieldName) => {
Services.ppmm.initialProcessData.autofillSavedFieldNames.delete(fieldName);
});
--- a/browser/extensions/formautofill/content/manageProfiles.js
+++ b/browser/extensions/formautofill/content/manageProfiles.js
@@ -58,37 +58,47 @@ ManageProfileDialog.prototype = {
},
/**
* Load addresses and render them.
*
* @returns {promise}
*/
loadAddresses() {
- return this.getAddresses().then(addresses => {
+ return this.getRecords({collectionName: "addresses"}).then(addresses => {
log.debug("addresses:", addresses);
// Sort by last modified time starting with most recent
addresses.sort((a, b) => b.timeLastModified - a.timeLastModified);
this.renderAddressElements(addresses);
this.updateButtonsStates(this._selectedOptions.length);
});
},
/**
- * Get addresses from storage.
+ * Get records from storage.
*
- * @returns {promise}
+ * @private
+ * @param {Object} data
+ * Parameters for querying the corresponding result.
+ * @param {string} data.collectionName
+ * The name used to specify which collection to retrieve records.
+ * @param {string} data.searchString
+ * The typed string for filtering out the matched records.
+ * @param {string} data.info
+ * The input autocomplete property's information.
+ * @returns {Promise}
+ * Promise that resolves when addresses returned from parent process.
*/
- getAddresses() {
+ getRecords(data) {
return new Promise(resolve => {
- Services.cpmm.addMessageListener("FormAutofill:Addresses", function getResult(result) {
- Services.cpmm.removeMessageListener("FormAutofill:Addresses", getResult);
+ Services.cpmm.addMessageListener("FormAutofill:Records", function getResult(result) {
+ Services.cpmm.removeMessageListener("FormAutofill:Records", getResult);
resolve(result.data);
});
- Services.cpmm.sendAsyncMessage("FormAutofill:GetAddresses", {});
+ Services.cpmm.sendAsyncMessage("FormAutofill:GetRecords", data);
});
},
/**
* Render the addresses onto the page while maintaining selected options if
* they still exist.
*
* @param {array<object>} addresses
--- a/browser/extensions/formautofill/test/browser/browser_manageProfilesDialog.js
+++ b/browser/extensions/formautofill/test/browser/browser_manageProfilesDialog.js
@@ -4,20 +4,20 @@ const TEST_SELECTORS = {
selAddresses: "#profiles",
btnRemove: "#remove",
btnAdd: "#add",
btnEdit: "#edit",
};
const DIALOG_SIZE = "width=600,height=400";
-function waitForAddresses() {
+function waitForRecords() {
return new Promise(resolve => {
- Services.cpmm.addMessageListener("FormAutofill:Addresses", function getResult(result) {
- Services.cpmm.removeMessageListener("FormAutofill:Addresses", getResult);
+ Services.cpmm.addMessageListener("FormAutofill:Records", function getResult(result) {
+ Services.cpmm.removeMessageListener("FormAutofill:Records", getResult);
// Wait for the next tick for elements to get rendered.
SimpleTest.executeSoon(resolve.bind(null, result.data));
});
});
}
add_task(async function test_manageProfilesInitialState() {
await BrowserTestUtils.withNewTab({gBrowser, url: MANAGE_PROFILES_DIALOG_URL}, async function(browser) {
@@ -49,50 +49,50 @@ add_task(async function test_cancelManag
});
add_task(async function test_removingSingleAndMultipleProfiles() {
await saveAddress(TEST_ADDRESS_1);
await saveAddress(TEST_ADDRESS_2);
await saveAddress(TEST_ADDRESS_3);
let win = window.openDialog(MANAGE_PROFILES_DIALOG_URL, null, DIALOG_SIZE);
- await waitForAddresses();
+ await waitForRecords();
let selAddresses = win.document.querySelector(TEST_SELECTORS.selAddresses);
let btnRemove = win.document.querySelector(TEST_SELECTORS.btnRemove);
let btnEdit = win.document.querySelector(TEST_SELECTORS.btnEdit);
is(selAddresses.length, 3, "Three addresses");
EventUtils.synthesizeMouseAtCenter(selAddresses.children[0], {}, win);
is(btnRemove.disabled, false, "Remove button enabled");
is(btnEdit.disabled, false, "Edit button enabled");
EventUtils.synthesizeMouseAtCenter(btnRemove, {}, win);
- await waitForAddresses();
+ await waitForRecords();
is(selAddresses.length, 2, "Two addresses left");
EventUtils.synthesizeMouseAtCenter(selAddresses.children[0], {}, win);
EventUtils.synthesizeMouseAtCenter(selAddresses.children[1],
{shiftKey: true}, win);
is(btnEdit.disabled, true, "Edit button disabled when multi-select");
EventUtils.synthesizeMouseAtCenter(btnRemove, {}, win);
- await waitForAddresses();
+ await waitForRecords();
is(selAddresses.length, 0, "All addresses are removed");
win.close();
});
add_task(async function test_profilesDialogWatchesStorageChanges() {
let win = window.openDialog(MANAGE_PROFILES_DIALOG_URL, null, DIALOG_SIZE);
- await waitForAddresses();
+ await waitForRecords();
let selAddresses = win.document.querySelector(TEST_SELECTORS.selAddresses);
await saveAddress(TEST_ADDRESS_1);
- let addresses = await waitForAddresses();
+ let addresses = await waitForRecords();
is(selAddresses.length, 1, "One address is shown");
await removeAddresses([addresses[0].guid]);
- await waitForAddresses();
+ await waitForRecords();
is(selAddresses.length, 0, "Address is removed");
win.close();
});
--- a/browser/extensions/formautofill/test/browser/head.js
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -59,26 +59,30 @@ async function openPopupOn(browser, sele
await ContentTask.spawn(browser, {selector}, async function({selector}) {
content.document.querySelector(selector).focus();
});
await sleep(2000);
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
await expectPopupOpen(browser);
}
-function getAddresses() {
+function getRecords(data) {
return new Promise(resolve => {
- Services.cpmm.addMessageListener("FormAutofill:Addresses", function getResult(result) {
- Services.cpmm.removeMessageListener("FormAutofill:Addresses", getResult);
+ Services.cpmm.addMessageListener("FormAutofill:Records", function getResult(result) {
+ Services.cpmm.removeMessageListener("FormAutofill:Records", getResult);
resolve(result.data);
});
- Services.cpmm.sendAsyncMessage("FormAutofill:GetAddresses", {});
+ Services.cpmm.sendAsyncMessage("FormAutofill:GetRecords", data);
});
}
+function getAddresses() {
+ return getRecords({collectionName: "addresses"});
+}
+
function saveAddress(address) {
Services.cpmm.sendAsyncMessage("FormAutofill:SaveAddress", {address});
return TestUtils.topicObserved("formautofill-storage-changed");
}
function removeAddresses(guids) {
Services.cpmm.sendAsyncMessage("FormAutofill:RemoveAddresses", {guids});
return TestUtils.topicObserved("formautofill-storage-changed");
--- a/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
@@ -6,25 +6,25 @@
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
let {profileStorage} = Cu.import("resource://formautofill/ProfileStorage.jsm", {});
var ParentUtils = {
cleanUpAddress() {
- Services.cpmm.addMessageListener("FormAutofill:Addresses", function getResult(result) {
- Services.cpmm.removeMessageListener("FormAutofill:Addresses", getResult);
+ Services.cpmm.addMessageListener("FormAutofill:Records", function getResult(result) {
+ Services.cpmm.removeMessageListener("FormAutofill:Records", getResult);
let addresses = result.data;
Services.cpmm.sendAsyncMessage("FormAutofill:RemoveAddresses",
{guids: addresses.map(address => address.guid)});
});
- Services.cpmm.sendAsyncMessage("FormAutofill:GetAddresses", {searchString: ""});
+ Services.cpmm.sendAsyncMessage("FormAutofill:GetRecords", {searchString: "", collectionName: "addresses"});
},
updateAddress(type, chromeMsg, msgData, contentMsg) {
Services.cpmm.sendAsyncMessage(chromeMsg, msgData);
Services.obs.addObserver(function observer(subject, topic, data) {
if (data != type) {
return;
}
@@ -55,18 +55,18 @@ var ParentUtils = {
if (field in addressA && field in addressB && (addressA[field] !== addressB[field])) {
return false;
}
}
return true;
},
checkAddresses({expectedAddresses}) {
- Services.cpmm.addMessageListener("FormAutofill:Addresses", function getResult(result) {
- Services.cpmm.removeMessageListener("FormAutofill:Addresses", getResult);
+ Services.cpmm.addMessageListener("FormAutofill:Records", function getResult(result) {
+ Services.cpmm.removeMessageListener("FormAutofill:Records", getResult);
let addresses = result.data;
if (addresses.length !== expectedAddresses.length) {
sendAsyncMessage("FormAutofillTest:areAddressesMatching", false);
return;
}
for (let address of addresses) {
let matching = expectedAddresses.some((expectedAddress) => {
@@ -77,17 +77,17 @@ var ParentUtils = {
sendAsyncMessage("FormAutofillTest:areAddressesMatching", false);
return;
}
}
sendAsyncMessage("FormAutofillTest:areAddressesMatching", true);
});
- Services.cpmm.sendAsyncMessage("FormAutofill:GetAddresses", {searchString: ""});
+ Services.cpmm.sendAsyncMessage("FormAutofill:GetRecords", {searchString: "", collectionName: "addresses"});
},
};
Services.obs.addObserver(ParentUtils, "formautofill-storage-changed");
addMessageListener("FormAutofillTest:AddAddress", (msg) => {
ParentUtils.updateAddress("add", "FormAutofill:SaveAddress", msg, "FormAutofillTest:AddressAdded");
});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/test_getRecords.js
@@ -0,0 +1,50 @@
+/*
+ * Test for make sure getRecords can retrieve right collection from storag.
+ */
+
+"use strict";
+
+Cu.import("resource://formautofill/FormAutofillParent.jsm");
+Cu.import("resource://formautofill/ProfileStorage.jsm");
+
+add_task(async function test_getRecords() {
+ let formAutofillParent = new FormAutofillParent();
+
+ await formAutofillParent.init();
+ await formAutofillParent.profileStorage.initialize();
+
+ let fakeResult = {
+ addresses: [{
+ "given-name": "Timothy",
+ "additional-name": "John",
+ "family-name": "Berners-Lee",
+ "organization": "World Wide Web Consortium",
+ }],
+ creditCards: [{
+ "cc-name": "John Doe",
+ "cc-number": "1234567812345678",
+ "cc-exp-month": 4,
+ "cc-exp-year": 2017,
+ }],
+ };
+
+ ["addresses", "creditCards", "nonExisting"].forEach(collectionName => {
+ let collection = profileStorage[collectionName];
+ let expectedResult = fakeResult[collectionName] || [];
+ let target = {
+ sendAsyncMessage: function sendAsyncMessage(msg, payload) {},
+ };
+ let mock = sinon.mock(target);
+ mock.expects("sendAsyncMessage").once().withExactArgs("FormAutofill:Records", expectedResult);
+
+ if (collection) {
+ sinon.stub(collection, "getAll");
+ collection.getAll.returns(expectedResult);
+ }
+ formAutofillParent._getRecords({collectionName}, target);
+ mock.verify();
+ if (collection) {
+ do_check_eq(collection.getAll.called, true);
+ }
+ });
+});
--- a/browser/extensions/formautofill/test/unit/xpcshell.ini
+++ b/browser/extensions/formautofill/test/unit/xpcshell.ini
@@ -22,16 +22,17 @@ support-files =
[test_collectFormFields.js]
[test_creditCardRecords.js]
[test_extractLabelStrings.js]
[test_findLabelElements.js]
[test_getAdaptedProfiles.js]
[test_getCategoriesFromFieldNames.js]
[test_getFormInputDetails.js]
[test_getInfo.js]
+[test_getRecords.js]
[test_isCJKName.js]
[test_isFieldEligibleForAutofill.js]
[test_markAsAutofillField.js]
[test_migrateRecords.js]
[test_nameUtils.js]
[test_onFormSubmitted.js]
[test_profileAutocompleteResult.js]
[test_phoneNumber.js]