Bug 1345767 - Part 6: Factor IsRangeOverflow/Underflow() and HasStepMismatch() out of HTMLInputElement. r?smaug draft
authorJessica Jong <jjong@mozilla.com>
Thu, 04 May 2017 15:33:38 +0800
changeset 572492 af0798ce0d52d021c2dd0ba0943b0e2fcddfd49c
parent 572491 9aae21fad5340d0f24295c07e02afb19deac4ce3
child 572493 463332df0a3b904e7f4636485021cb9735410615
push id57094
push userjjong@mozilla.com
push dateThu, 04 May 2017 07:44:57 +0000
reviewerssmaug
bugs1345767
milestone55.0a1
Bug 1345767 - Part 6: Factor IsRangeOverflow/Underflow() and HasStepMismatch() out of HTMLInputElement. r?smaug MozReview-Commit-ID: 4jmjPK1Wxhz
dom/html/HTMLInputElement.cpp
dom/html/input/DateTimeInputTypes.cpp
dom/html/input/DateTimeInputTypes.h
dom/html/input/InputType.cpp
dom/html/input/InputType.h
dom/html/input/NumericInputTypes.cpp
dom/html/input/NumericInputTypes.h
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -122,24 +122,16 @@
 #include "js/Date.h"
 
 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Input)
 
 // XXX align=left, hspace, vspace, border? other nav4 attrs
 
 static NS_DEFINE_CID(kXULControllersCID,  NS_XULCONTROLLERS_CID);
 
