Bug 1928604 - P3. Make FormFillController to support textarea r=NeilDeakin,credential-management-reviewers,edgar

Depends on D240335

Differential Revision: https://phabricator.services.mozilla.com/D240337
This commit is contained in:
Dimi
2025-03-25 08:17:48 +00:00
parent 3f96ddac0b
commit f4df2a801c
11 changed files with 274 additions and 134 deletions

View File

@@ -58,7 +58,7 @@ add_task(
"@mozilla.org/satchel/form-fill-controller;1" "@mozilla.org/satchel/form-fill-controller;1"
].getService(Ci.nsIFormFillController); ].getService(Ci.nsIFormFillController);
Assert.equal( Assert.equal(
formFillController.focusedInput?.id, formFillController.focusedElement?.id,
"address-level1", "address-level1",
"formFillController has correct focusedInput" "formFillController has correct focusedInput"
); );

View File

@@ -1308,15 +1308,20 @@ nsresult nsContentUtils::Atob(const nsAString& aAsciiBase64String,
return rv; return rv;
} }
bool nsContentUtils::IsAutocompleteEnabled( bool nsContentUtils::IsAutocompleteEnabled(mozilla::dom::Element* aElement) {
mozilla::dom::HTMLInputElement* aInput) { MOZ_ASSERT(aElement, "aElement should not be null!");
MOZ_ASSERT(aInput, "aInput should not be null!");
nsAutoString autocomplete; nsAutoString autocomplete;
aInput->GetAutocomplete(autocomplete);
if (auto* input = HTMLInputElement::FromNodeOrNull(aElement)) {
input->GetAutocomplete(autocomplete);
} else if (auto* textarea = HTMLTextAreaElement::FromNodeOrNull(aElement)) {
textarea->GetAutocomplete(autocomplete);
}
if (autocomplete.IsEmpty()) { if (autocomplete.IsEmpty()) {
auto* form = aInput->GetForm(); auto* control = nsGenericHTMLFormControlElement::FromNode(aElement);
auto* form = control->GetForm();
if (!form) { if (!form) {
return true; return true;
} }

View File

@@ -2652,10 +2652,11 @@ class nsContentUtils {
* NOTE: the caller has to make sure autocomplete makes sense for the * NOTE: the caller has to make sure autocomplete makes sense for the
* element's type. * element's type.
* *
* @param aInput the input element to check. NOTE: aInput can't be null. * @param aElement the input or textarea element to check. NOTE: aElement
* can't be null.
* @return whether the input element has autocomplete enabled. * @return whether the input element has autocomplete enabled.
*/ */
static bool IsAutocompleteEnabled(mozilla::dom::HTMLInputElement* aInput); static bool IsAutocompleteEnabled(mozilla::dom::Element* aElement);
enum AutocompleteAttrState : uint8_t { enum AutocompleteAttrState : uint8_t {
eAutocompleteAttrState_Unknown = 1, eAutocompleteAttrState_Unknown = 1,

View File

@@ -1131,7 +1131,8 @@ JSObject* HTMLTextAreaElement::WrapNode(JSContext* aCx,
return HTMLTextAreaElement_Binding::Wrap(aCx, this, aGivenProto); return HTMLTextAreaElement_Binding::Wrap(aCx, this, aGivenProto);
} }
void HTMLTextAreaElement::GetAutocomplete(DOMString& aValue) { void HTMLTextAreaElement::GetAutocomplete(nsAString& aValue) {
aValue.Truncate();
const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete); const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
mAutocompleteAttrState = nsContentUtils::SerializeAutocompleteAttribute( mAutocompleteAttrState = nsContentUtils::SerializeAutocompleteAttribute(

View File

@@ -167,7 +167,7 @@ class HTMLTextAreaElement final : public TextControlElement,
ValidityStateType aType) override; ValidityStateType aType) override;
// Web IDL binding methods // Web IDL binding methods
void GetAutocomplete(DOMString& aValue); void GetAutocomplete(nsAString& aValue);
void SetAutocomplete(const nsAString& aValue, ErrorResult& aRv) { void SetAutocomplete(const nsAString& aValue, ErrorResult& aRv) {
SetHTMLAttr(nsGkAtoms::autocomplete, aValue, aRv); SetHTMLAttr(nsGkAtoms::autocomplete, aValue, aRv);
} }

View File

@@ -138,7 +138,7 @@ export class FormAutofillChild extends JSWindowActorChild {
this.#markAsAutofillField(fieldDetail); this.#markAsAutofillField(fieldDetail);
if ( if (
fieldDetail.element == lazy.FormAutofillContent.focusedInput && fieldDetail.element == lazy.FormAutofillContent.focusedElement &&
!isUpdate !isUpdate
) { ) {
this.showPopupIfEmpty(fieldDetail.element, fieldDetail.fieldName); this.showPopupIfEmpty(fieldDetail.element, fieldDetail.fieldName);
@@ -484,7 +484,7 @@ export class FormAutofillChild extends JSWindowActorChild {
this._hasDOMContentLoadedHandler = true; this._hasDOMContentLoadedHandler = true;
doc.addEventListener( doc.addEventListener(
"DOMContentLoaded", "DOMContentLoaded",
() => this.onFocusIn(lazy.FormAutofillContent.focusedInput), () => this.onFocusIn(lazy.FormAutofillContent.focusedElement),
{ once: true } { once: true }
); );
} }

View File

@@ -30,8 +30,8 @@ export var FormAutofillContent = {
return Services.cpmm.sharedData.get("FormAutofill:savedFieldNames"); return Services.cpmm.sharedData.get("FormAutofill:savedFieldNames");
}, },
get focusedInput() { get focusedElement() {
return formFillController.focusedInput; return formFillController.focusedElement;
}, },
/** /**

View File

@@ -1401,19 +1401,19 @@ export class LoginManagerChild extends JSWindowActorChild {
break; break;
} }
case "PasswordManager:OnFieldAutoComplete": { case "PasswordManager:OnFieldAutoComplete": {
const { focusedInput } = lazy.gFormFillService; const { focusedElement } = lazy.gFormFillService;
const login = lazy.LoginHelper.vanillaObjectToLogin(msg.data); const login = lazy.LoginHelper.vanillaObjectToLogin(msg.data);
this.onFieldAutoComplete(focusedInput, login); this.onFieldAutoComplete(focusedElement, login);
break; break;
} }
case "PasswordManager:FillGeneratedPassword": { case "PasswordManager:FillGeneratedPassword": {
const { focusedInput } = lazy.gFormFillService; const { focusedElement } = lazy.gFormFillService;
this.filledWithGeneratedPassword(focusedInput); this.filledWithGeneratedPassword(focusedElement);
break; break;
} }
case "PasswordManager:FillRelayUsername": { case "PasswordManager:FillRelayUsername": {
const { focusedInput } = lazy.gFormFillService; const { focusedElement } = lazy.gFormFillService;
this.fillRelayUsername(focusedInput, msg.data); this.fillRelayUsername(focusedElement, msg.data);
break; break;
} }
} }
@@ -1428,7 +1428,7 @@ export class LoginManagerChild extends JSWindowActorChild {
return; return;
} }
if (inputElement != lazy.gFormFillService.focusedInput) { if (inputElement != lazy.gFormFillService.focusedElement) {
lazy.log("Could not open popup on input that's no longer focused."); lazy.log("Could not open popup on input that's no longer focused.");
return; return;
} }
@@ -3041,7 +3041,7 @@ export class LoginManagerChild extends JSWindowActorChild {
Glean.pwmgr.formAutofillResult[autofillResult].add(1); Glean.pwmgr.formAutofillResult[autofillResult].add(1);
if (usernameField) { if (usernameField) {
let focusedElement = lazy.gFormFillService.focusedInput; let focusedElement = lazy.gFormFillService.focusedElement;
if ( if (
usernameField == focusedElement && usernameField == focusedElement &&
![ ![

View File

@@ -9,11 +9,14 @@
#include "mozilla/ClearOnShutdown.h" #include "mozilla/ClearOnShutdown.h"
#include "mozilla/ErrorResult.h" #include "mozilla/ErrorResult.h"
#include "mozilla/EventListenerManager.h" #include "mozilla/EventListenerManager.h"
#include "mozilla/TextControlElement.h"
#include "mozilla/dom/Document.h" #include "mozilla/dom/Document.h"
#include "mozilla/dom/Element.h" #include "mozilla/dom/Element.h"
#include "mozilla/dom/Event.h" // for Event #include "mozilla/dom/Event.h" // for Event
#include "mozilla/dom/HTMLDataListElement.h" #include "mozilla/dom/HTMLDataListElement.h"
#include "mozilla/dom/HTMLInputElement.h" #include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/HTMLTextAreaElement.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/KeyboardEvent.h" #include "mozilla/dom/KeyboardEvent.h"
#include "mozilla/dom/KeyboardEventBinding.h" #include "mozilla/dom/KeyboardEventBinding.h"
#include "mozilla/dom/MouseEvent.h" #include "mozilla/dom/MouseEvent.h"
@@ -64,7 +67,7 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFormFillController)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFormFillController) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFormFillController)
nsFormFillController::nsFormFillController() nsFormFillController::nsFormFillController()
: mFocusedInput(nullptr), : mFocusedElement(nullptr),
mRestartAfterAttributeChangeTask(nullptr), mRestartAfterAttributeChangeTask(nullptr),
mListNode(nullptr), mListNode(nullptr),
// The amount of time a context menu event supresses showing a // The amount of time a context menu event supresses showing a
@@ -96,9 +99,9 @@ nsFormFillController::~nsFormFillController() {
mListNode->RemoveMutationObserver(this); mListNode->RemoveMutationObserver(this);
mListNode = nullptr; mListNode = nullptr;
} }
if (mFocusedInput) { if (mFocusedElement) {
MaybeRemoveMutationObserver(mFocusedInput); MaybeRemoveMutationObserver(mFocusedElement);
mFocusedInput = nullptr; mFocusedElement = nullptr;
} }
RemoveForDocument(nullptr); RemoveForDocument(nullptr);
} }
@@ -126,7 +129,7 @@ void nsFormFillController::AttributeChanged(mozilla::dom::Element* aElement,
if ((aAttribute == nsGkAtoms::type || aAttribute == nsGkAtoms::readonly || if ((aAttribute == nsGkAtoms::type || aAttribute == nsGkAtoms::readonly ||
aAttribute == nsGkAtoms::autocomplete) && aAttribute == nsGkAtoms::autocomplete) &&
aNameSpaceID == kNameSpaceID_None) { aNameSpaceID == kNameSpaceID_None) {
RefPtr<HTMLInputElement> focusedInput(mFocusedInput); RefPtr<Element> focusedElement(mFocusedElement);
// Reset the current state of the controller, unconditionally. // Reset the current state of the controller, unconditionally.
StopControllingInput(); StopControllingInput();
// Then restart based on the new values. We have to delay this // Then restart based on the new values. We have to delay this
@@ -136,10 +139,10 @@ void nsFormFillController::AttributeChanged(mozilla::dom::Element* aElement,
// attribute change, cancel it. // attribute change, cancel it.
MaybeCancelAttributeChangeTask(); MaybeCancelAttributeChangeTask();
mRestartAfterAttributeChangeTask = mRestartAfterAttributeChangeTask =
mozilla::NewCancelableRunnableMethod<RefPtr<HTMLInputElement>>( mozilla::NewCancelableRunnableMethod<RefPtr<Element>>(
"nsFormFillController::MaybeStartControllingInput", this, "nsFormFillController::MaybeStartControllingInput", this,
&nsFormFillController::MaybeStartControllingInputScheduled, &nsFormFillController::MaybeStartControllingInputScheduled,
focusedInput); focusedElement);
RefPtr<Runnable> addrefedRunnable = mRestartAfterAttributeChangeTask; RefPtr<Runnable> addrefedRunnable = mRestartAfterAttributeChangeTask;
aElement->OwnerDoc()->Dispatch(addrefedRunnable.forget()); aElement->OwnerDoc()->Dispatch(addrefedRunnable.forget());
} }
@@ -151,9 +154,9 @@ void nsFormFillController::AttributeChanged(mozilla::dom::Element* aElement,
MOZ_CAN_RUN_SCRIPT_BOUNDARY MOZ_CAN_RUN_SCRIPT_BOUNDARY
void nsFormFillController::MaybeStartControllingInputScheduled( void nsFormFillController::MaybeStartControllingInputScheduled(
HTMLInputElement* aInput) { Element* aElement) {
mRestartAfterAttributeChangeTask = nullptr; mRestartAfterAttributeChangeTask = nullptr;
MaybeStartControllingInput(aInput); MaybeStartControllingInput(aElement);
} }
MOZ_CAN_RUN_SCRIPT_BOUNDARY MOZ_CAN_RUN_SCRIPT_BOUNDARY
@@ -215,8 +218,8 @@ void nsFormFillController::NodeWillBeDestroyed(nsINode* aNode) {
if (aNode == mListNode) { if (aNode == mListNode) {
mListNode = nullptr; mListNode = nullptr;
RevalidateDataList(); RevalidateDataList();
} else if (aNode == mFocusedInput) { } else if (aNode == mFocusedElement) {
mFocusedInput = nullptr; mFocusedElement = nullptr;
} }
} }
@@ -232,28 +235,32 @@ void nsFormFillController::MaybeRemoveMutationObserver(nsINode* aNode) {
//// nsIFormFillController //// nsIFormFillController
NS_IMETHODIMP NS_IMETHODIMP
nsFormFillController::MarkAsAutoCompletableField(HTMLInputElement* aInput) { nsFormFillController::MarkAsAutoCompletableField(Element* aElement) {
/* /*
* Support other components implementing form autofill and handle autocomplete * Support other components implementing form autofill and handle autocomplete
* for the field. * for the field.
*/ */
NS_ENSURE_STATE(aInput); NS_ENSURE_STATE(aElement);
if (!aElement->IsAnyOfHTMLElements(nsGkAtoms::input, nsGkAtoms::textarea)) {
return NS_ERROR_UNEXPECTED;
}
MOZ_LOG(sLogger, LogLevel::Verbose, MOZ_LOG(sLogger, LogLevel::Verbose,
("MarkAsAutoCompletableField: aInput = %p", aInput)); ("MarkAsAutoCompletableField: aElement = %p", aElement));
if (mAutoCompleteInputs.Get(aInput)) { if (mAutoCompleteInputs.Get(aElement)) {
return NS_OK; return NS_OK;
} }
mAutoCompleteInputs.InsertOrUpdate(aInput, true); mAutoCompleteInputs.InsertOrUpdate(aElement, true);
aInput->AddMutationObserverUnlessExists(this); aElement->AddMutationObserverUnlessExists(this);
aInput->EnablePreview(); EnablePreview(aElement);
if (nsFocusManager::GetFocusedElementStatic() == aInput) { if (nsFocusManager::GetFocusedElementStatic() == aElement) {
if (!mFocusedInput) { if (!mFocusedElement) {
MaybeStartControllingInput(aInput); MaybeStartControllingInput(aElement);
} else { } else {
// See `MarkAsLoginManagerField` for why this is needed. // See `MarkAsLoginManagerField` for why this is needed.
nsCOMPtr<nsIAutoCompleteController> controller = mController; nsCOMPtr<nsIAutoCompleteController> controller = mController;
@@ -265,9 +272,9 @@ nsFormFillController::MarkAsAutoCompletableField(HTMLInputElement* aInput) {
} }
NS_IMETHODIMP NS_IMETHODIMP
nsFormFillController::GetFocusedInput(HTMLInputElement** aInput) { nsFormFillController::GetFocusedElement(Element** aElement) {
*aInput = mFocusedInput; *aElement = mFocusedElement;
NS_IF_ADDREF(*aInput); NS_IF_ADDREF(*aElement);
return NS_OK; return NS_OK;
} }
@@ -308,9 +315,9 @@ nsFormFillController::SetPopupOpen(bool aPopupOpen) {
if (mFocusedPopup) { if (mFocusedPopup) {
if (aPopupOpen) { if (aPopupOpen) {
// make sure input field is visible before showing popup (bug 320938) // make sure input field is visible before showing popup (bug 320938)
nsCOMPtr<nsIContent> content = mFocusedInput; nsCOMPtr<nsIContent> content = mFocusedElement;
NS_ENSURE_STATE(content); NS_ENSURE_STATE(content);
nsCOMPtr<nsIDocShell> docShell = GetDocShellForInput(mFocusedInput); nsCOMPtr<nsIDocShell> docShell = GetDocShellForInput(mFocusedElement);
NS_ENSURE_STATE(docShell); NS_ENSURE_STATE(docShell);
RefPtr<PresShell> presShell = docShell->GetPresShell(); RefPtr<PresShell> presShell = docShell->GetPresShell();
NS_ENSURE_STATE(presShell); NS_ENSURE_STATE(presShell);
@@ -322,7 +329,7 @@ nsFormFillController::SetPopupOpen(bool aPopupOpen) {
// mFocusedPopup can be destroyed after ScrollContentIntoView, see bug // mFocusedPopup can be destroyed after ScrollContentIntoView, see bug
// 420089 // 420089
if (mFocusedPopup) { if (mFocusedPopup) {
mFocusedPopup->OpenAutocompletePopup(this, mFocusedInput); mFocusedPopup->OpenAutocompletePopup(this, mFocusedElement);
} }
} else { } else {
mFocusedPopup->ClosePopup(); mFocusedPopup->ClosePopup();
@@ -422,16 +429,17 @@ nsFormFillController::SetSearchParam(const nsAString& aSearchParam) {
NS_IMETHODIMP NS_IMETHODIMP
nsFormFillController::GetSearchParam(nsAString& aSearchParam) { nsFormFillController::GetSearchParam(nsAString& aSearchParam) {
if (!mFocusedInput) { if (!mFocusedElement) {
NS_WARNING( NS_WARNING(
"mFocusedInput is null for some reason! avoiding a crash. should find " "mFocusedElement is null for some reason! avoiding a crash. should "
"find "
"out why... - ben"); "out why... - ben");
return NS_ERROR_FAILURE; // XXX why? fix me. return NS_ERROR_FAILURE; // XXX why? fix me.
} }
mFocusedInput->GetName(aSearchParam); GetName(mFocusedElement, aSearchParam);
if (aSearchParam.IsEmpty()) { if (aSearchParam.IsEmpty()) {
mFocusedInput->GetId(aSearchParam); mFocusedElement->GetId(aSearchParam);
} }
return NS_OK; return NS_OK;
@@ -459,8 +467,8 @@ nsFormFillController::GetSearchAt(uint32_t index, nsACString& _retval) {
NS_IMETHODIMP NS_IMETHODIMP
nsFormFillController::GetTextValue(nsAString& aTextValue) { nsFormFillController::GetTextValue(nsAString& aTextValue) {
if (mFocusedInput) { if (mFocusedElement) {
mFocusedInput->GetValue(aTextValue, CallerType::System); GetValue(mFocusedElement, aTextValue);
} else { } else {
aTextValue.Truncate(); aTextValue.Truncate();
} }
@@ -469,10 +477,10 @@ nsFormFillController::GetTextValue(nsAString& aTextValue) {
NS_IMETHODIMP NS_IMETHODIMP
nsFormFillController::SetTextValue(const nsAString& aTextValue) { nsFormFillController::SetTextValue(const nsAString& aTextValue) {
if (mFocusedInput) { if (mFocusedElement) {
mSuppressOnInput = true; mSuppressOnInput = true;
mFocusedInput->SetUserInput(aTextValue, SetUserInput(mFocusedElement, aTextValue,
*nsContentUtils::GetSystemPrincipal()); *nsContentUtils::GetSystemPrincipal());
mSuppressOnInput = false; mSuppressOnInput = false;
} }
@@ -481,33 +489,32 @@ nsFormFillController::SetTextValue(const nsAString& aTextValue) {
NS_IMETHODIMP NS_IMETHODIMP
nsFormFillController::GetSelectionStart(int32_t* aSelectionStart) { nsFormFillController::GetSelectionStart(int32_t* aSelectionStart) {
if (!mFocusedInput) { if (!mFocusedElement) {
return NS_ERROR_UNEXPECTED; return NS_ERROR_UNEXPECTED;
} }
ErrorResult rv; ErrorResult rv;
*aSelectionStart = mFocusedInput->GetSelectionStartIgnoringType(rv); *aSelectionStart = GetSelectionStartInternal(mFocusedElement, rv);
return rv.StealNSResult(); return rv.StealNSResult();
} }
NS_IMETHODIMP NS_IMETHODIMP
nsFormFillController::GetSelectionEnd(int32_t* aSelectionEnd) { nsFormFillController::GetSelectionEnd(int32_t* aSelectionEnd) {
if (!mFocusedInput) { if (!mFocusedElement) {
return NS_ERROR_UNEXPECTED; return NS_ERROR_UNEXPECTED;
} }
ErrorResult rv; ErrorResult rv;
*aSelectionEnd = mFocusedInput->GetSelectionEndIgnoringType(rv); *aSelectionEnd = GetSelectionEndInternal(mFocusedElement, rv);
return rv.StealNSResult(); return rv.StealNSResult();
} }
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
nsFormFillController::SelectTextRange(int32_t aStartIndex, int32_t aEndIndex) { nsFormFillController::SelectTextRange(int32_t aStartIndex, int32_t aEndIndex) {
if (!mFocusedInput) { if (!mFocusedElement) {
return NS_ERROR_UNEXPECTED; return NS_ERROR_UNEXPECTED;
} }
RefPtr<HTMLInputElement> focusedInput(mFocusedInput); RefPtr<Element> focusedInput(mFocusedElement);
ErrorResult rv; ErrorResult rv;
focusedInput->SetSelectionRange(aStartIndex, aEndIndex, Optional<nsAString>(), SetSelectionRange(focusedInput, aStartIndex, aEndIndex, rv);
rv);
return rv.StealNSResult(); return rv.StealNSResult();
} }
@@ -519,7 +526,7 @@ nsFormFillController::OnSearchComplete() { return NS_OK; }
NS_IMETHODIMP NS_IMETHODIMP
nsFormFillController::OnTextEntered(Event* aEvent) { nsFormFillController::OnTextEntered(Event* aEvent) {
NS_ENSURE_TRUE(mFocusedInput, NS_OK); NS_ENSURE_TRUE(mFocusedElement, NS_OK);
return NS_OK; return NS_OK;
} }
@@ -537,12 +544,12 @@ nsFormFillController::GetConsumeRollupEvent(bool* aConsumeRollupEvent) {
NS_IMETHODIMP NS_IMETHODIMP
nsFormFillController::GetInPrivateContext(bool* aInPrivateContext) { nsFormFillController::GetInPrivateContext(bool* aInPrivateContext) {
if (!mFocusedInput) { if (!mFocusedElement) {
*aInPrivateContext = false; *aInPrivateContext = false;
return NS_OK; return NS_OK;
} }
RefPtr<Document> doc = mFocusedInput->OwnerDoc(); RefPtr<Document> doc = mFocusedElement->OwnerDoc();
nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext(); nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
*aInPrivateContext = loadContext && loadContext->UsePrivateBrowsing(); *aInPrivateContext = loadContext && loadContext->UsePrivateBrowsing();
return NS_OK; return NS_OK;
@@ -556,8 +563,8 @@ nsFormFillController::GetNoRollupOnCaretMove(bool* aNoRollupOnCaretMove) {
NS_IMETHODIMP NS_IMETHODIMP
nsFormFillController::GetNoRollupOnEmptySearch(bool* aNoRollupOnEmptySearch) { nsFormFillController::GetNoRollupOnEmptySearch(bool* aNoRollupOnEmptySearch) {
if (mFocusedInput && mFocusedPopup) { if (mFocusedElement && mFocusedPopup) {
return mFocusedPopup->GetNoRollupOnEmptySearch(mFocusedInput, return mFocusedPopup->GetNoRollupOnEmptySearch(mFocusedElement,
aNoRollupOnEmptySearch); aNoRollupOnEmptySearch);
} }
@@ -586,36 +593,36 @@ nsFormFillController::StartSearch(const nsAString& aSearchString,
const nsAString& aSearchParam, const nsAString& aSearchParam,
nsIAutoCompleteResult* aPreviousResult, nsIAutoCompleteResult* aPreviousResult,
nsIAutoCompleteObserver* aListener) { nsIAutoCompleteObserver* aListener) {
MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch for %p", mFocusedInput)); MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch for %p", mFocusedElement));
mLastListener = aListener; mLastListener = aListener;
if (mFocusedInput && mFocusedPopup) { if (mFocusedElement && mFocusedPopup) {
if (mAutoCompleteInputs.Get(mFocusedInput) || if (mAutoCompleteInputs.Get(mFocusedElement) ||
mFocusedInput->HasBeenTypePassword()) { HasBeenTypePassword(mFocusedElement)) {
MOZ_LOG(sLogger, LogLevel::Debug, MOZ_LOG(sLogger, LogLevel::Debug,
("StartSearch: formautofill or login field")); ("StartSearch: formautofill or login field"));
return mFocusedPopup->StartSearch(aSearchString, mFocusedInput, this); return mFocusedPopup->StartSearch(aSearchString, mFocusedElement, this);
} }
} }
MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch: form history field")); MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch: form history field"));
bool addDataList = IsTextControl(mFocusedInput); bool addDataList = IsTextControl(mFocusedElement);
if (addDataList) { if (addDataList) {
MaybeObserveDataListMutations(); MaybeObserveDataListMutations();
} }
return mFocusedPopup->StartSearch(aSearchString, mFocusedInput, this); return mFocusedPopup->StartSearch(aSearchString, mFocusedElement, this);
} }
void nsFormFillController::MaybeObserveDataListMutations() { void nsFormFillController::MaybeObserveDataListMutations() {
// If an <input> is focused, check if it has a list="<datalist>" which can // If an <input> is focused, check if it has a list="<datalist>" which can
// provide the list of suggestions. // provide the list of suggestions.
if (mFocusedInput) { if (mFocusedElement) {
Element* list = mFocusedInput->GetList(); Element* list = GetList(mFocusedElement);
// Add a mutation observer to check for changes to the items in the // Add a mutation observer to check for changes to the items in the
// <datalist> and update the suggestions accordingly. // <datalist> and update the suggestions accordingly.
@@ -739,7 +746,7 @@ nsFormFillController::HandleEvent(Event* aEvent) {
return NS_OK; return NS_OK;
} }
case eBlur: case eBlur:
if (mFocusedInput && !StaticPrefs::ui_popup_disable_autohide()) { if (mFocusedElement && !StaticPrefs::ui_popup_disable_autohide()) {
StopControllingInput(); StopControllingInput();
} }
return NS_OK; return NS_OK;
@@ -768,7 +775,7 @@ nsFormFillController::HandleEvent(Event* aEvent) {
return NS_OK; return NS_OK;
} }
if (mFocusedInput && doc == mFocusedInput->OwnerDoc()) { if (mFocusedElement && doc == mFocusedElement->OwnerDoc()) {
StopControllingInput(); StopControllingInput();
} }
@@ -815,9 +822,9 @@ void nsFormFillController::RemoveForDocument(Document* aDoc) {
for (auto iter = mAutoCompleteInputs.Iter(); !iter.Done(); iter.Next()) { for (auto iter = mAutoCompleteInputs.Iter(); !iter.Done(); iter.Next()) {
const nsINode* key = iter.Key(); const nsINode* key = iter.Key();
if (key && (!aDoc || key->OwnerDoc() == aDoc)) { if (key && (!aDoc || key->OwnerDoc() == aDoc)) {
// mFocusedInput's observer is tracked separately, so don't remove it // mFocusedElement's observer is tracked separately, so don't remove it
// here. // here.
if (key != mFocusedInput) { if (key != mFocusedElement) {
const_cast<nsINode*>(key)->RemoveMutationObserver(this); const_cast<nsINode*>(key)->RemoveMutationObserver(this);
} }
iter.Remove(); iter.Remove();
@@ -827,38 +834,42 @@ void nsFormFillController::RemoveForDocument(Document* aDoc) {
bool nsFormFillController::IsTextControl(nsINode* aNode) { bool nsFormFillController::IsTextControl(nsINode* aNode) {
const auto* formControl = nsIFormControl::FromNodeOrNull(aNode); const auto* formControl = nsIFormControl::FromNodeOrNull(aNode);
return formControl && formControl->IsSingleLineTextControl(false); return formControl && formControl->IsTextControl(false);
} }
void nsFormFillController::MaybeStartControllingInput( void nsFormFillController::MaybeStartControllingInput(Element* aElement) {
HTMLInputElement* aInput) {
MOZ_LOG(sLogger, LogLevel::Verbose, MOZ_LOG(sLogger, LogLevel::Verbose,
("MaybeStartControllingInput for %p", aInput)); ("MaybeStartControllingInput for %p", aElement));
if (!aInput) { if (!aElement) {
return; return;
} }
bool hasList = !!aInput->GetList(); bool hasList = !!GetList(aElement);
if (!IsTextControl(aInput)) { if (!IsTextControl(aElement)) {
// Even if this is not a text control yet, it can become one in the future // Even if this is not a text control yet, it can become one in the future
if (hasList) { if (hasList) {
StartControllingInput(aInput); StartControllingInput(aElement);
} }
return; return;
} }
if (mAutoCompleteInputs.Get(aInput) || aInput->HasBeenTypePassword() || if (mAutoCompleteInputs.Get(aElement) || HasBeenTypePassword(aElement) ||
hasList || nsContentUtils::IsAutocompleteEnabled(aInput)) { hasList || nsContentUtils::IsAutocompleteEnabled(aElement)) {
StartControllingInput(aInput); StartControllingInput(aElement);
} }
} }
nsresult nsFormFillController::HandleFocus(HTMLInputElement* aInput) { nsresult nsFormFillController::HandleFocus(Element* aElement) {
MaybeStartControllingInput(aInput); if (!aElement ||
!aElement->IsAnyOfHTMLElements(nsGkAtoms::input, nsGkAtoms::textarea)) {
return NS_OK;
}
MaybeStartControllingInput(aElement);
// Bail if we didn't start controlling the input. // Bail if we didn't start controlling the input.
if (!mFocusedInput) { if (!mFocusedElement) {
return NS_OK; return NS_OK;
} }
@@ -874,7 +885,7 @@ nsresult nsFormFillController::HandleFocus(HTMLInputElement* aInput) {
// multiple input forms and the fact that a mousedown into an already focused // multiple input forms and the fact that a mousedown into an already focused
// field does not trigger another focus. // field does not trigger another focus.
if (!mFocusedInput->HasBeenTypePassword()) { if (!HasBeenTypePassword(mFocusedElement)) {
return NS_OK; return NS_OK;
} }
@@ -897,7 +908,7 @@ nsresult nsFormFillController::HandleFocus(HTMLInputElement* aInput) {
nsresult nsFormFillController::Focus(Event* aEvent) { nsresult nsFormFillController::Focus(Event* aEvent) {
nsCOMPtr<nsIContent> input = do_QueryInterface(aEvent->GetComposedTarget()); nsCOMPtr<nsIContent> input = do_QueryInterface(aEvent->GetComposedTarget());
return HandleFocus(MOZ_KnownLive(HTMLInputElement::FromNodeOrNull(input))); return HandleFocus(MOZ_KnownLive(Element::FromNodeOrNull(input)));
} }
nsresult nsFormFillController::KeyDown(Event* aEvent) { nsresult nsFormFillController::KeyDown(Event* aEvent) {
@@ -962,8 +973,8 @@ nsresult nsFormFillController::KeyDown(Event* aEvent) {
// Get the writing-mode of the relevant input element, // Get the writing-mode of the relevant input element,
// so that we can remap arrow keys if necessary. // so that we can remap arrow keys if necessary.
mozilla::WritingMode wm; mozilla::WritingMode wm;
if (mFocusedInput) { if (mFocusedElement) {
nsIFrame* frame = mFocusedInput->GetPrimaryFrame(); nsIFrame* frame = mFocusedElement->GetPrimaryFrame();
if (frame) { if (frame) {
wm = frame->GetWritingMode(); wm = frame->GetWritingMode();
} }
@@ -1024,7 +1035,10 @@ nsresult nsFormFillController::MouseDown(Event* aEvent) {
} }
nsCOMPtr<nsINode> targetNode = do_QueryInterface(aEvent->GetComposedTarget()); nsCOMPtr<nsINode> targetNode = do_QueryInterface(aEvent->GetComposedTarget());
if (!HTMLInputElement::FromNodeOrNull(targetNode)) {
auto* element = Element::FromNodeOrNull(targetNode);
if (!element ||
!element->IsAnyOfHTMLElements(nsGkAtoms::input, nsGkAtoms::textarea)) {
return NS_OK; return NS_OK;
} }
@@ -1085,39 +1099,40 @@ NS_IMETHODIMP nsFormFillController::GetPasswordPopupAutomaticallyOpened(
return NS_OK; return NS_OK;
} }
void nsFormFillController::StartControllingInput(HTMLInputElement* aInput) { void nsFormFillController::StartControllingInput(Element* aElement) {
MOZ_LOG(sLogger, LogLevel::Verbose, ("StartControllingInput for %p", aInput)); MOZ_LOG(sLogger, LogLevel::Verbose,
("StartControllingInput for %p", aElement));
// Make sure we're not still attached to an input // Make sure we're not still attached to an input
StopControllingInput(); StopControllingInput();
if (!mController || !aInput) { if (!mController || !aElement) {
return; return;
} }
nsCOMPtr<nsIAutoCompletePopup> popup = nsCOMPtr<nsIAutoCompletePopup> popup =
do_QueryActor("AutoComplete", aInput->OwnerDoc()); do_QueryActor("AutoComplete", aElement->OwnerDoc());
if (!popup) { if (!popup) {
return; return;
} }
mFocusedPopup = popup; mFocusedPopup = popup;
aInput->AddMutationObserverUnlessExists(this); aElement->AddMutationObserverUnlessExists(this);
mFocusedInput = aInput; mFocusedElement = aElement;
if (Element* list = mFocusedInput->GetList()) { if (Element* list = GetList(mFocusedElement)) {
list->AddMutationObserverUnlessExists(this); list->AddMutationObserverUnlessExists(this);
mListNode = list; mListNode = list;
} }
if (!mFocusedInput->ReadOnly()) { if (!ReadOnly(mFocusedElement)) {
nsCOMPtr<nsIAutoCompleteController> controller = mController; nsCOMPtr<nsIAutoCompleteController> controller = mController;
controller->SetInput(this); controller->SetInput(this);
} }
} }
bool nsFormFillController::IsFocusedInputControlled() const { bool nsFormFillController::IsFocusedInputControlled() const {
return mFocusedInput && mController && !mFocusedInput->ReadOnly(); return mFocusedElement && mController && !ReadOnly(mFocusedElement);
} }
void nsFormFillController::StopControllingInput() { void nsFormFillController::StopControllingInput() {
@@ -1142,10 +1157,10 @@ void nsFormFillController::StopControllingInput() {
} }
MOZ_LOG(sLogger, LogLevel::Verbose, MOZ_LOG(sLogger, LogLevel::Verbose,
("StopControllingInput: Stopped controlling %p", mFocusedInput)); ("StopControllingInput: Stopped controlling %p", mFocusedElement));
if (mFocusedInput) { if (mFocusedElement) {
MaybeRemoveMutationObserver(mFocusedInput); MaybeRemoveMutationObserver(mFocusedElement);
mFocusedInput = nullptr; mFocusedElement = nullptr;
} }
if (mFocusedPopup) { if (mFocusedPopup) {
@@ -1154,12 +1169,113 @@ void nsFormFillController::StopControllingInput() {
mFocusedPopup = nullptr; mFocusedPopup = nullptr;
} }
nsIDocShell* nsFormFillController::GetDocShellForInput( nsIDocShell* nsFormFillController::GetDocShellForInput(Element* aElement) {
HTMLInputElement* aInput) { NS_ENSURE_TRUE(aElement, nullptr);
NS_ENSURE_TRUE(aInput, nullptr);
nsCOMPtr<nsPIDOMWindowOuter> win = aInput->OwnerDoc()->GetWindow(); nsCOMPtr<nsPIDOMWindowOuter> win = aElement->OwnerDoc()->GetWindow();
NS_ENSURE_TRUE(win, nullptr); NS_ENSURE_TRUE(win, nullptr);
return win->GetDocShell(); return win->GetDocShell();
} }
void nsFormFillController::GetName(mozilla::dom::Element* aElement,
nsAString& aValue) {
if (auto* input = HTMLInputElement::FromNodeOrNull(aElement)) {
input->GetName(aValue);
} else if (auto* textarea = HTMLTextAreaElement::FromNodeOrNull(aElement)) {
textarea->GetName(aValue);
}
}
void nsFormFillController::GetValue(mozilla::dom::Element* aElement,
nsAString& aValue) {
if (auto* input = HTMLInputElement::FromNodeOrNull(aElement)) {
input->GetValue(aValue, CallerType::System);
} else if (auto* textarea = HTMLTextAreaElement::FromNodeOrNull(aElement)) {
textarea->GetValue(aValue);
}
}
Element* nsFormFillController::GetList(mozilla::dom::Element* aElement) {
if (auto* input = HTMLInputElement::FromNodeOrNull(aElement)) {
return input->GetList();
}
return nullptr;
}
bool nsFormFillController::HasBeenTypePassword(
mozilla::dom::Element* aElement) {
if (auto* input = HTMLInputElement::FromNodeOrNull(aElement)) {
return input->HasBeenTypePassword();
}
return false;
}
bool nsFormFillController::ReadOnly(mozilla::dom::Element* aElement) const {
if (auto* input = HTMLInputElement::FromNodeOrNull(aElement)) {
return input->ReadOnly();
} else if (auto* textarea = HTMLTextAreaElement::FromNodeOrNull(aElement)) {
return textarea->ReadOnly();
}
return false;
}
uint32_t nsFormFillController::GetSelectionStartInternal(
mozilla::dom::Element* aElement, ErrorResult& aRv) {
if (auto* input = HTMLInputElement::FromNodeOrNull(aElement)) {
return input->GetSelectionStartIgnoringType(aRv);
} else if (auto* textarea = HTMLTextAreaElement::FromNodeOrNull(aElement)) {
Nullable<uint32_t> start = textarea->GetSelectionStart(aRv);
if (!start.IsNull()) {
return start.Value();
}
}
return 0;
}
uint32_t nsFormFillController::GetSelectionEndInternal(
mozilla::dom::Element* aElement, ErrorResult& aRv) {
if (auto* input = HTMLInputElement::FromNodeOrNull(aElement)) {
return input->GetSelectionEndIgnoringType(aRv);
} else if (auto* textarea = HTMLTextAreaElement::FromNodeOrNull(aElement)) {
Nullable<uint32_t> end = textarea->GetSelectionEnd(aRv);
if (!end.IsNull()) {
return end.Value();
}
}
return 0;
}
void nsFormFillController::SetSelectionRange(mozilla::dom::Element* aElement,
uint32_t aSelectionStart,
uint32_t aSelectionEnd,
ErrorResult& aRv) {
if (RefPtr<HTMLInputElement> input =
HTMLInputElement::FromNodeOrNull(aElement)) {
return input->SetSelectionRange(aSelectionStart, aSelectionEnd,
Optional<nsAString>(), aRv);
} else if (RefPtr<HTMLTextAreaElement> textarea =
HTMLTextAreaElement::FromNodeOrNull(aElement)) {
return textarea->SetSelectionRange(aSelectionStart, aSelectionEnd,
Optional<nsAString>(), aRv);
}
}
void nsFormFillController::SetUserInput(mozilla::dom::Element* aElement,
const nsAString& aValue,
nsIPrincipal& aSubjectPrincipal) {
if (auto* input = HTMLInputElement::FromNodeOrNull(aElement)) {
input->SetUserInput(aValue, aSubjectPrincipal);
} else if (auto* textarea = HTMLTextAreaElement::FromNodeOrNull(aElement)) {
textarea->SetUserInput(aValue, aSubjectPrincipal);
}
}
void nsFormFillController::EnablePreview(mozilla::dom::Element* aElement) {
if (auto* input = HTMLInputElement::FromNodeOrNull(aElement)) {
input->EnablePreview();
} else if (auto* textarea = HTMLTextAreaElement::FromNodeOrNull(aElement)) {
textarea->EnablePreview();
}
return;
}

View File

@@ -27,9 +27,10 @@ class nsINode;
namespace mozilla { namespace mozilla {
class CancelableRunnable; class CancelableRunnable;
class ErrorResult;
namespace dom { namespace dom {
class EventTarget; class EventTarget;
class HTMLInputElement; class Element;
} // namespace dom } // namespace dom
} // namespace mozilla } // namespace mozilla
@@ -65,13 +66,13 @@ class nsFormFillController final : public nsIFormFillController,
MOZ_CAN_RUN_SCRIPT virtual ~nsFormFillController(); MOZ_CAN_RUN_SCRIPT virtual ~nsFormFillController();
MOZ_CAN_RUN_SCRIPT MOZ_CAN_RUN_SCRIPT
void StartControllingInput(mozilla::dom::HTMLInputElement* aInput); void StartControllingInput(mozilla::dom::Element* aInput);
MOZ_CAN_RUN_SCRIPT void StopControllingInput(); MOZ_CAN_RUN_SCRIPT void StopControllingInput();
bool IsFocusedInputControlled() const; bool IsFocusedInputControlled() const;
MOZ_CAN_RUN_SCRIPT MOZ_CAN_RUN_SCRIPT
nsresult HandleFocus(mozilla::dom::HTMLInputElement* aInput); nsresult HandleFocus(mozilla::dom::Element* aInput);
void AttachListeners(mozilla::dom::EventTarget* aEventTarget); void AttachListeners(mozilla::dom::EventTarget* aEventTarget);
@@ -80,13 +81,12 @@ class nsFormFillController final : public nsIFormFillController,
* StartControllingInput on it. * StartControllingInput on it.
*/ */
MOZ_CAN_RUN_SCRIPT MOZ_CAN_RUN_SCRIPT
void MaybeStartControllingInput(mozilla::dom::HTMLInputElement* aElement); void MaybeStartControllingInput(mozilla::dom::Element* aElement);
// clears the reference mRestartAfterAttributeChangeTask before running // clears the reference mRestartAfterAttributeChangeTask before running
// MaybeStartControllingInput() // MaybeStartControllingInput()
MOZ_CAN_RUN_SCRIPT MOZ_CAN_RUN_SCRIPT
void MaybeStartControllingInputScheduled( void MaybeStartControllingInputScheduled(mozilla::dom::Element* aElement);
mozilla::dom::HTMLInputElement* aElement);
// cancels a scheduled AttributeChangeTask and clears the reference // cancels a scheduled AttributeChangeTask and clears the reference
// mRestartAfterAttributeChangeTask // mRestartAfterAttributeChangeTask
@@ -99,8 +99,7 @@ class nsFormFillController final : public nsIFormFillController,
bool RowMatch(nsFormHistory* aHistory, uint32_t aIndex, bool RowMatch(nsFormHistory* aHistory, uint32_t aIndex,
const nsAString& aInputName, const nsAString& aInputValue); const nsAString& aInputName, const nsAString& aInputValue);
inline nsIDocShell* GetDocShellForInput( inline nsIDocShell* GetDocShellForInput(mozilla::dom::Element* aInput);
mozilla::dom::HTMLInputElement* aInput);
void MaybeRemoveMutationObserver(nsINode* aNode); void MaybeRemoveMutationObserver(nsINode* aNode);
@@ -111,7 +110,7 @@ class nsFormFillController final : public nsIFormFillController,
// members ////////////////////////////////////////// // members //////////////////////////////////////////
nsCOMPtr<nsIAutoCompleteController> mController; nsCOMPtr<nsIAutoCompleteController> mController;
mozilla::dom::HTMLInputElement* mFocusedInput; mozilla::dom::Element* mFocusedElement;
RefPtr<mozilla::CancelableRunnable> mRestartAfterAttributeChangeTask; RefPtr<mozilla::CancelableRunnable> mRestartAfterAttributeChangeTask;
// mListNode is a <datalist> element which, is set, has the form fill // mListNode is a <datalist> element which, is set, has the form fill
@@ -141,6 +140,25 @@ class nsFormFillController final : public nsIFormFillController,
bool mPasswordPopupAutomaticallyOpened; bool mPasswordPopupAutomaticallyOpened;
bool mAutoCompleteActive = false; bool mAutoCompleteActive = false;
bool mInvalidatePreviousResult = false; bool mInvalidatePreviousResult = false;
private:
void GetName(mozilla::dom::Element* aInput, nsAString& aValue);
void GetValue(mozilla::dom::Element* aInput, nsAString& aValue);
mozilla::dom::Element* GetList(mozilla::dom::Element* aInput);
bool HasBeenTypePassword(mozilla::dom::Element* aInput);
bool ReadOnly(mozilla::dom::Element* aInput) const;
uint32_t GetSelectionStartInternal(mozilla::dom::Element* aInput,
mozilla::ErrorResult& aRv);
uint32_t GetSelectionEndInternal(mozilla::dom::Element* aInput,
mozilla::ErrorResult& aRv);
MOZ_CAN_RUN_SCRIPT
void SetSelectionRange(mozilla::dom::Element* aInput,
uint32_t aSelectionStart, uint32_t aSelectionEnd,
mozilla::ErrorResult& aRv);
void SetUserInput(mozilla::dom::Element* aInput, const nsAString& aValue,
nsIPrincipal& aSubjectPrincipal);
void EnablePreview(mozilla::dom::Element* aInput);
}; };
#endif // __nsFormFillController__ #endif // __nsFormFillController__

View File

@@ -10,7 +10,6 @@ interface nsIAutoCompleteResult;
webidl Document; webidl Document;
webidl Element; webidl Element;
webidl Event; webidl Event;
webidl HTMLInputElement;
/* /*
* nsIFormFillController is an interface for controlling form fill behavior * nsIFormFillController is an interface for controlling form fill behavior
@@ -25,9 +24,9 @@ webidl HTMLInputElement;
interface nsIFormFillController : nsISupports interface nsIFormFillController : nsISupports
{ {
/* /*
* The input element the form fill controller is currently bound to. * The input or textarea element the form fill controller is currently bound to.
*/ */
readonly attribute HTMLInputElement focusedInput; readonly attribute Element focusedElement;
/* /*
* Whether the autocomplete popup on a password field was automatically opened * Whether the autocomplete popup on a password field was automatically opened
@@ -39,9 +38,9 @@ interface nsIFormFillController : nsISupports
* Mark the specified <input> element as being managed by autocomplete entry provider. * Mark the specified <input> element as being managed by autocomplete entry provider.
* Autocomplete requests will be handed off to the AutoCompleteChild. * Autocomplete requests will be handed off to the AutoCompleteChild.
* *
* @param aInput - The HTML <input> element to mark * @param aElement - The HTML <input> or <textarea> element to mark
*/ */
[can_run_script] void markAsAutoCompletableField(in HTMLInputElement aInput); [can_run_script] void markAsAutoCompletableField(in Element aElement);
/* /*
* Open the autocomplete popup, if possible. * Open the autocomplete popup, if possible.