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 #include "config.h" 33 #include "core/html/shadow/SliderThumbElement.h" 34 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/page/EventHandler.h" 44 #include "core/rendering/RenderFlexibleBox.h" 45 #include "core/rendering/RenderSlider.h" 46 #include "core/rendering/RenderSliderContainer.h" 47 #include "core/rendering/RenderSliderThumb.h" 48 #include "core/rendering/RenderTheme.h" 49 50 namespace blink { 51 52 using namespace HTMLNames; 53 54 inline static bool hasVerticalAppearance(HTMLInputElement* input) 55 { 56 ASSERT(input->renderer()); 57 RenderStyle* sliderStyle = input->renderer()->style(); 58 59 return sliderStyle->appearance() == SliderVerticalPart; 60 } 61 62 inline SliderThumbElement::SliderThumbElement(Document& document) 63 : HTMLDivElement(document) 64 , m_inDragMode(false) 65 { 66 } 67 68 PassRefPtrWillBeRawPtr<SliderThumbElement> SliderThumbElement::create(Document& document) 69 { 70 RefPtrWillBeRawPtr<SliderThumbElement> element = adoptRefWillBeNoop(new SliderThumbElement(document)); 71 element->setAttribute(idAttr, ShadowElementNames::sliderThumb()); 72 return element.release(); 73 } 74 75 void SliderThumbElement::setPositionFromValue() 76 { 77 // Since the code to calculate position is in the RenderSliderThumb layout 78 // path, we don't actually update the value here. Instead, we poke at the 79 // renderer directly to trigger layout. 80 if (renderer()) 81 renderer()->setNeedsLayoutAndFullPaintInvalidation(); 82 } 83 84 RenderObject* SliderThumbElement::createRenderer(RenderStyle*) 85 { 86 return new RenderSliderThumb(this); 87 } 88 89 bool SliderThumbElement::isDisabledFormControl() const 90 { 91 return hostInput() && hostInput()->isDisabledFormControl(); 92 } 93 94 bool SliderThumbElement::matchesReadOnlyPseudoClass() const 95 { 96 return hostInput() && hostInput()->matchesReadOnlyPseudoClass(); 97 } 98 99 bool SliderThumbElement::matchesReadWritePseudoClass() const 100 { 101 return hostInput() && hostInput()->matchesReadWritePseudoClass(); 102 } 103 104 Node* SliderThumbElement::focusDelegate() 105 { 106 return hostInput(); 107 } 108 109 void SliderThumbElement::dragFrom(const LayoutPoint& point) 110 { 111 RefPtrWillBeRawPtr<SliderThumbElement> protector(this); 112 startDragging(); 113 setPositionFromPoint(point); 114 } 115 116 void SliderThumbElement::setPositionFromPoint(const LayoutPoint& point) 117 { 118 RefPtrWillBeRawPtr<HTMLInputElement> input(hostInput()); 119 Element* trackElement = input->userAgentShadowRoot()->getElementById(ShadowElementNames::sliderTrack()); 120 121 if (!input->renderer() || !renderBox() || !trackElement->renderBox()) 122 return; 123 124 LayoutPoint offset = roundedLayoutPoint(input->renderer()->absoluteToLocal(point, UseTransforms)); 125 bool isVertical = hasVerticalAppearance(input.get()); 126 bool isLeftToRightDirection = renderBox()->style()->isLeftToRightDirection(); 127 LayoutUnit trackSize; 128 LayoutUnit position; 129 LayoutUnit currentPosition; 130 // We need to calculate currentPosition from absolute points becaue the 131 // renderer for this node is usually on a layer and renderBox()->x() and 132 // y() are unusable. 133 // FIXME: This should probably respect transforms. 134 LayoutPoint absoluteThumbOrigin = renderBox()->absoluteBoundingBoxRectIgnoringTransforms().location(); 135 LayoutPoint absoluteSliderContentOrigin = roundedLayoutPoint(input->renderer()->localToAbsolute()); 136 IntRect trackBoundingBox = trackElement->renderer()->absoluteBoundingBoxRectIgnoringTransforms(); 137 IntRect inputBoundingBox = input->renderer()->absoluteBoundingBoxRectIgnoringTransforms(); 138 if (isVertical) { 139 trackSize = trackElement->renderBox()->contentHeight() - renderBox()->height(); 140 position = offset.y() - renderBox()->height() / 2 - trackBoundingBox.y() + inputBoundingBox.y() - renderBox()->marginBottom(); 141 currentPosition = absoluteThumbOrigin.y() - absoluteSliderContentOrigin.y(); 142 } else { 143 trackSize = trackElement->renderBox()->contentWidth() - renderBox()->width(); 144 position = offset.x() - renderBox()->width() / 2 - trackBoundingBox.x() + inputBoundingBox.x(); 145 position -= isLeftToRightDirection ? renderBox()->marginLeft() : renderBox()->marginRight(); 146 currentPosition = absoluteThumbOrigin.x() - absoluteSliderContentOrigin.x(); 147 } 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)); 153 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) 161 value = closest; 162 } 163 164 String valueString = serializeForNumberType(value); 165 if (valueString == input->value()) 166 return; 167 168 // FIXME: This is no longer being set from renderer. Consider updating the method name. 169 input->setValueFromRenderer(valueString); 170 if (renderer()) 171 renderer()->setNeedsLayoutAndFullPaintInvalidation(); 172 } 173 174 void SliderThumbElement::startDragging() 175 { 176 if (LocalFrame* frame = document().frame()) { 177 frame->eventHandler().setCapturingMouseEventsNode(this); 178 m_inDragMode = true; 179 } 180 } 181 182 void SliderThumbElement::stopDragging() 183 { 184 if (!m_inDragMode) 185 return; 186 187 if (LocalFrame* frame = document().frame()) 188 frame->eventHandler().setCapturingMouseEventsNode(nullptr); 189 m_inDragMode = false; 190 if (renderer()) 191 renderer()->setNeedsLayoutAndFullPaintInvalidation(); 192 if (hostInput()) 193 hostInput()->dispatchFormControlChangeEvent(); 194 } 195 196 void SliderThumbElement::defaultEventHandler(Event* event) 197 { 198 if (!event->isMouseEvent()) { 199 HTMLDivElement::defaultEventHandler(event); 200 return; 201 } 202 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()) { 207 stopDragging(); 208 HTMLDivElement::defaultEventHandler(event); 209 return; 210 } 211 212 MouseEvent* mouseEvent = toMouseEvent(event); 213 bool isLeftButton = mouseEvent->button() == LeftButton; 214 const AtomicString& eventType = event->type(); 215 216 // We intentionally do not call event->setDefaultHandled() here because 217 // MediaControlTimelineElement::defaultEventHandler() wants to handle these 218 // mouse events. 219 if (eventType == EventTypeNames::mousedown && isLeftButton) { 220 startDragging(); 221 return; 222 } else if (eventType == EventTypeNames::mouseup && isLeftButton) { 223 stopDragging(); 224 return; 225 } else if (eventType == EventTypeNames::mousemove) { 226 if (m_inDragMode) 227 setPositionFromPoint(mouseEvent->absoluteLocation()); 228 return; 229 } 230 231 HTMLDivElement::defaultEventHandler(event); 232 } 233 234 bool SliderThumbElement::willRespondToMouseMoveEvents() 235 { 236 const HTMLInputElement* input = hostInput(); 237 if (input && !input->isDisabledOrReadOnly() && m_inDragMode) 238 return true; 239 240 return HTMLDivElement::willRespondToMouseMoveEvents(); 241 } 242 243 bool SliderThumbElement::willRespondToMouseClickEvents() 244 { 245 const HTMLInputElement* input = hostInput(); 246 if (input && !input->isDisabledOrReadOnly()) 247 return true; 248 249 return HTMLDivElement::willRespondToMouseClickEvents(); 250 } 251 252 void SliderThumbElement::detach(const AttachContext& context) 253 { 254 if (m_inDragMode) { 255 if (LocalFrame* frame = document().frame()) 256 frame->eventHandler().setCapturingMouseEventsNode(nullptr); 257 } 258 HTMLDivElement::detach(context); 259 } 260 261 HTMLInputElement* SliderThumbElement::hostInput() const 262 { 263 // Only HTMLInputElement creates SliderThumbElement instances as its shadow nodes. 264 // So, shadowHost() must be an HTMLInputElement. 265 return toHTMLInputElement(shadowHost()); 266 } 267 268 static const AtomicString& sliderThumbShadowPartId() 269 { 270 DEFINE_STATIC_LOCAL(const AtomicString, sliderThumb, ("-webkit-slider-thumb", AtomicString::ConstructFromLiteral)); 271 return sliderThumb; 272 } 273 274 static const AtomicString& mediaSliderThumbShadowPartId() 275 { 276 DEFINE_STATIC_LOCAL(const AtomicString, mediaSliderThumb, ("-webkit-media-slider-thumb", AtomicString::ConstructFromLiteral)); 277 return mediaSliderThumb; 278 } 279 280 const AtomicString& SliderThumbElement::shadowPseudoId() const 281 { 282 HTMLInputElement* input = hostInput(); 283 if (!input || !input->renderer()) 284 return sliderThumbShadowPartId(); 285 286 RenderStyle* sliderStyle = input->renderer()->style(); 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(); 295 default: 296 return sliderThumbShadowPartId(); 297 } 298 } 299 300 // -------------------------------- 301 302 inline SliderContainerElement::SliderContainerElement(Document& document) 303 : HTMLDivElement(document) 304 { 305 } 306 307 DEFINE_NODE_FACTORY(SliderContainerElement) 308 309 RenderObject* SliderContainerElement::createRenderer(RenderStyle*) 310 { 311 return new RenderSliderContainer(this); 312 } 313 314 const AtomicString& SliderContainerElement::shadowPseudoId() const 315 { 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)); 318 319 if (!shadowHost() || !shadowHost()->renderer()) 320 return sliderContainer; 321 322 RenderStyle* sliderStyle = shadowHost()->renderer()->style(); 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; 331 default: 332 return sliderContainer; 333 } 334 } 335 336 } 337