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