avm2: Preserve ability to `callmethod` on slot IDs that have not yet been bound, by binding them at that time.
This commit is contained in:
parent
d0d19bcf38
commit
1b5869e15a
|
@ -1126,11 +1126,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
||||||
) -> Result<FrameControl<'gc>, Error> {
|
) -> Result<FrameControl<'gc>, Error> {
|
||||||
let args = self.context.avm2.pop_args(arg_count);
|
let args = self.context.avm2.pop_args(arg_count);
|
||||||
let receiver = self.context.avm2.pop().coerce_to_object(self)?;
|
let receiver = self.context.avm2.pop().coerce_to_object(self)?;
|
||||||
let function: Result<Object<'gc>, Error> = receiver
|
|
||||||
.get_method(index.0)
|
let value = receiver.call_method(index.0, &args, self)?;
|
||||||
.ok_or_else(|| format!("Object method {} does not exist", index.0).into());
|
|
||||||
let superclass_object = receiver.instance_of();
|
|
||||||
let value = function?.call(Some(receiver), &args, self, superclass_object)?;
|
|
||||||
|
|
||||||
self.context.avm2.push(value);
|
self.context.avm2.push(value);
|
||||||
|
|
||||||
|
|
|
@ -765,6 +765,17 @@ impl<'gc> Class<'gc> {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Determines if this class provides a given trait on its instances.
|
||||||
|
pub fn has_instance_trait_by_id(&self, id: u32) -> bool {
|
||||||
|
for trait_entry in self.instance_traits.iter() {
|
||||||
|
if id == trait_entry.slot_id() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// Return instance traits provided by this class.
|
/// Return instance traits provided by this class.
|
||||||
pub fn instance_traits(&self) -> &[Trait<'gc>] {
|
pub fn instance_traits(&self) -> &[Trait<'gc>] {
|
||||||
&self.instance_traits[..]
|
&self.instance_traits[..]
|
||||||
|
|
|
@ -487,11 +487,56 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
|
||||||
base.init_slot(id, value, mc)
|
base.init_slot(id, value, mc)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve a method by its index.
|
/// Call a method by its index.
|
||||||
fn get_method(self, id: u32) -> Option<Object<'gc>> {
|
///
|
||||||
let base = self.base();
|
/// This directly corresponds with the AVM2 operation `callmethod`.
|
||||||
|
#[allow(unused_mut)] //Not unused.
|
||||||
|
fn call_method(
|
||||||
|
mut self,
|
||||||
|
id: u32,
|
||||||
|
arguments: &[Value<'gc>],
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
) -> Result<Value<'gc>, Error> {
|
||||||
|
if self.base().get_method(id).is_none() {
|
||||||
|
if let Some(class) = self.instance_of() {
|
||||||
|
if let Some((bound_method, name, is_final)) =
|
||||||
|
class.bound_instance_method_by_id(activation, self.into(), id)?
|
||||||
|
{
|
||||||
|
self.install_method(
|
||||||
|
activation.context.gc_context,
|
||||||
|
name.clone(),
|
||||||
|
id,
|
||||||
|
bound_method,
|
||||||
|
is_final,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
base.get_method(id)
|
if let Some(class) = self.as_class_object() {
|
||||||
|
if let Some((bound_method, name, is_final)) =
|
||||||
|
class.bound_class_method_by_id(activation, id)?
|
||||||
|
{
|
||||||
|
self.install_method(
|
||||||
|
activation.context.gc_context,
|
||||||
|
name.clone(),
|
||||||
|
id,
|
||||||
|
bound_method,
|
||||||
|
is_final,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(method_object) = self.base().get_method(id) {
|
||||||
|
return method_object.call(
|
||||||
|
Some(self.into()),
|
||||||
|
arguments,
|
||||||
|
activation,
|
||||||
|
self.instance_of(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(format!("Cannot call unknown method id {}", id).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve a multiname into a single QName, if any of the namespaces
|
/// Resolve a multiname into a single QName, if any of the namespaces
|
||||||
|
|
|
@ -365,6 +365,27 @@ impl<'gc> ClassObject<'gc> {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve the class object that a particular QName trait is defined in.
|
||||||
|
///
|
||||||
|
/// Must be called on a class object; will error out if called on
|
||||||
|
/// anything else.
|
||||||
|
///
|
||||||
|
/// This function returns `None` for non-trait properties, such as actually
|
||||||
|
/// defined prototype methods for ES3-style classes.
|
||||||
|
pub fn find_class_for_trait_by_id(self, id: u32) -> Result<Option<ClassObject<'gc>>, Error> {
|
||||||
|
let class_definition = self.inner_class_definition();
|
||||||
|
|
||||||
|
if class_definition.read().has_instance_trait_by_id(id) {
|
||||||
|
return Ok(Some(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(base) = self.superclass_object() {
|
||||||
|
return base.find_class_for_trait_by_id(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
/// Determine if this class has a given type in its superclass chain.
|
/// Determine if this class has a given type in its superclass chain.
|
||||||
///
|
///
|
||||||
/// The given object `test_class` should be either a superclass or
|
/// The given object `test_class` should be either a superclass or
|
||||||
|
@ -741,6 +762,51 @@ impl<'gc> ClassObject<'gc> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve a bound instance method by slot ID.
|
||||||
|
///
|
||||||
|
/// This returns the bound method object itself, as well as it's name,
|
||||||
|
/// and if it's a final method. You will need the additional properties in
|
||||||
|
/// order to install the method into your object.
|
||||||
|
///
|
||||||
|
/// You should only call this method once per reciever/name pair, and cache
|
||||||
|
/// the result. Otherwise, code that relies on bound methods having stable
|
||||||
|
/// object identitities (e.g. `EventDispatcher.removeEventListener`) will
|
||||||
|
/// fail.
|
||||||
|
pub fn bound_instance_method_by_id(
|
||||||
|
self,
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
receiver: Object<'gc>,
|
||||||
|
id: u32,
|
||||||
|
) -> Result<Option<(Object<'gc>, QName<'gc>, bool)>, Error> {
|
||||||
|
if let Some(superclass) = self.find_class_for_trait_by_id(id)? {
|
||||||
|
let superclassdef = superclass.inner_class_definition();
|
||||||
|
let traits = superclassdef.read().lookup_instance_traits_by_slot(id)?;
|
||||||
|
|
||||||
|
if let Some((_superclass, method_trait)) = traits
|
||||||
|
.and_then(|t| match t.kind() {
|
||||||
|
TraitKind::Method { .. } => Some(t),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.map(|t| (superclass, t))
|
||||||
|
{
|
||||||
|
let name = method_trait.name().clone();
|
||||||
|
let method = method_trait.as_method().unwrap();
|
||||||
|
let is_final = method_trait.is_final();
|
||||||
|
let scope = self.instance_scope();
|
||||||
|
|
||||||
|
Ok(Some((
|
||||||
|
FunctionObject::from_method(activation, method, scope, Some(receiver)),
|
||||||
|
name,
|
||||||
|
is_final,
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Retrieve a class method by name.
|
/// Retrieve a class method by name.
|
||||||
///
|
///
|
||||||
/// This does not return a defining class as class methods are not
|
/// This does not return a defining class as class methods are not
|
||||||
|
@ -789,6 +855,43 @@ impl<'gc> ClassObject<'gc> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve a bound class method by id.
|
||||||
|
///
|
||||||
|
/// This returns the bound method object itself, as well as it's name,
|
||||||
|
/// and if it's a final method. You will need the additional properties in
|
||||||
|
/// order to install the method into your object.
|
||||||
|
///
|
||||||
|
/// You should only call this method once per reciever/name pair, and cache
|
||||||
|
/// the result. Otherwise, code that relies on bound methods having stable
|
||||||
|
/// object identitities (e.g. `EventDispatcher.removeEventListener`) will
|
||||||
|
/// fail.
|
||||||
|
pub fn bound_class_method_by_id(
|
||||||
|
self,
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
id: u32,
|
||||||
|
) -> Result<Option<(Object<'gc>, QName<'gc>, bool)>, Error> {
|
||||||
|
let classdef = self.inner_class_definition();
|
||||||
|
let traits = classdef.read().lookup_class_traits_by_slot(id)?;
|
||||||
|
|
||||||
|
if let Some(method_trait) = traits.and_then(|t| match t.kind() {
|
||||||
|
TraitKind::Method { .. } => Some(t),
|
||||||
|
_ => None,
|
||||||
|
}) {
|
||||||
|
let method = method_trait.as_method().unwrap();
|
||||||
|
let name = method_trait.name().clone();
|
||||||
|
let is_final = method_trait.is_final();
|
||||||
|
let scope = self.class_scope();
|
||||||
|
|
||||||
|
Ok(Some((
|
||||||
|
FunctionObject::from_method(activation, method, scope, Some(self.into())),
|
||||||
|
name,
|
||||||
|
is_final,
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn inner_class_definition(self) -> GcCell<'gc, Class<'gc>> {
|
pub fn inner_class_definition(self) -> GcCell<'gc, Class<'gc>> {
|
||||||
self.0.read().class
|
self.0.read().class
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue