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