Bug 1698315 - Manage placeholder and autofill preview visibility using CSS rather than custom code. r=masayuki

Should be much simpler and doesn't need to deal with the different
stuff. We already have pseudo-classes for this, :autofill and
:placeholder-shown.

I initially wrote this because this is the only limitation that forces
us to have the placeholder text as a direct child of the text control
frame. In the end I kept that as-is, but this simplification is still
worth it.

We remove dom.placeholder.show_on_focus because it doesn't behave
correctly (it doesn't match the :placeholder-shown pseudo-class and it
should). It was introduced in bug 807613 and never turned to false by
default. I suspect nobody will miss this, but if somebody complains
about it we can reintroduce it properly (handling the pref in DOM
instead, changing the right state bits).

Differential Revision: https://phabricator.services.mozilla.com/D108304
This commit is contained in:
Emilio Cobos Álvarez 2021-03-15 08:52:43 +00:00
parent 81b14e2ac7
commit 678b10493d
14 changed files with 66 additions and 276 deletions

View File

@ -1093,11 +1093,9 @@ nsresult HTMLInputElement::Clone(dom::NodeInfo* aNodeInfo,
nsAutoString value;
GetNonFileValueInternal(value);
// SetValueInternal handles setting the VALUE_CHANGED bit for us
if (NS_WARN_IF(NS_FAILED(
rv = it->SetValueInternal(
value,
{ValueSetterOption::
UpdateOverlayTextVisibilityAndInvalidateFrame})))) {
if (NS_WARN_IF(
NS_FAILED(rv = it->SetValueInternal(
value, {ValueSetterOption::SetValueChanged})))) {
return rv;
}
}
@ -1616,8 +1614,7 @@ void HTMLInputElement::SetValue(const nsAString& aValue, CallerType aCallerType,
// get the unsanitized value?
nsresult rv = SetValueInternal(
aValue, SanitizesOnValueGetter() ? nullptr : &currentValue,
{ValueSetterOption::ByContentAPI,
ValueSetterOption::UpdateOverlayTextVisibilityAndInvalidateFrame,
{ValueSetterOption::ByContentAPI, ValueSetterOption::SetValueChanged,
ValueSetterOption::MoveCursorToEndIfValueChanged});
if (NS_FAILED(rv)) {
aRv.Throw(rv);
@ -1630,8 +1627,7 @@ void HTMLInputElement::SetValue(const nsAString& aValue, CallerType aCallerType,
} else {
nsresult rv = SetValueInternal(
aValue,
{ValueSetterOption::ByContentAPI,
ValueSetterOption::UpdateOverlayTextVisibilityAndInvalidateFrame,
{ValueSetterOption::ByContentAPI, ValueSetterOption::SetValueChanged,
ValueSetterOption::MoveCursorToEndIfValueChanged});
if (NS_FAILED(rv)) {
aRv.Throw(rv);
@ -2246,9 +2242,9 @@ void HTMLInputElement::SetUserInput(const nsAString& aValue,
GetValueMode() == VALUE_MODE_VALUE && IsSingleLineTextControl(false);
nsresult rv = SetValueInternal(
aValue, {ValueSetterOption::BySetUserInputAPI,
ValueSetterOption::UpdateOverlayTextVisibilityAndInvalidateFrame,
ValueSetterOption::MoveCursorToEndIfValueChanged});
aValue,
{ValueSetterOption::BySetUserInputAPI, ValueSetterOption::SetValueChanged,
ValueSetterOption::MoveCursorToEndIfValueChanged});
NS_ENSURE_SUCCESS_VOID(rv);
if (!isInputEventDispatchedByTextControlState) {
@ -2335,22 +2331,6 @@ nsresult HTMLInputElement::CreateEditor() {
return NS_ERROR_FAILURE;
}
void HTMLInputElement::UpdateOverlayTextVisibility(bool aNotify) {
TextControlState* state = GetEditorState();
if (state) {
state->UpdateOverlayTextVisibility(aNotify);
}
}
bool HTMLInputElement::GetPlaceholderVisibility() {
TextControlState* state = GetEditorState();
if (!state) {
return false;
}
return state->GetPlaceholderVisibility();
}
void HTMLInputElement::SetPreviewValue(const nsAString& aValue) {
TextControlState* state = GetEditorState();
if (state) {
@ -2378,15 +2358,6 @@ void HTMLInputElement::EnablePreview() {
bool HTMLInputElement::IsPreviewEnabled() { return mIsPreviewEnabled; }
bool HTMLInputElement::GetPreviewVisibility() {
TextControlState* state = GetEditorState();
if (!state) {
return false;
}
return state->GetPreviewVisibility();
}
void HTMLInputElement::GetDisplayFileName(nsAString& aValue) const {
MOZ_ASSERT(mFileData);
@ -2667,8 +2638,8 @@ nsresult HTMLInputElement::SetValueInternal(
}
// else DoneCreatingElement calls us again once mDoneCreating is true
const bool setValueChanged = aOptions.contains(
ValueSetterOption::UpdateOverlayTextVisibilityAndInvalidateFrame);
const bool setValueChanged =
aOptions.contains(ValueSetterOption::SetValueChanged);
if (setValueChanged) {
SetValueChanged(true);
}
@ -3381,10 +3352,8 @@ void HTMLInputElement::CancelRangeThumbDrag(bool aIsForUserEvent) {
mInputType->ConvertNumberToString(mRangeThumbDragStartValue, val);
// TODO: What should we do if SetValueInternal fails? (The allocation
// is small, so we should be fine here.)
SetValueInternal(
val,
{ValueSetterOption::BySetUserInputAPI,
ValueSetterOption::UpdateOverlayTextVisibilityAndInvalidateFrame});
SetValueInternal(val, {ValueSetterOption::BySetUserInputAPI,
ValueSetterOption::SetValueChanged});
nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
if (frame) {
frame->UpdateForValueChange();
@ -3404,9 +3373,8 @@ void HTMLInputElement::SetValueOfRangeForUserEvent(Decimal aValue) {
mInputType->ConvertNumberToString(aValue, val);
// TODO: What should we do if SetValueInternal fails? (The allocation
// is small, so we should be fine here.)
SetValueInternal(
val, {ValueSetterOption::BySetUserInputAPI,
ValueSetterOption::UpdateOverlayTextVisibilityAndInvalidateFrame});
SetValueInternal(val, {ValueSetterOption::BySetUserInputAPI,
ValueSetterOption::SetValueChanged});
nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
if (frame) {
frame->UpdateForValueChange();
@ -3496,10 +3464,8 @@ void HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection) {
mInputType->ConvertNumberToString(newValue, newVal);
// TODO: What should we do if SetValueInternal fails? (The allocation
// is small, so we should be fine here.)
SetValueInternal(
newVal,
{ValueSetterOption::BySetUserInputAPI,
ValueSetterOption::UpdateOverlayTextVisibilityAndInvalidateFrame});
SetValueInternal(newVal, {ValueSetterOption::BySetUserInputAPI,
ValueSetterOption::SetValueChanged});
}
static bool SelectTextFieldOnFocus() {
@ -5452,10 +5418,8 @@ void HTMLInputElement::GetValueFromSetRangeText(nsAString& aValue) {
}
nsresult HTMLInputElement::SetValueFromSetRangeText(const nsAString& aValue) {
return SetValueInternal(
aValue,
{ValueSetterOption::ByContentAPI,
ValueSetterOption::UpdateOverlayTextVisibilityAndInvalidateFrame});
return SetValueInternal(aValue, {ValueSetterOption::ByContentAPI,
ValueSetterOption::SetValueChanged});
}
Nullable<uint32_t> HTMLInputElement::GetSelectionStart(ErrorResult& aRv) {
@ -6040,9 +6004,8 @@ bool HTMLInputElement::RestoreState(PresState* aState) {
// TODO: What should we do if SetValueInternal fails? (The allocation
// may potentially be big, but most likely we've failed to allocate
// before the type change.)
SetValueInternal(
inputState.get_TextContentData().value(),
ValueSetterOption::UpdateOverlayTextVisibilityAndInvalidateFrame);
SetValueInternal(inputState.get_TextContentData().value(),
ValueSetterOption::SetValueChanged);
if (inputState.get_TextContentData().lastValueChangeWasInteractive()) {
mLastValueChangeWasInteractive = true;
UpdateState(true);

View File

@ -224,13 +224,10 @@ class HTMLInputElement final : public TextControlElement,
MOZ_CAN_RUN_SCRIPT virtual void UnbindFromFrame(
nsTextControlFrame* aFrame) override;
MOZ_CAN_RUN_SCRIPT virtual nsresult CreateEditor() override;
virtual void UpdateOverlayTextVisibility(bool aNotify) override;
virtual void SetPreviewValue(const nsAString& aValue) override;
virtual void GetPreviewValue(nsAString& aValue) override;
virtual void EnablePreview() override;
virtual bool IsPreviewEnabled() override;
virtual bool GetPlaceholderVisibility() override;
virtual bool GetPreviewVisibility() override;
virtual void InitializeKeyboardEventListeners() override;
virtual void OnValueChanged(ValueChangeKind) override;
virtual void GetValueFromSetRangeText(nsAString& aValue) override;

View File

@ -120,10 +120,9 @@ nsresult HTMLTextAreaElement::Clone(dom::NodeInfo* aNodeInfo,
GetValueInternal(value, true);
// SetValueInternal handles setting mValueChanged for us
if (NS_WARN_IF(NS_FAILED(
rv = it->SetValueInternal(
value, ValueSetterOption::
UpdateOverlayTextVisibilityAndInvalidateFrame)))) {
if (NS_WARN_IF(
NS_FAILED(rv = it->SetValueInternal(
value, {ValueSetterOption::SetValueChanged})))) {
return rv;
}
}
@ -236,16 +235,6 @@ nsresult HTMLTextAreaElement::CreateEditor() {
return mState->PrepareEditor();
}
void HTMLTextAreaElement::UpdateOverlayTextVisibility(bool aNotify) {
MOZ_ASSERT(mState);
mState->UpdateOverlayTextVisibility(aNotify);
}
bool HTMLTextAreaElement::GetPlaceholderVisibility() {
MOZ_ASSERT(mState);
return mState->GetPlaceholderVisibility();
}
void HTMLTextAreaElement::SetPreviewValue(const nsAString& aValue) {
MOZ_ASSERT(mState);
mState->SetPreviewText(aValue, true);
@ -269,21 +258,14 @@ void HTMLTextAreaElement::EnablePreview() {
bool HTMLTextAreaElement::IsPreviewEnabled() { return mIsPreviewEnabled; }
bool HTMLTextAreaElement::GetPreviewVisibility() {
MOZ_ASSERT(mState);
return mState->GetPreviewVisibility();
}
nsresult HTMLTextAreaElement::SetValueInternal(
const nsAString& aValue, const ValueSetterOptions& aOptions) {
MOZ_ASSERT(mState);
// Need to set the value changed flag here if our value has in fact changed
// (i.e. if ValueSetterOption::UpdateOverlayTextVisibilityAndInvalidateFrame
// is in aOptions), so that nsTextControlFrame::UpdateValueDisplay retrieves
// the correct value if needed.
if (aOptions.contains(
ValueSetterOption::UpdateOverlayTextVisibilityAndInvalidateFrame)) {
// (i.e. if ValueSetterOption::SetValueChanged is in aOptions), so that
// retrieves the correct value if needed.
if (aOptions.contains(ValueSetterOption::SetValueChanged)) {
SetValueChanged(true);
}
@ -307,9 +289,9 @@ void HTMLTextAreaElement::SetValue(const nsAString& aValue,
GetValueInternal(currentValue, true);
nsresult rv = SetValueInternal(
aValue, {ValueSetterOption::ByContentAPI,
ValueSetterOption::UpdateOverlayTextVisibilityAndInvalidateFrame,
ValueSetterOption::MoveCursorToEndIfValueChanged});
aValue,
{ValueSetterOption::ByContentAPI, ValueSetterOption::SetValueChanged,
ValueSetterOption::MoveCursorToEndIfValueChanged});
if (NS_WARN_IF(NS_FAILED(rv))) {
aError.Throw(rv);
return;
@ -322,10 +304,9 @@ void HTMLTextAreaElement::SetValue(const nsAString& aValue,
void HTMLTextAreaElement::SetUserInput(const nsAString& aValue,
nsIPrincipal& aSubjectPrincipal) {
SetValueInternal(
aValue, {ValueSetterOption::BySetUserInputAPI,
ValueSetterOption::UpdateOverlayTextVisibilityAndInvalidateFrame,
ValueSetterOption::MoveCursorToEndIfValueChanged});
SetValueInternal(aValue, {ValueSetterOption::BySetUserInputAPI,
ValueSetterOption::SetValueChanged,
ValueSetterOption::MoveCursorToEndIfValueChanged});
}
nsresult HTMLTextAreaElement::SetValueChanged(bool aValueChanged) {
@ -660,10 +641,8 @@ void HTMLTextAreaElement::GetValueFromSetRangeText(nsAString& aValue) {
nsresult HTMLTextAreaElement::SetValueFromSetRangeText(
const nsAString& aValue) {
return SetValueInternal(
aValue,
{ValueSetterOption::ByContentAPI,
ValueSetterOption::UpdateOverlayTextVisibilityAndInvalidateFrame});
return SetValueInternal(aValue, {ValueSetterOption::ByContentAPI,
ValueSetterOption::SetValueChanged});
}
nsresult HTMLTextAreaElement::Reset() {

View File

@ -91,9 +91,6 @@ class HTMLTextAreaElement final : public TextControlElement,
MOZ_CAN_RUN_SCRIPT virtual void UnbindFromFrame(
nsTextControlFrame* aFrame) override;
MOZ_CAN_RUN_SCRIPT virtual nsresult CreateEditor() override;
virtual void UpdateOverlayTextVisibility(bool aNotify) override;
virtual bool GetPlaceholderVisibility() override;
virtual bool GetPreviewVisibility() override;
virtual void SetPreviewValue(const nsAString& aValue) override;
virtual void GetPreviewValue(nsAString& aValue) override;
virtual void EnablePreview() override;

View File

@ -174,22 +174,6 @@ class TextControlElement : public nsGenericHTMLFormElementWithState {
*/
virtual void InitializeKeyboardEventListeners() = 0;
/**
* Update the visibility of both the placholder and preview text based on the
* element's state.
*/
virtual void UpdateOverlayTextVisibility(bool aNotify) = 0;
/**
* Returns the current expected placeholder visibility state.
*/
virtual bool GetPlaceholderVisibility() = 0;
/**
* Returns the current expected preview visibility state.
*/
virtual bool GetPreviewVisibility() = 0;
enum class ValueChangeKind {
Internal,
Script,

View File

@ -1017,12 +1017,10 @@ TextInputListener::HandleEvent(Event* aEvent) {
nsresult TextInputListener::OnEditActionHandled(TextEditor& aTextEditor) {
if (mFrame) {
// XXX Do we still need this or can we just remove the mFrame and
// frame.IsAlive() conditions below?
AutoWeakFrame weakFrame = mFrame;
nsITextControlFrame* frameBase = do_QueryFrame(mFrame);
nsTextControlFrame* frame = static_cast<nsTextControlFrame*>(frameBase);
NS_ASSERTION(frame, "Where is our frame?");
//
// Update the undo / redo menus
//
size_t numUndoItems = aTextEditor.NumberOfUndoItems();
@ -1037,23 +1035,18 @@ nsresult TextInputListener::OnEditActionHandled(TextEditor& aTextEditor) {
}
if (weakFrame.IsAlive()) {
HandleValueChanged(frame);
HandleValueChanged();
}
}
return mTextControlState ? mTextControlState->OnEditActionHandled() : NS_OK;
}
void TextInputListener::HandleValueChanged(nsTextControlFrame* aFrame) {
void TextInputListener::HandleValueChanged() {
// 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) {
if (!aFrame) {
nsITextControlFrame* frameBase = do_QueryFrame(mFrame);
aFrame = static_cast<nsTextControlFrame*>(frameBase);
NS_ASSERTION(aFrame, "Where is our frame?");
}
aFrame->SetValueChanged(true);
mTxtCtrlElement->SetValueChanged(true);
}
if (!mSettingValue) {
@ -1223,8 +1216,8 @@ class MOZ_STACK_CLASS AutoTextControlHandlingState {
// mTextInputListener by ourselves since TextEditor users special path
// for the performance.
mTextInputListener->SettingValue(true);
mTextInputListener->SetValueChanged(mValueSetterOptions.contains(
ValueSetterOption::UpdateOverlayTextVisibilityAndInvalidateFrame));
mTextInputListener->SetValueChanged(
mValueSetterOptions.contains(ValueSetterOption::SetValueChanged));
mEditActionHandled = false;
// Even if falling back to `TextControlState::SetValueWithoutTextEditor()`
// due to editor destruction, it shouldn't dispatch "beforeinput" event
@ -1255,13 +1248,6 @@ class MOZ_STACK_CLASS AutoTextControlHandlingState {
mTextInputListener->SetValueChanged(true);
mTextInputListener->SettingValue(
mParent && mParent->IsHandling(TextControlAction::SetValue));
if (!mValueSetterOptions.contains(
ValueSetterOption::
UpdateOverlayTextVisibilityAndInvalidateFrame)) {
// Listener doesn't update frame, but it is required for
// placeholder
mTextControlState.ValueWasChanged();
}
}
if (!IsOriginalTextControlFrameAlive()) {
return SetValueWithoutTextEditorAgain() ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
@ -1291,15 +1277,13 @@ class MOZ_STACK_CLASS AutoTextControlHandlingState {
return true;
}
// XXX It's odd to drop flags except
// ValueSetterOption::UpdateOverlayTextVisibilityAndInvalidateFrame.
// ValueSetterOption::SetValueChanged.
// Probably, this intended to drop ValueSetterOption::BySetUserInputAPI
// and ValueSetterOption::ByContentAPI, but other flags are added later.
ErrorResult error;
AutoTextControlHandlingState handlingSetValueWithoutEditor(
mTextControlState, TextControlAction::SetValue, mSettingValue,
mOldValue,
mValueSetterOptions &
ValueSetterOption::UpdateOverlayTextVisibilityAndInvalidateFrame,
mOldValue, mValueSetterOptions & ValueSetterOption::SetValueChanged,
error);
if (error.Failed()) {
MOZ_ASSERT(error.ErrorCodeIs(NS_ERROR_OUT_OF_MEMORY));
@ -1402,9 +1386,7 @@ TextControlState::TextControlState(TextControlElement* aOwningElement)
mEverInited(false),
mEditorInitialized(false),
mValueTransferInProgress(false),
mSelectionCached(true),
mPlaceholderVisibility(false),
mPreviewVisibility(false)
mSelectionCached(true)
// When adding more member variable initializations here, add the same
// also to ::Construct.
{
@ -1424,8 +1406,6 @@ TextControlState* TextControlState::Construct(
state->mEditorInitialized = false;
state->mValueTransferInProgress = false;
state->mSelectionCached = true;
state->mPlaceholderVisibility = false;
state->mPreviewVisibility = false;
// When adding more member variable initializations here, add the same
// also to the constructor.
return state;
@ -2850,8 +2830,8 @@ bool TextControlState::SetValueWithoutTextEditor(
mValue.emplace();
}
// We can't just early-return here, because ValueWasChanged and
// OnValueChanged below still need to be called.
// We can't just early-return here, because OnValueChanged below still need to
// be called.
if (!mValue->Equals(aHandlingSetValue.GetSettingValue()) ||
!StaticPrefs::dom_input_skip_cursor_move_for_same_value_set()) {
bool handleSettingValue = true;
@ -2979,7 +2959,6 @@ bool TextControlState::SetValueWithoutTextEditor(
}
}
ValueWasChanged();
return true;
}
@ -3013,8 +2992,6 @@ void TextControlState::InitializeKeyboardEventListeners() {
mSelCon->SetScrollableFrame(mBoundFrame->GetScrollTargetFrame());
}
void TextControlState::ValueWasChanged() { UpdateOverlayTextVisibility(true); }
void TextControlState::SetPreviewText(const nsAString& aValue, bool aNotify) {
// If we don't have a preview div, there's nothing to do.
Element* previewDiv = GetPreviewNode();
@ -3027,8 +3004,6 @@ void TextControlState::SetPreviewText(const nsAString& aValue, bool aNotify) {
nsContentUtils::RemoveNewlines(previewValue);
MOZ_ASSERT(previewDiv->GetFirstChild(), "preview div has no child");
previewDiv->GetFirstChild()->AsText()->SetText(previewValue, aNotify);
UpdateOverlayTextVisibility(aNotify);
}
void TextControlState::GetPreviewText(nsAString& aValue) {
@ -3045,24 +3020,6 @@ void TextControlState::GetPreviewText(nsAString& aValue) {
text->AppendTo(aValue);
}
void TextControlState::UpdateOverlayTextVisibility(bool aNotify) {
nsAutoString value, previewValue;
bool valueIsEmpty = !HasNonEmptyValue();
GetPreviewText(previewValue);
mPreviewVisibility = valueIsEmpty && !previewValue.IsEmpty();
mPlaceholderVisibility = valueIsEmpty && previewValue.IsEmpty();
if (mPlaceholderVisibility && !StaticPrefs::dom_placeholder_show_on_focus()) {
mPlaceholderVisibility =
!nsContentUtils::IsFocusedContent(mTextCtrlElement);
}
if (mBoundFrame && aNotify) {
mBoundFrame->InvalidateFrame();
}
}
bool TextControlState::EditorHasComposition() {
return mTextEditor && mTextEditor->IsIMEComposing();
}

View File

@ -183,8 +183,9 @@ class TextControlState final : public SupportsWeakPtr {
// The value is changed by changing value attribute of the element or
// something like setRangeText().
ByContentAPI,
// Whether the value change should be notified to the frame/contet nor not.
UpdateOverlayTextVisibilityAndInvalidateFrame,
// Whether SetValueChanged should be called as a result of this value
// change.
SetValueChanged,
// Whether to move the cursor to end of the value (in the case when we have
// cached selection offsets), in the case when the value has changed. If
// this is not set and MoveCursorToBeginSetSelectionDirectionForward
@ -266,15 +267,9 @@ class TextControlState final : public SupportsWeakPtr {
}
int32_t GetRows() { return mTextCtrlElement->GetRows(); }
void UpdateOverlayTextVisibility(bool aNotify);
// placeholder methods
bool GetPlaceholderVisibility() { return mPlaceholderVisibility; }
// preview methods
void SetPreviewText(const nsAString& aValue, bool aNotify);
void GetPreviewText(nsAString& aValue);
bool GetPreviewVisibility() { return mPreviewVisibility; }
struct SelectionProperties {
public:
@ -416,8 +411,6 @@ class TextControlState final : public SupportsWeakPtr {
MOZ_CAN_RUN_SCRIPT void UnlinkInternal();
void ValueWasChanged();
MOZ_CAN_RUN_SCRIPT void DestroyEditor();
MOZ_CAN_RUN_SCRIPT void Clear();
@ -473,8 +466,6 @@ class TextControlState final : public SupportsWeakPtr {
bool mValueTransferInProgress; // Whether a value is being transferred to the
// frame
bool mSelectionCached; // Whether mSelectionProperties is valid
bool mPlaceholderVisibility;
bool mPreviewVisibility;
/**
* For avoiding allocation cost of the instance, we should reuse instances

View File

@ -19,6 +19,7 @@ class nsTextControlFrame;
namespace mozilla {
class TextControlElement;
class TextControlState;
class TextEditor;
namespace dom {
class Selection;
@ -39,7 +40,7 @@ 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(nsTextControlFrame* aFrame = nullptr);
void HandleValueChanged();
/**
* OnEditActionHandled() is called when the editor handles each edit action.

View File

@ -13,11 +13,13 @@
#include "nsIEditor.h"
#include "nsCaret.h"
#include "nsCSSPseudoElements.h"
#include "nsDisplayList.h"
#include "nsGenericHTMLElement.h"
#include "nsTextFragment.h"
#include "nsNameSpaceManager.h"
#include "nsIContent.h"
#include "nsIScrollableFrame.h"
#include "nsPresContext.h"
#include "nsGkAtoms.h"
#include "nsLayoutUtils.h"
@ -406,12 +408,6 @@ nsresult nsTextControlFrame::CreateAnonymousContent(
aElements.AppendElement(mRootNode);
CreatePlaceholderIfNeeded();
if (mPlaceholderDiv) {
if (!IsSingleLineTextControl()) {
// For textareas, UpdateValueDisplay doesn't initialize the visibility
// status of the placeholder because it returns early, so we have to
// do that manually here.
textControlElement->UpdateOverlayTextVisibility(true);
}
aElements.AppendElement(mPlaceholderDiv);
}
CreatePreviewIfNeeded();
@ -738,10 +734,6 @@ void nsTextControlFrame::SetFocus(bool aOn, bool aRepaint) {
// If 'dom.placeholeder.show_on_focus' preference is 'false', focusing or
// blurring the frame can have an impact on the placeholder visibility.
if (mPlaceholderDiv) {
textControlElement->UpdateOverlayTextVisibility(true);
}
if (!aOn) {
return;
}
@ -1159,21 +1151,6 @@ void nsTextControlFrame::SetInitialChildList(ChildListID aListID,
}
}
void nsTextControlFrame::SetValueChanged(bool aValueChanged) {
auto* textControlElement = TextControlElement::FromNode(GetContent());
MOZ_ASSERT(textControlElement);
if (mPlaceholderDiv) {
AutoWeakFrame weakFrame(this);
textControlElement->UpdateOverlayTextVisibility(true);
if (!weakFrame.IsAlive()) {
return;
}
}
textControlElement->SetValueChanged(aValueChanged);
}
nsresult nsTextControlFrame::UpdateValueDisplay(bool aNotify,
bool aBeforeEditorInit,
const nsAString* aValue) {
@ -1218,12 +1195,6 @@ nsresult nsTextControlFrame::UpdateValueDisplay(bool aNotify,
// Update the display of the placeholder value and preview text if needed.
// We don't need to do this if we're about to initialize the editor, since
// EnsureEditorInitialized takes care of this.
if ((mPlaceholderDiv || mPreviewDiv) && !aBeforeEditorInit) {
AutoWeakFrame weakFrame(this);
textControlElement->UpdateOverlayTextVisibility(aNotify);
NS_ENSURE_STATE(weakFrame.IsAlive());
}
if (aBeforeEditorInit && value.IsEmpty()) {
if (nsIContent* node = mRootNode->GetFirstChild()) {
mRootNode->RemoveChildNode(node, true);
@ -1295,9 +1266,6 @@ void nsTextControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
*/
DO_GLOBAL_REFLOW_COUNT_DSP("nsTextControlFrame");
auto* control = TextControlElement::FromNode(GetContent());
MOZ_ASSERT(control);
DisplayBorderBackgroundOutline(aBuilder, aLists);
// Redirect all lists to the Content list so that nothing can escape, ie
@ -1306,44 +1274,19 @@ void nsTextControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
nsDisplayList* content = aLists.Content();
nsDisplayListSet set(content, content, content, content, content, content);
// Clip the placeholder and preview text to the root box, so that it doesn't,
// e.g., overlay with our <input type="number"> spin buttons.
//
// For other input types, this will be a noop since we size the root via
// ReflowTextControlChild, which sets the same available space for all
// children.
auto clipToRoot = [&](Maybe<DisplayListClipState::AutoSaveRestore>& aClip) {
if (mRootNode) {
if (auto* root = mRootNode->GetPrimaryFrame()) {
aClip.emplace(aBuilder);
nsRect rootBox(aBuilder->ToReferenceFrame(root), root->GetSize());
aClip->ClipContentDescendants(rootBox);
}
}
};
// We build the ::placeholder first so that it renders below mRootNode which
// draws the caret and we always want that on top (bug 1637476).
if (mPlaceholderDiv && control->GetPlaceholderVisibility() &&
mPlaceholderDiv->GetPrimaryFrame()) {
Maybe<DisplayListClipState::AutoSaveRestore> overlayTextClip;
clipToRoot(overlayTextClip);
//
// TODO(emilio): We should consider just changing the DOM order instead.
if (mPlaceholderDiv && mPlaceholderDiv->GetPrimaryFrame()) {
auto* kid = mPlaceholderDiv->GetPrimaryFrame();
MOZ_ASSERT(kid->GetParent() == this);
BuildDisplayListForChild(aBuilder, kid, set);
}
for (auto* kid : mFrames) {
nsIContent* kidContent = kid->GetContent();
Maybe<DisplayListClipState::AutoSaveRestore> overlayTextClip;
if (kidContent == mPlaceholderDiv) {
continue; // we handled mPlaceholderDiv explicitly above
}
if (kidContent == mPreviewDiv && !control->GetPreviewVisibility()) {
continue;
}
if (kidContent == mPreviewDiv) {
clipToRoot(overlayTextClip);
if (kid->GetContent() == mPlaceholderDiv) {
continue; // Handled above already.
}
BuildDisplayListForChild(aBuilder, kid, set);
}

View File

@ -207,9 +207,7 @@ class nsTextControlFrame : public nsContainerFrame,
void ComputeBaseline(const ReflowInput&, ReflowOutput&);
public: // for methods who access nsTextControlFrame directly
void SetValueChanged(bool aValueChanged);
public:
Element* GetRootNode() const { return mRootNode; }
Element* GetPreviewNode() const { return mPreviewDiv; }

View File

@ -1,20 +0,0 @@
<!DOCTYPE html>
<html class="reftest-wait">
<link rel='stylesheet' type='text/css' href='placeholder-style.css'>
<!-- Test: when focused, element should not placeholder when
'dom.placeholder.show_on_focus' pref is false. -->
<script type="text/javascript">
function focusPlaceholder()
{
document.getElementById('p1').focus();
}
function disableReftestWait()
{
document.documentElement.className = '';
}
</script>
<body onload="focusPlaceholder();">
<input type="text" id="p1" value="" placeholder="my placeholder" onfocus="disableReftestWait();">
</body>
</html>

View File

@ -34,4 +34,3 @@ needs-focus == placeholder-10.html placeholder-visible-ref.html
needs-focus == placeholder-21.html placeholder-blank-ref.html
needs-focus == placeholder-22.html placeholder-blank-ref.html
== placeholder-rtl.html placeholder-rtl-ref.html
pref(dom.placeholder.show_on_focus,false) needs-focus == placeholder-focus-pref.html placeholder-blank-ref.html

View File

@ -155,6 +155,13 @@ textarea > scrollbar {
scrollbar-width: inherit;
-moz-control-character-visibility: visible;
overflow-clip-box: inherit;
visibility: hidden;
}
::-moz-text-control-editing-root,
:placeholder-shown:not(:autofill)::placeholder,
:autofill::-moz-text-control-preview {
visibility: inherit;
}
input::-moz-text-control-editing-root,

View File

@ -2447,12 +2447,6 @@
value: false
mirror: always
# Whether we should show the placeholder when the element is focused but empty.
- name: dom.placeholder.show_on_focus
type: bool
value: true
mirror: always
# Is support for Element.requestPointerLock enabled?
# This is added for accessibility purpose. When user has no way to exit
# pointer lock (e.g. no keyboard available), they can use this pref to