Bug 854538 - Add a ::-moz-range-progress pseudo-element to <input type=range> for Firefox OS. r=dholbert
☠☠ backed out by cee46001a526 ☠ ☠
authorJonathan Watt <jwatt@jwatt.org>
Tue, 26 Mar 2013 23:04:41 +0000
changeset 136715 e912de49c12b055392430cd46ce203fa1b1680ec
parent 136714 96f2990b1124679a9a2f2b581b820fb3179a7c26
child 136716 9a33ff156a4547315c5ce35432faedf96dc11cd3
push id2452
push userlsblakk@mozilla.com
push dateMon, 13 May 2013 16:59:38 +0000
treeherdermozilla-beta@d4b152d29d8d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert
bugs854538
milestone22.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 854538 - Add a ::-moz-range-progress pseudo-element to <input type=range> for Firefox OS. r=dholbert
content/html/content/src/nsHTMLInputElement.cpp
layout/forms/nsRangeFrame.cpp
layout/forms/nsRangeFrame.h
layout/style/forms.css
layout/style/nsCSSPseudoElementList.h
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -2597,33 +2597,33 @@ nsHTMLInputElement::CancelRangeThumbDrag
     // Don't dispatch an 'input' event - at least not using
     // DispatchTrustedEvent.
     // TODO: decide what we should do here - bug 851782.
     nsAutoString val;
     ConvertNumberToString(mRangeThumbDragStartValue, val);
     SetValueInternal(val, true, true);
     nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
     if (frame) {
-      frame->UpdateThumbPositionForValueChange();
+      frame->UpdateForValueChange();
     }
   }
   mIsDraggingRange = false;
 }
 
 void
 nsHTMLInputElement::SetValueOfRangeForUserEvent(double aValue)
 {
   MOZ_ASSERT(MOZ_DOUBLE_IS_FINITE(aValue));
 
   nsAutoString val;
   ConvertNumberToString(aValue, val);
   SetValueInternal(val, true, true);
   nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
   if (frame) {
-    frame->UpdateThumbPositionForValueChange();
+    frame->UpdateForValueChange();
   }
   nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
                                        static_cast<nsIDOMHTMLInputElement*>(this),
                                        NS_LITERAL_STRING("input"), true,
                                        false);
 }
 
 static bool
--- a/layout/forms/nsRangeFrame.cpp
+++ b/layout/forms/nsRangeFrame.cpp
@@ -58,69 +58,73 @@ nsRangeFrame::DestroyFrom(nsIFrame* aDes
                "need to call RegUnregAccessKey only for the first.");
   nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
   nsContentUtils::DestroyAnonymousContent(&mTrackDiv);
   nsContentUtils::DestroyAnonymousContent(&mThumbDiv);
   nsContainerFrame::DestroyFrom(aDestructRoot);
 }
 
 nsresult
-nsRangeFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
+nsRangeFrame::MakeAnonymousDiv(nsIContent** aResult,
+                               nsCSSPseudoElements::Type aPseudoType,
+                               nsTArray<ContentInfo>& aElements)
 {
   // Get the NodeInfoManager and tag necessary to create the anonymous divs.
   nsCOMPtr<nsIDocument> doc = mContent->GetDocument();
 
-  // Create the track div:
   nsCOMPtr<nsINodeInfo> nodeInfo;
   nodeInfo = doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::div, nullptr,
                                                  kNameSpaceID_XHTML,
                                                  nsIDOMNode::ELEMENT_NODE);
   NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
