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