aa750cab1d604f6cf0feb1b05aa0a40ff12beb3e
[WebKit-https.git] / Source / WebCore / html / shadow / SliderThumbElement.cpp
1 /*
2  * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
3  * Copyright (C) 2010 Google Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
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
14  * distribution.
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.
18  *
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.
30  */
31
32
33 #include "config.h"
34 #include "SliderThumbElement.h"
35
36 #include "CSSValueKeywords.h"
37 #include "ElementShadow.h"
38 #include "Event.h"
39 #include "Frame.h"
40 #include "HTMLInputElement.h"
41 #include "HTMLParserIdioms.h"
42 #include "MouseEvent.h"
43 #include "RenderDeprecatedFlexibleBox.h"
44 #include "RenderSlider.h"
45 #include "RenderTheme.h"
46 #include "ShadowRoot.h"
47 #include "StepRange.h"
48 #include <wtf/MathExtras.h>
49
50 using namespace std;
51
52 namespace WebCore {
53
54 inline static InputNumber sliderPosition(HTMLInputElement* element)
55 {
56     const StepRange stepRange(element->createStepRange(RejectAny));
57     const InputNumber oldValue = parseToDecimalForNumberType(element->value(), stepRange.defaultValue());
58     return stepRange.proportionFromValue(stepRange.clampValue(oldValue));
59 }
60
61 inline static bool hasVerticalAppearance(HTMLInputElement* input)
62 {
63     ASSERT(input->renderer());
64     RenderStyle* sliderStyle = input->renderer()->style();
65     RenderTheme* sliderTheme = input->renderer()->theme();
66
67     return sliderStyle->appearance() == SliderVerticalPart
68         || (sliderStyle->appearance() == MediaVolumeSliderPart && sliderTheme->usesVerticalVolumeSlider());
69 }
70
71 SliderThumbElement* sliderThumbElementOf(Node* node)
72 {
73     ASSERT(node);
74     ShadowRoot* shadow = node->toInputElement()->shadow()->oldestShadowRoot();
75     ASSERT(shadow);
76     Node* thumb = shadow->firstChild()->firstChild()->firstChild();
77     ASSERT(thumb);
78     return toSliderThumbElement(thumb);
79 }
80
81 // --------------------------------
82
83 RenderSliderThumb::RenderSliderThumb(Node* node)
84     : RenderBlock(node)
85 {
86 }
87
88 void RenderSliderThumb::updateAppearance(RenderStyle* parentStyle)
89 {
90     if (parentStyle->appearance() == SliderVerticalPart)
91         style()->setAppearance(SliderThumbVerticalPart);
92     else if (parentStyle->appearance() == SliderHorizontalPart)
93         style()->setAppearance(SliderThumbHorizontalPart);
94     else if (parentStyle->appearance() == MediaSliderPart)
95         style()->setAppearance(MediaSliderThumbPart);
96     else if (parentStyle->appearance() == MediaVolumeSliderPart)
97         style()->setAppearance(MediaVolumeSliderThumbPart);
98     else if (parentStyle->appearance() == MediaFullScreenVolumeSliderPart)
99         style()->setAppearance(MediaFullScreenVolumeSliderThumbPart);
100     if (style()->hasAppearance())
101         theme()->adjustSliderThumbSize(style(), toElement(node()));
102 }
103
104 bool RenderSliderThumb::isSliderThumb() const
105 {
106     return true;
107 }
108
109 void RenderSliderThumb::layout()
110 {
111     // Do not cast node() to SliderThumbElement. This renderer is used for
112     // TrackLimitElement too.
113     HTMLInputElement* input = node()->shadowAncestorNode()->toInputElement();
114     bool isVertical = hasVerticalAppearance(input);
115
116     double fraction = convertInputNumberToDouble(sliderPosition(input) * 100);
117     if (isVertical)
118         style()->setTop(Length(100 - fraction, Percent));
119     else if (style()->isLeftToRightDirection())
120         style()->setLeft(Length(fraction, Percent));
121     else
122         style()->setRight(Length(fraction, Percent));
123
124     RenderBlock::layout();
125 }
126
127 // --------------------------------
128
129 // FIXME: Find a way to cascade appearance and adjust heights, and get rid of this class.
130 // http://webkit.org/b/62535
131 class RenderSliderContainer : public RenderDeprecatedFlexibleBox {
132 public:
133     RenderSliderContainer(Node* node)
134         : RenderDeprecatedFlexibleBox(node) { }
135
136 private:
137     virtual void layout();
138 };
139
140 void RenderSliderContainer::layout()
141 {
142     HTMLInputElement* input = node()->shadowAncestorNode()->toInputElement();
143     bool isVertical = hasVerticalAppearance(input);
144     style()->setBoxOrient(isVertical ? VERTICAL : HORIZONTAL);
145     // Sets the concrete height if the height of the <input> is not fixed or a
146     // percentage value because a percentage height value of this box won't be
147     // based on the <input> height in such case.
148     Length inputHeight = input->renderer()->style()->height();
149     RenderObject* trackRenderer = node()->firstChild()->renderer();
150     if (!isVertical && input->renderer()->isSlider() && !inputHeight.isFixed() && !inputHeight.isPercent()) {
151         RenderObject* thumbRenderer = input->shadow()->oldestShadowRoot()->firstChild()->firstChild()->firstChild()->renderer();
152         if (thumbRenderer) {
153             style()->setHeight(thumbRenderer->style()->height());
154             if (trackRenderer)
155                 trackRenderer->style()->setHeight(thumbRenderer->style()->height());
156         }
157     } else {
158         style()->setHeight(Length(100, Percent));
159         if (trackRenderer)
160             trackRenderer->style()->setHeight(Length());
161     }
162
163     RenderDeprecatedFlexibleBox::layout();
164
165     // Percentage 'top' for the thumb doesn't work if the parent style has no
166     // concrete height.
167     Node* track = node()->firstChild();
168     if (track && track->renderer()->isBox()) {
169         RenderBox* trackBox = track->renderBox();
170         trackBox->style()->setHeight(Length(trackBox->height() - trackBox->borderAndPaddingHeight(), Fixed));
171     }
172 }
173
174 // --------------------------------
175
176 void SliderThumbElement::setPositionFromValue()
177 {
178     // Since the code to calculate position is in the RenderSliderThumb layout
179     // path, we don't actually update the value here. Instead, we poke at the
180     // renderer directly to trigger layout.
181     if (renderer())
182         renderer()->setNeedsLayout(true);
183 }
184
185 RenderObject* SliderThumbElement::createRenderer(RenderArena* arena, RenderStyle*)
186 {
187     return new (arena) RenderSliderThumb(this);
188 }
189
190 bool SliderThumbElement::isEnabledFormControl() const
191 {
192     return hostInput()->isEnabledFormControl();
193 }
194
195 bool SliderThumbElement::isReadOnlyFormControl() const
196 {
197     return hostInput()->isReadOnlyFormControl();
198 }
199
200 Node* SliderThumbElement::focusDelegate()
201 {
202     return hostInput();
203 }
204
205 void SliderThumbElement::dragFrom(const LayoutPoint& point)
206 {
207     setPositionFromPoint(point);
208     startDragging();
209 }
210
211 void SliderThumbElement::setPositionFromPoint(const LayoutPoint& point)
212 {
213     HTMLInputElement* input = hostInput();
214
215     if (!input->renderer() || !renderer())
216         return;
217
218     LayoutPoint offset = roundedLayoutPoint(input->renderer()->absoluteToLocal(point, false, true));
219     bool isVertical = hasVerticalAppearance(input);
220     LayoutUnit trackSize;
221     LayoutUnit position;
222     LayoutUnit currentPosition;
223     // We need to calculate currentPosition from absolute points becaue the
224     // renderer for this node is usually on a layer and renderBox()->x() and
225     // y() are unusable.
226     // FIXME: This should probably respect transforms.
227     LayoutPoint absoluteThumbOrigin = renderBox()->absoluteBoundingBoxRectIgnoringTransforms().location();
228     LayoutPoint absoluteSliderContentOrigin = roundedLayoutPoint(input->renderer()->localToAbsolute());
229     if (isVertical) {
230         trackSize = input->renderBox()->contentHeight() - renderBox()->height();
231         position = offset.y() - renderBox()->height() / 2;
232         currentPosition = absoluteThumbOrigin.y() - absoluteSliderContentOrigin.y();
233     } else {
234         trackSize = input->renderBox()->contentWidth() - renderBox()->width();
235         position = offset.x() - renderBox()->width() / 2;
236         currentPosition = absoluteThumbOrigin.x() - absoluteSliderContentOrigin.x();
237     }
238     position = max<LayoutUnit>(0, min(position, trackSize));
239     if (position == currentPosition)
240         return;
241
242     const InputNumber ratio = convertDoubleToInputNumber(static_cast<double>(position) / trackSize);
243     const InputNumber fraction = isVertical || !renderBox()->style()->isLeftToRightDirection() ? InputNumber(1) - ratio : ratio;
244     StepRange stepRange(input->createStepRange(RejectAny));
245     const InputNumber value = stepRange.clampValue(stepRange.valueFromProportion(fraction));
246
247     // FIXME: This is no longer being set from renderer. Consider updating the method name.
248     input->setValueFromRenderer(serializeForNumberType(value));
249     renderer()->setNeedsLayout(true);
250     input->dispatchFormControlChangeEvent();
251 }
252
253 void SliderThumbElement::startDragging()
254 {
255     if (Frame* frame = document()->frame()) {
256         frame->eventHandler()->setCapturingMouseEventsNode(this);
257         m_inDragMode = true;
258     }
259 }
260
261 void SliderThumbElement::stopDragging()
262 {
263     if (!m_inDragMode)
264         return;
265
266     if (Frame* frame = document()->frame())
267         frame->eventHandler()->setCapturingMouseEventsNode(0);
268     m_inDragMode = false;
269     if (renderer())
270         renderer()->setNeedsLayout(true);
271 }
272
273 void SliderThumbElement::defaultEventHandler(Event* event)
274 {
275     if (!event->isMouseEvent()) {
276         HTMLDivElement::defaultEventHandler(event);
277         return;
278     }
279
280     // FIXME: Should handle this readonly/disabled check in more general way.
281     // Missing this kind of check is likely to occur elsewhere if adding it in each shadow element.
282     HTMLInputElement* input = hostInput();
283     if (!input || input->isReadOnlyFormControl() || !input->isEnabledFormControl()) {
284         stopDragging();
285         HTMLDivElement::defaultEventHandler(event);
286         return;
287     }
288
289     MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
290     bool isLeftButton = mouseEvent->button() == LeftButton;
291     const AtomicString& eventType = event->type();
292
293     // We intentionally do not call event->setDefaultHandled() here because
294     // MediaControlTimelineElement::defaultEventHandler() wants to handle these
295     // mouse events.
296     if (eventType == eventNames().mousedownEvent && isLeftButton) {
297         startDragging();
298         return;
299     } else if (eventType == eventNames().mouseupEvent && isLeftButton) {
300         stopDragging();
301         return;
302     } else if (eventType == eventNames().mousemoveEvent) {
303         if (m_inDragMode)
304             setPositionFromPoint(mouseEvent->absoluteLocation());
305         return;
306     }
307
308     HTMLDivElement::defaultEventHandler(event);
309 }
310
311 void SliderThumbElement::detach()
312 {
313     if (m_inDragMode) {
314         if (Frame* frame = document()->frame())
315             frame->eventHandler()->setCapturingMouseEventsNode(0);
316     }
317     HTMLDivElement::detach();
318 }
319
320 HTMLInputElement* SliderThumbElement::hostInput() const
321 {
322     // Only HTMLInputElement creates SliderThumbElement instances as its shadow nodes.
323     // So, shadowAncestorNode() must be an HTMLInputElement.
324     return shadowAncestorNode()->toInputElement();
325 }
326
327 const AtomicString& SliderThumbElement::shadowPseudoId() const
328 {
329     DEFINE_STATIC_LOCAL(AtomicString, sliderThumb, ("-webkit-slider-thumb"));
330     return sliderThumb;
331 }
332
333 // --------------------------------
334
335 inline TrackLimiterElement::TrackLimiterElement(Document* document)
336     : HTMLDivElement(HTMLNames::divTag, document)
337 {
338 }
339
340 PassRefPtr<TrackLimiterElement> TrackLimiterElement::create(Document* document)
341 {
342     RefPtr<TrackLimiterElement> element = adoptRef(new TrackLimiterElement(document));
343
344     element->setInlineStyleProperty(CSSPropertyVisibility, CSSValueHidden);
345     element->setInlineStyleProperty(CSSPropertyPosition, CSSValueStatic);
346
347     return element.release();
348 }
349
350 RenderObject* TrackLimiterElement::createRenderer(RenderArena* arena, RenderStyle*)
351 {
352     return new (arena) RenderSliderThumb(this);
353 }
354
355 const AtomicString& TrackLimiterElement::shadowPseudoId() const
356 {
357     DEFINE_STATIC_LOCAL(AtomicString, sliderThumb, ("-webkit-slider-thumb"));
358     return sliderThumb;
359 }
360
361 TrackLimiterElement* trackLimiterElementOf(Node* node)
362 {
363     ASSERT(node);
364     ASSERT(node->toInputElement()->shadow());
365     ShadowRoot* shadow = node->toInputElement()->shadow()->oldestShadowRoot();
366     ASSERT(shadow);
367     Node* limiter = shadow->firstChild()->lastChild();
368     ASSERT(limiter);
369     return static_cast<TrackLimiterElement*>(limiter);
370 }
371
372 // --------------------------------
373
374 inline SliderContainerElement::SliderContainerElement(Document* document)
375     : HTMLDivElement(HTMLNames::divTag, document)
376 {
377 }
378
379 PassRefPtr<SliderContainerElement> SliderContainerElement::create(Document* document)
380 {
381     return adoptRef(new SliderContainerElement(document));
382 }
383
384 RenderObject* SliderContainerElement::createRenderer(RenderArena* arena, RenderStyle*)
385 {
386     return new (arena) RenderSliderContainer(this);
387 }
388
389 const AtomicString& SliderContainerElement::shadowPseudoId() const
390 {
391     DEFINE_STATIC_LOCAL(AtomicString, sliderThumb, ("-webkit-slider-container"));
392     return sliderThumb;
393 }
394
395 }