Bug 763367 - Add support for [EnforceRange] and [Clamp]; r=bz
authorMs2ger <ms2ger@gmail.com>
Thu, 06 Sep 2012 09:25:03 +0200
changeset 110680 7682c01baef38245340c7b98a3bbc52dc358dcd2
parent 110679 1c616799d242ee169783ddd1f9cba3e89f920acb
child 110681 42e9a4b8dc3e0802562adcf771a4b26bed88c704
push id1708
push userakeybl@mozilla.com
push dateMon, 19 Nov 2012 21:10:21 +0000
treeherdermozilla-beta@27b14fe50103 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs763367
milestone18.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 763367 - Add support for [EnforceRange] and [Clamp]; r=bz
dom/bindings/Codegen.py
dom/bindings/Errors.msg
dom/bindings/PrimitiveConversions.h
dom/bindings/parser/WebIDL.py
dom/bindings/test/TestBindingHeader.h
dom/bindings/test/TestCodeGen.webidl
js/xpconnect/src/XPCConvert.cpp
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -1578,17 +1578,19 @@ if (!tmp) {
 
 def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None,
                                     isDefinitelyObject=False,
                                     isMember=False,
                                     isOptional=False,
                                     invalidEnumValueFatal=True,
                                     defaultValue=None,
                                     treatNullAs="Default",
-                                    treatUndefinedAs="Default"):
+                                    treatUndefinedAs="Default",
+                                    isEnforceRange=False,
+                                    isClamp=False):
     """
     Get a template for converting a JS value to a native object based on the
     given type and descriptor.  If failureCode is given, then we're actually
     testing whether we can convert the argument to the desired type.  That
     means that failures to convert due to the JS value being the wrong type of
     value need to use failureCode instead of throwing exceptions.  Failures to
     convert that are due to JS exceptions (from toString or valueOf methods) or
     out of memory conditions need to throw exceptions no matter what
@@ -1605,16 +1607,22 @@ def getJSToNativeConversionTemplate(type
     argument with no default value.
 
     invalidEnumValueFatal controls whether an invalid enum value conversion
     attempt will throw (if true) or simply return without doing anything (if
     false).
 
     If defaultValue is not None, it's the IDL default value for this conversion
 
+    If isEnforceRange is true, we're converting an integer and throwing if the
+    value is out of range.
+
+    If isClamp is true, we're converting an integer and clamping if the
+    value is out of range.
+
     The return value from this function is a tuple consisting of four things:
 
     1)  A string representing the conversion code.  This will have template
         substitution performed on it as follows:
 
           ${val} replaced by an expression for the JS::Value in question
           ${valPtr} is a pointer to the JS::Value in question
           ${holderName} replaced by the holder's name, if any
@@ -1701,20 +1709,24 @@ def getJSToNativeConversionTemplate(type
                 "}")
             if type.nullable():
                 templateBody = handleDefaultNull(templateBody, codeToSetNull)
             else:
                 assert(defaultValue is None)
 
         return templateBody
 
+    assert not (isEnforceRange and isClamp) # These are mutually exclusive
+
     if type.isArray():
         raise TypeError("Can't handle array arguments yet")
 
     if type.isSequence():
+        assert not isEnforceRange and not isClamp
+
         if isMember:
             # XXXbz we probably _could_ handle this; we just have to be careful
             # with reallocation behavior for arrays.  In particular, if we have
             # a return value that's a sequence of dictionaries of sequences,
             # that will cause us to have an nsTArray containing objects with
             # nsAutoTArray members, which is a recipe for badness as the
             # outermost array is resized.
             raise TypeError("Can't handle unrooted sequences")
@@ -1995,16 +2007,18 @@ for (uint32_t i = 0; i < length; ++i) {
                 valueMissing = ""
             templateBody = handleNull(templateBody, mutableDecl,
                                       extraConditionForNull=valueMissing)
         templateBody = CGList([constructDecl, templateBody], "\n")
 
         return templateBody.define(), declType, holderType, False
 
     if type.isGeckoInterface():
+        assert not isEnforceRange and not isClamp
+
         descriptor = descriptorProvider.getDescriptor(
             type.unroll().inner.identifier.name)
         # This is an interface that we implement as a concrete class
         # or an XPCOM interface.
 
         # Allow null pointers for nullable types and old-binding classes
         argIsPointer = type.nullable() or type.unroll().inner.isExternal()
 
@@ -2104,16 +2118,17 @@ for (uint32_t i = 0; i < length; ++i) {
                                           failureCode)
 
         declType = CGGeneric(declType)
         if holderType is not None:
             holderType = CGGeneric(holderType)
         return (templateBody, declType, holderType, isOptional)
 
     if type.isSpiderMonkeyInterface():
+        assert not isEnforceRange and not isClamp
         if isMember:
             raise TypeError("Can't handle member arraybuffers or "
                             "arraybuffer views because making sure all the "
                             "objects are properly rooted is hard")
         name = type.name
         # By default, we use a Maybe<> to hold our typed array.  And in the optional
         # non-nullable case we want to pass Optional<TypedArray> to consumers, not
         # Optional<NonNull<TypedArray> >, so jump though some hoops to do that.
@@ -2159,18 +2174,17 @@ for (uint32_t i = 0; i < length; ++i) {
                                       failureCode)
 
         if holderType is not None:
             holderType = CGGeneric(holderType)
         # We handle all the optional stuff ourselves; no need for caller to do it.
         return (template, CGGeneric(declType), holderType, False)
 
     if type.isString():
-        # XXXbz Need to figure out string behavior based on extended args?  Also, how to
-        # detect them?
+        assert not isEnforceRange and not isClamp
 
         treatAs = {
             "Default": "eStringify",
             "EmptyString": "eEmpty",
             "Null": "eNull"
         }
         if type.nullable():
             # For nullable strings null becomes a null string.
@@ -2224,16 +2238,18 @@ for (uint32_t i = 0; i < length; ++i) {
             "%s\n"
             "const_cast<%s&>(${declName}) = &${holderName};" %
             (getConversionCode("${holderName}"), declType),
             CGGeneric("const " + declType), CGGeneric("FakeDependentString"),
             # No need to deal with Optional here; we have handled it already
             False)
 
     if type.isEnum():
+        assert not isEnforceRange and not isClamp
+
         if type.nullable():
             raise TypeError("We don't support nullable enumerated arguments "
                             "yet")
         enum = type.inner.identifier.name
         if invalidEnumValueFatal:
             handleInvalidEnumValueCode = "  MOZ_ASSERT(index >= 0);\n"
         else:
             handleInvalidEnumValueCode = (
@@ -2259,16 +2275,18 @@ for (uint32_t i = 0; i < length; ++i) {
             assert(defaultValue.type.tag() == IDLType.Tags.domstring)
             template = handleDefault(template,
                                      ("${declName} = %sValues::%s" %
                                       (enum,
                                        getEnumValueName(defaultValue.value))))
         return (template, CGGeneric(enum), None, isOptional)
 
     if type.isCallback():
+        assert not isEnforceRange and not isClamp
+
         if isMember:
             raise TypeError("Can't handle member callbacks; need to sort out "
                             "rooting issues")
         # XXXbz we're going to assume that callback types are always
         # nullable and always have [TreatNonCallableAsNull] for now.
         haveCallable = "${val}.isObject() && JS_ObjectIsCallable(cx, &${val}.toObject())"
         if defaultValue is not None:
             assert(isinstance(defaultValue, IDLNullValue))
@@ -2277,25 +2295,29 @@ for (uint32_t i = 0; i < length; ++i) {
             "if (%s) {\n"
             "  ${declName} = &${val}.toObject();\n"
             "} else {\n"
             "  ${declName} = NULL;\n"
             "}" % haveCallable,
             CGGeneric("JSObject*"), None, isOptional)
 
     if type.isAny():
+        assert not isEnforceRange and not isClamp
+
         if isMember:
             raise TypeError("Can't handle member 'any'; need to sort out "
                             "rooting issues")
         templateBody = "${declName} = ${val};"
         templateBody = handleDefaultNull(templateBody,
                                          "${declName} = JS::NullValue()")
         return (templateBody, CGGeneric("JS::Value"), None, isOptional)
 
     if type.isObject():
+        assert not isEnforceRange and not isClamp
+
         if isMember:
             raise TypeError("Can't handle member 'object'; need to sort out "
                             "rooting issues")
         template = wrapObjectTemplate("${declName} = &${val}.toObject();",
                                       isDefinitelyObject, type,
                                       "${declName} = NULL",
                                       failureCode)
         if type.nullable():
@@ -2338,38 +2360,44 @@ for (uint32_t i = 0; i < length; ++i) {
                     "  return false;\n"
                     "}" % (selfRef, val))
 
         return (template, declType, None, False)
 
     if not type.isPrimitive():
         raise TypeError("Need conversion for argument type '%s'" % str(type))
 
-    # XXXbz need to add support for [EnforceRange] and [Clamp]
     typeName = builtinNames[type.tag()]
+
+    conversionBehavior = "eDefault"
+    if isEnforceRange:
+        conversionBehavior = "eEnforceRange"
+    elif isClamp:
+        conversionBehavior = "eClamp"
+
     if type.nullable():
         dataLoc = "${declName}.SetValue()"
         nullCondition = "${val}.isNullOrUndefined()"
         if defaultValue is not None and isinstance(defaultValue, IDLNullValue):
             nullCondition = "!(${haveValue}) || " + nullCondition
         template = (
             "if (%s) {\n"
             "  ${declName}.SetNull();\n"
-            "} else if (!ValueToPrimitive<%s>(cx, ${val}, &%s)) {\n"
+            "} else if (!ValueToPrimitive<%s, %s>(cx, ${val}, &%s)) {\n"
             "  return false;\n"
-            "}" % (nullCondition, typeName, dataLoc))
+            "}" % (nullCondition, typeName, conversionBehavior, dataLoc))
         declType = CGGeneric("Nullable<" + typeName + ">")
     else:
         assert(defaultValue is None or
                not isinstance(defaultValue, IDLNullValue))
         dataLoc = "${declName}"
         template = (
-            "if (!ValueToPrimitive<%s>(cx, ${val}, &%s)) {\n"
+            "if (!ValueToPrimitive<%s, %s>(cx, ${val}, &%s)) {\n"
             "  return false;\n"
-            "}" % (typeName, dataLoc))
+            "}" % (typeName, conversionBehavior, dataLoc))
         declType = CGGeneric(typeName)
     if (defaultValue is not None and
         # We already handled IDLNullValue, so just deal with the other ones
         not isinstance(defaultValue, IDLNullValue)):
         tag = defaultValue.type.tag()
         if tag in numericTags:
             defaultStr = defaultValue.value
         else:
@@ -2527,17 +2555,19 @@ class CGArgumentConverter(CGThing):
     def define(self):
         return instantiateJSToNativeConversionTemplate(
             getJSToNativeConversionTemplate(self.argument.type,
                                             self.descriptorProvider,
                                             isOptional=(self.argcAndIndex is not None),
                                             invalidEnumValueFatal=self.invalidEnumValueFatal,
                                             defaultValue=self.argument.defaultValue,
                                             treatNullAs=self.argument.treatNullAs,
-                                            treatUndefinedAs=self.argument.treatUndefinedAs),
+                                            treatUndefinedAs=self.argument.treatUndefinedAs,
+                                            isEnforceRange=self.argument.enforceRange,
+                                            isClamp=self.argument.clamp),
             self.replacementVariables,
             self.argcAndIndex).define()
 
 def getWrapTemplateForType(type, descriptorProvider, result, successCode,
                            isCreator):
     """
     Reflect a C++ value stored in "result", of IDL type "type" into JS.  The
     "successCode" is the code to run once we have successfully done the
