1/* 2 * Copyright (C) 2010 Google Inc. All rights reserved. 3 * Copyright (C) 2011 Apple 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 "RangeInputType.h" 34 35#include "AXObjectCache.h" 36#include "ExceptionCodePlaceholder.h" 37#include "HTMLInputElement.h" 38#include "HTMLParserIdioms.h" 39#include "InputTypeNames.h" 40#include "KeyboardEvent.h" 41#include "MouseEvent.h" 42#include "PlatformMouseEvent.h" 43#include "RenderSlider.h" 44#include "ScopedEventQueue.h" 45#include "ShadowRoot.h" 46#include "SliderThumbElement.h" 47#include <limits> 48#include <wtf/MathExtras.h> 49 50#if ENABLE(TOUCH_EVENTS) 51#include "Touch.h" 52#include "TouchEvent.h" 53#include "TouchList.h" 54#endif 55 56#if ENABLE(DATALIST_ELEMENT) 57#include "HTMLDataListElement.h" 58#include "HTMLOptionElement.h" 59#endif 60 61namespace WebCore { 62 63using namespace HTMLNames; 64 65static const int rangeDefaultMinimum = 0; 66static const int rangeDefaultMaximum = 100; 67static const int rangeDefaultStep = 1; 68static const int rangeDefaultStepBase = 0; 69static const int rangeStepScaleFactor = 1; 70 71static Decimal ensureMaximum(const Decimal& proposedValue, const Decimal& minimum, const Decimal& fallbackValue) 72{ 73 return proposedValue >= minimum ? proposedValue : std::max(minimum, fallbackValue); 74} 75 76RangeInputType::RangeInputType(HTMLInputElement& element) 77 : InputType(element) 78#if ENABLE(DATALIST_ELEMENT) 79 , m_tickMarkValuesDirty(true) 80#endif 81{ 82} 83 84bool RangeInputType::isRangeControl() const 85{ 86 return true; 87} 88 89const AtomicString& RangeInputType::formControlType() const 90{ 91 return InputTypeNames::range(); 92} 93 94double RangeInputType::valueAsDouble() const 95{ 96 return parseToDoubleForNumberType(element().value()); 97} 98 99void RangeInputType::setValueAsDecimal(const Decimal& newValue, TextFieldEventBehavior eventBehavior, ExceptionCode&) const 100{ 101 element().setValue(serialize(newValue), eventBehavior); 102} 103 104bool RangeInputType::typeMismatchFor(const String& value) const 105{ 106 return !value.isEmpty() && !std::isfinite(parseToDoubleForNumberType(value)); 107} 108 109bool RangeInputType::supportsRequired() const 110{ 111 return false; 112} 113 114StepRange RangeInputType::createStepRange(AnyStepHandling anyStepHandling) const 115{ 116 DEPRECATED_DEFINE_STATIC_LOCAL(const StepRange::StepDescription, stepDescription, (rangeDefaultStep, rangeDefaultStepBase, rangeStepScaleFactor)); 117 118 const Decimal minimum = parseToNumber(element().fastGetAttribute(minAttr), rangeDefaultMinimum); 119 const Decimal maximum = ensureMaximum(parseToNumber(element().fastGetAttribute(maxAttr), rangeDefaultMaximum), minimum, rangeDefaultMaximum); 120 121 const AtomicString& precisionValue = element().fastGetAttribute(precisionAttr); 122 if (!precisionValue.isNull()) { 123 const Decimal step = equalIgnoringCase(precisionValue, "float") ? Decimal::nan() : 1; 124 return StepRange(minimum, minimum, maximum, step, stepDescription); 125 } 126 127 const Decimal step = StepRange::parseStep(anyStepHandling, stepDescription, element().fastGetAttribute(stepAttr)); 128 return StepRange(minimum, minimum, maximum, step, stepDescription); 129} 130 131bool RangeInputType::isSteppable() const 132{ 133 return true; 134} 135 136#if !PLATFORM(IOS) 137void RangeInputType::handleMouseDownEvent(MouseEvent* event) 138{ 139 if (element().isDisabledOrReadOnly()) 140 return; 141 142 Node* targetNode = event->target()->toNode(); 143 if (event->button() != LeftButton || !targetNode) 144 return; 145 ASSERT(element().shadowRoot()); 146 if (targetNode != &element() && !targetNode->isDescendantOf(element().userAgentShadowRoot())) 147 return; 148 SliderThumbElement& thumb = typedSliderThumbElement(); 149 if (targetNode == &thumb) 150 return; 151 thumb.dragFrom(event->absoluteLocation()); 152} 153#endif 154 155#if ENABLE(TOUCH_EVENTS) 156void RangeInputType::handleTouchEvent(TouchEvent* event) 157{ 158#if PLATFORM(IOS) 159 typedSliderThumbElement().handleTouchEvent(event); 160#elif ENABLE(TOUCH_SLIDER) 161 if (element().isDisabledOrReadOnly()) 162 return; 163 164 if (event->type() == eventNames().touchendEvent) { 165 event->setDefaultHandled(); 166 return; 167 } 168 169 TouchList* touches = event->targetTouches(); 170 if (touches->length() == 1) { 171 typedSliderThumbElement().setPositionFromPoint(touches->item(0)->absoluteLocation()); 172 event->setDefaultHandled(); 173 } 174#else 175 UNUSED_PARAM(event); 176#endif 177} 178 179#if ENABLE(TOUCH_SLIDER) 180bool RangeInputType::hasTouchEventHandler() const 181{ 182 return true; 183} 184#endif 185 186#if PLATFORM(IOS) 187void RangeInputType::disabledAttributeChanged() 188{ 189 typedSliderThumbElement().disabledAttributeChanged(); 190} 191#endif 192#endif // ENABLE(TOUCH_EVENTS) 193 194void RangeInputType::handleKeydownEvent(KeyboardEvent* event) 195{ 196 if (element().isDisabledOrReadOnly()) 197 return; 198 199 const String& key = event->keyIdentifier(); 200 201 const Decimal current = parseToNumberOrNaN(element().value()); 202 ASSERT(current.isFinite()); 203 204 StepRange stepRange(createStepRange(RejectAny)); 205 206 207 // FIXME: We can't use stepUp() for the step value "any". So, we increase 208 // or decrease the value by 1/100 of the value range. Is it reasonable? 209 const Decimal step = equalIgnoringCase(element().fastGetAttribute(stepAttr), "any") ? (stepRange.maximum() - stepRange.minimum()) / 100 : stepRange.step(); 210 const Decimal bigStep = std::max((stepRange.maximum() - stepRange.minimum()) / 10, step); 211 212 bool isVertical = false; 213 if (element().renderer()) { 214 ControlPart part = element().renderer()->style().appearance(); 215 isVertical = part == SliderVerticalPart || part == MediaVolumeSliderPart; 216 } 217 218 Decimal newValue; 219 if (key == "Up") 220 newValue = current + step; 221 else if (key == "Down") 222 newValue = current - step; 223 else if (key == "Left") 224 newValue = isVertical ? current + step : current - step; 225 else if (key == "Right") 226 newValue = isVertical ? current - step : current + step; 227 else if (key == "PageUp") 228 newValue = current + bigStep; 229 else if (key == "PageDown") 230 newValue = current - bigStep; 231 else if (key == "Home") 232 newValue = isVertical ? stepRange.maximum() : stepRange.minimum(); 233 else if (key == "End") 234 newValue = isVertical ? stepRange.minimum() : stepRange.maximum(); 235 else 236 return; // Did not match any key binding. 237 238 newValue = stepRange.clampValue(newValue); 239 240 if (newValue != current) { 241 EventQueueScope scope; 242 setValueAsDecimal(newValue, DispatchInputAndChangeEvent, IGNORE_EXCEPTION); 243 244 if (AXObjectCache* cache = element().document().existingAXObjectCache()) 245 cache->postNotification(&element(), AXObjectCache::AXValueChanged); 246 } 247 248 event->setDefaultHandled(); 249} 250 251void RangeInputType::createShadowSubtree() 252{ 253 ASSERT(element().userAgentShadowRoot()); 254 255 Document& document = element().document(); 256 RefPtr<HTMLDivElement> track = HTMLDivElement::create(document); 257 track->setPseudo(AtomicString("-webkit-slider-runnable-track", AtomicString::ConstructFromLiteral)); 258 track->appendChild(SliderThumbElement::create(document), IGNORE_EXCEPTION); 259 RefPtr<HTMLElement> container = SliderContainerElement::create(document); 260 container->appendChild(track.release(), IGNORE_EXCEPTION); 261 element().userAgentShadowRoot()->appendChild(container.release(), IGNORE_EXCEPTION); 262} 263 264HTMLElement* RangeInputType::sliderTrackElement() const 265{ 266 ASSERT(element().userAgentShadowRoot()); 267 ASSERT(element().userAgentShadowRoot()->firstChild()); // container 268 ASSERT(element().userAgentShadowRoot()->firstChild()->isHTMLElement()); 269 ASSERT(element().userAgentShadowRoot()->firstChild()->firstChild()); // track 270 271 return &toHTMLElement(*element().userAgentShadowRoot()->firstChild()->firstChild()); 272} 273 274SliderThumbElement& RangeInputType::typedSliderThumbElement() const 275{ 276 ASSERT(sliderTrackElement()->firstChild()); // thumb 277 ASSERT(sliderTrackElement()->firstChild()->isHTMLElement()); 278 279 return static_cast<SliderThumbElement&>(*sliderTrackElement()->firstChild()); 280} 281 282HTMLElement* RangeInputType::sliderThumbElement() const 283{ 284 return &typedSliderThumbElement(); 285} 286 287RenderPtr<RenderElement> RangeInputType::createInputRenderer(PassRef<RenderStyle> style) 288{ 289 return createRenderer<RenderSlider>(element(), WTF::move(style)); 290} 291 292Decimal RangeInputType::parseToNumber(const String& src, const Decimal& defaultValue) const 293{ 294 return parseToDecimalForNumberType(src, defaultValue); 295} 296 297String RangeInputType::serialize(const Decimal& value) const 298{ 299 if (!value.isFinite()) 300 return String(); 301 return serializeForNumberType(value); 302} 303 304// FIXME: Could share this with BaseClickableWithKeyInputType and BaseCheckableInputType if we had a common base class. 305void RangeInputType::accessKeyAction(bool sendMouseEvents) 306{ 307 InputType::accessKeyAction(sendMouseEvents); 308 309 element().dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents); 310} 311 312void RangeInputType::minOrMaxAttributeChanged() 313{ 314 InputType::minOrMaxAttributeChanged(); 315 316 // Sanitize the value. 317 if (element().hasDirtyValue()) 318 element().setValue(element().value()); 319 320 typedSliderThumbElement().setPositionFromValue(); 321} 322 323void RangeInputType::setValue(const String& value, bool valueChanged, TextFieldEventBehavior eventBehavior) 324{ 325 InputType::setValue(value, valueChanged, eventBehavior); 326 327 if (!valueChanged) 328 return; 329 330 if (eventBehavior == DispatchNoEvent) 331 element().setTextAsOfLastFormControlChangeEvent(value); 332 333 typedSliderThumbElement().setPositionFromValue(); 334} 335 336String RangeInputType::fallbackValue() const 337{ 338 return serializeForNumberType(createStepRange(RejectAny).defaultValue()); 339} 340 341String RangeInputType::sanitizeValue(const String& proposedValue) const 342{ 343 StepRange stepRange(createStepRange(RejectAny)); 344 const Decimal proposedNumericValue = parseToNumber(proposedValue, stepRange.defaultValue()); 345 return serializeForNumberType(stepRange.clampValue(proposedNumericValue)); 346} 347 348bool RangeInputType::shouldRespectListAttribute() 349{ 350 return InputType::themeSupportsDataListUI(this); 351} 352 353#if ENABLE(DATALIST_ELEMENT) 354void RangeInputType::listAttributeTargetChanged() 355{ 356 m_tickMarkValuesDirty = true; 357 HTMLElement* sliderTrackElement = this->sliderTrackElement(); 358 if (sliderTrackElement->renderer()) 359 sliderTrackElement->renderer()->setNeedsLayout(); 360} 361 362void RangeInputType::updateTickMarkValues() 363{ 364 if (!m_tickMarkValuesDirty) 365 return; 366 m_tickMarkValues.clear(); 367 m_tickMarkValuesDirty = false; 368 HTMLDataListElement* dataList = element().dataList(); 369 if (!dataList) 370 return; 371 RefPtr<HTMLCollection> options = dataList->options(); 372 m_tickMarkValues.reserveCapacity(options->length()); 373 for (unsigned i = 0; i < options->length(); ++i) { 374 Node* node = options->item(i); 375 HTMLOptionElement* optionElement = toHTMLOptionElement(node); 376 String optionValue = optionElement->value(); 377 if (!element().isValidValue(optionValue)) 378 continue; 379 m_tickMarkValues.append(parseToNumber(optionValue, Decimal::nan())); 380 } 381 m_tickMarkValues.shrinkToFit(); 382 std::sort(m_tickMarkValues.begin(), m_tickMarkValues.end()); 383} 384 385Decimal RangeInputType::findClosestTickMarkValue(const Decimal& value) 386{ 387 updateTickMarkValues(); 388 if (!m_tickMarkValues.size()) 389 return Decimal::nan(); 390 391 size_t left = 0; 392 size_t right = m_tickMarkValues.size(); 393 size_t middle; 394 while (true) { 395 ASSERT(left <= right); 396 middle = left + (right - left) / 2; 397 if (!middle) 398 break; 399 if (middle == m_tickMarkValues.size() - 1 && m_tickMarkValues[middle] < value) { 400 middle++; 401 break; 402 } 403 if (m_tickMarkValues[middle - 1] <= value && m_tickMarkValues[middle] >= value) 404 break; 405 406 if (m_tickMarkValues[middle] < value) 407 left = middle; 408 else 409 right = middle; 410 } 411 const Decimal closestLeft = middle ? m_tickMarkValues[middle - 1] : Decimal::infinity(Decimal::Negative); 412 const Decimal closestRight = middle != m_tickMarkValues.size() ? m_tickMarkValues[middle] : Decimal::infinity(Decimal::Positive); 413 if (closestRight - value < value - closestLeft) 414 return closestRight; 415 return closestLeft; 416} 417#endif 418 419} // namespace WebCore 420