Bug 1833181 - Avoid attr lookups to check whether input value is empty. r=smaug

Differential Revision: https://phabricator.services.mozilla.com/D178078
This commit is contained in:
Emilio Cobos Álvarez 2023-05-15 17:16:41 +00:00
parent e7716be371
commit f37d9339a7
9 changed files with 85 additions and 70 deletions

View File

@ -171,7 +171,8 @@ bitflags! {
Self::MODAL.bits |
Self::INERT.bits |
Self::TOPMOST_MODAL.bits |
Self::REVEALED.bits;
Self::REVEALED.bits |
Self::VALUE_EMPTY.bits;
const INTRINSIC_STATES = !Self::EXTERNALLY_MANAGED_STATES.bits;
}

View File

@ -1035,7 +1035,7 @@ HTMLInputElement::HTMLInputElement(already_AddRefed<dom::NodeInfo>&& aNodeInfo,
// until someone calls UpdateEditableState on us, apparently! Also
// by default we don't have to show validity UI and so forth.
AddStatesSilently(ElementState::ENABLED | ElementState::OPTIONAL_ |
ElementState::VALID);
ElementState::VALID | ElementState::VALUE_EMPTY);
UpdateApzAwareFlag();
}
@ -1571,16 +1571,6 @@ void HTMLInputElement::GetNonFileValueInternal(nsAString& aValue) const {
}
}
bool HTMLInputElement::IsValueEmpty() const {
if (GetValueMode() == VALUE_MODE_VALUE && IsSingleLineTextControl(false)) {
return !mInputData.mState->HasNonEmptyValue();
}
nsAutoString value;
GetNonFileValueInternal(value);
return value.IsEmpty();
}
void HTMLInputElement::ClearFiles(bool aSetValueChanged) {
nsTArray<OwningFileOrDirectory> data;
SetFilesOrDirectories(data, aSetValueChanged);
@ -2668,6 +2658,12 @@ nsresult HTMLInputElement::SetValueInternal(
SetValueChanged(true);
}
if (value.IsEmpty()) {
AddStates(ElementState::VALUE_EMPTY);
} else {
RemoveStates(ElementState::VALUE_EMPTY);
}
if (IsSingleLineTextControl(false)) {
// Note that if aOptions includes
// ValueSetterOption::BySetUserInputAPI, "input" event is automatically
@ -2711,7 +2707,7 @@ nsresult HTMLInputElement::SetValueInternal(
}
}
if (mDoneCreating) {
OnValueChanged(ValueChangeKind::Internal);
OnValueChanged(ValueChangeKind::Internal, value.IsEmpty(), &value);
}
// else DoneCreatingElement calls us again once mDoneCreating is true
}
@ -2727,7 +2723,7 @@ nsresult HTMLInputElement::SetValueInternal(
// This call might be useless in some situations because if the element is
// a single line text control, TextControlState::SetValue will call
// nsHTMLInputElement::OnValueChanged which is going to call UpdateState()
// HTMLInputElement::OnValueChanged which is going to call UpdateState()
// if the element is focused. This bug 665547.
if (PlaceholderApplies() && HasAttr(nsGkAtoms::placeholder)) {
UpdateState(true);
@ -5729,11 +5725,15 @@ nsresult HTMLInputElement::SetDefaultValueAsValue() {
return SetValueInternal(resetVal, ValueSetterOption::ByInternalAPI);
}
void HTMLInputElement::SetDirectionFromValue(bool aNotify) {
void HTMLInputElement::SetDirectionFromValue(bool aNotify,
const nsAString* aKnownValue) {
if (IsSingleLineTextControl(true)) {
nsAutoString value;
GetValue(value, CallerType::System);
SetDirectionalityFromValue(this, value, aNotify);
if (!aKnownValue) {
GetValue(value, CallerType::System);
aKnownValue = &value;
}
SetDirectionalityFromValue(this, *aKnownValue, aNotify);
}
}
@ -5888,7 +5888,7 @@ HTMLInputElement::SubmitNamesValues(FormData* aFormData) {
static nsTArray<FileContentData> SaveFileContentData(
const nsTArray<OwningFileOrDirectory>& aArray) {
nsTArray<FileContentData> res(aArray.Length());
for (auto& it : aArray) {
for (const auto& it : aArray) {
if (it.IsFile()) {
RefPtr<BlobImpl> impl = it.GetAsFile()->Impl();
res.AppendElement(std::move(impl));
@ -6089,11 +6089,9 @@ ElementState HTMLInputElement::IntrinsicState() const {
}
}
if (mType != FormControlType::InputFile && IsValueEmpty()) {
state |= ElementState::VALUE_EMPTY;
if (PlaceholderApplies() && HasAttr(nsGkAtoms::placeholder)) {
state |= ElementState::PLACEHOLDER_SHOWN;
}
if (IsValueEmpty() && PlaceholderApplies() &&
HasAttr(nsGkAtoms::placeholder)) {
state |= ElementState::PLACEHOLDER_SHOWN;
}
return state;
@ -6102,7 +6100,7 @@ ElementState HTMLInputElement::IntrinsicState() const {
static nsTArray<OwningFileOrDirectory> RestoreFileContentData(
nsPIDOMWindowInner* aWindow, const nsTArray<FileContentData>& aData) {
nsTArray<OwningFileOrDirectory> res(aData.Length());
for (auto& it : aData) {
for (const auto& it : aData) {
if (it.type() == FileContentData::TBlobImpl) {
if (!it.get_BlobImpl()) {
// Serialization failed, skip this file.
@ -6810,19 +6808,27 @@ void HTMLInputElement::InitializeKeyboardEventListeners() {
}
}
void HTMLInputElement::OnValueChanged(ValueChangeKind aKind) {
void HTMLInputElement::OnValueChanged(ValueChangeKind aKind,
bool aNewValueEmpty,
const nsAString* aKnownNewValue) {
MOZ_ASSERT_IF(aKnownNewValue, aKnownNewValue->IsEmpty() == aNewValueEmpty);
if (aKind != ValueChangeKind::Internal) {
mLastValueChangeWasInteractive = aKind == ValueChangeKind::UserInteraction;
}
if (aNewValueEmpty) {
AddStates(ElementState::VALUE_EMPTY);
} else {
RemoveStates(ElementState::VALUE_EMPTY);
}
UpdateAllValidityStates(true);
if (HasDirAuto()) {
SetDirectionFromValue(true);
SetDirectionFromValue(true, aKnownNewValue);
}
// :placeholder-shown and value-empty pseudo-class may change when the value
// changes.
// :placeholder-shown pseudo-class may change when the value changes.
UpdateState(true);
}

View File

@ -242,7 +242,8 @@ class HTMLInputElement final : public TextControlElement,
void EnablePreview() override;
bool IsPreviewEnabled() override;
void InitializeKeyboardEventListeners() override;
void OnValueChanged(ValueChangeKind) override;
void OnValueChanged(ValueChangeKind, bool aNewValueEmpty,
const nsAString* aKnownNewValue) override;
void GetValueFromSetRangeText(nsAString& aValue) override;
MOZ_CAN_RUN_SCRIPT nsresult
SetValueFromSetRangeText(const nsAString& aValue) override;
@ -851,7 +852,9 @@ class HTMLInputElement final : public TextControlElement,
*
* @return whether the current value is the empty string.
*/
bool IsValueEmpty() const;
bool IsValueEmpty() const {
return State().HasState(ElementState::VALUE_EMPTY);
}
// Parse a simple (hex) color.
static mozilla::Maybe<nscolor> ParseSimpleColor(const nsAString& aColor);
@ -1092,7 +1095,12 @@ class HTMLInputElement final : public TextControlElement,
MOZ_CAN_RUN_SCRIPT
nsresult SetDefaultValueAsValue();
void SetDirectionFromValue(bool aNotify);
/**
* Sets the direction from the input value. if aKnownValue is provided, it
* saves a GetValue call.
*/
void SetDirectionFromValue(bool aNotify,
const nsAString* aKnownValue = nullptr);
/**
* Return if an element should have a specific validity UI

View File

@ -69,7 +69,7 @@ HTMLTextAreaElement::HTMLTextAreaElement(
// until someone calls UpdateEditableState on us, apparently! Also
// by default we don't have to show validity UI and so forth.
AddStatesSilently(ElementState::ENABLED | ElementState::OPTIONAL_ |
ElementState::VALID);
ElementState::VALID | ElementState::VALUE_EMPTY);
}
HTMLTextAreaElement::~HTMLTextAreaElement() {
@ -784,7 +784,7 @@ ElementState HTMLTextAreaElement::IntrinsicState() const {
}
}
if (HasAttr(nsGkAtoms::placeholder) && IsValueEmpty()) {
if (IsValueEmpty() && HasAttr(nsGkAtoms::placeholder)) {
state |= ElementState::PLACEHOLDER_SHOWN;
}
@ -948,13 +948,6 @@ bool HTMLTextAreaElement::IsMutable() const {
return (!HasAttr(kNameSpaceID_None, nsGkAtoms::readonly) && !IsDisabled());
}
bool HTMLTextAreaElement::IsValueEmpty() const {
nsAutoString value;
GetValueInternal(value, true);
return value.IsEmpty();
}
void HTMLTextAreaElement::SetCustomValidity(const nsAString& aError) {
ConstraintValidation::SetCustomValidity(aError);
@ -1001,7 +994,6 @@ bool HTMLTextAreaElement::IsValueMissing() const {
if (!Required() || !IsMutable()) {
return false;
}
return IsValueEmpty();
}
@ -1121,18 +1113,28 @@ void HTMLTextAreaElement::InitializeKeyboardEventListeners() {
mState->InitializeKeyboardEventListeners();
}
void HTMLTextAreaElement::OnValueChanged(ValueChangeKind aKind) {
void HTMLTextAreaElement::OnValueChanged(ValueChangeKind aKind,
bool aNewValueEmpty,
const nsAString*) {
if (aKind != ValueChangeKind::Internal) {
mLastValueChangeWasInteractive = aKind == ValueChangeKind::UserInteraction;
}
const bool emptyBefore = IsValueEmpty();
if (aNewValueEmpty) {
AddStates(ElementState::VALUE_EMPTY);
} else {
RemoveStates(ElementState::VALUE_EMPTY);
}
// Update the validity state
bool validBefore = IsValid();
const bool validBefore = IsValid();
UpdateTooLongValidityState();
UpdateTooShortValidityState();
UpdateValueMissingValidityState();
if (validBefore != IsValid() || HasAttr(nsGkAtoms::placeholder)) {
if (validBefore != IsValid() ||
(emptyBefore != IsValueEmpty() && HasAttr(nsGkAtoms::placeholder))) {
UpdateState(true);
}
}

View File

@ -97,7 +97,8 @@ class HTMLTextAreaElement final : public TextControlElement,
void EnablePreview() override;
bool IsPreviewEnabled() override;
void InitializeKeyboardEventListeners() override;
void OnValueChanged(ValueChangeKind) override;
void OnValueChanged(ValueChangeKind, bool aNewValueEmpty,
const nsAString* aKnownNewValue) override;
void GetValueFromSetRangeText(nsAString& aValue) override;
MOZ_CAN_RUN_SCRIPT nsresult
SetValueFromSetRangeText(const nsAString& aValue) override;
@ -377,7 +378,9 @@ class HTMLTextAreaElement final : public TextControlElement,
*
* @return whether the current value is the empty string.
*/
bool IsValueEmpty() const;
bool IsValueEmpty() const {
return State().HasState(ElementState::VALUE_EMPTY);
}
/**
* A helper to get the current selection range. Will throw on the ErrorResult

View File

@ -182,8 +182,16 @@ class TextControlElement : public nsGenericHTMLFormControlElementWithState {
/**
* Callback called whenever the value is changed.
*
* aKnownNewValue can be used to avoid value lookups if present (might be
* null, if the caller doesn't know the specific value that got set).
*/
virtual void OnValueChanged(ValueChangeKind) = 0;
virtual void OnValueChanged(ValueChangeKind, bool aNewValueEmpty,
const nsAString* aKnownNewValue) = 0;
void OnValueChanged(ValueChangeKind aKind, const nsAString& aNewValue) {
return OnValueChanged(aKind, aNewValue.IsEmpty(), &aNewValue);
}
/**
* Helpers for value manipulation from SetRangeText.

View File

@ -1032,14 +1032,14 @@ nsresult TextInputListener::OnEditActionHandled(TextEditor& aTextEditor) {
}
if (weakFrame.IsAlive()) {
HandleValueChanged();
HandleValueChanged(aTextEditor);
}
}
return mTextControlState ? mTextControlState->OnEditActionHandled() : NS_OK;
}
void TextInputListener::HandleValueChanged() {
void TextInputListener::HandleValueChanged(TextEditor& aTextEditor) {
// Make sure we know we were changed (do NOT set this to false if there are
// no undo items; JS could change the value and we'd still need to save it)
if (mSetValueChanged) {
@ -1050,7 +1050,8 @@ void TextInputListener::HandleValueChanged() {
// NOTE(emilio): execCommand might get here even though it might not be a
// "proper" user-interactive change. Might be worth reconsidering which
// ValueChangeKind are we passing down.
mTxtCtrlElement->OnValueChanged(ValueChangeKind::UserInteraction);
mTxtCtrlElement->OnValueChanged(ValueChangeKind::UserInteraction,
aTextEditor.IsEmpty(), nullptr);
if (mTextControlState) {
mTextControlState->ClearLastInteractiveValue();
}
@ -2711,7 +2712,8 @@ bool TextControlState::SetValue(const nsAString& aValue,
// If we were handling SetValue() before, don't update the DOM state twice,
// just let the outer call do so.
if (!wasHandlingSetValue) {
handlingSetValue.GetTextControlElement()->OnValueChanged(changeKind);
handlingSetValue.GetTextControlElement()->OnValueChanged(
changeKind, handlingSetValue.GetSettingValue());
}
return true;
}
@ -2973,7 +2975,8 @@ bool TextControlState::SetValueWithoutTextEditor(
// Update validity state before dispatching "input" event for its
// listeners like `EditorBase::NotifyEditorObservers()`.
aHandlingSetValue.GetTextControlElement()->OnValueChanged(
ValueChangeKind::UserInteraction);
ValueChangeKind::UserInteraction,
aHandlingSetValue.GetSettingValue());
ClearLastInteractiveValue();
@ -3000,20 +3003,6 @@ bool TextControlState::SetValueWithoutTextEditor(
return true;
}
bool TextControlState::HasNonEmptyValue() const {
// If the frame for editor is alive, we can compute it with mTextEditor.
// Otherwise, we need to check cached value via GetValue().
if (mTextEditor && mBoundFrame && mEditorInitialized &&
!(mHandlingState &&
mHandlingState->IsHandling(TextControlAction::CommitComposition))) {
return !mTextEditor->IsEmpty();
}
nsAutoString value;
GetValue(value, true);
return !value.IsEmpty();
}
void TextControlState::InitializeKeyboardEventListeners() {
// register key listeners
EventListenerManager* manager =

View File

@ -294,7 +294,6 @@ class TextControlState final : public SupportsWeakPtr {
* nsContentUtils::PlatformToDOMLineBreaks().
*/
bool ValueEquals(const nsAString& aValue) const;
bool HasNonEmptyValue() const;
// The following methods are for textarea element to use whether default
// value or not.
// XXX We might have to add assertion when it is into editable,

View File

@ -40,13 +40,12 @@ class TextInputListener final : public nsIDOMEventListener,
* aFrame is an optional pointer to our frame, if not passed the method will
* use mFrame to compute it lazily.
*/
void HandleValueChanged();
void HandleValueChanged(TextEditor&);
/**
* OnEditActionHandled() is called when the editor handles each edit action.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
OnEditActionHandled(TextEditor& aTextEditor);
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult OnEditActionHandled(TextEditor&);
/**
* OnSelectionChange() is called when selection is changed in the editor.