| 1 | /* |
| 2 | * Copyright (C) 2010 Google Inc. All rights reserved. |
| 3 | * Copyright (C) 2011-2018 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 "NumberInputType.h" |
| 34 | |
| 35 | #include "HTMLInputElement.h" |
| 36 | #include "HTMLNames.h" |
| 37 | #include "HTMLParserIdioms.h" |
| 38 | #include "InputTypeNames.h" |
| 39 | #include "KeyboardEvent.h" |
| 40 | #include "LocalizedStrings.h" |
| 41 | #include "PlatformLocale.h" |
| 42 | #include "RenderTextControl.h" |
| 43 | #include <limits> |
| 44 | #include <wtf/ASCIICType.h> |
| 45 | #include <wtf/MathExtras.h> |
| 46 | |
| 47 | namespace WebCore { |
| 48 | |
| 49 | using namespace HTMLNames; |
| 50 | |
| 51 | static const int numberDefaultStep = 1; |
| 52 | static const int numberDefaultStepBase = 0; |
| 53 | static const int numberStepScaleFactor = 1; |
| 54 | |
| 55 | struct RealNumberRenderSize { |
| 56 | unsigned sizeBeforeDecimalPoint; |
| 57 | unsigned sizeAfteDecimalPoint; |
| 58 | |
| 59 | RealNumberRenderSize max(const RealNumberRenderSize& other) const |
| 60 | { |
| 61 | return { |
| 62 | std::max(sizeBeforeDecimalPoint, other.sizeBeforeDecimalPoint), |
| 63 | std::max(sizeAfteDecimalPoint, other.sizeAfteDecimalPoint) |
| 64 | }; |
| 65 | } |
| 66 | }; |
| 67 | |
| 68 | static RealNumberRenderSize calculateRenderSize(const Decimal& value) |
| 69 | { |
| 70 | ASSERT(value.isFinite()); |
| 71 | const unsigned sizeOfDigits = String::number(value.value().coefficient()).length(); |
| 72 | const unsigned sizeOfSign = value.isNegative() ? 1 : 0; |
| 73 | const int exponent = value.exponent(); |
| 74 | if (exponent >= 0) |
| 75 | return { sizeOfSign + sizeOfDigits, 0 }; |
| 76 | |
| 77 | const int sizeBeforeDecimalPoint = exponent + sizeOfDigits; |
| 78 | if (sizeBeforeDecimalPoint > 0) { |
| 79 | // In case of "123.456" |
| 80 | return { sizeOfSign + sizeBeforeDecimalPoint, sizeOfDigits - sizeBeforeDecimalPoint }; |
| 81 | } |
| 82 | |
| 83 | // In case of "0.00012345" |
| 84 | const unsigned sizeOfZero = 1; |
| 85 | const unsigned numberOfZeroAfterDecimalPoint = -sizeBeforeDecimalPoint; |
| 86 | return { sizeOfSign + sizeOfZero , numberOfZeroAfterDecimalPoint + sizeOfDigits }; |
| 87 | } |
| 88 | |
| 89 | const AtomicString& NumberInputType::formControlType() const |
| 90 | { |
| 91 | return InputTypeNames::number(); |
| 92 | } |
| 93 | |
| 94 | void NumberInputType::setValue(const String& sanitizedValue, bool valueChanged, TextFieldEventBehavior eventBehavior) |
| 95 | { |
| 96 | ASSERT(element()); |
| 97 | if (!valueChanged && sanitizedValue.isEmpty() && !element()->innerTextValue().isEmpty()) |
| 98 | updateInnerTextValue(); |
| 99 | TextFieldInputType::setValue(sanitizedValue, valueChanged, eventBehavior); |
| 100 | } |
| 101 | |
| 102 | double NumberInputType::valueAsDouble() const |
| 103 | { |
| 104 | ASSERT(element()); |
| 105 | return parseToDoubleForNumberType(element()->value()); |
| 106 | } |
| 107 | |
| 108 | ExceptionOr<void> NumberInputType::setValueAsDouble(double newValue, TextFieldEventBehavior eventBehavior) const |
| 109 | { |
| 110 | // FIXME: We should use numeric_limits<double>::max for number input type. |
| 111 | const double floatMax = std::numeric_limits<float>::max(); |
| 112 | if (newValue < -floatMax || newValue > floatMax) |
| 113 | return Exception { InvalidStateError }; |
| 114 | ASSERT(element()); |
| 115 | element()->setValue(serializeForNumberType(newValue), eventBehavior); |
| 116 | return { }; |
| 117 | } |
| 118 | |
| 119 | ExceptionOr<void> NumberInputType::setValueAsDecimal(const Decimal& newValue, TextFieldEventBehavior eventBehavior) const |
| 120 | { |
| 121 | // FIXME: We should use numeric_limits<double>::max for number input type. |
| 122 | const Decimal floatMax = Decimal::fromDouble(std::numeric_limits<float>::max()); |
| 123 | if (newValue < -floatMax || newValue > floatMax) |
| 124 | return Exception { InvalidStateError }; |
| 125 | ASSERT(element()); |
| 126 | element()->setValue(serializeForNumberType(newValue), eventBehavior); |
| 127 | return { }; |
| 128 | } |
| 129 | |
| 130 | bool NumberInputType::typeMismatchFor(const String& value) const |
| 131 | { |
| 132 | return !value.isEmpty() && !std::isfinite(parseToDoubleForNumberType(value)); |
| 133 | } |
| 134 | |
| 135 | bool NumberInputType::typeMismatch() const |
| 136 | { |
| 137 | ASSERT(element()); |
| 138 | ASSERT(!typeMismatchFor(element()->value())); |
| 139 | return false; |
| 140 | } |
| 141 | |
| 142 | StepRange NumberInputType::createStepRange(AnyStepHandling anyStepHandling) const |
| 143 | { |
| 144 | static NeverDestroyed<const StepRange::StepDescription> stepDescription(numberDefaultStep, numberDefaultStepBase, numberStepScaleFactor); |
| 145 | |
| 146 | ASSERT(element()); |
| 147 | const Decimal stepBase = parseToDecimalForNumberType(element()->attributeWithoutSynchronization(minAttr), numberDefaultStepBase); |
| 148 | // FIXME: We should use numeric_limits<double>::max for number input type. |
| 149 | const Decimal floatMax = Decimal::fromDouble(std::numeric_limits<float>::max()); |
| 150 | const Element& element = *this->element(); |
| 151 | |
| 152 | RangeLimitations rangeLimitations = RangeLimitations::Invalid; |
| 153 | auto = [&] (const QualifiedName& attributeName, const Decimal& defaultValue) -> Decimal { |
| 154 | const AtomicString& attributeValue = element.attributeWithoutSynchronization(attributeName); |
| 155 | Decimal valueFromAttribute = parseToNumberOrNaN(attributeValue); |
| 156 | if (valueFromAttribute.isFinite()) { |
| 157 | rangeLimitations = RangeLimitations::Valid; |
| 158 | return valueFromAttribute; |
| 159 | } |
| 160 | return defaultValue; |
| 161 | }; |
| 162 | Decimal minimum = extractBound(minAttr, -floatMax); |
| 163 | Decimal maximum = extractBound(maxAttr, floatMax); |
| 164 | |
| 165 | const Decimal step = StepRange::parseStep(anyStepHandling, stepDescription, element.attributeWithoutSynchronization(stepAttr)); |
| 166 | return StepRange(stepBase, rangeLimitations, minimum, maximum, step, stepDescription); |
| 167 | } |
| 168 | |
| 169 | bool NumberInputType::sizeShouldIncludeDecoration(int defaultSize, int& preferredSize) const |
| 170 | { |
| 171 | preferredSize = defaultSize; |
| 172 | |
| 173 | ASSERT(element()); |
| 174 | auto& stepString = element()->attributeWithoutSynchronization(stepAttr); |
| 175 | if (equalLettersIgnoringASCIICase(stepString, "any" )) |
| 176 | return false; |
| 177 | |
| 178 | const Decimal minimum = parseToDecimalForNumberType(element()->attributeWithoutSynchronization(minAttr)); |
| 179 | if (!minimum.isFinite()) |
| 180 | return false; |
| 181 | |
| 182 | const Decimal maximum = parseToDecimalForNumberType(element()->attributeWithoutSynchronization(maxAttr)); |
| 183 | if (!maximum.isFinite()) |
| 184 | return false; |
| 185 | |
| 186 | const Decimal step = parseToDecimalForNumberType(stepString, 1); |
| 187 | ASSERT(step.isFinite()); |
| 188 | |
| 189 | RealNumberRenderSize size = calculateRenderSize(minimum).max(calculateRenderSize(maximum).max(calculateRenderSize(step))); |
| 190 | |
| 191 | preferredSize = size.sizeBeforeDecimalPoint + size.sizeAfteDecimalPoint + (size.sizeAfteDecimalPoint ? 1 : 0); |
| 192 | |
| 193 | return true; |
| 194 | } |
| 195 | |
| 196 | float NumberInputType::decorationWidth() const |
| 197 | { |
| 198 | ASSERT(element()); |
| 199 | |
| 200 | float width = 0; |
| 201 | RefPtr<HTMLElement> spinButton = element()->innerSpinButtonElement(); |
| 202 | if (RenderBox* spinRenderer = spinButton ? spinButton->renderBox() : 0) { |
| 203 | width += spinRenderer->borderAndPaddingLogicalWidth(); |
| 204 | // Since the width of spinRenderer is not calculated yet, spinRenderer->logicalWidth() returns 0. |
| 205 | // So computedStyle()->logicalWidth() is used instead. |
| 206 | width += spinButton->computedStyle()->logicalWidth().value(); |
| 207 | } |
| 208 | return width; |
| 209 | } |
| 210 | |
| 211 | bool NumberInputType::isSteppable() const |
| 212 | { |
| 213 | return true; |
| 214 | } |
| 215 | |
| 216 | auto NumberInputType::handleKeydownEvent(KeyboardEvent& event) -> ShouldCallBaseEventHandler |
| 217 | { |
| 218 | handleKeydownEventForSpinButton(event); |
| 219 | if (!event.defaultHandled()) |
| 220 | return TextFieldInputType::handleKeydownEvent(event); |
| 221 | return ShouldCallBaseEventHandler::Yes; |
| 222 | } |
| 223 | |
| 224 | Decimal NumberInputType::parseToNumber(const String& src, const Decimal& defaultValue) const |
| 225 | { |
| 226 | return parseToDecimalForNumberType(src, defaultValue); |
| 227 | } |
| 228 | |
| 229 | String NumberInputType::serialize(const Decimal& value) const |
| 230 | { |
| 231 | if (!value.isFinite()) |
| 232 | return String(); |
| 233 | return serializeForNumberType(value); |
| 234 | } |
| 235 | |
| 236 | static bool isE(UChar ch) |
| 237 | { |
| 238 | return ch == 'e' || ch == 'E'; |
| 239 | } |
| 240 | |
| 241 | String NumberInputType::localizeValue(const String& proposedValue) const |
| 242 | { |
| 243 | if (proposedValue.isEmpty()) |
| 244 | return proposedValue; |
| 245 | // We don't localize scientific notations. |
| 246 | if (proposedValue.find(isE) != notFound) |
| 247 | return proposedValue; |
| 248 | ASSERT(element()); |
| 249 | return element()->locale().convertToLocalizedNumber(proposedValue); |
| 250 | } |
| 251 | |
| 252 | String NumberInputType::visibleValue() const |
| 253 | { |
| 254 | ASSERT(element()); |
| 255 | return localizeValue(element()->value()); |
| 256 | } |
| 257 | |
| 258 | String NumberInputType::convertFromVisibleValue(const String& visibleValue) const |
| 259 | { |
| 260 | if (visibleValue.isEmpty()) |
| 261 | return visibleValue; |
| 262 | // We don't localize scientific notations. |
| 263 | if (visibleValue.find(isE) != notFound) |
| 264 | return visibleValue; |
| 265 | ASSERT(element()); |
| 266 | return element()->locale().convertFromLocalizedNumber(visibleValue); |
| 267 | } |
| 268 | |
| 269 | String NumberInputType::sanitizeValue(const String& proposedValue) const |
| 270 | { |
| 271 | if (proposedValue.isEmpty()) |
| 272 | return proposedValue; |
| 273 | return std::isfinite(parseToDoubleForNumberType(proposedValue)) ? proposedValue : emptyString(); |
| 274 | } |
| 275 | |
| 276 | bool NumberInputType::hasBadInput() const |
| 277 | { |
| 278 | ASSERT(element()); |
| 279 | String standardValue = convertFromVisibleValue(element()->innerTextValue()); |
| 280 | return !standardValue.isEmpty() && !std::isfinite(parseToDoubleForNumberType(standardValue)); |
| 281 | } |
| 282 | |
| 283 | String NumberInputType::badInputText() const |
| 284 | { |
| 285 | return validationMessageBadInputForNumberText(); |
| 286 | } |
| 287 | |
| 288 | bool NumberInputType::supportsPlaceholder() const |
| 289 | { |
| 290 | return true; |
| 291 | } |
| 292 | |
| 293 | bool NumberInputType::isNumberField() const |
| 294 | { |
| 295 | return true; |
| 296 | } |
| 297 | |
| 298 | void NumberInputType::attributeChanged(const QualifiedName& name) |
| 299 | { |
| 300 | ASSERT(element()); |
| 301 | if (name == maxAttr || name == minAttr) { |
| 302 | if (auto* element = this->element()) { |
| 303 | element->invalidateStyleForSubtree(); |
| 304 | if (auto* renderer = element->renderer()) |
| 305 | renderer->setNeedsLayoutAndPrefWidthsRecalc(); |
| 306 | } |
| 307 | } else if (name == stepAttr) { |
| 308 | if (auto* element = this->element()) { |
| 309 | if (auto* renderer = element->renderer()) |
| 310 | renderer->setNeedsLayoutAndPrefWidthsRecalc(); |
| 311 | } |
| 312 | } |
| 313 | TextFieldInputType::attributeChanged(name); |
| 314 | } |
| 315 | |
| 316 | } // namespace WebCore |
| 317 | |