Bug 457801 (1/3) - Implement -moz-placeholder pseudo-class. r=dbaron a2.0=blocking

This commit is contained in:
Mounir Lamouri 2010-08-24 21:02:09 +02:00
parent 0ba0a47001
commit 5de293a274
47 changed files with 564 additions and 4 deletions

View File

@ -217,4 +217,7 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsIEventStateManager, NS_IEVENTSTATEMANAGER_IID)
// content has focus and should show a ring
#define NS_EVENT_STATE_FOCUSRING (1 << 29)
// Content shows its placeholder
#define NS_EVENT_STATE_MOZ_PLACEHOLDER (1 << 30)
#endif // nsIEventStateManager_h__

View File

@ -507,7 +507,8 @@ nsHTMLInputElement::AfterSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
NS_EVENT_STATE_OPTIONAL |
NS_EVENT_STATE_VALID |
NS_EVENT_STATE_INVALID |
NS_EVENT_STATE_INDETERMINATE;
NS_EVENT_STATE_INDETERMINATE |
NS_EVENT_STATE_MOZ_PLACEHOLDER;
}
if (aName == nsGkAtoms::required || aName == nsGkAtoms::disabled ||
@ -986,6 +987,15 @@ nsHTMLInputElement::SetValueInternal(const nsAString& aValue,
}
mInputData.mState->SetValue(value, aUserInput);
if (PlaceholderApplies() &&
HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)) {
nsIDocument* doc = GetCurrentDoc();
if (doc) {
mozAutoDocUpdate upd(doc, UPDATE_CONTENT_STATE, PR_TRUE);
doc->ContentStatesChanged(this, nsnull, NS_EVENT_STATE_MOZ_PLACEHOLDER);
}
}
return NS_OK;
}
@ -1647,6 +1657,19 @@ nsHTMLInputElement::PostHandleEvent(nsEventChainPostVisitor& aVisitor)
return NS_OK;
}
if (PlaceholderApplies() &&
HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder) &&
// TODO: checking if the value is empty could be a good idea but we do not
// have a simple way to do that, see bug 585100
(aVisitor.mEvent->message == NS_FOCUS_CONTENT ||
aVisitor.mEvent->message == NS_BLUR_CONTENT)) {
nsIDocument* doc = GetCurrentDoc();
if (doc) {
MOZ_AUTO_DOC_UPDATE(doc, UPDATE_CONTENT_STATE, PR_TRUE);
doc->ContentStatesChanged(this, nsnull, NS_EVENT_STATE_MOZ_PLACEHOLDER);
}
}
// ignore the activate event fired by the "Browse..." button
// (file input controls fire their own) (bug 500885)
if (mType == NS_FORM_INPUT_FILE) {
@ -2796,6 +2819,23 @@ nsHTMLInputElement::IntrinsicState() const
state |= IsValid() ? NS_EVENT_STATE_VALID : NS_EVENT_STATE_INVALID;
}
if (PlaceholderApplies() && HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder) &&
!nsContentUtils::IsFocusedContent((nsIContent*)(this))) {
// TODO: we really need a GetValue(...) const method, see bug 585097
nsTextEditorState* edState = GetEditorState();
nsAutoString value;
if (edState) {
edState->GetValue(value, PR_TRUE);
} else {
GetAttr(kNameSpaceID_None, nsGkAtoms::value, value);
}
if (value.IsEmpty()) {
state |= NS_EVENT_STATE_MOZ_PLACEHOLDER;
}
}
return state;
}
@ -3872,5 +3912,17 @@ NS_IMETHODIMP_(void)
nsHTMLInputElement::OnValueChanged(PRBool aNotify)
{
UpdateAllValidityStates(aNotify);
// :-moz-placeholder pseudo-class may change when the value changes.
// However, we don't want to waste cycles if the state doesn't apply.
if (aNotify && PlaceholderApplies()
&& HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)
&& !nsContentUtils::IsFocusedContent((nsIContent*)(this))) {
nsIDocument* doc = GetCurrentDoc();
if (doc) {
MOZ_AUTO_DOC_UPDATE(doc, UPDATE_CONTENT_STATE, PR_TRUE);
doc->ContentStatesChanged(this, nsnull, NS_EVENT_STATE_MOZ_PLACEHOLDER);
}
}
}

