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:
parent
ba2c1f5750
commit
f493cf954f
|
@ -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, '_>,
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue