Merge remote-tracking branch 'origin/tracking' into custom

This commit is contained in:
roytam1 2025-07-29 10:44:33 +08:00
commit 4c1a4a4402
31 changed files with 827 additions and 36 deletions

View file

@ -1603,6 +1603,16 @@ private:
// Data members
EventStates mState;
public:
// Public helper to add or remove event states
void SetEventState(mozilla::EventStates aState, bool aAdd) {
if (aAdd) {
this->AddStates(aState);
} else {
this->RemoveStates(aState);
}
}
};
class RemoveFromBindingManagerRunnable : public mozilla::Runnable

View file

@ -4058,3 +4058,35 @@ nsTranslationNodeList::GetLength(uint32_t* aRetVal)
*aRetVal = mLength;
return NS_OK;
}
NS_IMETHODIMP
nsDOMWindowUtils::AddElementEventState(nsIDOMElement* aElement, uint64_t aState)
{
NS_ENSURE_ARG_POINTER(aElement);
nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
if (!content) {
return NS_ERROR_INVALID_ARG;
}
mozilla::dom::Element* element = static_cast<mozilla::dom::Element*>(content.get());
if (!element) {
return NS_ERROR_INVALID_ARG;
}
element->SetEventState(mozilla::EventStates(aState), true);
return NS_OK;
}
NS_IMETHODIMP
nsDOMWindowUtils::RemoveElementEventState(nsIDOMElement* aElement, uint64_t aState)
{
NS_ENSURE_ARG_POINTER(aElement);
nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
if (!content) {
return NS_ERROR_INVALID_ARG;
}
mozilla::dom::Element* element = static_cast<mozilla::dom::Element*>(content.get());
if (!element) {
return NS_ERROR_INVALID_ARG;
}
element->SetEventState(mozilla::EventStates(aState), false);
return NS_OK;
}

View file

@ -293,6 +293,9 @@ private:
// Modal <dialog> element
#define NS_EVENT_STATE_MODAL_DIALOG NS_DEFINE_EVENT_STATE_MACRO(54)
// Autofilled input element (for :autofill pseudo-class)
#define NS_EVENT_STATE_AUTOFILL NS_DEFINE_EVENT_STATE_MACRO(55)
#define DIR_ATTR_STATES (NS_EVENT_STATE_HAS_DIR_ATTR | \
NS_EVENT_STATE_DIR_ATTR_LTR | \
NS_EVENT_STATE_DIR_ATTR_RTL | \

View file

@ -2832,6 +2832,20 @@ HTMLInputElement::SetUserInput(const nsAString& aValue)
return NS_OK;
}
void
HTMLInputElement::SetAutofilled(bool aAutofilled)
{
nsAutoString value;
GetValueInternal(value);
if (aAutofilled) {
AddStates(NS_EVENT_STATE_AUTOFILL);
mAutofilledValue = value;
} else {
RemoveStates(NS_EVENT_STATE_AUTOFILL);
mAutofilledValue.Truncate();
}
}
nsIEditor*
HTMLInputElement::GetEditor()
{
@ -3562,7 +3576,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)
@ -7070,6 +7095,13 @@ HTMLInputElement::IntrinsicState() const
state |= NS_EVENT_STATE_MOZ_SUBMITINVALID;
}
// Autofill highlight should persist as long as the value matches the autofilled value
nsAutoString value;
GetValueInternal(value);
if (!mAutofilledValue.IsEmpty() && value == mAutofilledValue) {
state |= NS_EVENT_STATE_AUTOFILL;
}
return state;
}
@ -8505,8 +8537,23 @@ HTMLInputElement::InitializeKeyboardEventListeners()
NS_IMETHODIMP_(void)
HTMLInputElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange)
{
nsAutoString value;
GetValueInternal(value);
mLastValueChangeWasInteractive = aWasInteractiveUserChange;
// Only remove autofilled state if the value actually changed from autofilled value
if (aWasInteractiveUserChange && State().HasState(NS_EVENT_STATE_AUTOFILL)) {
if (!mAutofilledValue.IsEmpty() && mAutofilledValue != value) {
RemoveStates(NS_EVENT_STATE_AUTOFILL);
mAutofilledValue.Truncate();
}
} else if (aWasInteractiveUserChange && !State().HasState(NS_EVENT_STATE_AUTOFILL)) {
// If the value is changed back to the autofilled value, restore the state
if (!mAutofilledValue.IsEmpty() && mAutofilledValue == value) {
AddStates(NS_EVENT_STATE_AUTOFILL);
}
}
UpdateAllValidityStates(aNotify);
if (HasDirAuto()) {
@ -8537,6 +8584,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)
{

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; }
@ -845,6 +847,15 @@ public:
void SetUserInput(const nsAString& aInput,
nsIPrincipal& aSubjectPrincipal);
/**
* Sets or clears the autofilled state of this input element.
* When setting, also stores the autofilled value for persistence.
* When clearing, clears the stored autofilled value.
*
* @param aAutofilled Whether the element should be marked as autofilled
*/
void SetAutofilled(bool aAutofilled);
// XPCOM GetPhonetic() is OK
/**
@ -860,6 +871,8 @@ public:
void UpdateEntries(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories);
void SetAutofilledValue(const nsAString& aValue) { mAutofilledValue = aValue; }
protected:
virtual ~HTMLInputElement();
@ -1103,7 +1116,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.
@ -1629,6 +1642,11 @@ protected:
bool mNumberControlSpinnerSpinsUp : 1;
bool mPickerRunning : 1;
bool mSelectionCached : 1;
/**
* The value that was autofilled by the browser. Used to persist the autofill
* highlight as long as the value matches, regardless of focus/blur.
*/
nsString mAutofilledValue;
private:
static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
@ -1772,6 +1790,8 @@ private:
nsCOMPtr<nsIFilePicker> mFilePicker;
RefPtr<HTMLInputElement> mInput;
};
void EnsureAutofillState();
};
} // namespace dom