@@ -3318,16 +3348,18 @@ class FakeArgument():
     """
     def __init__(self, type, interfaceMember):
         self.type = type
         self.optional = False
         self.variadic = False
         self.defaultValue = None
         self.treatNullAs = interfaceMember.treatNullAs
         self.treatUndefinedAs = interfaceMember.treatUndefinedAs
+        self.enforceRange = False
+        self.clamp = False
 
 class CGSetterCall(CGPerSignatureCall):
     """
     A class to generate a native object setter call for a particular IDL
     setter.
     """
     def __init__(self, argType, nativeMethodName, descriptor, attr):
         CGPerSignatureCall.__init__(self, None, [],
--- a/dom/bindings/Errors.msg
+++ b/dom/bindings/Errors.msg
@@ -20,9 +20,11 @@
  */
 
 MSG_DEF(MSG_INVALID_ENUM_VALUE, 2, "Value '{0}' is not a valid value for enumeration {1}.")
 MSG_DEF(MSG_MISSING_ARGUMENTS, 1, "Not enough arguments to {0}.")
 MSG_DEF(MSG_NOT_OBJECT, 0, "Value not an object.")
 MSG_DEF(MSG_DOES_NOT_IMPLEMENT_INTERFACE, 1, "Value does not implement interface {0}.")
 MSG_DEF(MSG_NOT_IN_UNION, 1, "Value could not be converted to any of: {0}.")
 MSG_DEF(MSG_ILLEGAL_CONSTRUCTOR, 0, "Illegal constructor.")
-MSG_DEF(MSG_NO_PROPERTY_SETTER, 1, "{0} doesn't have an indexed property setter.")
\ No newline at end of file
+MSG_DEF(MSG_NO_PROPERTY_SETTER, 1, "{0} doesn't have an indexed property setter.")
+MSG_DEF(MSG_ENFORCE_RANGE_NON_FINITE, 1, "Non-finite value is out of range for {0}.")
+MSG_DEF(MSG_ENFORCE_RANGE_OUT_OF_RANGE, 1, "Value is out of range for {0}.")
--- a/dom/bindings/PrimitiveConversions.h
+++ b/dom/bindings/PrimitiveConversions.h
@@ -6,25 +6,102 @@
 
 /**
  * Conversions from jsval to primitive values
  */
 
 #ifndef mozilla_dom_PrimitiveConversions_h
 #define mozilla_dom_PrimitiveConversions_h
 
+#include <limits>
+#include <math.h>
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/FloatingPoint.h"
 #include "xpcpublic.h"
 
 namespace mozilla {
 namespace dom {
 
 template<typename T>
+struct TypeName {
+};
+
+template<>
+struct TypeName<int8_t> {
+  static const char* value() {
+    return "byte";
+  }
+};
+template<>
+struct TypeName<uint8_t> {
+  static const char* value() {
+    return "octet";
+  }
+};
+template<>
+struct TypeName<int16_t> {
+  static const char* value() {
+    return "short";
+  }
+};
+template<>
+struct TypeName<uint16_t> {
+  static const char* value() {
+    return "unsigned short";
+  }
+};
+template<>
+struct TypeName<int32_t> {
+  static const char* value() {
+    return "long";
+  }
+};
+template<>
+struct TypeName<uint32_t> {
+  static const char* value() {
+    return "unsigned long";
+  }
+};
+template<>
+struct TypeName<int64_t> {
+  static const char* value() {
+    return "long long";
+  }
+};
+template<>
+struct TypeName<uint64_t> {
+  static const char* value() {
+    return "unsigned long long";
+  }
+};
+
+
+enum ConversionBehavior {
+  eDefault,
+  eEnforceRange,
+  eClamp
+};
+
+template<typename T, ConversionBehavior B>
 struct PrimitiveConversionTraits {
 };
 
+template<typename T>
+struct DisallowedConversion {
+  typedef int jstype;
+  typedef int intermediateType;
+
+private:
+  static inline bool converter(JSContext* cx, JS::Value v, jstype* retval) {
+    MOZ_NOT_REACHED("This should never be instantiated!");
+    return false;
+  }
+};
+
 struct PrimitiveConversionTraits_smallInt {
   // The output of JS::ToInt32 is determined as follows:
   //   1) The value is converted to a double
   //   2) Anything that's not a finite double returns 0
   //   3) The double is rounded towards zero to the nearest integer
   //   4) The resulting integer is reduced mod 2^32.  The output of this
   //      operation is an integer in the range [0, 2^32).
   //   5) If the resulting number is >= 2^31, 2^32 is subtracted from it.
@@ -51,86 +128,223 @@ struct PrimitiveConversionTraits_smallIn
   // corresponding unsigned type.
   typedef int32_t jstype;
   typedef int32_t intermediateType;
   static inline bool converter(JSContext* cx, JS::Value v, jstype* retval) {
     return JS::ToInt32(cx, v, retval);
   }
 };
 template<>
-struct PrimitiveConversionTraits<int8_t> : PrimitiveConversionTraits_smallInt {
+struct PrimitiveConversionTraits<int8_t, eDefault> : PrimitiveConversionTraits_smallInt {
   typedef uint8_t intermediateType;
 };
 template<>
-struct PrimitiveConversionTraits<uint8_t> : PrimitiveConversionTraits_smallInt {
+struct PrimitiveConversionTraits<uint8_t, eDefault> : PrimitiveConversionTraits_smallInt {
 };
 template<>
-struct PrimitiveConversionTraits<int16_t> : PrimitiveConversionTraits_smallInt {
+struct PrimitiveConversionTraits<int16_t, eDefault> : PrimitiveConversionTraits_smallInt {
   typedef uint16_t intermediateType;
 };
 template<>
-struct PrimitiveConversionTraits<uint16_t> : PrimitiveConversionTraits_smallInt {
+struct PrimitiveConversionTraits<uint16_t, eDefault> : PrimitiveConversionTraits_smallInt {
 };
 template<>
-struct PrimitiveConversionTraits<int32_t> : PrimitiveConversionTraits_smallInt {
+struct PrimitiveConversionTraits<int32_t, eDefault> : PrimitiveConversionTraits_smallInt {
 };
 template<>
-struct PrimitiveConversionTraits<uint32_t> : PrimitiveConversionTraits_smallInt {
+struct PrimitiveConversionTraits<uint32_t, eDefault> : PrimitiveConversionTraits_smallInt {
 };
 
 template<>
-struct PrimitiveConversionTraits<int64_t> {
+struct PrimitiveConversionTraits<int64_t, eDefault> {
   typedef int64_t jstype;
   typedef int64_t intermediateType;
   static inline bool converter(JSContext* cx, JS::Value v, jstype* retval) {
     return JS::ToInt64(cx, v, retval);
   }
 };
 
 template<>
-struct PrimitiveConversionTraits<uint64_t> {
+struct PrimitiveConversionTraits<uint64_t, eDefault> {
   typedef uint64_t jstype;
   typedef uint64_t intermediateType;
   static inline bool converter(JSContext* cx, JS::Value v, jstype* retval) {
     return JS::ToUint64(cx, v, retval);
   }
 };
 
+template<typename T>
+struct PrimitiveConversionTraits_Limits {
+  static inline T min() {
+    return std::numeric_limits<T>::min();
+  }
+  static inline T max() {
+    return std::numeric_limits<T>::max();
+  }
+};
+
 template<>
-struct PrimitiveConversionTraits<bool> {
+struct PrimitiveConversionTraits_Limits<int64_t> {
+  static inline int64_t min() {
+    return -(1LL << 53) + 1;
+  }
+  static inline int64_t max() {
+    return (1LL << 53) - 1;
+  }
+};
+
+template<>
+struct PrimitiveConversionTraits_Limits<uint64_t> {
+  static inline uint64_t min() {
+    return 0;
+  }
+  static inline uint64_t max() {
+    return (1LL << 53) - 1;
+  }
+};
+
+template<typename T, bool (*Enforce)(JSContext* cx, const double& d, T* retval)>
+struct PrimitiveConversionTraits_ToCheckedIntHelper {
+  typedef T jstype;
+  typedef T intermediateType;
+
+  static inline bool converter(JSContext* cx, JS::Value v, jstype* retval) {
+    double intermediate;
+    if (!JS::ToNumber(cx, v, &intermediate)) {
+      return false;
+    }
+
+    return Enforce(cx, intermediate, retval);
+  }
+};
+
+template<typename T>
+inline bool
+PrimitiveConversionTraits_EnforceRange(JSContext* cx, const double& d, T* retval)
+{
+  MOZ_STATIC_ASSERT(std::numeric_limits<T>::is_integer,
+                    "This can only be applied to integers!");
+
+  if (!MOZ_DOUBLE_IS_FINITE(d)) {
+    return ThrowErrorMessage(cx, MSG_ENFORCE_RANGE_NON_FINITE, TypeName<T>::value());
+  }
+
+  bool neg = (d < 0);
+  double rounded = floor(neg ? -d : d);
+  rounded = neg ? -rounded : rounded;
+  if (rounded < PrimitiveConversionTraits_Limits<T>::min() ||
+      rounded > PrimitiveConversionTraits_Limits<T>::max()) {
+    return ThrowErrorMessage(cx, MSG_ENFORCE_RANGE_OUT_OF_RANGE, TypeName<T>::value());
+  }
+
+  *retval = static_cast<T>(rounded);
+  return true;
+}
+
+template<typename T>
+struct PrimitiveConversionTraits<T, eEnforceRange> :
+  public PrimitiveConversionTraits_ToCheckedIntHelper<T, PrimitiveConversionTraits_EnforceRange<T> > {
+};
+
+template<typename T>
+inline bool
+PrimitiveConversionTraits_Clamp(JSContext* cx, const double& d, T* retval)
+{
+  MOZ_STATIC_ASSERT(std::numeric_limits<T>::is_integer,
+                    "This can only be applied to integers!");
+
+  if (MOZ_DOUBLE_IS_NaN(d)) {
+    *retval = 0;
+    return true;
+  }
+  if (d >= PrimitiveConversionTraits_Limits<T>::max()) {
+    *retval = PrimitiveConversionTraits_Limits<T>::max();
+    return true;
+  }
+  if (d <= PrimitiveConversionTraits_Limits<T>::min()) {
+    *retval = PrimitiveConversionTraits_Limits<T>::min();
+    return true;
+  }
+
+  MOZ_ASSERT(MOZ_DOUBLE_IS_FINITE(d));
+
+  // Banker's rounding (round ties towards even).
+  // We move away from 0 by 0.5f and then truncate.  That gets us the right
+  // answer for any starting value except plus or minus N.5.  With a starting
+  // value of that form, we now have plus or minus N+1.  If N is odd, this is
+  // the correct result.  If N is even, plus or minus N is the correct result.
+  double toTruncate = (d < 0) ? d - 0.5 : d + 0.5;
+
+  T truncated(toTruncate);
+
+  if (truncated == toTruncate) {
+    /*
+     * It was a tie (since moving away from 0 by 0.5 gave us the exact integer
+     * we want). Since we rounded away from 0, we either already have an even
+     * number or we have an odd number but the number we want is one closer to
+     * 0. So just unconditionally masking out the ones bit should do the trick
+     * to get us the value we want.
+     */
+    truncated &= ~1;
+  }
+
+  *retval = truncated;
+  return true;
+}
+
+template<typename T>
+struct PrimitiveConversionTraits<T, eClamp> :
+  public PrimitiveConversionTraits_ToCheckedIntHelper<T, PrimitiveConversionTraits_Clamp<T> > {
+};
+
+
+template<ConversionBehavior B>
+struct PrimitiveConversionTraits<bool, B> : public DisallowedConversion<bool> {};
+
+template<>
+struct PrimitiveConversionTraits<bool, eDefault> {
   typedef JSBool jstype;
   typedef bool intermediateType;
   static inline bool converter(JSContext* /* unused */, JS::Value v, jstype* retval) {
     *retval = JS::ToBoolean(v);
     return true;
   }
 };
 
+
+template<ConversionBehavior B>
+struct PrimitiveConversionTraits<float, B> : public DisallowedConversion<float> {};
+
+template<ConversionBehavior B>
+struct PrimitiveConversionTraits<double, B> : public DisallowedConversion<double> {};
+
 struct PrimitiveConversionTraits_float {
   typedef double jstype;
   typedef double intermediateType;
   static inline bool converter(JSContext* cx, JS::Value v, jstype* retval) {
     return JS::ToNumber(cx, v, retval);
   }
 };
+
 template<>
-struct PrimitiveConversionTraits<float> : PrimitiveConversionTraits_float {
+struct PrimitiveConversionTraits<float, eDefault> : PrimitiveConversionTraits_float {
 };
 template<>
-struct PrimitiveConversionTraits<double> : PrimitiveConversionTraits_float {
+struct PrimitiveConversionTraits<double, eDefault> : PrimitiveConversionTraits_float {
 };
 
-template<typename T>
+
+template<typename T, ConversionBehavior B>
 bool ValueToPrimitive(JSContext* cx, JS::Value v, T* retval)
 {
-  typename PrimitiveConversionTraits<T>::jstype t;
-  if (!PrimitiveConversionTraits<T>::converter(cx, v, &t))
+  typename PrimitiveConversionTraits<T, B>::jstype t;
+  if (!PrimitiveConversionTraits<T, B>::converter(cx, v, &t))
     return false;
 
   *retval =
-    static_cast<typename PrimitiveConversionTraits<T>::intermediateType>(t);
+    static_cast<typename PrimitiveConversionTraits<T, B>::intermediateType>(t);
   return true;
 }
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_PrimitiveConversions_h */
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -1973,27 +1973,41 @@ class IDLArgument(IDLObjectWithIdentifie
         assert isinstance(type, IDLType)
         self.type = type
 
         self.optional = optional
         self.defaultValue = defaultValue
         self.variadic = variadic
         self.dictionaryMember = dictionaryMember
         self._isComplete = False
+        self.enforceRange = False
+        self.clamp = False
 
         assert not variadic or optional
 
     def addExtendedAttributes(self, attrs):
         attrs = self.checkForStringHandlingExtendedAttributes(
             attrs,
             isDictionaryMember=self.dictionaryMember,
             isOptional=self.optional)
-        if len(attrs) != 0:
-            raise WebIDLError("Unhandled extended attribute on an argument",
-                              [self.location])
+        for attribute in attrs:
+            attr = attribute[0]
+            if attr == "Clamp":
+                if self.enforceRange:
+                    raise WebIDLError("[EnforceRange] and [Clamp] are mutually exclusive",
+                                      [self.location]);
+                self.clamp = True
+            elif attr == "EnforceRange":
+                if self.clamp:
+                    raise WebIDLError("[EnforceRange] and [Clamp] are mutually exclusive",
+                                      [self.location]);
+                self.enforceRange = True
+            else:
+                raise WebIDLError("Unhandled extended attribute on an argument",
+                                  [self.location])
 
     def isComplete(self):
         return self._isComplete
 
     def complete(self, scope):
         if self._isComplete:
             return
 
--- a/dom/bindings/test/TestBindingHeader.h
+++ b/dom/bindings/test/TestBindingHeader.h
@@ -403,16 +403,21 @@ public:
   bool GetImplementedParentProperty();
   void SetImplementedParentProperty(bool);
   void ImplementedParentMethod();
   bool GetIndirectlyImplementedProperty();
   void SetIndirectlyImplementedProperty(bool);
   void IndirectlyImplementedMethod();
   uint32_t GetDiamondImplementedProperty();
 
+  // Test EnforceRange/Clamp
+  void DontEnforceRangeOrClamp(int8_t);
+  void DoEnforceRange(int8_t);
+  void DoClamp(int8_t);
+
 private:
   // We add signatures here that _could_ start matching if the codegen
   // got data types wrong.  That way if it ever does we'll have a call
   // to these private deleted methods and compilation will fail.
   void SetReadonlyByte(int8_t) MOZ_DELETE;
   template<typename T>
   void SetWritableByte(T) MOZ_DELETE;
   template<typename T>
--- a/dom/bindings/test/TestCodeGen.webidl
+++ b/dom/bindings/test/TestCodeGen.webidl
@@ -299,16 +299,21 @@ interface TestInterface {
 
   void passDictionary(optional Dict x);
   void passOtherDictionary(optional GrandparentDict x);
   void passSequenceOfDictionaries(sequence<Dict> x);
   void passDictionaryOrLong(optional Dict x);
   void passDictionaryOrLong(long x);
 
   void passDictContainingDict(optional DictContainingDict arg);
+
+  // EnforceRange/Clamp tests
+  void dontEnforceRangeOrClamp(byte arg);
+  void doEnforceRange([EnforceRange] byte arg);
+  void doClamp([Clamp] byte arg);
 };
 
 interface TestNonWrapperCacheInterface {
 };
 
 interface ImplementedInterfaceParent {
   void implementedParentMethod();
   attribute boolean implementedParentProperty;
--- a/js/xpconnect/src/XPCConvert.cpp
+++ b/js/xpconnect/src/XPCConvert.cpp
@@ -352,16 +352,22 @@ CheckJSCharInCharRange(jschar c)
         NS_WARNING(msg);
         return false;
     }
 
     return true;
 }
 #endif
 
+template<typename T>
+bool ConvertToPrimitive(JSContext *cx, const JS::Value& v, T *retval)
+{
+    return ValueToPrimitive<T, eDefault>(cx, v, retval);
+}
+
 // static
 JSBool
 XPCConvert::JSData2Native(XPCCallContext& ccx, void* d, jsval s,
                           const nsXPTType& type,
                           JSBool useAllocator, const nsID* iid,
                           nsresult* pErr)
 {
     NS_PRECONDITION(d, "bad param");
@@ -370,37 +376,37 @@ XPCConvert::JSData2Native(XPCCallContext
 
     JSBool isDOMString = true;
 
     if (pErr)
         *pErr = NS_ERROR_XPC_BAD_CONVERT_JS;
 
     switch (type.TagPart()) {
     case nsXPTType::T_I8     :
-        return ValueToPrimitive(cx, s, static_cast<int8_t*>(d));
+        return ConvertToPrimitive(cx, s, static_cast<int8_t*>(d));
     case nsXPTType::T_I16    :
-        return ValueToPrimitive(cx, s, static_cast<int16_t*>(d));
+        return ConvertToPrimitive(cx, s, static_cast<int16_t*>(d));
     case nsXPTType::T_I32    :
-        return ValueToPrimitive(cx, s, static_cast<int32_t*>(d));
+        return ConvertToPrimitive(cx, s, static_cast<int32_t*>(d));
     case nsXPTType::T_I64    :
-        return ValueToPrimitive(cx, s, static_cast<int64_t*>(d));
+        return ConvertToPrimitive(cx, s, static_cast<int64_t*>(d));
     case nsXPTType::T_U8     :
-        return ValueToPrimitive(cx, s, static_cast<uint8_t*>(d));
+        return ConvertToPrimitive(cx, s, static_cast<uint8_t*>(d));
     case nsXPTType::T_U16    :
-        return ValueToPrimitive(cx, s, static_cast<uint16_t*>(d));
+        return ConvertToPrimitive(cx, s, static_cast<uint16_t*>(d));
     case nsXPTType::T_U32    :
-        return ValueToPrimitive(cx, s, static_cast<uint32_t*>(d));
+        return ConvertToPrimitive(cx, s, static_cast<uint32_t*>(d));
     case nsXPTType::T_U64    :
-        return ValueToPrimitive(cx, s, static_cast<uint64_t*>(d));
+        return ConvertToPrimitive(cx, s, static_cast<uint64_t*>(d));
     case nsXPTType::T_FLOAT  :
-        return ValueToPrimitive(cx, s, static_cast<float*>(d));
+        return ConvertToPrimitive(cx, s, static_cast<float*>(d));
     case nsXPTType::T_DOUBLE :
-        return ValueToPrimitive(cx, s, static_cast<double*>(d));
+        return ConvertToPrimitive(cx, s, static_cast<double*>(d));
     case nsXPTType::T_BOOL   :
-        return ValueToPrimitive(cx, s, static_cast<bool*>(d));
+        return ConvertToPrimitive(cx, s, static_cast<bool*>(d));
     case nsXPTType::T_CHAR   :
     {
         JSString* str = JS_ValueToString(cx, s);
         if (!str) {
             return false;
         }
         size_t length;
         const jschar* chars = JS_GetStringCharsAndLength(cx, str, &length);