-  nsresult rv = NS_NewHTMLElement(getter_AddRefs(mTrackDiv), nodeInfo.forget(),
+  nsresult rv = NS_NewHTMLElement(aResult, nodeInfo.forget(),
                                   mozilla::dom::NOT_FROM_PARSER);
   NS_ENSURE_SUCCESS(rv, rv);
-  // Associate ::-moz-range-track pseudo-element to the anonymous child.
-  nsCSSPseudoElements::Type pseudoType =
-    nsCSSPseudoElements::ePseudo_mozRangeTrack;
+  // Associate the pseudo-element with the anonymous child.
   nsRefPtr<nsStyleContext> newStyleContext =
     PresContext()->StyleSet()->ResolvePseudoElementStyle(mContent->AsElement(),
-                                                         pseudoType,
+                                                         aPseudoType,
                                                          StyleContext());
 
-  if (!aElements.AppendElement(ContentInfo(mTrackDiv, newStyleContext))) {
+  if (!aElements.AppendElement(ContentInfo(*aResult, newStyleContext))) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
+  return NS_OK;
+}
 
-  // Create the thumb div:
-  nodeInfo = doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::div, nullptr,
-                                                 kNameSpaceID_XHTML,
-                                                 nsIDOMNode::ELEMENT_NODE);
-  NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
-  rv = NS_NewHTMLElement(getter_AddRefs(mThumbDiv), nodeInfo.forget(),
-                         mozilla::dom::NOT_FROM_PARSER);
+nsresult
+nsRangeFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
+{
+  nsresult rv;
+
+  // Create the ::-moz-range-track pseuto-element (a div):
+  rv = MakeAnonymousDiv(getter_AddRefs(mTrackDiv),
+                        nsCSSPseudoElements::ePseudo_mozRangeTrack,
+                        aElements);
   NS_ENSURE_SUCCESS(rv, rv);
-  // Associate ::-moz-range-thumb pseudo-element to the anonymous child.
-  pseudoType = nsCSSPseudoElements::ePseudo_mozRangeThumb;
-  newStyleContext =
-    PresContext()->StyleSet()->ResolvePseudoElementStyle(mContent->AsElement(),
-                                                         pseudoType,
-                                                         StyleContext());
 
-  if (!aElements.AppendElement(ContentInfo(mThumbDiv, newStyleContext))) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
+  // Create the ::-moz-range-progress pseudo-element (a div):
+  rv = MakeAnonymousDiv(getter_AddRefs(mProgressDiv),
+                        nsCSSPseudoElements::ePseudo_mozRangeProgress,
+                        aElements);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  return NS_OK;
+  // Create the ::-moz-range-thumb pseudo-element (a div):
+  rv = MakeAnonymousDiv(getter_AddRefs(mThumbDiv),
+                        nsCSSPseudoElements::ePseudo_mozRangeThumb,
+                        aElements);
+  return rv;
 }
 
 void
 nsRangeFrame::AppendAnonymousContentTo(nsBaseContentList& aElements,
                                        uint32_t aFilter)
 {
   aElements.MaybeAppendElement(mTrackDiv);
+  aElements.MaybeAppendElement(mProgressDiv);
   aElements.MaybeAppendElement(mThumbDiv);
 }
 
 void
 nsRangeFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                const nsRect&           aDirtyRect,
                                const nsDisplayListSet& aLists)
 {
@@ -131,18 +135,19 @@ NS_IMETHODIMP
 nsRangeFrame::Reflow(nsPresContext*           aPresContext,
                      nsHTMLReflowMetrics&     aDesiredSize,
                      const nsHTMLReflowState& aReflowState,
                      nsReflowStatus&          aStatus)
 {
   DO_GLOBAL_REFLOW_COUNT("nsRangeFrame");
   DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
 
-  NS_ASSERTION(mTrackDiv, "Track div must exist!");
-  NS_ASSERTION(mThumbDiv, "Thumb div must exist!");
+  NS_ASSERTION(mTrackDiv, "::-moz-range-track div must exist!");
+  NS_ASSERTION(mProgressDiv, "::-moz-range-progress div must exist!");
+  NS_ASSERTION(mThumbDiv, "::-moz-range-thumb div must exist!");
   NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
                "nsRangeFrame should not have continuations; if it does we "
                "need to call RegUnregAccessKey only for the first.");
 
   if (mState & NS_FRAME_FIRST_REFLOW) {
     nsFormControlFrame::RegUnRegAccessKey(this, true);
   }
 
@@ -161,16 +166,21 @@ nsRangeFrame::Reflow(nsPresContext*     
 
   aDesiredSize.SetOverflowAreasToDesiredBounds();
 
   nsIFrame* trackFrame = mTrackDiv->GetPrimaryFrame();
   if (trackFrame) {
     ConsiderChildOverflow(aDesiredSize.mOverflowAreas, trackFrame);
   }
 
+  nsIFrame* rangeProgressFrame = mProgressDiv->GetPrimaryFrame();
+  if (rangeProgressFrame) {
+    ConsiderChildOverflow(aDesiredSize.mOverflowAreas, rangeProgressFrame);
+  }
+
   nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
   if (thumbFrame) {
     ConsiderChildOverflow(aDesiredSize.mOverflowAreas, thumbFrame);
   }
 
   FinishAndStoreOverflow(&aDesiredSize);
 
   aStatus = NS_FRAME_COMPLETE;
@@ -260,16 +270,44 @@ nsRangeFrame::ReflowAnonymousContent(nsP
     rv = FinishReflowChild(thumbFrame, aPresContext, &thumbReflowState,
                            thumbDesiredSize, 0, 0, 0);
     NS_ENSURE_SUCCESS(rv, rv);
 
     DoUpdateThumbPosition(thumbFrame, nsSize(aDesiredSize.width,
                                              aDesiredSize.height));
   }
 
+  nsIFrame* rangeProgressFrame = mProgressDiv->GetPrimaryFrame();
+
+  if (rangeProgressFrame) { // display:none?
+    nsHTMLReflowState progressReflowState(aPresContext, aReflowState,
+                                          rangeProgressFrame,
+                                          nsSize(aReflowState.ComputedWidth(),
+                                                 NS_UNCONSTRAINEDSIZE));
+
+    // We first reflow the range-progress frame at {0,0} to obtain its
+    // unadjusted dimensions, then we adjust it to so that the appropriate edge
+    // ends at the thumb.
+
+    nsReflowStatus frameStatus = NS_FRAME_COMPLETE;
+    nsHTMLReflowMetrics progressDesiredSize;
+    nsresult rv = ReflowChild(rangeProgressFrame, aPresContext,
+                              progressDesiredSize, progressReflowState, 0, 0,
+                              0, frameStatus);
+    NS_ENSURE_SUCCESS(rv, rv);
+    MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(frameStatus),
+               "We gave our child unconstrained height, so it should be complete");
+    rv = FinishReflowChild(rangeProgressFrame, aPresContext,
+                           &progressReflowState, progressDesiredSize, 0, 0, 0);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    DoUpdateRangeProgressFrame(rangeProgressFrame, nsSize(aDesiredSize.width,
+                                                          aDesiredSize.height));
+  }
+
   return NS_OK;
 }
 
 double
 nsRangeFrame::GetValueAsFractionOfRange()
 {
   MOZ_ASSERT(mContent->IsHTML(nsGkAtoms::input), "bad cast");
   nsHTMLInputElement* input = static_cast<nsHTMLInputElement*>(mContent);
@@ -377,26 +415,32 @@ nsRangeFrame::GetValueAtEventPoint(nsGUI
     fraction = 1.0 - (posOfPoint - posAtStart) / double(traversableDistance);
   }
 
   MOZ_ASSERT(fraction >= 0.0 && fraction <= 1.0);
   return minimum + fraction * range;
 }
 
 void