View File

@ -452,6 +452,11 @@ protected:
*/
void SanitizeValue(nsAString& aValue);
/**
* Returns whether the placeholder attribute applies for the current type.
*/
bool PlaceholderApplies() const { return IsSingleLineTextControlInternal(PR_FALSE, mType); }
/**
* Set the current default value to the value of the input element.
*/

View File

@ -546,6 +546,15 @@ nsHTMLTextAreaElement::SetValueChanged(PRBool aValueChanged)
if (!aValueChanged && !mState->IsEmpty()) {
mState->EmptyValue();
}
if (HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)) {
nsIDocument* doc = GetCurrentDoc();
if (doc) {
mozAutoDocUpdate upd(doc, UPDATE_CONTENT_STATE, PR_TRUE);
doc->ContentStatesChanged(this, nsnull, NS_EVENT_STATE_MOZ_PLACEHOLDER);
}
}
return NS_OK;
}
@ -693,6 +702,18 @@ nsHTMLTextAreaElement::PostHandleEvent(nsEventChainPostVisitor& aVisitor)
mHandlingSelect = PR_FALSE;
}
if (HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder) &&
// TODO: checking if the value is empty could be a good idea but we do not
// have a simple way to do that, see bug 585100
(aVisitor.mEvent->message == NS_FOCUS_CONTENT ||
aVisitor.mEvent->message == NS_BLUR_CONTENT)) {
nsIDocument* doc = GetCurrentDoc();
if (doc) {
MOZ_AUTO_DOC_UPDATE(doc, UPDATE_CONTENT_STATE, PR_TRUE);
doc->ContentStatesChanged(this, nsnull, NS_EVENT_STATE_MOZ_PLACEHOLDER);
}
}
// Reset the flag for other content besides this text field
aVisitor.mEvent->flags |= (aVisitor.mItemFlags & NS_NO_CONTENT_DISPATCH)
? NS_EVENT_FLAG_NO_CONTENT_DISPATCH : NS_EVENT_FLAG_NONE;
@ -894,7 +915,6 @@ nsHTMLTextAreaElement::SubmitNamesValues(nsFormSubmission* aFormSubmission)
return rv;
}
NS_IMETHODIMP
nsHTMLTextAreaElement::SaveState()
{
@ -971,6 +991,15 @@ nsHTMLTextAreaElement::IntrinsicState() const
state |= IsValid() ? NS_EVENT_STATE_VALID : NS_EVENT_STATE_INVALID;
}
if (HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder) &&
!nsContentUtils::IsFocusedContent((nsIContent*)(this))) {
nsAutoString value;
GetValueInternal(value, PR_TRUE);
if (value.IsEmpty()) {
state |= NS_EVENT_STATE_MOZ_PLACEHOLDER;
}
}
return state;
}
@ -1308,8 +1337,14 @@ nsHTMLTextAreaElement::OnValueChanged(PRBool aNotify)
if (aNotify) {
nsIDocument* doc = GetCurrentDoc();
if (doc) {
MOZ_AUTO_DOC_UPDATE(doc, UPDATE_CONTENT_STATE, PR_TRUE);
doc->ContentStatesChanged(this, nsnull, NS_EVENT_STATE_VALID |
NS_EVENT_STATE_INVALID);
NS_EVENT_STATE_INVALID |
// We could check if that is
// really needed but considering
// we are already updating the
// state for valid/invalid...
NS_EVENT_STATE_MOZ_PLACEHOLDER);
}
}
}

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html class='reftest-wait'>
<link rel='stylesheet' type='text/css' href='style.css'>
<script>
function loadHandler()
{
document.getElementById('i').placeholder='foo';
document.documentElement.className = '';
}
</script>
<body onload='loadHandler();'>
<input id='i'>
</body>
</html>

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html class='reftest-wait'>
<link rel='stylesheet' type='text/css' href='style.css'>
<script>
function loadHandler()
{
document.getElementById('i').focus();
}
function focusHandler()
{
document.getElementById('i').blur();
}
function blurHandler()
{
document.documentElement.className = '';
}
</script>
<body onload='loadHandler();'>
<input id='i' placeholder='foo' onfocus='focusHandler();' onblur='blurHandler();'>
</body>
</html>

