avm2: Implement resolveValue

This commit is contained in:
sleepycatcoding 2023-09-19 17:53:08 +03:00 committed by TÖRÖK Attila
parent 4285d998dc
commit 709306be81
2 changed files with 142 additions and 3 deletions

View File

@ -53,7 +53,7 @@ impl<'gc> XmlListObject<'gc> {
pub fn new( pub fn new(
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
children: Vec<E4XOrXml<'gc>>, children: Vec<E4XOrXml<'gc>>,
target_object: Option<Object<'gc>>, target_object: Option<XmlOrXmlListObject<'gc>>,
target_property: Option<Multiname<'gc>>, target_property: Option<Multiname<'gc>>,
) -> Self { ) -> Self {
let base = ScriptObjectData::new(activation.context.avm2.classes().xml_list); let base = ScriptObjectData::new(activation.context.avm2.classes().xml_list);
@ -97,7 +97,7 @@ impl<'gc> XmlListObject<'gc> {
self.0.write(mc).children = children; self.0.write(mc).children = children;
} }
pub fn target_object(&self) -> Option<Object<'gc>> { pub fn target_object(&self) -> Option<XmlOrXmlListObject<'gc>> {
self.0.read().target_object self.0.read().target_object
} }
@ -119,6 +119,67 @@ impl<'gc> XmlListObject<'gc> {
) )
} }
pub fn resolve_value(
&self,
activation: &mut Activation<'_, 'gc>,
) -> Result<Option<XmlOrXmlListObject<'gc>>, Error<'gc>> {
// 1. If x.[[Length]] > 0, return x
if self.length() > 0 {
Ok(Some(XmlOrXmlListObject::XmlList(*self)))
// 2. Else
} else {
// 2.a. If (x.[[TargetObject]] == null)
let Some(target_object) = self.target_object() else {
// 2.a.i. Return null
return Ok(None);
};
// or (x.[[TargetProperty]] == null)
let Some(target_property) = self.target_property() else {
// 2.a.i. Return null
return Ok(None);
};
// or (type(x.[[TargetProperty]]) is AttributeName) or (x.[[TargetProperty]].localName == "*")
if target_property.is_attribute() || target_property.is_any_name() {
// 2.a.i. Return null
return Ok(None);
}
// 2.b. Let base be the result of calling the [[ResolveValue]] method of x.[[TargetObject]] recursively
let Some(base) = target_object.resolve_value(activation)? else {
// 2.c. If base == null, return null
return Ok(None);
};
// 2.d. Let target be the result of calling [[Get]] on base with argument x.[[TargetProperty]]
let Some(target) = base.get_property_local(&target_property, activation)? else {
// NOTE: Not specified in spec, but avmplus checks if null/undefined was returned, so we do the same, since there is
// an invariant in get_property_local of XmlListObject/XmlObject.
return Ok(None);
};
// 2.e. If (target.[[Length]] == 0)
if target.length().unwrap_or(0) == 0 {
// 2.e.i. If (Type(base) is XMLList) and (base.[[Length]] > 1), return null
if let XmlOrXmlListObject::XmlList(x) = &base {
if x.length() > 1 {
return Ok(None);
}
}
// 2.e.ii. Call [[Put]] on base with arguments x.[[TargetProperty]] and the empty string
base.as_object()
.set_property_local(&target_property, "".into(), activation)?;
// 2.e.iii. Let target be the result of calling [[Get]] on base with argument x.[[TargetProperty]]
return base.get_property_local(&target_property, activation);
}
// 2.f. Return target
Ok(Some(target))
}
}
pub fn equals( pub fn equals(
&self, &self,
other: &Value<'gc>, other: &Value<'gc>,
@ -189,7 +250,7 @@ pub struct XmlListObjectData<'gc> {
/// The XML or XMLList object that this list was created from. /// The XML or XMLList object that this list was created from.
/// If `Some`, then modifications to this list are reflected /// If `Some`, then modifications to this list are reflected
/// in the original object. /// in the original object.
target_object: Option<Object<'gc>>, target_object: Option<XmlOrXmlListObject<'gc>>,
target_property: Option<Multiname<'gc>>, target_property: Option<Multiname<'gc>>,
} }
@ -244,6 +305,79 @@ impl<'a, 'gc> Deref for E4XWrapper<'a, 'gc> {
} }
} }
/// Represents either a XmlObject or a XmlListObject. Used
/// for resolving the value of empty XMLLists.
#[derive(Clone, Collect, Copy, Debug)]
#[collect(no_drop)]
pub enum XmlOrXmlListObject<'gc> {
XmlList(XmlListObject<'gc>),
Xml(XmlObject<'gc>),
}
impl<'gc> XmlOrXmlListObject<'gc> {
pub fn length(&self) -> Option<usize> {
match self {
XmlOrXmlListObject::Xml(x) => x.length(),
XmlOrXmlListObject::XmlList(x) => Some(x.length()),
}
}
pub fn resolve_value(
&self,
activation: &mut Activation<'_, 'gc>,
) -> Result<Option<XmlOrXmlListObject<'gc>>, Error<'gc>> {
match self {
// NOTE: XmlObjects just resolve to themselves.
XmlOrXmlListObject::Xml(x) => Ok(Some(XmlOrXmlListObject::Xml(*x))),
XmlOrXmlListObject::XmlList(x) => x.resolve_value(activation),
}
}
pub fn as_object(&self) -> Object<'gc> {
match self {
XmlOrXmlListObject::Xml(x) => Object::XmlObject(*x),
XmlOrXmlListObject::XmlList(x) => Object::XmlListObject(*x),
}
}
pub fn get_property_local(
&self,
name: &Multiname<'gc>,
activation: &mut Activation<'_, 'gc>,
) -> Result<Option<XmlOrXmlListObject<'gc>>, Error<'gc>> {
let value = self.as_object().get_property_local(name, activation)?;
if let Some(xml) = value.as_object().and_then(|x| x.as_xml_object()) {
return Ok(Some(XmlOrXmlListObject::Xml(xml)));
}
if let Some(list) = value.as_object().and_then(|x| x.as_xml_list_object()) {
return Ok(Some(XmlOrXmlListObject::XmlList(list)));
}
if matches!(value, Value::Null | Value::Undefined) {
return Ok(None);
}
unreachable!(
"Invalid value {:?}, expected XmlListObject/XmlObject or a null value",
value
);
}
}
impl<'gc> From<XmlListObject<'gc>> for XmlOrXmlListObject<'gc> {
fn from(value: XmlListObject<'gc>) -> XmlOrXmlListObject<'gc> {
XmlOrXmlListObject::XmlList(value)
}
}
impl<'gc> From<XmlObject<'gc>> for XmlOrXmlListObject<'gc> {
fn from(value: XmlObject<'gc>) -> XmlOrXmlListObject<'gc> {
XmlOrXmlListObject::Xml(value)
}
}
impl<'gc> TObject<'gc> for XmlListObject<'gc> { impl<'gc> TObject<'gc> for XmlListObject<'gc> {
fn base(&self) -> Ref<ScriptObjectData<'gc>> { fn base(&self) -> Ref<ScriptObjectData<'gc>> {
Ref::map(self.0.read(), |read| &read.base) Ref::map(self.0.read(), |read| &read.base)

View File

@ -69,6 +69,11 @@ impl<'gc> XmlObject<'gc> {
}, },
)) ))
} }
pub fn length(&self) -> Option<usize> {
self.node().length()
}
pub fn set_node(&self, mc: &Mutation<'gc>, node: E4XNode<'gc>) { pub fn set_node(&self, mc: &Mutation<'gc>, node: E4XNode<'gc>) {
self.0.write(mc).node = node; self.0.write(mc).node = node;
} }