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.
33 #include "core/html/shadow/SliderThumbElement.h"
35 #include "core/events/Event.h"
36 #include "core/events/MouseEvent.h"
37 #include "core/dom/shadow/ShadowRoot.h"
38 #include "core/frame/LocalFrame.h"
39 #include "core/html/HTMLInputElement.h"
40 #include "core/html/forms/StepRange.h"
41 #include "core/html/parser/HTMLParserIdioms.h"
42 #include "core/html/shadow/ShadowElementNames.h"
43 #include "core/input/EventHandler.h"
44 #include "core/layout/LayoutFlexibleBox.h"
45 #include "core/layout/LayoutSlider.h"
46 #include "core/layout/LayoutSliderContainer.h"
47 #include "core/layout/LayoutSliderThumb.h"
48 #include "core/layout/LayoutTheme.h"
52 using namespace HTMLNames
;
54 inline static bool hasVerticalAppearance(HTMLInputElement
* input
)
56 ASSERT(input
->layoutObject());
57 const ComputedStyle
& sliderStyle
= input
->layoutObject()->styleRef();
59 return sliderStyle
.appearance() == SliderVerticalPart
;
62 inline SliderThumbElement::SliderThumbElement(Document
& document
)
63 : HTMLDivElement(document
)
68 PassRefPtrWillBeRawPtr
<SliderThumbElement
> SliderThumbElement::create(Document
& document
)
70 RefPtrWillBeRawPtr
<SliderThumbElement
> element
= adoptRefWillBeNoop(new SliderThumbElement(document
));
71 element
->setAttribute(idAttr
, ShadowElementNames::sliderThumb());
72 return element
.release();
75 void SliderThumbElement::setPositionFromValue()
77 // Since the code to calculate position is in the LayoutSliderThumb layout
78 // path, we don't actually update the value here. Instead, we poke at the
79 // layoutObject directly to trigger layout.
81 layoutObject()->setNeedsLayoutAndFullPaintInvalidation(LayoutInvalidationReason::SliderValueChanged
);
84 LayoutObject
* SliderThumbElement::createLayoutObject(const ComputedStyle
&)
86 return new LayoutSliderThumb(this);
89 bool SliderThumbElement::isDisabledFormControl() const
91 return hostInput() && hostInput()->isDisabledFormControl();
94 bool SliderThumbElement::matchesReadOnlyPseudoClass() const
96 return hostInput() && hostInput()->matchesReadOnlyPseudoClass();
99 bool SliderThumbElement::matchesReadWritePseudoClass() const
101 return hostInput() && hostInput()->matchesReadWritePseudoClass();
104 Node
* SliderThumbElement::focusDelegate()
109 void SliderThumbElement::dragFrom(const LayoutPoint
& point
)
111 RefPtrWillBeRawPtr
<SliderThumbElement
> protector(this);
113 setPositionFromPoint(point
);
116 void SliderThumbElement::setPositionFromPoint(const LayoutPoint
& point
)
118 RefPtrWillBeRawPtr
<HTMLInputElement
> input(hostInput());
119 Element
* trackElement
= input
->userAgentShadowRoot()->getElementById(ShadowElementNames::sliderTrack());
121 if (!input
->layoutObject() || !layoutBox() || !trackElement
->layoutBox())
124 LayoutPoint offset
= roundedLayoutPoint(input
->layoutObject()->absoluteToLocal(FloatPoint(point
), UseTransforms
));
125 bool isVertical
= hasVerticalAppearance(input
.get());
126 bool isLeftToRightDirection
= layoutBox()->style()->isLeftToRightDirection();
127 LayoutUnit trackSize
;
129 LayoutUnit currentPosition
;
130 // We need to calculate currentPosition from absolute points becaue the
131 // layoutObject for this node is usually on a layer and layoutBox()->x() and
133 // FIXME: This should probably respect transforms.
134 LayoutPoint absoluteThumbOrigin
= layoutBox()->absoluteBoundingBoxRectIgnoringTransforms().location();
135 LayoutPoint absoluteSliderContentOrigin
= roundedLayoutPoint(input
->layoutObject()->localToAbsolute());
136 IntRect trackBoundingBox
= trackElement
->layoutObject()->absoluteBoundingBoxRectIgnoringTransforms();
137 IntRect inputBoundingBox
= input
->layoutObject()->absoluteBoundingBoxRectIgnoringTransforms();
139 trackSize
= trackElement
->layoutBox()->contentHeight() - layoutBox()->size().height();
140 position
= offset
.y() - layoutBox()->size().height() / 2 - trackBoundingBox
.y() + inputBoundingBox
.y() - layoutBox()->marginBottom();
141 currentPosition
= absoluteThumbOrigin
.y() - absoluteSliderContentOrigin
.y();
143 trackSize
= trackElement
->layoutBox()->contentWidth() - layoutBox()->size().width();
144 position
= offset
.x() - layoutBox()->size().width() / 2 - trackBoundingBox
.x() + inputBoundingBox
.x();
145 position
-= isLeftToRightDirection
? layoutBox()->marginLeft() : layoutBox()->marginRight();
146 currentPosition
= absoluteThumbOrigin
.x() - absoluteSliderContentOrigin
.x();
148 position
= std::max
<LayoutUnit
>(0, std::min(position
, trackSize
));
149 const Decimal ratio
= Decimal::fromDouble(static_cast<double>(position
) / trackSize
);
150 const Decimal fraction
= isVertical
|| !isLeftToRightDirection
? Decimal(1) - ratio
: ratio
;
151 StepRange
stepRange(input
->createStepRange(RejectAny
));
152 Decimal value
= stepRange
.clampValue(stepRange
.valueFromProportion(fraction
));
154 Decimal closest
= input
->findClosestTickMarkValue(value
);
155 if (closest
.isFinite()) {
156 double closestFraction
= stepRange
.proportionFromValue(closest
).toDouble();
157 double closestRatio
= isVertical
|| !isLeftToRightDirection
? 1.0 - closestFraction
: closestFraction
;
158 LayoutUnit closestPosition
= trackSize
* closestRatio
;
159 const LayoutUnit snappingThreshold
= 5;
160 if ((closestPosition
- position
).abs() <= snappingThreshold
)
164 String valueString
= serializeForNumberType(value
);
165 if (valueString
== input
->value())
168 // FIXME: This is no longer being set from renderer. Consider updating the method name.
169 input
->setValueFromRenderer(valueString
);
171 layoutObject()->setNeedsLayoutAndFullPaintInvalidation(LayoutInvalidationReason::SliderValueChanged
);
174 void SliderThumbElement::startDragging()
176 if (LocalFrame
* frame
= document().frame()) {
177 frame
->eventHandler().setCapturingMouseEventsNode(this);
182 void SliderThumbElement::stopDragging()
187 if (LocalFrame
* frame
= document().frame())
188 frame
->eventHandler().setCapturingMouseEventsNode(nullptr);
189 m_inDragMode
= false;
191 layoutObject()->setNeedsLayoutAndFullPaintInvalidation(LayoutInvalidationReason::SliderValueChanged
);
193 hostInput()->dispatchFormControlChangeEvent();
196 void SliderThumbElement::defaultEventHandler(Event
* event
)
198 if (!event
->isMouseEvent()) {
199 HTMLDivElement::defaultEventHandler(event
);
203 // FIXME: Should handle this readonly/disabled check in more general way.
204 // Missing this kind of check is likely to occur elsewhere if adding it in each shadow element.
205 HTMLInputElement
* input
= hostInput();
206 if (!input
|| input
->isDisabledOrReadOnly()) {
208 HTMLDivElement::defaultEventHandler(event
);
212 MouseEvent
* mouseEvent
= toMouseEvent(event
);
213 bool isLeftButton
= mouseEvent
->button() == LeftButton
;
214 const AtomicString
& eventType
= event
->type();
216 // We intentionally do not call event->setDefaultHandled() here because
217 // MediaControlTimelineElement::defaultEventHandler() wants to handle these
219 if (eventType
== EventTypeNames::mousedown
&& isLeftButton
) {
222 } else if (eventType
== EventTypeNames::mouseup
&& isLeftButton
) {
225 } else if (eventType
== EventTypeNames::mousemove
) {
227 setPositionFromPoint(mouseEvent
->absoluteLocation());
231 HTMLDivElement::defaultEventHandler(event
);
234 bool SliderThumbElement::willRespondToMouseMoveEvents()
236 const HTMLInputElement
* input
= hostInput();
237 if (input
&& !input
->isDisabledOrReadOnly() && m_inDragMode
)
240 return HTMLDivElement::willRespondToMouseMoveEvents();
243 bool SliderThumbElement::willRespondToMouseClickEvents()
245 const HTMLInputElement
* input
= hostInput();
246 if (input
&& !input
->isDisabledOrReadOnly())
249 return HTMLDivElement::willRespondToMouseClickEvents();
252 void SliderThumbElement::detach(const AttachContext
& context
)
255 if (LocalFrame
* frame
= document().frame())
256 frame
->eventHandler().setCapturingMouseEventsNode(nullptr);
258 HTMLDivElement::detach(context
);
261 HTMLInputElement
* SliderThumbElement::hostInput() const
263 // Only HTMLInputElement creates SliderThumbElement instances as its shadow nodes.
264 // So, shadowHost() must be an HTMLInputElement.
265 return toHTMLInputElement(shadowHost());
268 static const AtomicString
& sliderThumbShadowPartId()
270 DEFINE_STATIC_LOCAL(const AtomicString
, sliderThumb
, ("-webkit-slider-thumb", AtomicString::ConstructFromLiteral
));
274 static const AtomicString
& mediaSliderThumbShadowPartId()
276 DEFINE_STATIC_LOCAL(const AtomicString
, mediaSliderThumb
, ("-webkit-media-slider-thumb", AtomicString::ConstructFromLiteral
));
277 return mediaSliderThumb
;
280 const AtomicString
& SliderThumbElement::shadowPseudoId() const
282 HTMLInputElement
* input
= hostInput();
283 if (!input
|| !input
->layoutObject())
284 return sliderThumbShadowPartId();
286 const ComputedStyle
& sliderStyle
= input
->layoutObject()->styleRef();
287 switch (sliderStyle
.appearance()) {
288 case MediaSliderPart
:
289 case MediaSliderThumbPart
:
290 case MediaVolumeSliderPart
:
291 case MediaVolumeSliderThumbPart
:
292 case MediaFullScreenVolumeSliderPart
:
293 case MediaFullScreenVolumeSliderThumbPart
:
294 return mediaSliderThumbShadowPartId();
296 return sliderThumbShadowPartId();
300 // --------------------------------
302 inline SliderContainerElement::SliderContainerElement(Document
& document
)
303 : HTMLDivElement(document
)
307 DEFINE_NODE_FACTORY(SliderContainerElement
)
309 LayoutObject
* SliderContainerElement::createLayoutObject(const ComputedStyle
&)
311 return new LayoutSliderContainer(this);
314 const AtomicString
& SliderContainerElement::shadowPseudoId() const
316 DEFINE_STATIC_LOCAL(const AtomicString
, mediaSliderContainer
, ("-webkit-media-slider-container", AtomicString::ConstructFromLiteral
));
317 DEFINE_STATIC_LOCAL(const AtomicString
, sliderContainer
, ("-webkit-slider-container", AtomicString::ConstructFromLiteral
));
319 if (!shadowHost() || !shadowHost()->layoutObject())
320 return sliderContainer
;
322 const ComputedStyle
& sliderStyle
= shadowHost()->layoutObject()->styleRef();
323 switch (sliderStyle
.appearance()) {
324 case MediaSliderPart
:
325 case MediaSliderThumbPart
:
326 case MediaVolumeSliderPart
:
327 case MediaVolumeSliderThumbPart
:
328 case MediaFullScreenVolumeSliderPart
:
329 case MediaFullScreenVolumeSliderThumbPart
:
330 return mediaSliderContainer
;
332 return sliderContainer
;