View File

@ -0,0 +1,8 @@
<!DOCTYPE html>
<html>
<link rel='stylesheet' type='text/css' href='style.css'>
<body>
<input type='button'>
</body>
</html>

View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<link rel='stylesheet' type='text/css' href='style.css'>
<body>
<input class='complex-ref' value='foo'>
</body>
</html>

View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<link rel='stylesheet' type='text/css' href='style.css'>
<body>
<input class='complex' placeholder='foo'>
</body>
</html>

View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<link rel='stylesheet' type='text/css' href='style.css'>
<body>
<input class='ref'>
</body>
</html>

View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<link rel='stylesheet' type='text/css' href='style.css'>
<body>
<input placeholder=''>
</body>
</html>

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html class='reftest-wait'>
<script>
function loadHandler()
{
document.getElementById('i').focus();
}
function focusHandler()
{
document.documentElement.className = '';
}
</script>
<body onload='loadHandler();'>
<input id='i' onfocus='focusHandler();'>
</body>
</html>

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html class='reftest-wait'>
<link rel='stylesheet' type='text/css' href='style.css'>
<script>
function loadHandler()
{
document.getElementById('i').focus();
}
function focusHandler()
{
document.documentElement.className = '';
}
</script>
<body onload='loadHandler();'>
<input id='i' placeholder='foo' onfocus='focusHandler();'>
</body>
</html>

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html class='reftest-wait'>
<link rel='stylesheet' type='text/css' href='style.css'>
<script>
function loadHandler()
{
document.getElementById('i').removeAttribute('placeholder');
document.documentElement.className = '';
}
</script>
<body onload='loadHandler();'>
<input id='i' placeholder='foo'>
</body>
</html>

View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<link rel='stylesheet' type='text/css' href='style.css'>
<body>
<input class='ref' value='foo'>
</body>
</html>

View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<link rel='stylesheet' type='text/css' href='style.css'>
<body>
<input placeholder='foo'>
</body>
</html>

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html class='reftest-wait'>
<link rel='stylesheet' type='text/css' href='style.css'>
<script>
function loadHandler()
{
document.getElementById('i').type = 'text';
document.documentElement.className = '';
}
</script>
<body onload='loadHandler();'>
<input type='button' id='i' placeholder='foo'>
</body>
</html>

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html class='reftest-wait'>
<link rel='stylesheet' type='text/css' href='style.css'>
<script>
function loadHandler()
{
document.getElementById('i').type = 'button';
document.documentElement.className = '';
}
</script>
<body onload='loadHandler();'>
<input id='i' placeholder='foo'>
</body>
</html>

View File

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<input value='bar'>
</body>
</html>

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html class='reftest-wait'>
<link rel='stylesheet' type='text/css' href='style.css'>
<script>
function loadHandler()
{
document.getElementById('i').value = 'bar';
document.forms[0].reset();
document.documentElement.className = '';
}
</script>
<body onload='loadHandler();'>
<form>
<input id='i' placeholder='foo'>
</form>
</body>
</html>

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html class='reftest-wait'>
<link rel='stylesheet' type='text/css' href='style.css'>
<script>
function loadHandler()
{
document.getElementById('i').value = 'bar';
document.documentElement.className = '';
}
</script>
<body onload='loadHandler();'>
<input id='i' placeholder='foo'>
</body>
</html>

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html class='reftest-wait'>
<link rel='stylesheet' type='text/css' href='style.css'>
<script>
function loadHandler()
{
document.getElementById('i').value = '';
document.documentElement.className = '';
}
</script>
<body onload='loadHandler();'>
<input id='i' placeholder='foo' value='bar'>
</body>
</html>