-nsRangeFrame::UpdateThumbPositionForValueChange()
+nsRangeFrame::UpdateForValueChange()
 {
   if (NS_SUBTREE_DIRTY(this)) {
     return; // we're going to be updated when we reflow
   }
+  nsIFrame* rangeProgressFrame = mProgressDiv->GetPrimaryFrame();
   nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
-  if (!thumbFrame) {
+  if (!rangeProgressFrame && !thumbFrame) {
     return; // diplay:none?
   }
-  DoUpdateThumbPosition(thumbFrame, GetSize());
+  if (rangeProgressFrame) {
+    DoUpdateRangeProgressFrame(rangeProgressFrame, GetSize());
+  }
+  if (thumbFrame) {
+    DoUpdateThumbPosition(thumbFrame, GetSize());
+  }
   if (IsThemed()) {
     // We don't know the exact dimensions or location of the thumb when native
     // theming is applied, so we just repaint the entire range.
     InvalidateFrame();
   }
   SchedulePaint();
 }
 
@@ -444,16 +488,61 @@ nsRangeFrame::DoUpdateThumbPosition(nsIF
         rangeContentBoxSize.height - thumbSize.height;
       newPosition.x += (rangeContentBoxSize.width - thumbSize.width)/2;
       newPosition.y += NSToCoordRound((1.0 - fraction) * traversableDistance);
     }
   }
   aThumbFrame->SetPosition(newPosition);
 }
 
