1
0
mirror of https://github.com/roytam1/UXP.git synced 2026-05-26 13:58:49 +00:00

Issue #2790 - Part 4: Working non persistent autofill highlight

This commit is contained in:
MeladJM
2025-07-25 11:12:29 +08:00
committed by roytam1
parent 4cdfb9e16b
commit bf8cfcc980
12 changed files with 207 additions and 68 deletions
+38 -11
View File
@@ -2835,16 +2835,11 @@ HTMLInputElement::SetUserInput(const nsAString& aValue)
void
HTMLInputElement::SetAutofilled(bool aAutofilled)
{
printf("🔍 AUTOFILL C++: SetAutofilled called with aAutofilled=%s\n", aAutofilled ? "true" : "false");
if (aAutofilled) {
printf("🔍 AUTOFILL C++: Adding NS_EVENT_STATE_AUTOFILL state\n");
AddStates(NS_EVENT_STATE_AUTOFILL);
printf("🔍 AUTOFILL C++: State added successfully\n");
} else {
printf("🔍 AUTOFILL C++: Removing NS_EVENT_STATE_AUTOFILL state\n");
RemoveStates(NS_EVENT_STATE_AUTOFILL);
printf("🔍 AUTOFILL C++: State removed successfully\n");
}
}
@@ -3578,7 +3573,18 @@ HTMLInputElement::Blur(ErrorResult& aError)
}
nsGenericHTMLElement::Blur(aError);
}
if (State().HasState(NS_EVENT_STATE_AUTOFILL)) {
// Force a complete restyle to ensure autofill pseudo-classes are processed
if (nsIDocument* doc = GetComposedDoc()) {
if (nsIPresShell* shell = doc->GetShell()) {
if (nsIFrame* frame = GetPrimaryFrame()) {
shell->FrameNeedsReflow(frame, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
}
}
}
}
}
void
HTMLInputElement::Focus(ErrorResult& aError)
@@ -8521,13 +8527,16 @@ HTMLInputElement::InitializeKeyboardEventListeners()
NS_IMETHODIMP_(void)
HTMLInputElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange)
{
nsAutoString value;
GetValueInternal(value);
mLastValueChangeWasInteractive = aWasInteractiveUserChange;
// Clear autofilled state if this was an interactive user change
// Only remove autofilled state if the value actually changed from autofilled value
if (aWasInteractiveUserChange && State().HasState(NS_EVENT_STATE_AUTOFILL)) {
printf("🔍 AUTOFILL C++: User changed autofilled input, clearing state\n");
RemoveStates(NS_EVENT_STATE_AUTOFILL);
printf("🔍 AUTOFILL C++: Autofill state cleared from input\n");
if (mAutofilledValue != value) {
RemoveStates(NS_EVENT_STATE_AUTOFILL);
mAutofilledValue.Truncate();
}
}
UpdateAllValidityStates(aNotify);
@@ -8560,6 +8569,24 @@ HTMLInputElement::HasCachedSelection()
return isCached;
}
NS_IMETHODIMP
HTMLInputElement::BeginProgrammaticValueSet() {
nsTextEditorState* state = GetEditorState();
if (state) {
state->SettingValue(true);
}
return NS_OK;
}
NS_IMETHODIMP
HTMLInputElement::EndProgrammaticValueSet() {
nsTextEditorState* state = GetEditorState();
if (state) {
state->SettingValue(false);
}
return NS_OK;
}
void
HTMLInputElement::FieldSetDisabledChanged(bool aNotify)
{
+4 -1
View File
@@ -160,6 +160,8 @@ public:
}
NS_IMETHOD SetUserInput(const nsAString& aInput) override;
NS_IMETHOD BeginProgrammaticValueSet() override;
NS_IMETHOD EndProgrammaticValueSet() override;
// Overriden nsIFormControl methods
NS_IMETHOD_(uint32_t) GetType() const override { return mType; }
@@ -1113,7 +1115,7 @@ protected:
bool MinOrMaxLengthApplies() const { return IsSingleLineTextControl(false, mType); }
void FreeData();
nsTextEditorState *GetEditorState() const;
nsTextEditorState* GetEditorState() const;
/**
* Manages the internal data storage across type changes.
@@ -1639,6 +1641,7 @@ protected:
bool mNumberControlSpinnerSpinsUp : 1;
bool mPickerRunning : 1;
bool mSelectionCached : 1;
nsString mAutofilledValue;
private:
static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+81 -30
View File
@@ -369,16 +369,46 @@ HTMLTextAreaElement::SetUserInput(const nsAString& aValue)
void
HTMLTextAreaElement::SetAutofilled(bool aAutofilled)
{
printf("🔍 AUTOFILL C++: HTMLTextAreaElement::SetAutofilled called with aAutofilled=%s\n", aAutofilled ? "true" : "false");
if (aAutofilled) {
printf("🔍 AUTOFILL C++: Adding NS_EVENT_STATE_AUTOFILL state to textarea\n");
AddStates(NS_EVENT_STATE_AUTOFILL);
printf("🔍 AUTOFILL C++: State added successfully to textarea\n");
GetValueInternal(mAutofilledValue, true); // Store the autofilled value
} else {
printf("🔍 AUTOFILL C++: Removing NS_EVENT_STATE_AUTOFILL state from textarea\n");
RemoveStates(NS_EVENT_STATE_AUTOFILL);
printf("🔍 AUTOFILL C++: State removed successfully from textarea\n");
mAutofilledValue.Truncate();
}
}
NS_IMETHODIMP_(void)
HTMLTextAreaElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange)
{
nsAutoString value;
GetValueInternal(value, true);
// printf("[TextArea] OnValueChanged: aWasInteractiveUserChange=%d, value='%s', autofilled='%s', autofill state=%d\n",
// aWasInteractiveUserChange,
// NS_ConvertUTF16toUTF8(value).get(),
// NS_ConvertUTF16toUTF8(mAutofilledValue).get(),
// State().HasState(NS_EVENT_STATE_AUTOFILL));
// Only remove autofilled state if the value actually changed from autofilled value
if (State().HasState(NS_EVENT_STATE_AUTOFILL) || !mAutofilledValue.IsEmpty()) {
if (aWasInteractiveUserChange && mAutofilledValue != value) {
RemoveStates(NS_EVENT_STATE_AUTOFILL);
mAutofilledValue.Truncate();
} else if (aWasInteractiveUserChange && mAutofilledValue == value) {
// Defensive: re-add the autofill state if it was removed by something else
AddStates(NS_EVENT_STATE_AUTOFILL);
}
}
// Update the validity state
bool validBefore = IsValid();
UpdateTooLongValidityState();
UpdateTooShortValidityState();
UpdateValueMissingValidityState();
if (validBefore != IsValid() ||
HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)) {
UpdateState(aNotify);
}
}
@@ -572,6 +602,19 @@ HTMLTextAreaElement::FireChangeEventIfNeeded()
false);
}
void
HTMLTextAreaElement::EnsureAutofillState()
{
nsAutoString value;
GetValueInternal(value, true);
if (!mAutofilledValue.IsEmpty() && mAutofilledValue == value) {
if (!State().HasState(NS_EVENT_STATE_AUTOFILL)) {
AddStates(NS_EVENT_STATE_AUTOFILL);
UpdateState(true); // Force style system to re-evaluate
}
}
}
nsresult
HTMLTextAreaElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
{
@@ -596,6 +639,9 @@ HTMLTextAreaElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
}
UpdateState(true);
// Defensive: re-apply autofill state if value is still autofilled value
EnsureAutofillState();
}
return NS_OK;
@@ -1202,6 +1248,11 @@ HTMLTextAreaElement::IntrinsicState() const
{
EventStates state = nsGenericHTMLFormElementWithState::IntrinsicState();
// PATCH: Persist autofill state if autofilled
if (!mAutofilledValue.IsEmpty()) {
state |= NS_EVENT_STATE_AUTOFILL;
}
if (HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
state |= NS_EVENT_STATE_REQUIRED;
} else {
@@ -1244,6 +1295,8 @@ HTMLTextAreaElement::IntrinsicState() const
state |= NS_EVENT_STATE_PLACEHOLDERSHOWN;
}
state |= NS_EVENT_STATE_AUTOFILL;
return state;
}
@@ -1644,30 +1697,6 @@ HTMLTextAreaElement::InitializeKeyboardEventListeners()
mState.InitializeKeyboardEventListeners();
}
NS_IMETHODIMP_(void)
HTMLTextAreaElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange)
{
mLastValueChangeWasInteractive = aWasInteractiveUserChange;
// Clear autofilled state if this was an interactive user change
if (aWasInteractiveUserChange && State().HasState(NS_EVENT_STATE_AUTOFILL)) {
printf("🔍 AUTOFILL C++: User changed autofilled textarea, clearing state\n");
RemoveStates(NS_EVENT_STATE_AUTOFILL);
printf("🔍 AUTOFILL C++: Autofill state cleared from textarea\n");
}
// Update the validity state
bool validBefore = IsValid();
UpdateTooLongValidityState();
UpdateTooShortValidityState();
UpdateValueMissingValidityState();
if (validBefore != IsValid() ||
HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)) {
UpdateState(aNotify);
}
}
NS_IMETHODIMP_(bool)
HTMLTextAreaElement::HasCachedSelection()
{
@@ -1683,11 +1712,33 @@ HTMLTextAreaElement::FieldSetDisabledChanged(bool aNotify)
nsGenericHTMLFormElementWithState::FieldSetDisabledChanged(aNotify);
}
NS_IMETHODIMP
HTMLTextAreaElement::BeginProgrammaticValueSet() {
nsTextEditorState* state = GetEditorState();
if (state) {
state->SettingValue(true);
}
return NS_OK;
}
NS_IMETHODIMP
HTMLTextAreaElement::EndProgrammaticValueSet() {
nsTextEditorState* state = GetEditorState();
if (state) {
state->SettingValue(false);
}
return NS_OK;
}
JSObject*
HTMLTextAreaElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return HTMLTextAreaElementBinding::Wrap(aCx, this, aGivenProto);
}
nsTextEditorState* HTMLTextAreaElement::GetEditorState() const {
return const_cast<nsTextEditorState*>(&mState);
}
} // namespace dom
} // namespace mozilla
+5
View File
@@ -69,6 +69,8 @@ public:
return nsGenericHTMLElement::GetEditor(aEditor);
}
NS_IMETHOD SetUserInput(const nsAString& aInput) override;
NS_IMETHOD BeginProgrammaticValueSet() override;
NS_IMETHOD EndProgrammaticValueSet() override;
/**
* Sets or clears the autofilled state of this textarea element.
@@ -302,6 +304,7 @@ public:
{
return mState.GetEditor();
}
nsTextEditorState* GetEditorState() const;
protected:
virtual ~HTMLTextAreaElement() {}
@@ -333,6 +336,7 @@ protected:
void FireChangeEventIfNeeded();
nsString mFocusedValue;
nsString mAutofilledValue;
/** The state of the text editor (selection controller and the editor) **/
nsTextEditorState mState;
@@ -406,6 +410,7 @@ protected:
private:
static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
nsRuleData* aData);
void EnsureAutofillState();
};
} // namespace dom
+2
View File
@@ -163,6 +163,7 @@ public:
// or reconsider fixing bug 597525 to remove these.
void EmptyValue() { if (mValue) mValue->Truncate(); }
bool IsEmpty() const { return mValue ? mValue->IsEmpty() : true; }
void SettingValue(bool aValue) { mSettingValue = aValue; }
nsresult CreatePlaceholderNode();
@@ -348,6 +349,7 @@ private:
mutable bool mSelectionRestoreEagerInit; // Whether we're eager initing because of selection restore
bool mPlaceholderVisibility;
bool mIsCommittingComposition;
bool mSettingValue;
};
inline void
@@ -25,4 +25,10 @@ interface nsIDOMNSEditableElement : nsISupports
// 'change' event for example will be dispatched when focusing out the
// element.
[noscript] void setUserInput(in DOMString input);
/**
* Call this before and after programmatically setting the value to prevent
* OnValueChanged from treating it as a user edit.
*/
void beginProgrammaticValueSet();
void endProgrammaticValueSet();
};
+8
View File
@@ -1270,6 +1270,14 @@ ComputeSelectorStateDependence(nsCSSSelector& aSelector)
continue;
}
// --- BEGIN PATCH: Explicit autofill state dependence ---
if (pseudoClass->mType == CSSPseudoClassType::autofill ||
pseudoClass->mType == CSSPseudoClassType::mozAutofillHighlight) {
states |= NS_EVENT_STATE_AUTOFILL;
continue;
}
// --- END PATCH ---
auto idx = static_cast<CSSPseudoClassTypeBase>(pseudoClass->mType);
states |= nsCSSPseudoClasses::sPseudoClassStateDependences[idx];
}
+10
View File
@@ -576,6 +576,16 @@ nsCSSRuleUtils::StateSelectorMatches(Element* aElement,
for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList;
pseudoClass;
pseudoClass = pseudoClass->mNext) {
// --- Autofill explicit matching ---
if (pseudoClass->mType == CSSPseudoClassType::autofill ||
pseudoClass->mType == CSSPseudoClassType::mozAutofillHighlight) {
// Match if the element has the autofill state, regardless of focus
if (!aElement->State().HasState(NS_EVENT_STATE_AUTOFILL)) {
return false;
}
continue; // This pseudo-class matches
}
// --- End autofill explicit matching ---
auto idx = static_cast<CSSPseudoClassTypeBase>(pseudoClass->mType);
EventStates statesToCheck = nsCSSPseudoClasses::sPseudoClassStates[idx];
if (!statesToCheck.IsEmpty() && !StateSelectorMatches(aElement,
+5
View File
@@ -1152,6 +1152,11 @@ input[type="time"] {
/* Autofill highlight for internal-only pseudo-class */
input:-moz-autofill-highlight,
select:-moz-autofill-highlight,
textarea:-moz-autofill-highlight {
background-color: #ffff99 !important;
}
.custom-autofill-highlight {
background: yellow !important;
}
@@ -9,9 +9,7 @@
* See the nsIFormAutofillContentService documentation for details.
*/
"use strict";
console.log('🔍 AUTOFILL: FormAutofillContentService.js loaded');
"use strict"
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
@@ -34,15 +32,11 @@ function FormHandler(aForm, aWindow) {
// Add a reset event listener to clear autofill state
this.form.addEventListener("reset", () => {
console.log('Form reset detected, clearing autofill state');
for (let element of this.form.elements) {
if (typeof element.setAutofilled === "function") {
element.setAutofilled(false);
console.log('setAutofilled(false) called on', element);
}
}
// Optionally, clear fieldDetails if you want to force re-collection
// this.fieldDetails = [];
});
}
@@ -145,9 +139,7 @@ FormHandler.prototype = {
return "cancel";
}
console.log('🔍 AUTOFILL: About to call autofillFormFields with result:', result);
this.autofillFormFields(result);
console.log('🔍 AUTOFILL: autofillFormFields completed');
return "success";
}),
@@ -243,10 +235,8 @@ FormHandler.prototype = {
* }
*/
autofillFormFields: function (aAutofillResult) {
console.log('🔍 AUTOFILL: autofillFormFields called with', aAutofillResult);
for (let field of aAutofillResult.fields) {
console.log('🔍 AUTOFILL: Processing field', field);
// Get the field details, if it was processed by the user interface.
let fieldDetail = this.fieldDetails
@@ -255,26 +245,36 @@ FormHandler.prototype = {
f.contactType == field.contactType &&
f.fieldName == field.fieldName);
console.log('🔍 AUTOFILL: Found fieldDetail?', !!fieldDetail, fieldDetail);
if (!fieldDetail) {
console.log('🔍 AUTOFILL: No fieldDetail found, skipping');
continue;
}
console.log('🔍 AUTOFILL: Setting value on element', fieldDetail.element);
fieldDetail.element.value = field.value;
// Set the autofilled state on the element
console.log('🔍 AUTOFILL: Checking if setAutofilled exists on element');
console.log('🔍 AUTOFILL: setAutofilled type:', typeof fieldDetail.element.setAutofilled);
// Add event listeners for debugging
// try {
// fieldDetail.element.addEventListener('focus', () => console.log('[JS] input focused'));
// fieldDetail.element.addEventListener('blur', () => console.log('[JS] input blurred'));
// fieldDetail.element.addEventListener('input', () => console.log('[JS] input event, value:', fieldDetail.element.value));
// } catch (e) {
// console.log('[JS] Could not add event listeners:', e);
// }
// if (typeof fieldDetail.element.setAutofilled === 'function') {
// if (field.value) {
// console.log('AUTOFILL: Calling setAutofilled(true) on element');
// fieldDetail.element.setAutofilled(true);
// console.log('AUTOFILL: setAutofilled(true) called successfully');
// } else {
// console.log('AUTOFILL: Calling setAutofilled(false) on element (empty value)');
// fieldDetail.element.setAutofilled(false);
// }
// } else {
// console.log('AUTOFILL: setAutofilled is not a function on this element');
// }
// Highlight: Set autofilled state for all autofilled fields
if (typeof fieldDetail.element.setAutofilled === 'function') {
console.log('🔍 AUTOFILL: Calling setAutofilled(true) on element');
fieldDetail.element.setAutofilled(true);
console.log('🔍 AUTOFILL: setAutofilled(true) called successfully');
} else {
console.log('🔍 AUTOFILL: setAutofilled is not a function on this element');
fieldDetail.element.setAutofilled(!!field.value);
}
}
},
@@ -1189,7 +1189,7 @@ var LoginManagerContent = {
// Fill the form
if (usernameField) {
// Don't modify the username field if it's disabled or readOnly so we preserve its case.
// Don't modify the username field if it's disabled or readOnly so we preserve its case.
let disabledOrReadOnly = usernameField.disabled || usernameField.readOnly;
let userNameDiffers = selectedLogin.username != usernameField.value;
@@ -1202,6 +1202,10 @@ var LoginManagerContent = {
if (!disabledOrReadOnly && !userEnteredDifferentCase && userNameDiffers) {
usernameField.setUserInput(selectedLogin.username);
}
//Set autofilled state if value is present
if (typeof usernameField.setAutofilled === "function" && usernameField.value) {
usernameField.setAutofilled(true);
}
}
let doc = form.ownerDocument;
@@ -1216,6 +1220,10 @@ var LoginManagerContent = {
};
log("Saving autoFilledLogin", autoFilledLogin.guid, "for", form.rootElement);
this.stateForDocument(doc).fillsByRootElement.set(form.rootElement, autoFilledLogin);
// Patch: Set autofilled state if value is present
if (typeof passwordField.setAutofilled === "function" && passwordField.value) {
passwordField.setAutofilled(true);
}
}
log("_fillForm succeeded");
@@ -40,6 +40,7 @@
#include "nsIFrame.h"
#include "nsIScriptSecurityManager.h"
#include "nsFocusManager.h"
#include "mozilla/dom/HTMLInputElement.h"
using namespace mozilla;
using namespace mozilla::dom;
@@ -541,9 +542,21 @@ nsFormFillController::SetTextValue(const nsAString & aTextValue)
{
nsCOMPtr<nsIDOMNSEditableElement> editable = do_QueryInterface(mFocusedInput);
if (editable) {
editable->BeginProgrammaticValueSet();
mSuppressOnInput = true;
editable->SetUserInput(aTextValue);
mSuppressOnInput = false;
editable->EndProgrammaticValueSet();
if (mFocusedInput) {
nsCOMPtr<nsIContent> content = do_QueryInterface(mFocusedInput);
if (content) {
mozilla::dom::HTMLInputElement* htmlInput = mozilla::dom::HTMLInputElement::FromContentOrNull(content);
if (htmlInput) {
htmlInput->SetAutofilled(true);
}
}
}
}
return NS_OK;
}
@@ -1332,7 +1345,8 @@ nsFormFillController::StopControllingInput()
nsCOMPtr <nsIFormAutoComplete> formAutoComplete =
do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv);
if (formAutoComplete) {
formAutoComplete->StopControllingInput(mFocusedInput);
// PATCH: Do NOT call StopControllingInput here, so autofill state is NOT cleared on blur/focus.
// formAutoComplete->StopControllingInput(mFocusedInput);
}
mFocusedInputNode = nullptr;