Bug 854538 - Add a ::-moz-range-progress pseudo-element to <input type=range> for Firefox OS. r=dholbert

This commit is contained in:
Jonathan Watt 2013-03-26 23:04:41 +00:00
parent 06c70a5f97
commit ce9ffb2628
12 changed files with 397 additions and 44 deletions

View File

@ -2603,7 +2603,7 @@ nsHTMLInputElement::CancelRangeThumbDrag(bool aIsForUserEvent)
SetValueInternal(val, true, true);
nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
if (frame) {
frame->UpdateThumbPositionForValueChange();
frame->UpdateForValueChange();
}
}
mIsDraggingRange = false;
@ -2619,7 +2619,7 @@ nsHTMLInputElement::SetValueOfRangeForUserEvent(double aValue)
SetValueInternal(val, true, true);
nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
if (frame) {
frame->UpdateThumbPositionForValueChange();
frame->UpdateForValueChange();
}
nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
static_cast<nsIDOMHTMLInputElement*>(this),

View File

@ -58,64 +58,69 @@ nsRangeFrame::DestroyFrom(nsIFrame* aDestructRoot)
"need to call RegUnregAccessKey only for the first.");
nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
nsContentUtils::DestroyAnonymousContent(&mTrackDiv);
nsContentUtils::DestroyAnonymousContent(&mProgressDiv);
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;
}
// 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);
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;
}
return NS_OK;
}
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);
// Create the ::-moz-range-progress pseudo-element (a div):
rv = MakeAnonymousDiv(getter_AddRefs(mProgressDiv),
nsCSSPseudoElements::ePseudo_mozRangeProgress,
aElements);
NS_ENSURE_SUCCESS(rv, rv);
// 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);
}
@ -136,8 +141,9 @@ nsRangeFrame::Reflow(nsPresContext* aPresContext,
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.");
@ -166,6 +172,11 @@ nsRangeFrame::Reflow(nsPresContext* aPresContext,
ConsiderChildOverflow(aDesiredSize.mOverflowAreas, trackFrame);
}
nsIFrame* rangeProgressFrame = mProgressDiv->GetPrimaryFrame();
if (rangeProgressFrame) {
ConsiderChildOverflow(aDesiredSize.mOverflowAreas, rangeProgressFrame);
}
nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
if (thumbFrame) {
ConsiderChildOverflow(aDesiredSize.mOverflowAreas, thumbFrame);
@ -265,6 +276,34 @@ nsRangeFrame::ReflowAnonymousContent(nsPresContext* aPresContext,
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;
}
@ -382,16 +421,22 @@ nsRangeFrame::GetValueAtEventPoint(nsGUIEvent* aEvent)
}
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.
@ -449,6 +494,51 @@ nsRangeFrame::DoUpdateThumbPosition(nsIFrame* aThumbFrame,
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,
@ -467,17 +557,17 @@ nsRangeFrame::AttributeChanged(int32_t aNameSpaceID,
// 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,
@ -600,6 +690,8 @@ nsRangeFrame::ShouldUseNativeStyle() const
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);
}

View File

@ -97,14 +97,20 @@ public:
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,
@ -113,6 +119,9 @@ private:
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
@ -122,13 +131,21 @@ private:
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;

View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<title>Test ::-moz-range-progress</title>
<style>
div {
margin: 0;
padding: 0;
}
.range {
display: inline-block;
position: relative;
width: 200px;
height: 20px;
background-color: blue;
}
.range-progress {
display: inline-block;
position: absolute;
top: 5px;
width: 50px;
height: 10px;
background-color: lime;
}
</style>
</head>
<body>
<div class="range">
<div class="range-progress"></div>
</div>
</body>
</html>

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<title>Test ::-moz-range-progress</title>
<style>
input[type=range] {
width: 200px;
height: 20px;
margin: 0;
padding: 0;
background-color: blue;
}
input[type=range]::-moz-range-progress {
height: 10px;
background-color: lime;
}
input[type=range]::-moz-range-track,
input[type=range]::-moz-range-thumb {
visibility: hidden;
}
</style>
</head>
<body>
<input type=range value=25>
</body>
</html>

View File

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<title>Test ::-moz-range-progress</title>
<style>
input[type=range] {
width: 200px;
height: 20px;
margin: 0;
padding: 0;
background-color: blue;
}
input[type=range]::-moz-range-track {
border: 0;
height: 10px;
background-color: lime;
}
input[type=range]::-moz-range-thumb {
width: 10px;
height: 10px;
border: 0;
border-radius: 0;
background-image: none;
background-color: yellow;
}
</style>
</head>
<body>
<input type=range value=0>
</body>
</html>

View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<title>Test ::-moz-range-progress</title>
<style>
input[type=range] {
width: 200px;
height: 20px;
margin: 0;
padding: 0;
background-color: blue;
}
input[type=range]::-moz-range-track {
border: 0;
height: 10px;
background-color: lime;
}
input[type=range]::-moz-range-progress {
height: 10px;
background-color: red;
}
input[type=range]::-moz-range-thumb {
width: 10px;
height: 10px;
border: 0;
border-radius: 0;
background-image: none;
background-color: yellow;
}
</style>
</head>
<body>
<input type=range value=0>
</body>
</html>

View File

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<title>Test ::-moz-range-progress</title>
<style>
input[type=range] {
width: 200px;
height: 20px;
margin: 0;
padding: 0;
background-color: blue;
}
input[type=range]::-moz-range-track {
border: 0;
height: 10px;
background-color: lime;
}
input[type=range]::-moz-range-thumb {
width: 10px;
height: 10px;
border: 0;
border-radius: 0;
background-image: none;
background-color: yellow;
}
</style>
</head>
<body>
<input type=range value=100>
</body>
</html>

View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<title>Test ::-moz-range-progress</title>
<style>
input[type=range] {
width: 200px;
height: 20px;
margin: 0;
padding: 0;
background-color: blue;
}
input[type=range]::-moz-range-track {
border: 0;
height: 10px;
background-color: red;
}
input[type=range]::-moz-range-progress {
height: 10px;
background-color: lime;
}
input[type=range]::-moz-range-thumb {
width: 10px;
height: 10px;
border: 0;
border-radius: 0;
background-image: none;
background-color: yellow;
}
</style>
</head>
<body>
<input type=range value=100>
</body>
</html>

View File

@ -17,3 +17,8 @@ default-preferences pref(dom.experimental_forms_range,true)
# 'direction' property:
== input-range-direction-unthemed-1.html input-range-direction-unthemed-1-ref.html
# ::-moz-range-progress pseudo-element:
== input-range-moz-range-progress-1.html input-range-moz-range-progress-1-ref.html
== input-range-moz-range-progress-2.html input-range-moz-range-progress-2-ref.html
== input-range-moz-range-progress-3.html input-range-moz-range-progress-3-ref.html

View File

@ -777,6 +777,28 @@ input[type=range][orient=vertical]::-moz-range-track {
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.
*/
input[type=range]::-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

View File

@ -54,6 +54,7 @@ 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)