View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<link rel='stylesheet' type='text/css' href='style.css'>
<body>
<input placeholder='foo' value='bar'>
</body>
</html>

View File

@ -0,0 +1,13 @@
== placeholder-simple.html placeholder-simple-ref.html
== placeholder-focus.html placeholder-focus-ref.html
== placeholder-blur.html placeholder-simple-ref.html
== placeholder-value.html placeholder-value-ref.html
== placeholder-empty-string.html placeholder-empty-string-ref.html
== placeholder-complex.html placeholder-complex-ref.html
== placeholder-add.html placeholder-simple-ref.html
== placeholder-removal.html data:text/html,<input>
== placeholder-value-set.html placeholder-value-ref.html
== placeholder-value-unset.html placeholder-simple-ref.html
== placeholder-value-reset.html placeholder-simple-ref.html
== placeholder-type-change-1.html placeholder-simple-ref.html
== placeholder-type-change-2.html placeholder-button-ref.html

View File

@ -0,0 +1,15 @@
input:-moz-placeholder,
input.ref {
color: black;
background-color: green;
}
input:-moz-placeholder.complex,
input.complex-ref {
color: green;
background-color: #c7c7c7;
font-style: italic;
border: 2px solid green;
height: 200px;
width: 200px;
}

View File

@ -0,0 +1,2 @@
include input/reftest.list
include textarea/reftest.list

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html class='reftest-wait'>
<link rel='stylesheet' type='text/css' href='style.css'>
<script>
function loadHandler()
{
document.getElementById('t').placeholder='foo';
document.documentElement.className = '';
}
</script>
<body onload='loadHandler();'>
<textarea id='t'></textarea>
</body>
</html>

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html class='reftest-wait'>
<link rel='stylesheet' type='text/css' href='style.css'>
<script>
function loadHandler()
{
document.getElementById('t').focus();
}
function focusHandler()
{
document.getElementById('t').blur();
}
function blurHandler()
{
document.documentElement.className = '';
}
</script>
<body onload='loadHandler();'>
<textarea id='t' placeholder='foo' onfocus='focusHandler();' onblur='blurHandler();'></textarea>
</body>
</html>

View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<link rel='stylesheet' type='text/css' href='style.css'>
<body>
<textarea class='complex-ref'>foo</textarea>
</body>
</html>

View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<link rel='stylesheet' type='text/css' href='style.css'>
<body>
<textarea class='complex' placeholder='foo'></textarea>
</body>
</html>

View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<link rel='stylesheet' type='text/css' href='style.css'>
<body>
<textarea class='ref'></textarea>
</body>
</html>

View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<link rel='stylesheet' type='text/css' href='style.css'>
<body>
<textarea placeholder=''></textarea>
</body>
</html>

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html class='reftest-wait'>
<script>
function loadHandler()
{
document.getElementById('t').focus();
}
function focusHandler()
{
document.documentElement.className = '';
}
</script>
<body onload='loadHandler();'>
<textarea id='t' onfocus='focusHandler();'></textarea>
</body>
</html>

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html class='reftest-wait'>
<link rel='stylesheet' type='text/css' href='style.css'>
<script>
function loadHandler()
{
document.getElementById('t').focus();
}
function focusHandler()
{
document.documentElement.className = '';
}
</script>
<body onload='loadHandler();'>
<textarea id='t' placeholder='foo' onfocus='focusHandler();'></textarea>
</body>
</html>

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html class='reftest-wait'>
<link rel='stylesheet' type='text/css' href='style.css'>
<script>
function loadHandler()
{
document.getElementById('t').removeAttribute('placeholder');
document.documentElement.className = '';
}
</script>
<body onload='loadHandler();'>
<textarea id='t' placeholder='foo'></textarea>
</body>
</html>

