2 * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
3 * Copyright (C) 2010 Google Inc. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
15 * * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 #include "SliderThumbElement.h"
36 #include "CSSValueKeywords.h"
39 #include "HTMLInputElement.h"
40 #include "HTMLParserIdioms.h"
41 #include "MouseEvent.h"
42 #include "RenderDeprecatedFlexibleBox.h"
43 #include "RenderSlider.h"
44 #include "RenderTheme.h"
45 #include "ShadowRoot.h"
46 #include "StepRange.h"
47 #include <wtf/MathExtras.h>
53 inline static double sliderPosition(HTMLInputElement* element)
55 StepRange range(element);
56 return range.proportionFromValue(range.valueFromElement(element));
59 inline static bool hasVerticalAppearance(HTMLInputElement* input)
61 ASSERT(input->renderer());
62 RenderStyle* sliderStyle = input->renderer()->style();
63 return sliderStyle->appearance() == SliderVerticalPart || sliderStyle->appearance() == MediaVolumeSliderPart;
66 SliderThumbElement* sliderThumbElementOf(Node* node)
69 ShadowRoot* shadow = node->toInputElement()->shadowRoot();
71 Node* thumb = shadow->firstChild()->firstChild()->firstChild();
73 return toSliderThumbElement(thumb);
76 // --------------------------------
78 RenderSliderThumb::RenderSliderThumb(Node* node)
83 void RenderSliderThumb::updateAppearance(RenderStyle* parentStyle)
85 if (parentStyle->appearance() == SliderVerticalPart)
86 style()->setAppearance(SliderThumbVerticalPart);
87 else if (parentStyle->appearance() == SliderHorizontalPart)
88 style()->setAppearance(SliderThumbHorizontalPart);
89 else if (parentStyle->appearance() == MediaSliderPart)
90 style()->setAppearance(MediaSliderThumbPart);
91 else if (parentStyle->appearance() == MediaVolumeSliderPart)
92 style()->setAppearance(MediaVolumeSliderThumbPart);
93 if (style()->hasAppearance())
94 theme()->adjustSliderThumbSize(style());
97 bool RenderSliderThumb::isSliderThumb() const
102 void RenderSliderThumb::layout()
104 // Do not cast node() to SliderThumbElement. This renderer is used for
105 // TrackLimitElement too.
106 HTMLInputElement* input = node()->shadowAncestorNode()->toInputElement();
107 bool isVertical = style()->appearance() == SliderThumbVerticalPart || style()->appearance() == MediaVolumeSliderThumbPart;
109 double fraction = sliderPosition(input) * 100;
111 style()->setTop(Length(100 - fraction, Percent));
112 else if (style()->isLeftToRightDirection())
113 style()->setLeft(Length(fraction, Percent));
115 style()->setRight(Length(fraction, Percent));
117 RenderBlock::layout();
120 // --------------------------------
122 // FIXME: Find a way to cascade appearance and adjust heights, and get rid of this class.
123 // http://webkit.org/b/62535
124 class RenderSliderContainer : public RenderDeprecatedFlexibleBox {
126 RenderSliderContainer(Node* node)
127 : RenderDeprecatedFlexibleBox(node) { }
130 virtual void layout();
133 void RenderSliderContainer::layout()
135 HTMLInputElement* input = node()->shadowAncestorNode()->toInputElement();
136 bool isVertical = hasVerticalAppearance(input);
137 style()->setBoxOrient(isVertical ? VERTICAL : HORIZONTAL);
138 // Sets the concrete height if the height of the <input> is not fixed or a
139 // percentage value because a percentage height value of this box won't be
140 // based on the <input> height in such case.
141 Length inputHeight = input->renderer()->style()->height();
142 RenderObject* trackRenderer = node()->firstChild()->renderer();
143 if (!isVertical && input->renderer()->isSlider() && !inputHeight.isFixed() && !inputHeight.isPercent()) {
144 RenderObject* thumbRenderer = input->shadowRoot()->firstChild()->firstChild()->firstChild()->renderer();
146 style()->setHeight(thumbRenderer->style()->height());
148 trackRenderer->style()->setHeight(thumbRenderer->style()->height());
151 style()->setHeight(Length(100, Percent));
153 trackRenderer->style()->setHeight(Length());
156 RenderDeprecatedFlexibleBox::layout();
158 // Percentage 'top' for the thumb doesn't work if the parent style has no
160 Node* track = node()->firstChild();
161 if (track && track->renderer()->isBox()) {
162 RenderBox* trackBox = track->renderBox();
163 trackBox->style()->setHeight(Length(trackBox->height() - trackBox->borderAndPaddingHeight(), Fixed));
167 // --------------------------------
169 void SliderThumbElement::setPositionFromValue()
171 // Since the code to calculate position is in the RenderSliderThumb layout
172 // path, we don't actually update the value here. Instead, we poke at the
173 // renderer directly to trigger layout.
175 renderer()->setNeedsLayout(true);
178 RenderObject* SliderThumbElement::createRenderer(RenderArena* arena, RenderStyle*)
180 return new (arena) RenderSliderThumb(this);
183 bool SliderThumbElement::isEnabledFormControl() const
185 return hostInput()->isEnabledFormControl();
188 bool SliderThumbElement::isReadOnlyFormControl() const
190 return hostInput()->isReadOnlyFormControl();
193 Node* SliderThumbElement::focusDelegate()
198 void SliderThumbElement::dragFrom(const LayoutPoint& point)
200 setPositionFromPoint(point);
204 void SliderThumbElement::setPositionFromPoint(const LayoutPoint& point)
206 HTMLInputElement* input = hostInput();
208 if (!input->renderer() || !renderer())
211 LayoutPoint offset = roundedLayoutPoint(input->renderer()->absoluteToLocal(point, false, true));
212 bool isVertical = hasVerticalAppearance(input);
213 LayoutUnit trackSize;
215 LayoutUnit currentPosition;
216 // We need to calculate currentPosition from absolute points becaue the
217 // renderer for this node is usually on a layer and renderBox()->x() and
219 LayoutPoint absoluteThumbOrigin = renderBox()->absoluteBoundingBoxRect().location();
220 LayoutPoint absoluteSliderContentOrigin = roundedLayoutPoint(input->renderer()->localToAbsolute());
222 trackSize = input->renderBox()->contentHeight() - renderBox()->height();
223 position = offset.y() - renderBox()->height() / 2;
224 currentPosition = absoluteThumbOrigin.y() - absoluteSliderContentOrigin.y();
226 trackSize = input->renderBox()->contentWidth() - renderBox()->width();
227 position = offset.x() - renderBox()->width() / 2;
228 currentPosition = absoluteThumbOrigin.x() - absoluteSliderContentOrigin.x();
230 position = max<LayoutUnit>(0, min(position, trackSize));
231 if (position == currentPosition)
234 StepRange range(input);
235 double fraction = static_cast<double>(position) / trackSize;
236 if (isVertical || !renderBox()->style()->isLeftToRightDirection())
237 fraction = 1 - fraction;
238 double value = range.clampValue(range.valueFromProportion(fraction));
240 // FIXME: This is no longer being set from renderer. Consider updating the method name.
241 input->setValueFromRenderer(serializeForNumberType(value));
242 renderer()->setNeedsLayout(true);
243 input->dispatchFormControlChangeEvent();
246 void SliderThumbElement::startDragging()
248 if (Frame* frame = document()->frame()) {
249 frame->eventHandler()->setCapturingMouseEventsNode(this);
254 void SliderThumbElement::stopDragging()
259 if (Frame* frame = document()->frame())
260 frame->eventHandler()->setCapturingMouseEventsNode(0);
261 m_inDragMode = false;
263 renderer()->setNeedsLayout(true);
266 void SliderThumbElement::defaultEventHandler(Event* event)
268 if (!event->isMouseEvent()) {
269 HTMLDivElement::defaultEventHandler(event);
273 // FIXME: Should handle this readonly/disabled check in more general way.
274 // Missing this kind of check is likely to occur elsewhere if adding it in each shadow element.
275 HTMLInputElement* input = hostInput();
276 if (!input || input->isReadOnlyFormControl() || !input->isEnabledFormControl()) {
277 HTMLDivElement::defaultEventHandler(event);
281 MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
282 bool isLeftButton = mouseEvent->button() == LeftButton;
283 const AtomicString& eventType = event->type();
285 // We intentionally do not call event->setDefaultHandled() here because
286 // MediaControlTimelineElement::defaultEventHandler() wants to handle these
288 if (eventType == eventNames().mousedownEvent && isLeftButton) {
291 } else if (eventType == eventNames().mouseupEvent && isLeftButton) {
294 } else if (eventType == eventNames().mousemoveEvent) {
296 setPositionFromPoint(mouseEvent->absoluteLocation());
300 HTMLDivElement::defaultEventHandler(event);
303 void SliderThumbElement::detach()
306 if (Frame* frame = document()->frame())
307 frame->eventHandler()->setCapturingMouseEventsNode(0);
309 HTMLDivElement::detach();
312 HTMLInputElement* SliderThumbElement::hostInput() const
314 // Only HTMLInputElement creates SliderThumbElement instances as its shadow nodes.
315 // So, shadowAncestorNode() must be an HTMLInputElement.
316 return shadowAncestorNode()->toInputElement();
319 const AtomicString& SliderThumbElement::shadowPseudoId() const
321 DEFINE_STATIC_LOCAL(AtomicString, sliderThumb, ("-webkit-slider-thumb"));
325 // --------------------------------
327 inline TrackLimiterElement::TrackLimiterElement(Document* document)
328 : HTMLDivElement(HTMLNames::divTag, document)
332 PassRefPtr<TrackLimiterElement> TrackLimiterElement::create(Document* document)
334 RefPtr<TrackLimiterElement> element = adoptRef(new TrackLimiterElement(document));
335 element->getInlineStyleDecl()->setProperty(CSSPropertyVisibility, CSSValueHidden);
336 element->getInlineStyleDecl()->setProperty(CSSPropertyPosition, CSSValueStatic);
337 return element.release();
340 RenderObject* TrackLimiterElement::createRenderer(RenderArena* arena, RenderStyle*)
342 return new (arena) RenderSliderThumb(this);
345 const AtomicString& TrackLimiterElement::shadowPseudoId() const
347 DEFINE_STATIC_LOCAL(AtomicString, sliderThumb, ("-webkit-slider-thumb"));
351 TrackLimiterElement* trackLimiterElementOf(Node* node)
354 ShadowRoot* shadow = node->toInputElement()->shadowRoot();
356 Node* limiter = shadow->firstChild()->lastChild();
358 return static_cast<TrackLimiterElement*>(limiter);
361 // --------------------------------
363 inline SliderContainerElement::SliderContainerElement(Document* document)
364 : HTMLDivElement(HTMLNames::divTag, document)
368 PassRefPtr<SliderContainerElement> SliderContainerElement::create(Document* document)
370 return adoptRef(new SliderContainerElement(document));
373 RenderObject* SliderContainerElement::createRenderer(RenderArena* arena, RenderStyle*)
375 return new (arena) RenderSliderContainer(this);
378 const AtomicString& SliderContainerElement::shadowPseudoId() const
380 DEFINE_STATIC_LOCAL(AtomicString, sliderThumb, ("-webkit-slider-container"));