+void
+nsRangeFrame::DoUpdateRangeProgressFrame(nsIFrame* aRangeProgressFrame,
+                                         const nsSize& aRangeSize)
+{
+  MOZ_ASSERT(aRangeProgressFrame);
+
+  // The idea here is that we want to position the ::-moz-range-progress
+  // pseudo-element so that the center line running along its length is on the
+  // corresponding center line of the nsRangeFrame's content box. In the other
+  // dimension, we align the "start" edge of the ::-moz-range-progress
+  // pseudo-element's border-box with the corresponding edge of the
+  // nsRangeFrame's content box, and we size the progress element's border-box
+  // to have a length of GetValueAsFractionOfRange() times the nsRangeFrame's
+  // content-box size.
+
+  nsMargin borderAndPadding = GetUsedBorderAndPadding();
+  nsSize progSize = aRangeProgressFrame->GetSize();
+  nsRect progRect(borderAndPadding.left, borderAndPadding.top,
+                  progSize.width, progSize.height);
+
+  nsSize rangeContentBoxSize(aRangeSize);
+  rangeContentBoxSize.width -= borderAndPadding.LeftRight();
+  rangeContentBoxSize.height -= borderAndPadding.TopBottom();
+
+  double fraction = GetValueAsFractionOfRange();
+  MOZ_ASSERT(fraction >= 0.0 && fraction <= 1.0);
+
+  // We are called under Reflow, so we need to pass IsHorizontal a valid rect.
+  nsSize frameSizeOverride(aRangeSize.width, aRangeSize.height);
+  if (IsHorizontal(&frameSizeOverride)) {
+    nscoord progLength = NSToCoordRound(fraction * rangeContentBoxSize.width);
+    if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
+      progRect.x += rangeContentBoxSize.width - progLength;
+    }
+    progRect.y += (rangeContentBoxSize.height - progSize.height)/2;
+    progRect.width = progLength;
+  } else {
+    nscoord progLength = NSToCoordRound(fraction * rangeContentBoxSize.height);
+    progRect.x += (rangeContentBoxSize.width - progSize.width)/2;
+    progRect.y += rangeContentBoxSize.height - progLength;
+    progRect.height = progLength;
+  }
+  aRangeProgressFrame->SetRect(progRect);
+}
+
 NS_IMETHODIMP
 nsRangeFrame::AttributeChanged(int32_t  aNameSpaceID,
                                nsIAtom* aAttribute,
                                int32_t  aModType)
 {
   NS_ASSERTION(mTrackDiv, "The track div must exist!");
   NS_ASSERTION(mThumbDiv, "The thumb div must exist!");
 
@@ -462,27 +551,27 @@ nsRangeFrame::AttributeChanged(int32_t  
         aAttribute == nsGkAtoms::min ||
         aAttribute == nsGkAtoms::max ||
         aAttribute == nsGkAtoms::step) {
       // We want to update the position of the thumb, except in one special
       // case: If the value attribute is being set, it is possible that we are
       // in the middle of a type change away from type=range, under the
       // SetAttr(..., nsGkAtoms::value, ...) call in nsHTMLInputElement::
       // HandleTypeChange. In that case the nsHTMLInputElement's type will
-      // already have changed, and if we call UpdateThumbPositionForValueChange()
+      // already have changed, and if we call UpdateForValueChange()
       // we'll fail the asserts under that call that check the type of our
       // nsHTMLInputElement. Given that we're changing away from being a range
       // and this frame will shortly be destroyed, there's no point in calling
-      // UpdateThumbPositionForValueChange() anyway.
+      // UpdateForValueChange() anyway.
       MOZ_ASSERT(mContent->IsHTML(nsGkAtoms::input), "bad cast");
       bool typeIsRange = static_cast<nsHTMLInputElement*>(mContent)->GetType() ==
                            NS_FORM_INPUT_RANGE;
       MOZ_ASSERT(typeIsRange || aAttribute == nsGkAtoms::value, "why?");
       if (typeIsRange) {
-        UpdateThumbPositionForValueChange();
+        UpdateForValueChange();
       }
     } else if (aAttribute == nsGkAtoms::orient) {
       PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eResize,
                                                    NS_FRAME_IS_DIRTY);
     }
   }
 
   return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