-// This must come outside of any namespace, or else it won't overload with the
-// double based version in nsMathUtils.h
-inline mozilla::Decimal
-NS_floorModulo(mozilla::Decimal x, mozilla::Decimal y)
-{
-  return (x - y * (x / y).floor());
-}
-
 namespace mozilla {
 namespace dom {
 
 // First bits are needed for the control type.
 #define NS_OUTER_ACTIVATE_EVENT   (1 << 9)
 #define NS_ORIGINAL_CHECKED_VALUE (1 << 10)
 #define NS_NO_CONTENT_DISPATCH    (1 << 11)
 #define NS_ORIGINAL_INDETERMINATE_VALUE (1 << 12)
@@ -1468,69 +1460,41 @@ HTMLInputElement::AfterSetAttr(int32_t a
     } else if (aName == nsGkAtoms::minlength) {
       UpdateTooShortValidityState();
     } else if (aName == nsGkAtoms::pattern && mDoneCreating) {
       UpdatePatternMismatchValidityState();
     } else if (aName == nsGkAtoms::multiple) {
       UpdateTypeMismatchValidityState();
     } else if (aName == nsGkAtoms::max) {
       UpdateHasRange();
-      if (mType == NS_FORM_INPUT_RANGE) {
-        // The value may need to change when @max changes since the value may
-        // have been invalid and can now change to a valid value, or vice
-        // versa. For example, consider:
-        // <input type=range value=-1 max=1 step=3>. The valid range is 0 to 1
-        // while the nearest valid steps are -1 and 2 (the max value having
-        // prevented there being a valid step in range). Changing @max to/from
-        // 1 and a number greater than on equal to 3 should change whether we
-        // have a step mismatch or not.
-        // The value may also need to change between a value that results in
-        // a step mismatch and a value that results in overflow. For example,
-        // if @max in the example above were to change from 1 to -1.
-        nsAutoString value;
-        GetNonFileValueInternal(value);
-        nsresult rv =
-          SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
-        NS_ENSURE_SUCCESS(rv, rv);
-      }
-      // Validity state must be updated *after* the SetValueInternal call above
-      // or else the following assert will not be valid.
+      nsresult rv = mInputType->MinMaxStepAttrChanged();
+      NS_ENSURE_SUCCESS(rv, rv);
+      // Validity state must be updated *after* the UpdateValueDueToAttrChange
+      // call above or else the following assert will not be valid.
       // We don't assert the state of underflow during creation since
       // DoneCreatingElement sanitizes.
       UpdateRangeOverflowValidityState();
       MOZ_ASSERT(!mDoneCreating ||
                  mType != NS_FORM_INPUT_RANGE ||
                  !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
                  "HTML5 spec does not allow underflow for type=range");
     } else if (aName == nsGkAtoms::min) {
       UpdateHasRange();
-      if (mType == NS_FORM_INPUT_RANGE) {
-        // See @max comment
-        nsAutoString value;
-        GetNonFileValueInternal(value);
-        nsresult rv =
-          SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
-        NS_ENSURE_SUCCESS(rv, rv);
-      }
+      nsresult rv = mInputType->MinMaxStepAttrChanged();
+      NS_ENSURE_SUCCESS(rv, rv);
       // See corresponding @max comment
       UpdateRangeUnderflowValidityState();
       UpdateStepMismatchValidityState();
       MOZ_ASSERT(!mDoneCreating ||
                  mType != NS_FORM_INPUT_RANGE ||
                  !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
                  "HTML5 spec does not allow underflow for type=range");
     } else if (aName == nsGkAtoms::step) {
-      if (mType == NS_FORM_INPUT_RANGE) {
-        // See @max comment
-        nsAutoString value;
-        GetNonFileValueInternal(value);
-        nsresult rv =
-          SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
-        NS_ENSURE_SUCCESS(rv, rv);
-      }
+      nsresult rv = mInputType->MinMaxStepAttrChanged();
+      NS_ENSURE_SUCCESS(rv, rv);
       // See corresponding @max comment
       UpdateStepMismatchValidityState();
       MOZ_ASSERT(!mDoneCreating ||
                  mType != NS_FORM_INPUT_RANGE ||
                  !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
                  "HTML5 spec does not allow underflow for type=range");
     } else if (aName == nsGkAtoms::dir &&
                aValue && aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) {
@@ -7522,77 +7486,29 @@ bool
 HTMLInputElement::HasPatternMismatch() const
 {
   return mInputType->HasPatternMismatch();
 }
 
 bool
 HTMLInputElement::IsRangeOverflow() const
 {
-  if (!DoesMinMaxApply()) {
-    return false;
-  }
-
-  Decimal maximum = GetMaximum();
-  if (maximum.isNaN()) {
-    return false;
-  }
-
-  Decimal value = GetValueAsDecimal();
-  if (value.isNaN()) {
-    return false;
-  }
-
-  return value > maximum;
+  return mInputType->IsRangeOverflow();
 }
 
 bool
 HTMLInputElement::IsRangeUnderflow() const
 {
-  if (!DoesMinMaxApply()) {
-    return false;
-  }
-
-  Decimal minimum = GetMinimum();
-  if (minimum.isNaN()) {
-    return false;
-  }
-
-  Decimal value = GetValueAsDecimal();
-  if (value.isNaN()) {
-    return false;
-  }
-
-  return value < minimum;
+  return mInputType->IsRangeUnderflow();
 }
 
 bool
 HTMLInputElement::HasStepMismatch(bool aUseZeroIfValueNaN) const
 {
-  if (!DoesStepApply()) {
-    return false;
-  }
-
-  Decimal value = GetValueAsDecimal();
-  if (value.isNaN()) {
-    if (aUseZeroIfValueNaN) {
-      value = Decimal(0);
-    } else {
-      // The element can't suffer from step mismatch if it's value isn't a number.
-      return false;
-    }
-  }
-
-  Decimal step = GetStep();
-  if (step == kStepAny) {
-    return false;
-  }
-
-  // Value has to be an integral multiple of step.
-  return NS_floorModulo(value - GetStepBase(), step) != Decimal(0);
+  return mInputType->HasStepMismatch(aUseZeroIfValueNaN);
 }
 
 /**
  * Takes aEmail and attempts to convert everything after the first "@"
  * character (if anything) to punycode before returning the complete result via
  * the aEncodedEmail out-param. The aIndexOfAt out-param is set to the index of
  * the "@" character.
  *
--- a/dom/html/input/DateTimeInputTypes.cpp
+++ b/dom/html/input/DateTimeInputTypes.cpp
@@ -23,8 +23,62 @@ DateTimeInputTypeBase::IsValueMissing() 
   }
 
   if (!IsMutable()) {
     return false;
   }
 
   return IsValueEmpty();
 }
+
+bool
+DateTimeInputTypeBase::IsRangeOverflow() const
+{
+  mozilla::Decimal maximum = mInputElement->GetMaximum();
+  if (maximum.isNaN()) {
+    return false;
+  }
+
+  mozilla::Decimal value = mInputElement->GetValueAsDecimal();
+  if (value.isNaN()) {
+    return false;
+  }
+
+  return value > maximum;
+}
+
+bool
+DateTimeInputTypeBase::IsRangeUnderflow() const
+{
+  mozilla::Decimal minimum = mInputElement->GetMinimum();
+  if (minimum.isNaN()) {
+    return false;
+  }
+
+  mozilla::Decimal value = mInputElement->GetValueAsDecimal();
+  if (value.isNaN()) {
+    return false;
+  }
+
+  return value < minimum;
+}
+
+bool
+DateTimeInputTypeBase::HasStepMismatch(bool aUseZeroIfValueNaN) const
+{
+  mozilla::Decimal value = mInputElement->GetValueAsDecimal();
+  if (value.isNaN()) {
+    if (aUseZeroIfValueNaN) {
+      value = mozilla::Decimal(0);
+    } else {
+      // The element can't suffer from step mismatch if it's value isn't a number.
+      return false;
+    }
+  }
+
+  mozilla::Decimal step = mInputElement->GetStep();
+  if (step == kStepAny) {
+    return false;
+  }
+
+  // Value has to be an integral multiple of step.
+  return NS_floorModulo(value - GetStepBase(), step) != mozilla::Decimal(0);
+}
--- a/dom/html/input/DateTimeInputTypes.h
+++ b/dom/html/input/DateTimeInputTypes.h
@@ -10,16 +10,19 @@
 #include "InputType.h"
 
 class DateTimeInputTypeBase : public ::InputType
 {
 public:
   ~DateTimeInputTypeBase() override {}
 
   bool IsValueMissing() const override;
+  bool IsRangeOverflow() const override;
+  bool IsRangeUnderflow() const override;
+  bool HasStepMismatch(bool aUseZeroIfValueNaN) const override;
 
 protected:
   explicit DateTimeInputTypeBase(mozilla::dom::HTMLInputElement* aInputElement)
     : InputType(aInputElement)
   {}
 
   bool IsMutable() const override;
 };
--- a/dom/html/input/InputType.cpp
+++ b/dom/html/input/InputType.cpp
@@ -11,16 +11,17 @@
 #include "CheckableInputTypes.h"
 #include "ColorInputType.h"
 #include "DateTimeInputTypes.h"
 #include "FileInputType.h"
 #include "HiddenInputType.h"
 #include "NumericInputTypes.h"
 #include "SingleLineTextInputTypes.h"
 
+const mozilla::Decimal InputType::kStepAny = mozilla::Decimal(0);
 
 /* static */ mozilla::UniquePtr<InputType, DoNotDelete>
 InputType::Create(mozilla::dom::HTMLInputElement* aInputElement, uint8_t aType,
                   void* aMemory)
 {
   mozilla::UniquePtr<InputType, DoNotDelete> inputType;
   switch(aType) {
     // Single line text
@@ -115,16 +116,28 @@ InputType::IsValueEmpty() const
 }
 
 void
 InputType::GetNonFileValueInternal(nsAString& aValue) const
 {
   return mInputElement->GetNonFileValueInternal(aValue);
 }
 
+nsresult
+InputType::SetValueInternal(const nsAString& aValue, uint32_t aFlags)
+{
+  return mInputElement->SetValueInternal(aValue, aFlags);
+}
+
+mozilla::Decimal
+InputType::GetStepBase() const
+{
+  return mInputElement->GetStepBase();
+}
+
 void
 InputType::DropReference()
 {
   // Drop our (non ref-counted) reference.
   mInputElement = nullptr;
 }
 
 bool
@@ -151,8 +164,32 @@ InputType::HasTypeMismatch() const
   return false;
 }
 
 bool
 InputType::HasPatternMismatch() const
 {
   return false;
 }
+
+bool
+InputType::IsRangeOverflow() const
+{
+  return false;
+}
+
+bool
+InputType::IsRangeUnderflow() const
+{
+  return false;
+}
+
+bool
+InputType::HasStepMismatch(bool aUseZeroIfValueNaN) const
+{
+  return false;
+}
+
+nsresult
+InputType::MinMaxStepAttrChanged()
+{
+  return NS_OK;
+}
--- a/dom/html/input/InputType.h
+++ b/dom/html/input/InputType.h
@@ -3,18 +3,28 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef InputType_h__
 #define InputType_h__
 
 #include <stdint.h>
+#include "mozilla/Decimal.h"
 #include "mozilla/UniquePtr.h"
 #include "nsString.h"
+#include "nsError.h"
+
+// This must come outside of any namespace, or else it won't overload with the
+// double based version in nsMathUtils.h
+inline mozilla::Decimal
+NS_floorModulo(mozilla::Decimal x, mozilla::Decimal y)
+{
+  return (x - y * (x / y).floor());
+}
 
 namespace mozilla {
 namespace dom {
 class HTMLInputElement;
 } // namespace dom
 } // namespace mozilla
 
 struct DoNotDelete;
@@ -26,26 +36,34 @@ class InputType
 {
 public:
   static mozilla::UniquePtr<InputType, DoNotDelete>
   Create(mozilla::dom::HTMLInputElement* aInputElement, uint8_t aType,
          void* aMemory);
 
   virtual ~InputType() {}
 
+  // Float value returned by GetStep() when the step attribute is set to 'any'.
+  static const mozilla::Decimal kStepAny;
+
   /**
    * Drop the reference to the input element.
    */
   void DropReference();
 
   virtual bool IsTooLong() const;
   virtual bool IsTooShort() const;
   virtual bool IsValueMissing() const;
   virtual bool HasTypeMismatch() const;
   virtual bool HasPatternMismatch() const;
+  virtual bool IsRangeOverflow() const;
+  virtual bool IsRangeUnderflow() const;
+  virtual bool HasStepMismatch(bool aUseZeroIfValueNaN) const;
+
+  virtual nsresult MinMaxStepAttrChanged();
 
 protected:
   explicit InputType(mozilla::dom::HTMLInputElement* aInputElement)
     : mInputElement(aInputElement)
   {}
 
   /**
    * Get the mutable state of the element.
@@ -64,16 +82,32 @@ protected:
    * @return whether the input element's current value is the empty string.
    */
   bool IsValueEmpty() const;
 
   // A getter for callers that know we're not dealing with a file input, so they
   // don't have to think about the caller type.
   void GetNonFileValueInternal(nsAString& aValue) const;
 
+  /**
+   * Setting the input element's value.
+   *
+   * @param aValue      String to set.
+   * @param aFlags      See nsTextEditorState::SetValueFlags.
+   */
+  nsresult SetValueInternal(const nsAString& aValue, uint32_t aFlags);
+
+  /**
+   * Return the base used to compute if a value matches step.
+   * Basically, it's the min attribute if present and a default value otherwise.
+   *
+   * @return The step base.
+   */
+  mozilla::Decimal GetStepBase() const;
+
   mozilla::dom::HTMLInputElement* mInputElement;
 };
 
 // Custom deleter for UniquePtr<InputType> to avoid freeing memory pre-allocated
 // for InputType, but we still need to call the destructor explictly.
 struct DoNotDelete { void operator()(::InputType* p) { p->~InputType(); } };
 
 #endif /* InputType_h__ */
--- a/dom/html/input/NumericInputTypes.cpp
+++ b/dom/html/input/NumericInputTypes.cpp
@@ -2,29 +2,105 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "NumericInputTypes.h"
 
 #include "mozilla/dom/HTMLInputElement.h"
+#include "nsTextEditorState.h"
 
 bool
 NumberInputType::IsMutable() const
 {
   return !mInputElement->IsDisabled() &&
          !mInputElement->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly);
 }
 
 bool
+NumericInputTypeBase::IsRangeOverflow() const
+{
+  mozilla::Decimal maximum = mInputElement->GetMaximum();
+  if (maximum.isNaN()) {
+    return false;
+  }
+
+  mozilla::Decimal value = mInputElement->GetValueAsDecimal();
+  if (value.isNaN()) {
+    return false;
+  }
+
+  return value > maximum;
+}
+
+bool
+NumericInputTypeBase::IsRangeUnderflow() const
+{
+  mozilla::Decimal minimum = mInputElement->GetMinimum();
+  if (minimum.isNaN()) {
+    return false;
+  }
+
+  mozilla::Decimal value = mInputElement->GetValueAsDecimal();
+  if (value.isNaN()) {
+    return false;
+  }
+
+  return value < minimum;
+}
+
+bool
+NumericInputTypeBase::HasStepMismatch(bool aUseZeroIfValueNaN) const
+{
+  mozilla::Decimal value = mInputElement->GetValueAsDecimal();
+  if (value.isNaN()) {
+    if (aUseZeroIfValueNaN) {
+      value = mozilla::Decimal(0);
+    } else {
+      // The element can't suffer from step mismatch if it's value isn't a number.
+      return false;
+    }
+  }
+
+  mozilla::Decimal step = mInputElement->GetStep();
+  if (step == kStepAny) {
+    return false;
+  }
+
+  // Value has to be an integral multiple of step.
+  return NS_floorModulo(value - GetStepBase(), step) != mozilla::Decimal(0);
+}
+
+/* input type=numer */
+
+bool
 NumberInputType::IsValueMissing() const
 {
   if (!mInputElement->HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
     return false;
   }
 
   if (!IsMutable()) {
     return false;
   }
 
   return IsValueEmpty();
 }
+
+/* input type=range */
+nsresult
+RangeInputType::MinMaxStepAttrChanged()
+{
+  // The value may need to change when @min/max/step changes since the value may
+  // have been invalid and can now change to a valid value, or vice versa. For
+  // example, consider: <input type=range value=-1 max=1 step=3>. The valid
+  // range is 0 to 1 while the nearest valid steps are -1 and 2 (the max value
+  // having prevented there being a valid step in range). Changing @max to/from
+  // 1 and a number greater than on equal to 3 should change whether we have a
+  // step mismatch or not.
+  // The value may also need to change between a value that results in a step
+  // mismatch and a value that results in overflow. For example, if @max in the
+  // example above were to change from 1 to -1.
+  nsAutoString value;
+  GetNonFileValueInternal(value);
+  return SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
+}
--- a/dom/html/input/NumericInputTypes.h
+++ b/dom/html/input/NumericInputTypes.h
@@ -9,16 +9,20 @@
 
 #include "InputType.h"
 
 class NumericInputTypeBase : public ::InputType
 {
 public:
   ~NumericInputTypeBase() override {}
 
+  bool IsRangeOverflow() const override;
+  bool IsRangeUnderflow() const override;
+  bool HasStepMismatch(bool aUseZeroIfValueNaN) const override;
+
 protected:
   explicit NumericInputTypeBase(mozilla::dom::HTMLInputElement* aInputElement)
     : InputType(aInputElement)
   {}
 };
 
 // input type=number
 class NumberInputType : public NumericInputTypeBase
@@ -45,15 +49,17 @@ class RangeInputType : public NumericInp
 {
 public:
   static InputType*
   Create(mozilla::dom::HTMLInputElement* aInputElement, void* aMemory)
   {
     return new (aMemory) RangeInputType(aInputElement);
   }
 
+  nsresult MinMaxStepAttrChanged() override;
+
 private:
   explicit RangeInputType(mozilla::dom::HTMLInputElement* aInputElement)
     : NumericInputTypeBase(aInputElement)
   {}
 };
 
 #endif /* NumericInputTypes_h__ */