avm2: Implement XML.namespace

This commit is contained in:
Tom Schuster 2024-05-30 20:38:29 +02:00
parent 3f99866ca4
commit f20f57c6f1
4 changed files with 71 additions and 39 deletions

View File

@ -14,7 +14,7 @@ use crate::{avm2::TObject, xml::custom_unescape};
use super::{
error::{make_error_1010, make_error_1118, type_error},
object::{E4XOrXml, FunctionObject},
object::{E4XOrXml, FunctionObject, NamespaceObject},
string::AvmString,
Activation, Error, Multiname, Value,
};
@ -110,6 +110,34 @@ impl<'gc> E4XNamespace<'gc> {
pub fn new_uri(uri: AvmString<'gc>) -> Self {
E4XNamespace { prefix: None, uri }
}
pub fn default_namespace() -> Self {
E4XNamespace {
prefix: None,
uri: "".into(),
}
}
}
impl<'gc> E4XNamespace<'gc> {
pub fn as_namespace_object(
&self,
activation: &mut Activation<'_, 'gc>,
) -> Result<NamespaceObject<'gc>, Error<'gc>> {
let args = if let Some(prefix) = self.prefix {
vec![prefix.into(), self.uri.into()]
} else {
vec![self.uri.into()]
};
let obj = activation
.avm2()
.classes()
.namespace
.construct(activation, &args)?;
Ok(obj
.as_namespace_object()
.expect("just constructed a namespace"))
}
}
#[derive(Collect, Debug)]

View File

@ -1,15 +1,12 @@
//! XML builtin and prototype
use crate::avm2::api_version::ApiVersion;
use crate::avm2::e4x::{name_to_multiname, E4XNamespace, E4XNode, E4XNodeKind};
use crate::avm2::error::{make_error_1117, type_error};
pub use crate::avm2::object::xml_allocator;
use crate::avm2::object::{
E4XOrXml, NamespaceObject, QNameObject, TObject, XmlListObject, XmlObject,
};
use crate::avm2::object::{E4XOrXml, QNameObject, TObject, XmlListObject, XmlObject};
use crate::avm2::parameters::ParametersExt;
use crate::avm2::string::AvmString;
use crate::avm2::{Activation, ArrayObject, Error, Multiname, Namespace, Object, Value};
use crate::avm2::{Activation, ArrayObject, Error, Multiname, Object, Value};
use crate::avm2_stub_method;
fn ill_formed_markup_err<'gc>(
@ -121,7 +118,7 @@ pub fn name<'gc>(
let xml = this.as_xml_object().unwrap();
if let Some(local_name) = xml.local_name() {
let namespace = xml.namespace(activation);
let namespace = xml.namespace_object(activation, &[])?.namespace();
let mut multiname = Multiname::new(namespace, local_name);
multiname.set_is_attribute(xml.node().is_attribute());
Ok(QNameObject::from_name(activation, multiname)?.into())
@ -179,17 +176,15 @@ pub fn namespace_internal_impl<'gc>(
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
avm2_stub_method!(activation, "XML", "namespace");
let xml = this.as_xml_object().unwrap();
let node = xml.node();
// FIXME:
// 1. Let y = x
// 2. Let inScopeNS = { }
// 3. While (y is not null)
// a. For each ns in y.[[InScopeNamespaces]]
// ....
let xml = this.as_xml_object().unwrap();
let node = xml.node();
let in_scope_ns = node.in_scope_namespaces();
// 4. If prefix was not specified
if args[0] == Value::Bool(false) {
@ -205,28 +200,20 @@ pub fn namespace_internal_impl<'gc>(
}
// b. Return the result of calling the [[GetNamespace]] method of x.[[Name]] with argument inScopeNS
// FIXME: Use inScopeNS
let namespace = xml.namespace(activation);
Ok(NamespaceObject::from_namespace(activation, namespace)?.into())
Ok(xml.namespace_object(activation, &in_scope_ns)?.into())
} else {
// a. Let prefix = ToString(prefix)
let prefix = args.get_string(activation, 1)?;
// b. Find a Namespace ns ∈ inScopeNS, such that ns.prefix = prefix. If no such ns exists, let ns = undefined.
// c. Return ns
// FIXME: Nodes currently either have zero or one namespace, which has the prefix "" (empty string)
Ok(match node.namespace() {
Some(ns) if prefix.is_empty() => {
let namespace = Namespace::package(
ns.uri,
ApiVersion::AllVersions,
&mut activation.context.borrow_gc(),
);
NamespaceObject::from_namespace(activation, namespace)?.into()
}
_ => Value::Undefined,
})
Ok(
if let Some(ns) = in_scope_ns.iter().find(|ns| ns.prefix == Some(prefix)) {
ns.as_namespace_object(activation)?.into()
} else {
Value::Undefined
},
)
}
}

View File

@ -1,15 +1,15 @@
//! Object representation for XML objects
use crate::avm2::activation::Activation;
use crate::avm2::api_version::ApiVersion;
use crate::avm2::e4x::{string_to_multiname, E4XNamespace, E4XNode, E4XNodeKind};
use crate::avm2::error::make_error_1087;
use crate::avm2::multiname::NamespaceSet;
use crate::avm2::object::script_object::ScriptObjectData;
use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject, XmlListObject};
use crate::avm2::object::{
ClassObject, NamespaceObject, Object, ObjectPtr, TObject, XmlListObject,
};
use crate::avm2::string::AvmString;
use crate::avm2::value::Value;
use crate::avm2::Namespace;
use crate::avm2::{Error, Multiname};
use core::fmt;
use gc_arena::{Collect, GcCell, GcWeakCell, Mutation};
@ -158,16 +158,34 @@ impl<'gc> XmlObject<'gc> {
self.0.read().node.local_name()
}
pub fn namespace(&self, activation: &mut Activation<'_, 'gc>) -> Namespace<'gc> {
pub fn namespace_object(
&self,
activation: &mut Activation<'_, 'gc>,
in_scope_ns: &[E4XNamespace<'gc>],
) -> Result<NamespaceObject<'gc>, Error<'gc>> {
// 13.3.5.4 [[GetNamespace]] ( [ InScopeNamespaces ] )
// 1. If q.uri is null, throw a TypeError exception
// NOTE: As stated in the spec, this not really possible
match self.0.read().node.namespace() {
Some(ns) => Namespace::package(
ns.uri,
ApiVersion::AllVersions,
&mut activation.context.borrow_gc(),
),
None => activation.avm2().public_namespace_base_version,
None => E4XNamespace::default_namespace(),
Some(ns) => {
// 2. If InScopeNamespaces was not specified, let InScopeNamespaces = { }
// 3. Find a Namespace ns in InScopeNamespaces, such that ns.uri == q.uri. If more than one such
// Namespace ns exists, the implementation may choose one of the matching Namespaces arbitrarily.
// NOTE: Flash just uses whatever namespace URI matches first. They don't do anything with the prefix.
if let Some(ns) = in_scope_ns.iter().find(|scope_ns| scope_ns.uri == ns.uri) {
*ns
} else {
// 4. If no such namespace ns exists
// a. Let ns be a new namespace created as if by calling the constructor new Namespace(q.uri)
// NOTE: We could preserve the prefix here, but Flash doesn't bother.
E4XNamespace::new_uri(ns.uri)
}
}
}
// 5. Return ns
.as_namespace_object(activation)
}
pub fn matches_name(&self, multiname: &Multiname<'gc>) -> bool {
self.0.read().node.matches_name(multiname)

View File

@ -1,2 +1 @@
num_ticks = 1
known_failure = true