Upstream version 9.37.195.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / 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 "core/html/shadow/SliderThumbElement.h"
35
36 #include "core/events/Event.h"
37 #include "core/events/MouseEvent.h"
38 #include "core/dom/shadow/ShadowRoot.h"
39 #include "core/frame/LocalFrame.h"
40 #include "core/html/HTMLInputElement.h"
41 #include "core/html/forms/StepRange.h"
42 #include "core/html/parser/HTMLParserIdioms.h"
43 #include "core/html/shadow/ShadowElementNames.h"
44 #include "core/page/EventHandler.h"
45 #include "core/rendering/RenderFlexibleBox.h"
46 #include "core/rendering/RenderSlider.h"
47 #include "core/rendering/RenderTheme.h"
48
49 namespace WebCore {
50
51 using namespace HTMLNames;
52
53 inline static Decimal sliderPosition(HTMLInputElement* element)
54 {
55     const StepRange stepRange(element->createStepRange(RejectAny));
56     const Decimal oldValue = parseToDecimalForNumberType(element->value(), stepRange.defaultValue());
57     return stepRange.proportionFromValue(stepRange.clampValue(oldValue));
58 }
59
60 inline static bool hasVerticalAppearance(HTMLInputElement* input)
61 {
62     ASSERT(input->renderer());
63     RenderStyle* sliderStyle = input->renderer()->style();
64
65     return sliderStyle->appearance() == SliderVerticalPart;
66 }
67
68 // --------------------------------
69
70 RenderSliderThumb::RenderSliderThumb(SliderThumbElement* element)
71     : RenderBlockFlow(element)
72 {
73 }
74
75 void RenderSliderThumb::updateAppearance(RenderStyle* parentStyle)
76 {
77     if (parentStyle->appearance() == SliderVerticalPart)
78         style()->setAppearance(SliderThumbVerticalPart);
79     else if (parentStyle->appearance() == SliderHorizontalPart)
80         style()->setAppearance(SliderThumbHorizontalPart);
81     else if (parentStyle->appearance() == MediaSliderPart)
82         style()->setAppearance(MediaSliderThumbPart);
83     else if (parentStyle->appearance() == MediaVolumeSliderPart)
84         style()->setAppearance(MediaVolumeSliderThumbPart);
85     else if (parentStyle->appearance() == MediaFullScreenVolumeSliderPart)
86         style()->setAppearance(MediaFullScreenVolumeSliderThumbPart);
87     if (style()->hasAppearance())
88         RenderTheme::theme().adjustSliderThumbSize(style(), toElement(node()));
89 }
90
91 bool RenderSliderThumb::isSliderThumb() const
92 {
93     return true;
94 }
95
96 // --------------------------------
97
98 // FIXME: Find a way to cascade appearance and adjust heights, and get rid of this class.
99 // http://webkit.org/b/62535
100 class RenderSliderContainer : public RenderFlexibleBox {
101 public:
102     RenderSliderContainer(SliderContainerElement* element)
103         : RenderFlexibleBox(element) { }
104 public:
105     virtual void computeLogicalHeight(LayoutUnit logicalHeight, LayoutUnit logicalTop, LogicalExtentComputedValues&) const OVERRIDE;
106
107 private:
108     virtual void layout() OVERRIDE;
109 };
110
111 void RenderSliderContainer::computeLogicalHeight(LayoutUnit logicalHeight, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const
112 {
113     HTMLInputElement* input = toHTMLInputElement(node()->shadowHost());
114     bool isVertical = hasVerticalAppearance(input);
115
116     if (input->renderer()->isSlider() && !isVertical && input->list()) {
117         int offsetFromCenter = RenderTheme::theme().sliderTickOffsetFromTrackCenter();
118         LayoutUnit trackHeight = 0;
119         if (offsetFromCenter < 0)
120             trackHeight = -2 * offsetFromCenter;
121         else {
122             int tickLength = RenderTheme::theme().sliderTickSize().height();
123             trackHeight = 2 * (offsetFromCenter + tickLength);
124         }
125         float zoomFactor = style()->effectiveZoom();
126         if (zoomFactor != 1.0)
127             trackHeight *= zoomFactor;
128
129         // FIXME: The trackHeight should have been added before updateLogicalHeight was called to avoid this hack.
130         updateIntrinsicContentLogicalHeight(trackHeight);
131
132         RenderBox::computeLogicalHeight(trackHeight, logicalTop, computedValues);
133         return;
134     }
135     if (isVertical)
136         logicalHeight = RenderSlider::defaultTrackLength;
137
138     // FIXME: The trackHeight should have been added before updateLogicalHeight was called to avoid this hack.
139     updateIntrinsicContentLogicalHeight(logicalHeight);
140
141     RenderBox::computeLogicalHeight(logicalHeight, logicalTop, computedValues);
142 }
143
144 void RenderSliderContainer::layout()
145 {
146     HTMLInputElement* input = toHTMLInputElement(node()->shadowHost());
147     bool isVertical = hasVerticalAppearance(input);
148     style()->setFlexDirection(isVertical ? FlowColumn : FlowRow);
149     TextDirection oldTextDirection = style()->direction();
150     if (isVertical) {
151         // FIXME: Work around rounding issues in RTL vertical sliders. We want them to
152         // render identically to LTR vertical sliders. We can remove this work around when
153         // subpixel rendering is enabled on all ports.
154         style()->setDirection(LTR);
155     }
156
157     Element* thumbElement = input->userAgentShadowRoot()->getElementById(ShadowElementNames::sliderThumb());
158     Element* trackElement = input->userAgentShadowRoot()->getElementById(ShadowElementNames::sliderTrack());
159     RenderBox* thumb = thumbElement ? thumbElement->renderBox() : 0;
160     RenderBox* track = trackElement ? trackElement->renderBox() : 0;
161
162     SubtreeLayoutScope layoutScope(*this);
163     // Force a layout to reset the position of the thumb so the code below doesn't move the thumb to the wrong place.
164     // FIXME: Make a custom Render class for the track and move the thumb positioning code there.
165     if (track)
166         layoutScope.setChildNeedsLayout(track);
167
168     RenderFlexibleBox::layout();
169
170     style()->setDirection(oldTextDirection);
171     // These should always exist, unless someone mutates the shadow DOM (e.g., in the inspector).
172     if (!thumb || !track)
173         return;
174
175     double percentageOffset = sliderPosition(input).toDouble();
176     LayoutUnit availableExtent = isVertical ? track->contentHeight() : track->contentWidth();
177     availableExtent -= isVertical ? thumb->height() : thumb->width();
178     LayoutUnit offset = percentageOffset * availableExtent;
179     LayoutPoint thumbLocation = thumb->location();
180     if (isVertical)
181         thumbLocation.setY(thumbLocation.y() + track->contentHeight() - thumb->height() - offset);
182     else if (style()->isLeftToRightDirection())
183         thumbLocation.setX(thumbLocation.x() + offset);
184     else
185         thumbLocation.setX(thumbLocation.x() - offset);
186     thumb->setLocation(thumbLocation);
187     if (checkForPaintInvalidationDuringLayout() && parent()
188         && (parent()->style()->appearance() == MediaVolumeSliderPart || parent()->style()->appearance() == MediaSliderPart)) {
189         // This will sometimes repaint too much. However, it is necessary to
190         // correctly repaint media controls (volume and timeline sliders) -
191         // they have special painting code in RenderMediaControls.cpp:paintMediaVolumeSlider
192         // and paintMediaSlider that gets called via -webkit-appearance and RenderTheme,
193         // so nothing else would otherwise invalidate the slider.
194         paintInvalidationForWholeRenderer();
195     }
196
197     // We need one-off invalidation code here because painting of the timeline element does not go through style.
198     // Instead it has a custom implementation in C++ code.
199     // Therefore the style system cannot understand when it needs to be repainted.
200     setShouldDoFullPaintInvalidationAfterLayout(true);
201 }
202
203 // --------------------------------
204
205 inline SliderThumbElement::SliderThumbElement(Document& document)
206     : HTMLDivElement(document)
207     , m_inDragMode(false)
208 {
209 }
210
211 PassRefPtrWillBeRawPtr<SliderThumbElement> SliderThumbElement::create(Document& document)
212 {
213     RefPtrWillBeRawPtr<SliderThumbElement> element = adoptRefWillBeNoop(new SliderThumbElement(document));
214     element->setAttribute(idAttr, ShadowElementNames::sliderThumb());
215     return element.release();
216 }
217
218 void SliderThumbElement::setPositionFromValue()
219 {
220     // Since the code to calculate position is in the RenderSliderThumb layout
221     // path, we don't actually update the value here. Instead, we poke at the
222     // renderer directly to trigger layout.
223     if (renderer())
224         renderer()->setNeedsLayoutAndFullPaintInvalidation();
225 }
226
227 RenderObject* SliderThumbElement::createRenderer(RenderStyle*)
228 {
229     return new RenderSliderThumb(this);
230 }
231
232 bool SliderThumbElement::isDisabledFormControl() const
233 {
234     return hostInput() && hostInput()->isDisabledFormControl();
235 }
236
237 bool SliderThumbElement::matchesReadOnlyPseudoClass() const
238 {
239     return hostInput() && hostInput()->matchesReadOnlyPseudoClass();
240 }
241
242 bool SliderThumbElement::matchesReadWritePseudoClass() const
243 {
244     return hostInput() && hostInput()->matchesReadWritePseudoClass();
245 }
246
247 Node* SliderThumbElement::focusDelegate()
248 {
249     return hostInput();
250 }
251
252 void SliderThumbElement::dragFrom(const LayoutPoint& point)
253 {
254     RefPtrWillBeRawPtr<SliderThumbElement> protector(this);
255     startDragging();
256     setPositionFromPoint(point);
257 }
258
259 void SliderThumbElement::setPositionFromPoint(const LayoutPoint& point)
260 {
261     RefPtrWillBeRawPtr<HTMLInputElement> input(hostInput());
262     Element* trackElement = input->userAgentShadowRoot()->getElementById(ShadowElementNames::sliderTrack());
263
264     if (!input->renderer() || !renderBox() || !trackElement->renderBox())
265         return;
266
267     LayoutPoint offset = roundedLayoutPoint(input->renderer()->absoluteToLocal(point, UseTransforms));
268     bool isVertical = hasVerticalAppearance(input.get());
269     bool isLeftToRightDirection = renderBox()->style()->isLeftToRightDirection();
270     LayoutUnit trackSize;
271     LayoutUnit position;
272     LayoutUnit currentPosition;
273     // We need to calculate currentPosition from absolute points becaue the
274     // renderer for this node is usually on a layer and renderBox()->x() and
275     // y() are unusable.
276     // FIXME: This should probably respect transforms.
277     LayoutPoint absoluteThumbOrigin = renderBox()->absoluteBoundingBoxRectIgnoringTransforms().location();
278     LayoutPoint absoluteSliderContentOrigin = roundedLayoutPoint(input->renderer()->localToAbsolute());
279     IntRect trackBoundingBox = trackElement->renderer()->absoluteBoundingBoxRectIgnoringTransforms();
280     IntRect inputBoundingBox = input->renderer()->absoluteBoundingBoxRectIgnoringTransforms();
281     if (isVertical) {
282         trackSize = trackElement->renderBox()->contentHeight() - renderBox()->height();
283         position = offset.y() - renderBox()->height() / 2 - trackBoundingBox.y() + inputBoundingBox.y() - renderBox()->marginBottom();
284         currentPosition = absoluteThumbOrigin.y() - absoluteSliderContentOrigin.y();
285     } else {
286         trackSize = trackElement->renderBox()->contentWidth() - renderBox()->width();
287         position = offset.x() - renderBox()->width() / 2 - trackBoundingBox.x() + inputBoundingBox.x();
288         position -= isLeftToRightDirection ? renderBox()->marginLeft() : renderBox()->marginRight();
289         currentPosition = absoluteThumbOrigin.x() - absoluteSliderContentOrigin.x();
290     }
291     position = std::max<LayoutUnit>(0, std::min(position, trackSize));
292     const Decimal ratio = Decimal::fromDouble(static_cast<double>(position) / trackSize);
293     const Decimal fraction = isVertical || !isLeftToRightDirection ? Decimal(1) - ratio : ratio;
294     StepRange stepRange(input->createStepRange(RejectAny));
295     Decimal value = stepRange.clampValue(stepRange.valueFromProportion(fraction));
296
297     Decimal closest = input->findClosestTickMarkValue(value);
298     if (closest.isFinite()) {
299         double closestFraction = stepRange.proportionFromValue(closest).toDouble();
300         double closestRatio = isVertical || !isLeftToRightDirection ? 1.0 - closestFraction : closestFraction;
301         LayoutUnit closestPosition = trackSize * closestRatio;
302         const LayoutUnit snappingThreshold = 5;
303         if ((closestPosition - position).abs() <= snappingThreshold)
304             value = closest;
305     }
306
307     String valueString = serializeForNumberType(value);
308     if (valueString == input->value())
309         return;
310
311     // FIXME: This is no longer being set from renderer. Consider updating the method name.
312     input->setValueFromRenderer(valueString);
313     if (renderer())
314         renderer()->setNeedsLayoutAndFullPaintInvalidation();
315 }
316
317 void SliderThumbElement::startDragging()
318 {
319     if (LocalFrame* frame = document().frame()) {
320         frame->eventHandler().setCapturingMouseEventsNode(this);
321         m_inDragMode = true;
322     }
323 }
324
325 void SliderThumbElement::stopDragging()
326 {
327     if (!m_inDragMode)
328         return;
329
330     if (LocalFrame* frame = document().frame())
331         frame->eventHandler().setCapturingMouseEventsNode(nullptr);
332     m_inDragMode = false;
333     if (renderer())
334         renderer()->setNeedsLayoutAndFullPaintInvalidation();
335     if (hostInput())
336         hostInput()->dispatchFormControlChangeEvent();
337 }
338
339 void SliderThumbElement::defaultEventHandler(Event* event)
340 {
341     if (!event->isMouseEvent()) {
342         HTMLDivElement::defaultEventHandler(event);
343         return;
344     }
345
346     // FIXME: Should handle this readonly/disabled check in more general way.
347     // Missing this kind of check is likely to occur elsewhere if adding it in each shadow element.
348     HTMLInputElement* input = hostInput();
349     if (!input || input->isDisabledOrReadOnly()) {
350         stopDragging();
351         HTMLDivElement::defaultEventHandler(event);
352         return;
353     }
354
355     MouseEvent* mouseEvent = toMouseEvent(event);
356     bool isLeftButton = mouseEvent->button() == LeftButton;
357     const AtomicString& eventType = event->type();
358
359     // We intentionally do not call event->setDefaultHandled() here because
360     // MediaControlTimelineElement::defaultEventHandler() wants to handle these
361     // mouse events.
362     if (eventType == EventTypeNames::mousedown && isLeftButton) {
363         startDragging();
364         return;
365     } else if (eventType == EventTypeNames::mouseup && isLeftButton) {
366         stopDragging();
367         return;
368     } else if (eventType == EventTypeNames::mousemove) {
369         if (m_inDragMode)
370             setPositionFromPoint(mouseEvent->absoluteLocation());
371         return;
372     }
373
374     HTMLDivElement::defaultEventHandler(event);
375 }
376
377 bool SliderThumbElement::willRespondToMouseMoveEvents()
378 {
379     const HTMLInputElement* input = hostInput();
380     if (input && !input->isDisabledOrReadOnly() && m_inDragMode)
381         return true;
382
383     return HTMLDivElement::willRespondToMouseMoveEvents();
384 }
385
386 bool SliderThumbElement::willRespondToMouseClickEvents()
387 {
388     const HTMLInputElement* input = hostInput();
389     if (input && !input->isDisabledOrReadOnly())
390         return true;
391
392     return HTMLDivElement::willRespondToMouseClickEvents();
393 }
394
395 void SliderThumbElement::detach(const AttachContext& context)
396 {
397     if (m_inDragMode) {
398         if (LocalFrame* frame = document().frame())
399             frame->eventHandler().setCapturingMouseEventsNode(nullptr);
400     }
401     HTMLDivElement::detach(context);
402 }
403
404 HTMLInputElement* SliderThumbElement::hostInput() const
405 {
406     // Only HTMLInputElement creates SliderThumbElement instances as its shadow nodes.
407     // So, shadowHost() must be an HTMLInputElement.
408     return toHTMLInputElement(shadowHost());
409 }
410
411 static const AtomicString& sliderThumbShadowPartId()
412 {
413     DEFINE_STATIC_LOCAL(const AtomicString, sliderThumb, ("-webkit-slider-thumb", AtomicString::ConstructFromLiteral));
414     return sliderThumb;
415 }
416
417 static const AtomicString& mediaSliderThumbShadowPartId()
418 {
419     DEFINE_STATIC_LOCAL(const AtomicString, mediaSliderThumb, ("-webkit-media-slider-thumb", AtomicString::ConstructFromLiteral));
420     return mediaSliderThumb;
421 }
422
423 const AtomicString& SliderThumbElement::shadowPseudoId() const
424 {
425     HTMLInputElement* input = hostInput();
426     if (!input || !input->renderer())
427         return sliderThumbShadowPartId();
428
429     RenderStyle* sliderStyle = input->renderer()->style();
430     switch (sliderStyle->appearance()) {
431     case MediaSliderPart:
432     case MediaSliderThumbPart:
433     case MediaVolumeSliderPart:
434     case MediaVolumeSliderThumbPart:
435     case MediaFullScreenVolumeSliderPart:
436     case MediaFullScreenVolumeSliderThumbPart:
437         return mediaSliderThumbShadowPartId();
438     default:
439         return sliderThumbShadowPartId();
440     }
441 }
442
443 // --------------------------------
444
445 inline SliderContainerElement::SliderContainerElement(Document& document)
446     : HTMLDivElement(document)
447 {
448 }
449
450 DEFINE_NODE_FACTORY(SliderContainerElement)
451
452 RenderObject* SliderContainerElement::createRenderer(RenderStyle*)
453 {
454     return new RenderSliderContainer(this);
455 }
456
457 const AtomicString& SliderContainerElement::shadowPseudoId() const
458 {
459     DEFINE_STATIC_LOCAL(const AtomicString, mediaSliderContainer, ("-webkit-media-slider-container", AtomicString::ConstructFromLiteral));
460     DEFINE_STATIC_LOCAL(const AtomicString, sliderContainer, ("-webkit-slider-container", AtomicString::ConstructFromLiteral));
461
462     if (!shadowHost() || !shadowHost()->renderer())
463         return sliderContainer;
464
465     RenderStyle* sliderStyle = shadowHost()->renderer()->style();
466     switch (sliderStyle->appearance()) {
467     case MediaSliderPart:
468     case MediaSliderThumbPart:
469     case MediaVolumeSliderPart:
470     case MediaVolumeSliderThumbPart:
471     case MediaFullScreenVolumeSliderPart:
472     case MediaFullScreenVolumeSliderThumbPart:
473         return mediaSliderContainer;
474     default:
475         return sliderContainer;
476     }
477 }
478
479 }