Make `toString` and `valueOf` methods of `TObject`, called `to_string` and `value_of` respectively.

The reason for this is that, in AVM2, `toString` and `valueOf` are not defined on the classes or prototypes of `Function` or `Class`. Instead, they use the `Object.prototype` versions of those functions. Ergo, string and primitive coercion are inherent object methods (the ones that get `[[DoubleSquareBrackets]]` in the ECMA standards). In Ruffle, our equivalent to `[[DoubleSquareBrackets]]` methods are methods on the `TObject` trait, so we're adding them there.

This mechanism will make implementing boxed value types (ala AVM1's `BoxedObject`) easier, too.

We also add some reasonable defaults for `ScriptObject` and `FunctionObject` which will appear on objects, functions, and classes.
This commit is contained in:
David Wendt 2020-03-08 20:11:59 -04:00
parent ba2c1f5750
commit f493cf954f
5 changed files with 80 additions and 16 deletions

View File

@ -646,6 +646,19 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
.into()) .into())
} }
fn to_string(&self) -> Result<Value<'gc>, Error> {
if let ScriptObjectClass::ClassConstructor(class, ..) = self.0.read().base.class() {
let name = QName::from_abc_multiname(&class.abc(), class.instance().name.clone())?;
Ok(format!("[class {}]", name.local_name()).into())
} else {
Ok("function Function() {}".into())
}
}
fn value_of(&self) -> Result<Value<'gc>, Error> {
Ok(Value::Object(Object::from(*self)))
}
fn install_method( fn install_method(
&mut self, &mut self,
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,

View File

@ -20,16 +20,6 @@ pub fn constructor<'gc>(
Ok(Value::Undefined.into()) Ok(Value::Undefined.into())
} }
/// Implements `Function.prototype.toString`
fn to_string<'gc>(
_: &mut Avm2<'gc>,
_: &mut UpdateContext<'_, 'gc, '_>,
_: Option<Object<'gc>>,
_: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(ReturnValue::Immediate("[type Function]".into()))
}
/// Implements `Function.prototype.call` /// Implements `Function.prototype.call`
fn call<'gc>( fn call<'gc>(
avm: &mut Avm2<'gc>, avm: &mut Avm2<'gc>,
@ -61,12 +51,6 @@ fn call<'gc>(
pub fn create_proto<'gc>(gc_context: MutationContext<'gc, '_>, proto: Object<'gc>) -> Object<'gc> { pub fn create_proto<'gc>(gc_context: MutationContext<'gc, '_>, proto: Object<'gc>) -> Object<'gc> {
let mut function_proto = ScriptObject::object(gc_context, proto); let mut function_proto = ScriptObject::object(gc_context, proto);
function_proto.install_method(
gc_context,
QName::new(Namespace::public_namespace(), "toString"),
0,
FunctionObject::from_builtin(gc_context, to_string, function_proto),
);
function_proto.install_method( function_proto.install_method(
gc_context, gc_context,
QName::new(Namespace::public_namespace(), "call"), QName::new(Namespace::public_namespace(), "call"),

View File

@ -19,6 +19,32 @@ pub fn constructor<'gc>(
Ok(Value::Undefined.into()) Ok(Value::Undefined.into())
} }
/// Implements `Object.prototype.toString`
fn to_string<'gc>(
_: &mut Avm2<'gc>,
_: &mut UpdateContext<'_, 'gc, '_>,
this: Option<Object<'gc>>,
_: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(this
.map(|t| t.to_string())
.unwrap_or(Ok(Value::Undefined))?
.into())
}
/// Implements `Object.prototype.valueOf`
fn value_of<'gc>(
_: &mut Avm2<'gc>,
_: &mut UpdateContext<'_, 'gc, '_>,
this: Option<Object<'gc>>,
_: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(this
.map(|t| t.value_of())
.unwrap_or(Ok(Value::Undefined))?
.into())
}
/// `Object.prototype.hasOwnProperty` /// `Object.prototype.hasOwnProperty`
pub fn has_own_property<'gc>( pub fn has_own_property<'gc>(
_avm: &mut Avm2<'gc>, _avm: &mut Avm2<'gc>,
@ -127,6 +153,18 @@ pub fn fill_proto<'gc>(
mut object_proto: Object<'gc>, mut object_proto: Object<'gc>,
fn_proto: Object<'gc>, fn_proto: Object<'gc>,
) { ) {
object_proto.install_method(
gc_context,
QName::new(Namespace::public_namespace(), "toString"),
0,
FunctionObject::from_builtin(gc_context, to_string, fn_proto),
);
object_proto.install_method(
gc_context,
QName::new(Namespace::public_namespace(), "valueOf"),
0,
FunctionObject::from_builtin(gc_context, value_of, fn_proto),
);
object_proto.install_method( object_proto.install_method(
gc_context, gc_context,
QName::new(Namespace::public_namespace(), "hasOwnProperty"), QName::new(Namespace::public_namespace(), "hasOwnProperty"),

View File

@ -627,6 +627,23 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
scope: Option<GcCell<'gc, Scope<'gc>>>, scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error>; ) -> Result<Object<'gc>, Error>;
/// Implement the result of calling `Object.prototype.toString` on this
/// object class.
///
/// `toString` is a method used to request an object be coerced to a string
/// value. The default implementation is stored here. User-specified string
/// coercions happen by defining `toString` in a downstream class or
/// prototype; this is then picked up by the VM runtime when doing
/// coercions.
fn to_string(&self) -> Result<Value<'gc>, Error>;
/// Implement the result of calling `Object.prototype.valueOf` on this
/// object class.
///
/// `valueOf` is a method used to request an object be coerced to a
/// primitive value. Typically, this would be a number of some kind.
fn value_of(&self) -> Result<Value<'gc>, Error>;
/// Get a raw pointer value for this object. /// Get a raw pointer value for this object.
fn as_ptr(&self) -> *const ObjectPtr; fn as_ptr(&self) -> *const ObjectPtr;

View File

@ -247,6 +247,14 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
)) ))
} }
fn to_string(&self) -> Result<Value<'gc>, Error> {
Ok("[object Object]".into())
}
fn value_of(&self) -> Result<Value<'gc>, Error> {
Ok(Value::Object(Object::from(*self)))
}
fn install_method( fn install_method(
&mut self, &mut self,
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
@ -797,6 +805,10 @@ impl<'gc> ScriptObjectData<'gc> {
Ok(()) Ok(())
} }
pub fn class(&self) -> &ScriptObjectClass<'gc> {
&self.class
}
/// Install a method into the object. /// Install a method into the object.
pub fn install_method(&mut self, name: QName, disp_id: u32, function: Object<'gc>) { pub fn install_method(&mut self, name: QName, disp_id: u32, function: Object<'gc>) {
if disp_id > 0 { if disp_id > 0 {