View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<link rel='stylesheet' type='text/css' href='style.css'>
<body>
<textarea class='ref'>foo</textarea>
</body>
</html>

View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<link rel='stylesheet' type='text/css' href='style.css'>
<body>
<textarea placeholder='foo'></textarea>
</body>
</html>

View File

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<textarea>bar</textarea>
</body>
</html>

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html class='reftest-wait'>
<link rel='stylesheet' type='text/css' href='style.css'>
<script>
function loadHandler()
{
document.getElementById('t').value = 'bar';
document.forms[0].reset();
document.documentElement.className = '';
}
</script>
<body onload='loadHandler();'>
<form>
<textarea id='t' placeholder='foo'></textarea>
</form>
</body>
</html>

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html class='reftest-wait'>
<link rel='stylesheet' type='text/css' href='style.css'>
<script>
function loadHandler()
{
document.getElementById('t').value = 'bar';
document.documentElement.className = '';
}
</script>
<body onload='loadHandler();'>
<textarea id='t' placeholder='foo'></textarea>
</body>
</html>

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html class='reftest-wait'>
<link rel='stylesheet' type='text/css' href='style.css'>
<script>
function loadHandler()
{
document.getElementById('t').value = '';
document.documentElement.className = '';
}
</script>
<body onload='loadHandler();'>
<textarea id='t' placeholder='foo'>bar</textarea>
</body>
</html>

View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<link rel='stylesheet' type='text/css' href='style.css'>
<body>
<textarea placeholder='foo'>bar</textarea>
</body>
</html>

View File

@ -0,0 +1,11 @@
== placeholder-simple.html placeholder-simple-ref.html
== placeholder-focus.html placeholder-focus-ref.html
== placeholder-blur.html placeholder-simple-ref.html
== placeholder-value.html placeholder-value-ref.html
== placeholder-empty-string.html placeholder-empty-string-ref.html
== placeholder-complex.html placeholder-complex-ref.html
== placeholder-add.html placeholder-simple-ref.html
== placeholder-removal.html data:text/html,<textarea></textarea>
== placeholder-value-set.html placeholder-value-ref.html
== placeholder-value-unset.html placeholder-simple-ref.html
== placeholder-value-reset.html placeholder-simple-ref.html

View File

@ -0,0 +1,15 @@
textarea:-moz-placeholder,
textarea.ref {
color: black;
background-color: green;
}
textarea:-moz-placeholder.complex,
textarea.complex-ref {
color: green;
background-color: #c7c7c7;
font-style: italic;
border: 2px solid green;
height: 300px;
width: 300px;
}

View File

@ -65,6 +65,9 @@ include css-namespace/reftest.list
# css parsing
include css-parsing/reftest.list
# css placeholder
include css-placeholder/reftest.list
# css required
include css-required/reftest.list

View File

@ -164,9 +164,13 @@ input > .anonymous-div.inherit-overflow {
overflow: inherit;
}
input:-moz-placeholder,
textarea:-moz-placeholder {
color: GrayText;
}
input > .placeholder,
textarea > .placeholder {
color: GrayText;
overflow: hidden;
pointer-events: none;
resize: none;

View File

@ -174,6 +174,8 @@ CSS_STATE_PSEUDO_CLASS(mozReadOnly, ":-moz-read-only",
NS_EVENT_STATE_MOZ_READONLY)
CSS_STATE_PSEUDO_CLASS(mozReadWrite, ":-moz-read-write",
NS_EVENT_STATE_MOZ_READWRITE)
CSS_STATE_PSEUDO_CLASS(mozPlaceholder, ":-moz-placeholder",
NS_EVENT_STATE_MOZ_PLACEHOLDER)
#ifdef DEFINED_CSS_STATE_PSEUDO_CLASS
#undef DEFINED_CSS_STATE_PSEUDO_CLASS