@@ -595,11 +684,13 @@ nsRangeFrame::GetType() const
 bool
 nsRangeFrame::ShouldUseNativeStyle() const
 {
   return (StyleDisplay()->mAppearance == NS_THEME_RANGE) &&
          !PresContext()->HasAuthorSpecifiedRules(const_cast<nsRangeFrame*>(this),
                                                  STYLES_DISABLING_NATIVE_THEMING) &&
          !PresContext()->HasAuthorSpecifiedRules(mTrackDiv->GetPrimaryFrame(),
                                                  STYLES_DISABLING_NATIVE_THEMING) &&
+         !PresContext()->HasAuthorSpecifiedRules(mProgressDiv->GetPrimaryFrame(),
+                                                 STYLES_DISABLING_NATIVE_THEMING) &&
          !PresContext()->HasAuthorSpecifiedRules(mThumbDiv->GetPrimaryFrame(),
                                                  STYLES_DISABLING_NATIVE_THEMING);
 }
--- a/layout/forms/nsRangeFrame.h
+++ b/layout/forms/nsRangeFrame.h
@@ -92,46 +92,63 @@ public:
   /**
    * Returns whether the frame and its child should use the native style.
    */
   bool ShouldUseNativeStyle() const;
 
   double GetValueAtEventPoint(nsGUIEvent* aEvent);
 
   /**
-   * Helper to reposition the thumb and schedule a repaint when the value of
-   * the range changes. (This does not reflow, since the position and size of
-   * the thumb do not affect the position or size of any other frames.)
+   * Helper that's used when the value of the range changes to reposition the
+   * thumb, resize the range-progress element, and schedule a repaint. (This
+   * does not reflow, since the position and size of the thumb and
+   * range-progress element do not affect the position or size of any other
+   * frames.)
    */
-  void UpdateThumbPositionForValueChange();
+  void UpdateForValueChange();
 
 private:
 
+  nsresult MakeAnonymousDiv(nsIContent** aResult,
+                            nsCSSPseudoElements::Type aPseudoType,
+                            nsTArray<ContentInfo>& aElements);
+
   // Helper function which reflows the anonymous div frames.
   nsresult ReflowAnonymousContent(nsPresContext*           aPresContext,
                                   nsHTMLReflowMetrics&     aDesiredSize,
                                   const nsHTMLReflowState& aReflowState);
 
   void DoUpdateThumbPosition(nsIFrame* aThumbFrame,
                              const nsSize& aRangeSize);
 