View file

@ -366,6 +366,46 @@ HTMLTextAreaElement::SetUserInput(const nsAString& aValue)
return SetValueInternal(aValue, nsTextEditorState::eSetValue_BySetUserInput);
}
void
HTMLTextAreaElement::SetAutofilled(bool aAutofilled)
{
if (aAutofilled) {
AddStates(NS_EVENT_STATE_AUTOFILL);
GetValueInternal(mAutofilledValue, true); // Store the autofilled value
} else {
RemoveStates(NS_EVENT_STATE_AUTOFILL);
mAutofilledValue.Truncate();
}
}
NS_IMETHODIMP_(void)
HTMLTextAreaElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange)
{
nsAutoString value;
GetValueInternal(value, true);
// 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) {
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);
}
}
NS_IMETHODIMP
HTMLTextAreaElement::SetValueChanged(bool aValueChanged)
{
@ -556,6 +596,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)
{
@ -580,6 +633,9 @@ HTMLTextAreaElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
}
UpdateState(true);
// Defensive: re-apply autofill state if value is still autofilled value
EnsureAutofillState();
}
return NS_OK;
@ -1186,6 +1242,10 @@ HTMLTextAreaElement::IntrinsicState() const
{
EventStates state = nsGenericHTMLFormElementWithState::IntrinsicState();
if (!mAutofilledValue.IsEmpty()) {
state |= NS_EVENT_STATE_AUTOFILL;
}
if (HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
state |= NS_EVENT_STATE_REQUIRED;
} else {
@ -1628,23 +1688,6 @@ HTMLTextAreaElement::InitializeKeyboardEventListeners()
mState.InitializeKeyboardEventListeners();
}
NS_IMETHODIMP_(void)
HTMLTextAreaElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange)
{
mLastValueChangeWasInteractive = aWasInteractiveUserChange;
// 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()
{
@ -1660,11 +1703,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

View file

@ -69,6 +69,17 @@ 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.
* This is used by the browser's autofill system to indicate when
* a value has been automatically filled (e.g., from saved form data).
*
* @param aAutofilled Whether the element should be marked as autofilled
*/
void SetAutofilled(bool aAutofilled);
// nsIFormControl
NS_IMETHOD_(uint32_t) GetType() const override { return NS_FORM_TEXTAREA; }
@ -293,6 +304,7 @@ public:
{
return mState.GetEditor();
}
nsTextEditorState* GetEditorState() const;
protected:
virtual ~HTMLTextAreaElement() {}
@ -324,6 +336,7 @@ protected:
void FireChangeEventIfNeeded();
nsString mFocusedValue;
nsString mAutofilledValue;
/** The state of the text editor (selection controller and the editor) **/
nsTextEditorState mState;
@ -397,6 +410,7 @@ protected:
private:
static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
nsRuleData* aData);
void EnsureAutofillState();
};
} // namespace dom

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

View file

@ -1944,6 +1944,20 @@ interface nsIDOMWindowUtils : nsISupports {
const long MOUSE_BUTTONS_5TH_BUTTON = 0x10;
// Buttons are not specified, will be calculated from |aButton|.
const long MOUSE_BUTTONS_NOT_SPECIFIED = -1;
/**
* Add an EventState bit to an element (privileged only).
* @param element The element to modify.
* @param state The EventState bit (see EventStates.h, e.g. NS_EVENT_STATE_AUTOFILL).
*/
void addElementEventState(in nsIDOMElement element, in unsigned long long state);
/**
* Remove an EventState bit from an element (privileged only).
* @param element The element to modify.
* @param state The EventState bit.
*/
void removeElementEventState(in nsIDOMElement element, in unsigned long long state);
};
[scriptable, uuid(c694e359-7227-4392-a138-33c0cc1f15a6)]

View file

@ -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();
};

View file

@ -200,6 +200,13 @@ partial interface HTMLInputElement {
// for example will be dispatched when focusing out the element.
[Func="IsChromeOrXBL", NeedsSubjectPrincipal]
void setUserInput(DOMString input);
// Sets or clears the autofilled state of this input element.
// This is used by the browser's autofill system to indicate when
// a value has been automatically filled (e.g., from saved passwords
// or form data).
[ChromeOnly]
void setAutofilled(boolean autofilled);
};
partial interface HTMLInputElement {

View file

@ -97,4 +97,10 @@ partial interface HTMLTextAreaElement {
// element.
[ChromeOnly]
void setUserInput(DOMString input);
// Sets or clears the autofilled state of this textarea element.
// This is used by the browser's autofill system to indicate when
// a value has been automatically filled (e.g., from saved form data).
[ChromeOnly]
void setAutofilled(boolean autofilled);
};