From 052627f332d9e91fb74d340a3f0fab93dcdd4863 Mon Sep 17 00:00:00 2001 From: Tom Schuster Date: Tue, 20 Aug 2024 12:04:33 +0200 Subject: [PATCH 1/2] avm2: Implement XML.removeNamespace --- core/src/avm2/e4x.rs | 24 +++++++++ core/src/avm2/globals/xml.rs | 85 +++++++++++++++++++++++++++++- core/src/avm2/object/xml_object.rs | 25 ++------- 3 files changed, 110 insertions(+), 24 deletions(-) diff --git a/core/src/avm2/e4x.rs b/core/src/avm2/e4x.rs index 12ccf63e21a6..0d29871da9ea 100644 --- a/core/src/avm2/e4x.rs +++ b/core/src/avm2/e4x.rs @@ -1158,6 +1158,30 @@ impl<'gc> E4XNode<'gc> { self.0.read().notification } + // 13.3.5.4 [[GetNamespace]] ( [ InScopeNamespaces ] ) + pub fn get_namespace(&self, in_scope_ns: &[E4XNamespace<'gc>]) -> E4XNamespace<'gc> { + // 1. If q.uri is null, throw a TypeError exception + // NOTE: As stated in the spec, this isn't really possible. + match self.namespace() { + 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 + } + pub fn in_scope_namespaces(&self) -> Vec> { let mut result: Vec> = Vec::new(); diff --git a/core/src/avm2/globals/xml.rs b/core/src/avm2/globals/xml.rs index fb9f4d9831fe..7c6d9572989f 100644 --- a/core/src/avm2/globals/xml.rs +++ b/core/src/avm2/globals/xml.rs @@ -339,9 +339,90 @@ pub fn set_namespace<'gc>( pub fn remove_namespace<'gc>( activation: &mut Activation<'_, 'gc>, this: Object<'gc>, - _args: &[Value<'gc>], + args: &[Value<'gc>], ) -> Result, Error<'gc>> { - avm2_stub_method!(activation, "XML", "removeNamespace"); + let xml = this.as_xml_object().unwrap(); + let node = xml.node(); + + // 1. If x.[[Class]] ∈ {"text", "comment", "processing-instruction", "attribute"}, return x + if !node.is_element() { + return Ok(this.into()); + } + + // 2. Let ns be a Namespace object created 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(); + let ns = E4XNamespace { + prefix: ns.prefix(), + uri: ns.namespace().as_uri(), + }; + + // 3. Let thisNS be the result of calling [[GetNamespace]] on x.[[Name]] with argument x.[[InScopeNamespaces]] + let in_scope_ns = node.in_scope_namespaces(); + let this_ns = node.get_namespace(&in_scope_ns); + + // 4. If (thisNS == ns), return x + if this_ns == ns { + return Ok(this.into()); + } + + { + let E4XNodeKind::Element { attributes, .. } = &*node.kind() else { + unreachable!() + }; + + // 5. For each a in x.[[Attributes]] + for attr in attributes { + // 5.a. Let aNS be the result of calling [[GetNamespace]] on a.[[Name]] with argument x.[[InScopeNamespaces]] + let attr_ns = attr.get_namespace(&in_scope_ns); + // 5.b. If (aNS == ns), return x + if attr_ns == ns { + return Ok(this.into()); + } + } + } + + // 6. If ns.prefix == undefined + if ns.prefix.is_none() { + let E4XNodeKind::Element { + ref mut namespaces, .. + } = &mut *node.kind_mut(activation.gc()) + else { + unreachable!() + }; + // 6.a. If there exists a namespace n ∈ x.[[InScopeNamespaces]], + // such that n.uri == ns.uri, remove the namespace n from x.[[InScopeNamespaces]] + namespaces.retain(|namespace| namespace.uri != ns.uri); + } else { + // 7. Else + let E4XNodeKind::Element { + ref mut namespaces, .. + } = &mut *node.kind_mut(activation.gc()) + else { + unreachable!() + }; + // 7.a. If there exists a namespace n ∈ x.[[InScopeNamespaces]], + // such that n.uri == ns.uri and n.prefix == ns.prefix, remove the namespace n from x.[[InScopeNamespaces]] + namespaces.retain(|namespace| *namespace != ns); + } + + let E4XNodeKind::Element { children, .. } = &*node.kind() else { + unreachable!() + }; + // 8. For each property p of x + for child in children { + // 8.a. If p.[[Class]] = "element", call the removeNamespace method of p with argument ns + if child.is_element() { + let xml = E4XOrXml::E4X(*child).get_or_create_xml(activation); + remove_namespace(activation, xml.into(), args)?; + } + } // 9. Return x Ok(this.into()) diff --git a/core/src/avm2/object/xml_object.rs b/core/src/avm2/object/xml_object.rs index 09a7be4eef91..076cfe3e8967 100644 --- a/core/src/avm2/object/xml_object.rs +++ b/core/src/avm2/object/xml_object.rs @@ -168,28 +168,9 @@ impl<'gc> XmlObject<'gc> { activation: &mut Activation<'_, 'gc>, in_scope_ns: &[E4XNamespace<'gc>], ) -> Result, 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.node.get().namespace() { - 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) + self.node() + .get_namespace(in_scope_ns) + .as_namespace_object(activation) } pub fn matches_name(&self, multiname: &Multiname<'gc>) -> bool { From 91a7a7d597555882ae6f292ec5c97a40404f5288 Mon Sep 17 00:00:00 2001 From: Tom Schuster Date: Tue, 20 Aug 2024 12:05:02 +0200 Subject: [PATCH 2/2] tests: Mark XML test as passing --- tests/tests/swfs/from_avmplus/e4x/XML/e13_4_4_31/test.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/tests/swfs/from_avmplus/e4x/XML/e13_4_4_31/test.toml b/tests/tests/swfs/from_avmplus/e4x/XML/e13_4_4_31/test.toml index 29f3cef79022..cf6123969a1d 100644 --- a/tests/tests/swfs/from_avmplus/e4x/XML/e13_4_4_31/test.toml +++ b/tests/tests/swfs/from_avmplus/e4x/XML/e13_4_4_31/test.toml @@ -1,2 +1 @@ num_ticks = 1 -known_failure = true