+  void DoUpdateRangeProgressFrame(nsIFrame* aProgressFrame,
+                                  const nsSize& aRangeSize);
+
   /**
    * Returns the input element's value as a fraction of the difference between
    * the input's minimum and its maximum (i.e. returns 0.0 when the value is
    * the same as the minimum, and returns 1.0 when the value is the same as the
    * maximum).
    */
   double GetValueAsFractionOfRange();
 
   /**
-   * The div used to show the track.
+   * The div used to show the ::-moz-range-track pseudo-element.
    * @see nsRangeFrame::CreateAnonymousContent
    */
   nsCOMPtr<nsIContent> mTrackDiv;
 
   /**
-   * The div used to show the thumb.
+   * The div used to show the ::-moz-range-progress pseudo-element, which is
+   * used to (optionally) style the specific chunk of track leading up to the
+   * thumb's current position.
+   * @see nsRangeFrame::CreateAnonymousContent
+   */
+  nsCOMPtr<nsIContent> mProgressDiv;
+
+  /**
+   * The div used to show the ::-moz-range-thumb pseudo-element.
    * @see nsRangeFrame::CreateAnonymousContent
    */
   nsCOMPtr<nsIContent> mThumbDiv;
 };
 
 #endif
--- a/layout/style/forms.css
+++ b/layout/style/forms.css
@@ -767,16 +767,38 @@ input[type=range][orient=vertical]::-moz
   border-left: solid 0.1em lightgrey;
   border-right: solid 0.1em lightgrey;
   width: 0.2em;
   height: 100%;
 }
 
 /**
  * Layout handles positioning of this pseudo-element specially (so that content
+ * authors can concentrate on styling this pseudo-element without worrying
+ * about the logic to position it). Specifically the 'margin', 'top' and 'left'
+ * properties are ignored. Additionally, if the range is horizontal, the width
+ * property is ignored, and if the range range is vertical, the height property
+ * is ignored.
+ */
+::-moz-range-progress {
+  /* Prevent styling that would change the type of frame we construct. */
+  display: inline-block !important;
+  float: none !important;
+  position: static !important;
+  /* Since one of width/height will be ignored, this just sets the "other"
+     dimension.
+   */
+  width: 0.2em;
+  height: 0.2em;
+  /* Prevent nsFrame::HandlePress setting mouse capture to this element. */
+  -moz-user-select: none ! important;
+}
+
+/**
+ * Layout handles positioning of this pseudo-element specially (so that content
  * authors can concentrate on styling the thumb without worrying about the
  * logic to position it). Specifically the 'margin', 'top' and 'left'
  * properties are ignored.
  */
 input[type=range]::-moz-range-thumb {
   /* Prevent styling that would change the type of frame we construct. */
   display: inline-block !important;
   float: none !important;
--- a/layout/style/nsCSSPseudoElementList.h
+++ b/layout/style/nsCSSPseudoElementList.h
@@ -49,11 +49,12 @@ CSS_PSEUDO_ELEMENT(mozListBullet, ":-moz
 CSS_PSEUDO_ELEMENT(mozListNumber, ":-moz-list-number", 0)
 
 CSS_PSEUDO_ELEMENT(mozMathStretchy, ":-moz-math-stretchy", 0)
 CSS_PSEUDO_ELEMENT(mozMathAnonymous, ":-moz-math-anonymous", 0)
 
 // HTML5 Forms pseudo elements
 CSS_PSEUDO_ELEMENT(mozProgressBar, ":-moz-progress-bar", 0)
 CSS_PSEUDO_ELEMENT(mozRangeTrack, ":-moz-range-track", 0)
+CSS_PSEUDO_ELEMENT(mozRangeProgress, ":-moz-range-progress", 0)
 CSS_PSEUDO_ELEMENT(mozRangeThumb, ":-moz-range-thumb", 0)
 CSS_PSEUDO_ELEMENT(mozMeterBar, ":-moz-meter-bar", 0)
 CSS_PSEUDO_ELEMENT(mozPlaceholder, ":-moz-placeholder", 0)