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