Bug 1304634 - Support populating autocomplete results from form autofill code
MozReview-Commit-ID: BeZXuWMvcQg
--- a/browser/extensions/formautofill/bootstrap.js
+++ b/browser/extensions/formautofill/bootstrap.js
@@ -1,12 +1,20 @@
/* 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/. */
"use strict";
/* exported startup, shutdown, install, uninstall */
-function startup() {}
-function shutdown() {}
+const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
+
+Cu.import("resource://formautofill/ProfileAutocomplete.jsm");
+
+function startup() {
+ ProfileAutocomplete.ensureRegistered();
+}
+function shutdown() {
+ ProfileAutocomplete.ensureUnregistered();
+}
function install() {}
function uninstall() {}
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/content/ProfileAutocomplete.jsm
@@ -0,0 +1,116 @@
+/* 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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["ProfileAutocomplete"];
+
+const {classes: Cc, interfaces: Ci, manager: Cm, results: Cr, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+let {FormAutoCompleteResult} = Cu.import("resource://gre/modules/nsFormAutoCompleteResult.jsm", {});
+
+// Register/unregister a constructor as a factory.
+function AutocompleteFactory() {}
+AutocompleteFactory.prototype = {
+ register(targetConstructor) {
+ let proto = targetConstructor.prototype;
+ this._classID = proto.classID;
+
+ let factory = XPCOMUtils._getFactory(targetConstructor);
+ this._factory = factory;
+
+ let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.registerFactory(proto.classID, proto.classDescription,
+ proto.contractID, factory);
+
+ if (proto.classID2) {
+ this._classID2 = proto.classID2;
+ registrar.registerFactory(proto.classID2, proto.classDescription,
+ proto.contractID2, factory);
+ }
+ },
+
+ unregister() {
+ let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.unregisterFactory(this._classID, this._factory);
+ if (this._classID2) {
+ registrar.unregisterFactory(this._classID2, this._factory);
+ }
+ this._factory = null;
+ },
+};
+
+
+/**
+ * @constructor
+ *
+ * @implements {nsIAutoCompleteSearch}
+ */
+function AutofillProfileAutoCompleteSearch() {
+
+}
+AutofillProfileAutoCompleteSearch.prototype = {
+ classID: Components.ID("4f9f1e4c-7f2c-439e-9c9e-566b68bc187d"),
+ contractID: "@mozilla.org/autocomplete/search;1?name=autofill-profiles",
+ classDescription: "AutofillProfileAutoCompleteSearch",
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteSearch]),
+
+ // Begin nsIAutoCompleteSearch implementation
+
+ /**
+ * Searches for a given string and notifies a listener (either synchronously
+ * or asynchronously) of the result
+ *
+ * @param {string} searchString the string to search for
+ * @param searchParam
+ * @param previousResult a previous result to use for faster searchinig
+ * @param listener the listener to notify when the search is complete
+ */
+ startSearch(searchString, searchParam, previousResult, listener) {
+ let labels = ["Mary", "John"];
+ let values = ["Mary S.", "John S."];
+ let comments = ["123 Sesame Street.", "331 E. Evelyn Avenue"];
+ let result = new FormAutoCompleteResult(searchString,
+ Ci.nsIAutoCompleteResult.RESULT_SUCCESS,
+ 0, "", values, labels,
+ comments);
+
+ listener.onSearchResult(this, result);
+ },
+
+ /**
+ * Stops an asynchronous search that is in progress
+ */
+ stopSearch() {
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AutofillProfileAutoCompleteSearch]);
+
+let ProfileAutocomplete = {
+ _registered: false,
+ _factory: null,
+
+ ensureRegistered() {
+ if (this._registered) {
+ return;
+ }
+
+ this._factory = new AutocompleteFactory();
+ this._factory.register(AutofillProfileAutoCompleteSearch);
+ this._registered = true;
+ },
+
+ ensureUnregistered() {
+ if (!this._registered) {
+ return;
+ }
+
+ this._factory.unregister();
+ this._factory = null;
+ this._registered = false;
+ },
+};
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/jar.mn
@@ -0,0 +1,3 @@
+formautofill.jar:
+% resource formautofill %content/
+ content/ (content/*)
--- a/browser/extensions/formautofill/moz.build
+++ b/browser/extensions/formautofill/moz.build
@@ -10,9 +10,11 @@ DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['
FINAL_TARGET_FILES.features['formautofill@mozilla.org'] += [
'bootstrap.js'
]
FINAL_TARGET_PP_FILES.features['formautofill@mozilla.org'] += [
'install.rdf.in'
]
+JAR_MANIFESTS += ['jar.mn']
+
BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
--- a/toolkit/components/satchel/AutoCompletePopup.jsm
+++ b/toolkit/components/satchel/AutoCompletePopup.jsm
@@ -24,17 +24,30 @@ var AutoCompleteTreeView = {
treeBox: null,
results: [],
// nsITreeView
selection: null,
get rowCount() { return this.results.length; },
setTree: function(treeBox) { this.treeBox = treeBox; },
- getCellText: function(idx, column) { return this.results[idx].value },
+ getCellText: function(idx, column) {
+ let text;
+ switch (column.index) {
+ case 0:
+ text = this.results[idx].value;
+ break;
+ case 1:
+ text = this.results[idx].comment;
+ break;
+ default:
+ throw new Error("column out of bounds: " + column.index);
+ }
+ return text;
+ },
isContainer: function(idx) { return false; },
getCellValue: function(idx, column) { return false },
isContainerOpen: function(idx) { return false; },
isContainerEmpty: function(idx) { return false; },
isSeparator: function(idx) { return false; },
isSorted: function() { return false; },
isEditable: function(idx, column) { return false; },
canDrop: function(idx, orientation, dt) { return false; },
@@ -137,17 +150,17 @@ this.AutoCompletePopup = {
AutoCompleteTreeView.setResults(results);
this.openedPopup.view = AutoCompleteTreeView;
this.openedPopup.selectedIndex = -1;
this.openedPopup.invalidate();
if (results.length) {
// Reset fields that were set from the last time the search popup was open
this.openedPopup.mInput = null;
- this.openedPopup.showCommentColumn = false;
+ this.openedPopup.showCommentColumn = true;
this.openedPopup.showImageColumn = false;
this.openedPopup.openPopupAtScreenRect("after_start", rect.left, rect.top,
rect.width, rect.height, false,
false);
this.openedPopup.addEventListener("popuphidden", this);
} else {
this.closePopup();
}
--- a/toolkit/components/satchel/nsFormFillController.cpp
+++ b/toolkit/components/satchel/nsFormFillController.cpp
@@ -198,31 +198,32 @@ void
nsFormFillController::ParentChainChanged(nsIContent* aContent)
{
}
void
nsFormFillController::NodeWillBeDestroyed(const nsINode* aNode)
{
mPwmgrInputs.Remove(aNode);
+ mAutofillInputs.Remove(aNode);
if (aNode == mListNode) {
mListNode = nullptr;
RevalidateDataList();
} else if (aNode == mFocusedInputNode) {
mFocusedInputNode = nullptr;
mFocusedInput = nullptr;
}
}
void
nsFormFillController::MaybeRemoveMutationObserver(nsINode* aNode)
{
// Nodes being tracked in mPwmgrInputs will have their observers removed when
- // they stop being tracked.
- if (!mPwmgrInputs.Get(aNode)) {
+ // they stop being tracked.
+ if (!mPwmgrInputs.Get(aNode) && !mAutofillInputs.Get(aNode)) {
aNode->RemoveMutationObserver(this);
}
}
////////////////////////////////////////////////////////////////////////
//// nsIFormFillController
NS_IMETHODIMP
@@ -274,16 +275,31 @@ nsFormFillController::MarkAsLoginManager
node->AddMutationObserverUnlessExists(this);
if (!mLoginManager)
mLoginManager = do_GetService("@mozilla.org/login-manager;1");
return NS_OK;
}
+NS_IMETHODIMP
+nsFormFillController::MarkAsAutofillField(nsIDOMHTMLInputElement *aInput)
+{
+ /*
+ * Support other components implementing form autofill and handle autocomplete
+ * for the field.
+ */
+ nsCOMPtr<nsINode> node = do_QueryInterface(aInput);
+ NS_ENSURE_STATE(node);
+ mAutofillInputs.Put(node, true);
+ node->AddMutationObserverUnlessExists(this);
+
+ return NS_OK;
+}
+
////////////////////////////////////////////////////////////////////////
//// nsIAutoCompleteInput
NS_IMETHODIMP
nsFormFillController::GetPopup(nsIAutoCompletePopup **aPopup)
{
*aPopup = mFocusedPopup;
@@ -434,17 +450,22 @@ NS_IMETHODIMP nsFormFillController::SetS
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsFormFillController::GetShowCommentColumn(bool *aShowCommentColumn)
{
- *aShowCommentColumn = false;
+ if (mAutofillInputs.Get(mFocusedInputNode)) {
+ // TODO: check for autofill component
+ *aShowCommentColumn = true;
+ } else {
+ *aShowCommentColumn = false;
+ }
return NS_OK;
}
NS_IMETHODIMP nsFormFillController::SetShowCommentColumn(bool aShowCommentColumn)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
@@ -489,17 +510,22 @@ nsFormFillController::GetSearchCount(uin
{
*aSearchCount = 1;
return NS_OK;
}
NS_IMETHODIMP
nsFormFillController::GetSearchAt(uint32_t index, nsACString & _retval)
{
- _retval.AssignLiteral("form-history");
+ if (mAutofillInputs.Get(mFocusedInputNode)) {
+ // TODO: check for autofill component
+ _retval.AssignLiteral("autofill-profiles");
+ } else {
+ _retval.AssignLiteral("form-history");
+ }
return NS_OK;
}
NS_IMETHODIMP
nsFormFillController::GetTextValue(nsAString & aTextValue)
{
if (mFocusedInput) {
mFocusedInput->GetValue(aTextValue);
@@ -878,16 +904,28 @@ nsFormFillController::RemoveForDocument(
// mFocusedInputNode's observer is tracked separately, so don't remove it
// here.
if (key != mFocusedInputNode) {
const_cast<nsINode*>(key)->RemoveMutationObserver(this);
}
iter.Remove();
}
}
+
+ for (auto iter = mAutofillInputs.Iter(); !iter.Done(); iter.Next()) {
+ const nsINode* key = iter.Key();
+ if (key && (!aDoc || key->OwnerDoc() == aDoc)) {
+ // mFocusedInputNode's observer is tracked separately, so don't remove it
+ // here.
+ if (key != mFocusedInputNode) {
+ const_cast<nsINode*>(key)->RemoveMutationObserver(this);
+ }
+ iter.Remove();
+ }
+ }
}
void
nsFormFillController::MaybeStartControllingInput(nsIDOMHTMLInputElement* aInput)
{
nsCOMPtr<nsINode> inputNode = do_QueryInterface(aInput);
if (!inputNode)
return;
--- a/toolkit/components/satchel/nsFormFillController.h
+++ b/toolkit/components/satchel/nsFormFillController.h
@@ -105,16 +105,17 @@ protected:
// complete or the data from a datalist changes.
nsCOMPtr<nsIAutoCompleteObserver> mLastListener;
// This is cleared by StopSearch().
nsCOMPtr<nsIFormAutoComplete> mLastFormAutoComplete;
nsString mLastSearchString;
nsDataHashtable<nsPtrHashKey<const nsINode>, bool> mPwmgrInputs;
+ nsDataHashtable<nsPtrHashKey<const nsINode>, bool> mAutofillInputs;
uint32_t mTimeout;
uint32_t mMinResultsForPopup;
uint32_t mMaxRows;
bool mDisableAutoComplete;
bool mCompleteDefaultIndex;
bool mCompleteSelectedIndex;
bool mForceComplete;
--- a/toolkit/components/satchel/nsIFormFillController.idl
+++ b/toolkit/components/satchel/nsIFormFillController.idl
@@ -38,9 +38,17 @@ interface nsIFormFillController : nsISup
/*
* Mark the specified <input> element as being managed by password manager.
* Autocomplete requests will be handed off to the password manager, and will
* not be stored in form history.
*
* @param aInput - The HTML <input> element to tag
*/
void markAsLoginManagerField(in nsIDOMHTMLInputElement aInput);
+
+ /*
+ * Mark the specified <input> element as being managed by a form autofill component.
+ * Autocomplete requests will be handed off to the autofill component.
+ *
+ * @param aInput - The HTML <input> element to mark
+ */
+ void markAsAutofillField(in nsIDOMHTMLInputElement aInput);
};