From 3f99866ca494ae4d5e10c4ebfc0f031ca17ce69d Mon Sep 17 00:00:00 2001 From: Tom Schuster Date: Thu, 30 May 2024 20:18:19 +0200 Subject: [PATCH] avm2: Implement XML.addNamespace --- core/src/avm2/e4x.rs | 69 ++++++++++++++++++++++++++++++++++++ core/src/avm2/globals/xml.rs | 26 ++++++++++++-- 2 files changed, 92 insertions(+), 3 deletions(-) diff --git a/core/src/avm2/e4x.rs b/core/src/avm2/e4x.rs index b7e1d609c..d23494c03 100644 --- a/core/src/avm2/e4x.rs +++ b/core/src/avm2/e4x.rs @@ -1118,6 +1118,75 @@ impl<'gc> E4XNode<'gc> { result } + // ECMA-357 9.1.1.13 [[AddInScopeNamespace]] (N) + pub fn add_in_scope_namespace(&self, gc: &Mutation<'gc>, namespace: E4XNamespace<'gc>) { + // 1. If x.[[Class]] ∈ {"text", "comment", "processing-instruction", “attribute”}, return + if !self.is_element() { + return; + } + + // 2. If N.prefix != undefined + let Some(prefix) = namespace.prefix else { + // 3. Return + return; + }; + + // 2.a. If N.prefix == "" and x.[[Name]].uri == "", return + if prefix.is_empty() && self.namespace().map_or(true, |ns| ns.uri.is_empty()) { + return; + } + + { + let E4XNodeKind::Element { + ref mut namespaces, .. + } = &mut *self.kind_mut(gc) + else { + unreachable!("must be an element"); + }; + + // 2.b. Let match be null + // 2.c. For each ns in x.[[InScopeNamespaces]] + // 2.c.i. If N.prefix == ns.prefix, let match = ns + let found_index = namespaces.iter().position(|ns| Some(prefix) == ns.prefix); + + // 2.d. If match is not null and match.uri is not equal to N.uri + if let Some(found_index) = found_index { + if namespaces[found_index].uri != namespace.uri { + // 2.d.i. Remove match from x.[[InScopeNamespaces]] + namespaces.remove(found_index); + } + } + + // 2.e. Let x.[[InScopeNamespaces]] = x.[[InScopeNamespaces]] ∪ { N } + namespaces.push(namespace); + } + + // 2.f. If x.[[Name]].[[Prefix]] == N.prefix + match self.namespace() { + Some(self_ns) if self_ns.prefix == Some(prefix) => { + // 2.f.i. Let x.[[Name]].prefix = undefined + self.0.write(gc).namespace = Some(E4XNamespace::new_uri(self_ns.uri)); + } + _ => {} + } + + // 2.g. For each attr in x.[[Attributes]] + if let E4XNodeKind::Element { + ref mut attributes, .. + } = &mut *self.kind_mut(gc) + { + for attr in attributes.iter_mut() { + // 2.g.i. If attr.[[Name]].[[Prefix]] == N.prefix, let attr.[[Name]].prefix = undefined + match attr.namespace() { + Some(attr_ns) if attr_ns.prefix == Some(prefix) => { + attr.0.write(gc).namespace = Some(E4XNamespace::new_uri(attr_ns.uri)); + } + _ => {} + } + } + } + } + // FIXME - avmplus constructs an actual QName here, and does the normal // Multiname matching logic. We should do the same. pub fn matches_name(&self, name: &Multiname<'gc>) -> bool { diff --git a/core/src/avm2/globals/xml.rs b/core/src/avm2/globals/xml.rs index 1a86c29ab..ef145c698 100644 --- a/core/src/avm2/globals/xml.rs +++ b/core/src/avm2/globals/xml.rs @@ -1,7 +1,7 @@ //! XML builtin and prototype use crate::avm2::api_version::ApiVersion; -use crate::avm2::e4x::{name_to_multiname, E4XNode, E4XNodeKind}; +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::{ @@ -234,9 +234,29 @@ pub fn namespace_internal_impl<'gc>( pub fn add_namespace<'gc>( activation: &mut Activation<'_, 'gc>, this: Object<'gc>, - _args: &[Value<'gc>], + args: &[Value<'gc>], ) -> Result, Error<'gc>> { - avm2_stub_method!(activation, "XML", "addNamespace"); + let xml = this.as_xml_object().unwrap(); + let node = xml.node(); + + // 1. Let ns a Namespace constructed as if by calling the function Namespace(namespace) + let value = args.get_value(0); + let ns = activation + .avm2() + .classes() + .namespace + .construct(activation, &[value])? + .as_namespace_object() + .unwrap(); + + // 2. Call the [[AddInScopeNamespace]] method of x with parameter ns + node.add_in_scope_namespace( + activation.gc(), + E4XNamespace { + prefix: ns.prefix(), + uri: ns.namespace().as_uri(), + }, + ); // 3. Return x Ok(this.into())