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