avm2: Implement object space enumeration.
This required making enumerants into `Value`s, rather than `QName`s.
This commit is contained in:
parent
dbe9dffe0e
commit
c299f63784
|
@ -2491,9 +2491,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
let cur_index = self.context.avm2.pop().coerce_to_number(self)?;
|
||||
let object = self.context.avm2.pop().coerce_to_object(self)?;
|
||||
|
||||
let name = object
|
||||
.get_enumerant_name(cur_index as u32)
|
||||
.map(|n| n.local_name().into());
|
||||
let name = object.get_enumerant_name(cur_index as u32);
|
||||
|
||||
self.context.avm2.push(name.unwrap_or(Value::Undefined));
|
||||
|
||||
|
@ -2506,7 +2504,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
|
||||
let name = object.get_enumerant_name(cur_index as u32);
|
||||
let value = if let Some(name) = name {
|
||||
object.get_property(object, &name.into(), self)?
|
||||
let name = name.coerce_to_string(self)?;
|
||||
object.get_property(object, &QName::dynamic_name(name).into(), self)?
|
||||
} else {
|
||||
Value::Undefined
|
||||
};
|
||||
|
|
|
@ -558,7 +558,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
|
|||
/// Objects are responsible for maintaining a consistently ordered and
|
||||
/// indexed list of enumerable names which can be queried by this
|
||||
/// mechanism.
|
||||
fn get_enumerant_name(&self, index: u32) -> Option<QName<'gc>> {
|
||||
fn get_enumerant_name(&self, index: u32) -> Option<Value<'gc>> {
|
||||
let base = self.base();
|
||||
|
||||
base.get_enumerant_name(index)
|
||||
|
|
|
@ -110,4 +110,21 @@ impl<'gc> TObject<'gc> for DictionaryObject<'gc> {
|
|||
fn as_dictionary_object(self) -> Option<DictionaryObject<'gc>> {
|
||||
Some(self)
|
||||
}
|
||||
|
||||
fn get_enumerant_name(&self, index: u32) -> Option<Value<'gc>> {
|
||||
let read = self.0.read();
|
||||
let last_enumerant = read.base.get_last_enumerant();
|
||||
|
||||
if index < last_enumerant {
|
||||
read.base.get_enumerant_name(index)
|
||||
} else {
|
||||
let object_space_index = index.saturating_sub(last_enumerant);
|
||||
|
||||
read.object_space
|
||||
.keys()
|
||||
.nth(object_space_index as usize)
|
||||
.cloned()
|
||||
.map(|v| v.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -397,7 +397,7 @@ impl<'gc> ScriptObjectData<'gc> {
|
|||
self.proto = Some(proto)
|
||||
}
|
||||
|
||||
pub fn get_enumerant_name(&self, index: u32) -> Option<QName<'gc>> {
|
||||
pub fn get_enumerant_name(&self, index: u32) -> Option<Value<'gc>> {
|
||||
// NOTE: AVM2 object enumeration is one of the weakest parts of an
|
||||
// otherwise well-designed VM. Notably, because of the way they
|
||||
// implemented `hasnext` and `hasnext2`, all enumerants start from ONE.
|
||||
|
@ -406,7 +406,10 @@ impl<'gc> ScriptObjectData<'gc> {
|
|||
// sentinel.
|
||||
let true_index = (index as usize).checked_sub(1)?;
|
||||
|
||||
self.enumerants.get(true_index).cloned()
|
||||
self.enumerants
|
||||
.get(true_index)
|
||||
.cloned()
|
||||
.map(|q| q.local_name().into())
|
||||
}
|
||||
|
||||
pub fn property_is_enumerable(&self, name: &QName<'gc>) -> bool {
|
||||
|
@ -441,6 +444,14 @@ impl<'gc> ScriptObjectData<'gc> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the end of (standard) enumerant space.
|
||||
///
|
||||
/// Intended for objects that need to extend enumerant space. The index
|
||||
/// returned is guaranteed to be unused by the base enumerant list.
|
||||
pub fn get_last_enumerant(&self) -> u32 {
|
||||
(self.enumerants.len() as u32).saturating_add(1)
|
||||
}
|
||||
|
||||
/// Install a method into the object.
|
||||
pub fn install_method(
|
||||
&mut self,
|
||||
|
|
|
@ -76,7 +76,65 @@ a["false"] = "stringy false";
|
|||
trace('///a[a] = a');
|
||||
a[a] = a;
|
||||
|
||||
trace("/// for (var k in a) { ... }");
|
||||
var has_key2 = false;
|
||||
var has_key3 = false;
|
||||
var has_key4 = false;
|
||||
|
||||
trace("/// (enumerating object keys...)");
|
||||
for (var k in a) {
|
||||
trace(k);
|
||||
if (k === key2) {
|
||||
has_key2 = true;
|
||||
} else if (k === key3) {
|
||||
has_key3 = true;
|
||||
} else if (k === key4) {
|
||||
has_key4 = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_key2) {
|
||||
trace("/// (Found key2!)");
|
||||
}
|
||||
|
||||
if (has_key3) {
|
||||
trace("/// (Found key3!)");
|
||||
}
|
||||
|
||||
if (has_key4) {
|
||||
trace("/// (Found key4!)");
|
||||
}
|
||||
|
||||
trace("///a.setPropertyIsEnumerable(key2, false);");
|
||||
a.setPropertyIsEnumerable(key2, false);
|
||||
|
||||
trace("///a.setPropertyIsEnumerable(key3, false);");
|
||||
a.setPropertyIsEnumerable(key3, false);
|
||||
|
||||
trace("///a.setPropertyIsEnumerable(key4, false);");
|
||||
a.setPropertyIsEnumerable(key4, false);
|
||||
|
||||
has_key2 = false;
|
||||
has_key3 = false;
|
||||
has_key4 = false;
|
||||
|
||||
trace("/// (enumerating object keys...)");
|
||||
for (var k in a) {
|
||||
if (k === key2) {
|
||||
has_key2 = true;
|
||||
} else if (k === key3) {
|
||||
has_key3 = true;
|
||||
} else if (k === key4) {
|
||||
has_key4 = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_key2) {
|
||||
trace("/// (Found key2!)");
|
||||
}
|
||||
|
||||
if (has_key3) {
|
||||
trace("/// (Found key3!)");
|
||||
}
|
||||
|
||||
if (has_key4) {
|
||||
trace("/// (Found key4!)");
|
||||
}
|
|
@ -24,17 +24,14 @@
|
|||
///a[false] = "false"
|
||||
///a["false"] = "stringy false"
|
||||
///a[a] = a
|
||||
/// for (var k in a) { ... }
|
||||
13
|
||||
false
|
||||
key
|
||||
1.123
|
||||
[object Dictionary]
|
||||
[object Test]
|
||||
undefined
|
||||
key4
|
||||
null
|
||||
[object Test]
|
||||
key3
|
||||
true
|
||||
key4
|
||||
/// (enumerating object keys...)
|
||||
/// (Found key2!)
|
||||
/// (Found key3!)
|
||||
/// (Found key4!)
|
||||
///a.setPropertyIsEnumerable(key2, false);
|
||||
///a.setPropertyIsEnumerable(key3, false);
|
||||
///a.setPropertyIsEnumerable(key4, false);
|
||||
/// (enumerating object keys...)
|
||||
/// (Found key2!)
|
||||
/// (Found key3!)
|
||||
/// (Found key4!)
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue