Skip to content

Commit

Permalink
Add Clamp and EnforceRange support for webidl arguments.
Browse files Browse the repository at this point in the history
  • Loading branch information
snf authored and Ms2ger committed Jul 22, 2015
1 parent 0888e1a commit 7f152b6
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 57 deletions.
23 changes: 17 additions & 6 deletions components/script/dom/bindings/codegen/CodegenRust.py
Expand Up @@ -832,7 +832,9 @@ def wrapObjectTemplate(templateBody, nullValue, isDefinitelyObject, type,
if treatNullAs not in treatAs:
raise TypeError("We don't support [TreatNullAs=%s]" % treatNullAs)
if type.nullable():
nullBehavior = "()"
# Note: the actual behavior passed here doesn't matter for nullable
# strings.
nullBehavior = "StringificationBehavior::Default"
else:
nullBehavior = treatAs[treatNullAs]

Expand Down Expand Up @@ -1046,7 +1048,16 @@ def wrapObjectTemplate(templateBody, nullValue, isDefinitelyObject, type,
if not type.isPrimitive():
raise TypeError("Need conversion for argument type '%s'" % str(type))

assert not isEnforceRange and not isClamp
if type.isInteger():
if isEnforceRange:
conversionBehavior = "ConversionBehavior::EnforceRange"
elif isClamp:
conversionBehavior = "ConversionBehavior::Clamp"
else:
conversionBehavior = "ConversionBehavior::Default"
else:
assert not isEnforceRange and not isClamp
conversionBehavior = "()"

if failureCode is None:
failureCode = 'return 0'
Expand All @@ -1055,12 +1066,11 @@ def wrapObjectTemplate(templateBody, nullValue, isDefinitelyObject, type,
if type.nullable():
declType = CGWrapper(declType, pre="Option<", post=">")

# XXXjdm support conversionBehavior here
template = (
"match FromJSValConvertible::from_jsval(cx, ${val}, ()) {\n"
"match FromJSValConvertible::from_jsval(cx, ${val}, %s) {\n"
" Ok(v) => v,\n"
" Err(_) => { %s }\n"
"}" % exceptionCode)
"}" % (conversionBehavior, exceptionCode))

if defaultValue is not None:
if isinstance(defaultValue, IDLNullValue):
Expand Down Expand Up @@ -2002,6 +2012,7 @@ def UnionTypes(descriptors, dictionaries, callbacks, config):
'dom::bindings::codegen::PrototypeList',
'dom::bindings::conversions::FromJSValConvertible',
'dom::bindings::conversions::ToJSValConvertible',
'dom::bindings::conversions::ConversionBehavior',
'dom::bindings::conversions::native_from_handlevalue',
'dom::bindings::conversions::StringificationBehavior',
'dom::bindings::error::throw_not_in_union',
Expand Down Expand Up @@ -5209,7 +5220,7 @@ def __init__(self, config, prefix, webIDLFile):
'dom::bindings::callback::{CallbackContainer,CallbackInterface,CallbackFunction}',
'dom::bindings::callback::{CallSetup,ExceptionHandling}',
'dom::bindings::callback::wrap_call_this_object',
'dom::bindings::conversions::{FromJSValConvertible, ToJSValConvertible}',
'dom::bindings::conversions::{FromJSValConvertible, ToJSValConvertible, ConversionBehavior}',
'dom::bindings::conversions::{native_from_reflector, native_from_handlevalue, native_from_handleobject}',
'dom::bindings::conversions::DOM_OBJECT_SLOT',
'dom::bindings::conversions::IDLInterface',
Expand Down
171 changes: 133 additions & 38 deletions components/script/dom/bindings/conversions.rs
Expand Up @@ -57,13 +57,54 @@ use js::jsval::{StringValue, ObjectValue, ObjectOrNullValue};

use libc;
use num::Float;
use num::traits::{Bounded, Zero};
use std::borrow::ToOwned;
use std::default;
use std::slice;
use std::ptr;
use std::rc::Rc;
use core::nonzero::NonZero;

trait As<O>: Copy {
fn cast(self) -> O;
}

macro_rules! impl_as {
($I:ty, $O:ty) => (
impl As<$O> for $I {
fn cast(self) -> $O {
self as $O
}
}
)
}

impl_as!(f64, u8);
impl_as!(f64, u16);
impl_as!(f64, u32);
impl_as!(f64, u64);
impl_as!(f64, i8);
impl_as!(f64, i16);
impl_as!(f64, i32);
impl_as!(f64, i64);

impl_as!(u8, f64);
impl_as!(u16, f64);
impl_as!(u32, f64);
impl_as!(u64, f64);
impl_as!(i8, f64);
impl_as!(i16, f64);
impl_as!(i32, f64);
impl_as!(i64, f64);

impl_as!(i32, i8);
impl_as!(i32, u8);
impl_as!(i32, i16);
impl_as!(u16, u16);
impl_as!(i32, i32);
impl_as!(u32, u32);
impl_as!(i64, i64);
impl_as!(u64, u64);

/// A trait to retrieve the constants necessary to check if a `JSObject`
/// implements a given interface.
pub trait IDLInterface {
Expand Down Expand Up @@ -91,6 +132,54 @@ pub trait FromJSValConvertible {
fn from_jsval(cx: *mut JSContext, val: HandleValue, option: Self::Config) -> Result<Self, ()>;
}

/// Behavior for converting out-of-range integers.
#[derive(PartialEq, Eq)]
pub enum ConversionBehavior {
/// Wrap into the integer's range.
Default,
/// Throw an exception.
EnforceRange,
/// Clamp into the integer's range.
Clamp
}

/// Try to cast the number to a smaller type, but
/// if it doesn't fit, it will return an error.
fn enforce_range<D>(cx: *mut JSContext, d: f64) -> Result<D, ()>
where D: Bounded + As<f64>,
f64: As<D>
{
if d.is_infinite() {
throw_type_error(cx, "value out of range in an EnforceRange argument");
return Err(());
}

let rounded = d.round();
if D::min_value().cast() <= rounded && rounded <= D::max_value().cast() {
Ok(rounded.cast())
} else {
throw_type_error(cx, "value out of range in an EnforceRange argument");
Err(())
}
}

/// Try to cast the number to a smaller type, but if it doesn't fit,
/// round it to the MAX or MIN of the source type before casting it to
/// the destination type.
fn clamp_to<D>(d: f64) -> D
where D: Bounded + As<f64> + Zero,
f64: As<D>
{
if d.is_nan() {
D::zero()
} else if d > D::max_value().cast() {
D::max_value()
} else if d < D::min_value().cast() {
D::min_value()
} else {
d.cast()
}
}

impl ToJSValConvertible for () {
fn to_jsval(&self, _cx: *mut JSContext, rval: MutableHandleValue) {
Expand All @@ -116,6 +205,22 @@ impl ToJSValConvertible for HandleValue {
}
}

#[inline]
fn convert_int_from_jsval<T, M>(cx: *mut JSContext, value: HandleValue,
option: ConversionBehavior,
convert_fn: fn(*mut JSContext, HandleValue) -> Result<M, ()>)
-> Result<T, ()>
where T: Bounded + Zero + As<f64>,
M: Zero + As<T>,
f64: As<T>
{
match option {
ConversionBehavior::Default => Ok(try!(convert_fn(cx, value)).cast()),
ConversionBehavior::EnforceRange => enforce_range(cx, try!(ToNumber(cx, value))),
ConversionBehavior::Clamp => Ok(clamp_to(try!(ToNumber(cx, value)))),
}
}

impl ToJSValConvertible for bool {
fn to_jsval(&self, _cx: *mut JSContext, rval: MutableHandleValue) {
rval.set(BooleanValue(*self));
Expand All @@ -136,10 +241,9 @@ impl ToJSValConvertible for i8 {
}

impl FromJSValConvertible for i8 {
type Config = ();
fn from_jsval(cx: *mut JSContext, val: HandleValue, _option: ()) -> Result<i8, ()> {
let result = ToInt32(cx, val);
result.map(|v| v as i8)
type Config = ConversionBehavior;
fn from_jsval(cx: *mut JSContext, val: HandleValue, option: ConversionBehavior) -> Result<i8, ()> {
convert_int_from_jsval(cx, val, option, ToInt32)
}
}

Expand All @@ -150,10 +254,9 @@ impl ToJSValConvertible for u8 {
}

impl FromJSValConvertible for u8 {
type Config = ();
fn from_jsval(cx: *mut JSContext, val: HandleValue, _option: ()) -> Result<u8, ()> {
let result = ToInt32(cx, val);
result.map(|v| v as u8)
type Config = ConversionBehavior;
fn from_jsval(cx: *mut JSContext, val: HandleValue, option: ConversionBehavior) -> Result<u8, ()> {
convert_int_from_jsval(cx, val, option, ToInt32)
}
}

Expand All @@ -164,10 +267,9 @@ impl ToJSValConvertible for i16 {
}

impl FromJSValConvertible for i16 {
type Config = ();
fn from_jsval(cx: *mut JSContext, val: HandleValue, _option: ()) -> Result<i16, ()> {
let result = ToInt32(cx, val);
result.map(|v| v as i16)
type Config = ConversionBehavior;
fn from_jsval(cx: *mut JSContext, val: HandleValue, option: ConversionBehavior) -> Result<i16, ()> {
convert_int_from_jsval(cx, val, option, ToInt32)
}
}

Expand All @@ -178,9 +280,9 @@ impl ToJSValConvertible for u16 {
}

impl FromJSValConvertible for u16 {
type Config = ();
fn from_jsval(cx: *mut JSContext, val: HandleValue, _option: ()) -> Result<u16, ()> {
ToUint16(cx, val)
type Config = ConversionBehavior;
fn from_jsval(cx: *mut JSContext, val: HandleValue, option: ConversionBehavior) -> Result<u16, ()> {
convert_int_from_jsval(cx, val, option, ToUint16)
}
}

Expand All @@ -191,9 +293,9 @@ impl ToJSValConvertible for i32 {
}

impl FromJSValConvertible for i32 {
type Config = ();
fn from_jsval(cx: *mut JSContext, val: HandleValue, _option: ()) -> Result<i32, ()> {
ToInt32(cx, val)
type Config = ConversionBehavior;
fn from_jsval(cx: *mut JSContext, val: HandleValue, option: ConversionBehavior) -> Result<i32, ()> {
convert_int_from_jsval(cx, val, option, ToInt32)
}
}

Expand All @@ -204,9 +306,9 @@ impl ToJSValConvertible for u32 {
}

impl FromJSValConvertible for u32 {
type Config = ();
fn from_jsval(cx: *mut JSContext, val: HandleValue, _option: ()) -> Result<u32, ()> {
ToUint32(cx, val)
type Config = ConversionBehavior;
fn from_jsval(cx: *mut JSContext, val: HandleValue, option: ConversionBehavior) -> Result<u32, ()> {
convert_int_from_jsval(cx, val, option, ToUint32)
}
}

Expand All @@ -219,9 +321,9 @@ impl ToJSValConvertible for i64 {
}

impl FromJSValConvertible for i64 {
type Config = ();
fn from_jsval(cx: *mut JSContext, val: HandleValue, _option: ()) -> Result<i64, ()> {
ToInt64(cx, val)
type Config = ConversionBehavior;
fn from_jsval(cx: *mut JSContext, val: HandleValue, option: ConversionBehavior) -> Result<i64, ()> {
convert_int_from_jsval(cx, val, option, ToInt64)
}
}

Expand All @@ -234,9 +336,9 @@ impl ToJSValConvertible for u64 {
}

impl FromJSValConvertible for u64 {
type Config = ();
fn from_jsval(cx: *mut JSContext, val: HandleValue, _option: ()) -> Result<u64, ()> {
ToUint64(cx, val)
type Config = ConversionBehavior;
fn from_jsval(cx: *mut JSContext, val: HandleValue, option: ConversionBehavior) -> Result<u64, ()> {
convert_int_from_jsval(cx, val, option, ToUint64)
}
}

Expand Down Expand Up @@ -323,12 +425,6 @@ pub enum StringificationBehavior {
Empty,
}

impl default::Default for StringificationBehavior {
fn default() -> StringificationBehavior {
StringificationBehavior::Default
}
}

/// Convert the given `JSString` to a `DOMString`. Fails if the string does not
/// contain valid UTF-16.
pub fn jsstring_to_str(cx: *mut JSContext, s: *mut JSString) -> DOMString {
Expand Down Expand Up @@ -666,13 +762,12 @@ impl<T: ToJSValConvertible> ToJSValConvertible for Option<Rc<T>> {
}
}

impl<X: default::Default, T: FromJSValConvertible<Config=X>> FromJSValConvertible for Option<T> {
type Config = ();
fn from_jsval(cx: *mut JSContext, value: HandleValue, _: ()) -> Result<Option<T>, ()> {
impl<T: FromJSValConvertible> FromJSValConvertible for Option<T> {
type Config = T::Config;
fn from_jsval(cx: *mut JSContext, value: HandleValue, option: T::Config) -> Result<Option<T>, ()> {
if value.get().is_null_or_undefined() {
Ok(None)
} else {
let option: X = default::Default::default();
let result: Result<T, ()> = FromJSValConvertible::from_jsval(cx, value, option);
result.map(Some)
}
Expand Down
8 changes: 2 additions & 6 deletions components/script/dom/webidls/Blob.webidl
Expand Up @@ -16,12 +16,8 @@ interface Blob {

//slice Blob into byte-ranged chunks

//TODO: implement slice with [Clamp]
//Blob slice([Clamp] optional long long start,
// [Clamp] optional long long end,
// optional DOMString contentType);
Blob slice(optional long long start,
optional long long end,
Blob slice([Clamp] optional long long start,
[Clamp] optional long long end,
optional DOMString contentType);
//void close();

Expand Down
3 changes: 1 addition & 2 deletions components/script/dom/webidls/WebSocket.webidl
Expand Up @@ -21,8 +21,7 @@ interface WebSocket : EventTarget {
attribute EventHandler onclose;
//readonly attribute DOMString extensions;
//readonly attribute DOMString protocol;
//[Throws] void close([Clamp] optional unsigned short code, optional USVString reason); //Clamp doesn't work
[Throws] void close(optional unsigned short code, optional USVString reason); //No clamp version - works
[Throws] void close([Clamp] optional unsigned short code, optional USVString reason);

//messaging
//attribute EventHandler onmessage;
Expand Down
5 changes: 0 additions & 5 deletions tests/wpt/metadata/websockets/Close-clamp.htm.ini

This file was deleted.

0 comments on commit 7f152b6

Please sign in to comment.