avm2: Implement XML.removeNamespace

This commit is contained in:
Tom Schuster 2024-08-20 12:04:33 +02:00
parent d8b4ec0404
commit 5834f273e2
3 changed files with 110 additions and 24 deletions

View File

@ -1158,6 +1158,30 @@ impl<'gc> E4XNode<'gc> {
self.0.read().notification 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<E4XNamespace<'gc>> { pub fn in_scope_namespaces(&self) -> Vec<E4XNamespace<'gc>> {
let mut result: Vec<E4XNamespace<'gc>> = Vec::new(); let mut result: Vec<E4XNamespace<'gc>> = Vec::new();

View File

@ -339,9 +339,90 @@ pub fn set_namespace<'gc>(
pub fn remove_namespace<'gc>( pub fn remove_namespace<'gc>(
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
this: Object<'gc>, this: Object<'gc>,
_args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, 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 // 9. Return x
Ok(this.into()) Ok(this.into())

View File

@ -168,28 +168,9 @@ impl<'gc> XmlObject<'gc> {
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
in_scope_ns: &[E4XNamespace<'gc>], in_scope_ns: &[E4XNamespace<'gc>],
) -> Result<NamespaceObject<'gc>, Error<'gc>> { ) -> Result<NamespaceObject<'gc>, Error<'gc>> {
// 13.3.5.4 [[GetNamespace]] ( [ InScopeNamespaces ] ) self.node()
// 1. If q.uri is null, throw a TypeError exception .get_namespace(in_scope_ns)
// NOTE: As stated in the spec, this not really possible .as_namespace_object(activation)
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)
} }
pub fn matches_name(&self, multiname: &Multiname<'gc>) -> bool { pub fn matches_name(&self, multiname: &Multiname<'gc>) -> bool {