@@ -32,7 +32,7 @@ use crate::parser::expression::{BindingParser, find_comment_start};
3232use crate :: parser:: html:: { decode_entities_in_string, get_html_tag_definition, split_ns_name} ;
3333use crate :: schema:: {
3434 get_security_context_for, is_known_element, is_trusted_types_sink_at,
35- strips_namespaced_svg_script, strips_namespaced_svg_style,
35+ strips_namespaced_svg_script, strips_namespaced_svg_style, uses_namespaced_schema ,
3636} ;
3737use crate :: transform:: control_flow:: { parse_conditional_params, parse_defer_triggers} ;
3838use crate :: util:: ParseError ;
@@ -341,7 +341,7 @@ impl<'a> HtmlToR3Transform<'a> {
341341 Self :: resolve_element_name ( raw_name, parent_prefix)
342342 } ;
343343 let security_name = if element. is_component {
344- Self :: component_security_name ( host_tag. as_deref ( ) )
344+ Self :: component_security_name ( host_tag. as_deref ( ) , self . angular_version )
345345 } else {
346346 Self :: security_lookup_name ( & resolved_name)
347347 } ;
@@ -728,7 +728,8 @@ impl<'a> HtmlToR3Transform<'a> {
728728
729729 // `tagName === null` is not a Trusted Types sink. `full_name` is the class.
730730 let i18n_element_name = host_tag. as_deref ( ) ;
731- let security_name = Self :: component_security_name ( host_tag. as_deref ( ) ) ;
731+ let security_name =
732+ Self :: component_security_name ( host_tag. as_deref ( ) , self . angular_version ) ;
732733 let ( attributes, inputs, outputs, references, _variables, template_attr) =
733734 self . parse_attributes ( & component. attrs , & security_name, i18n_element_name, false ) ;
734735
@@ -916,15 +917,19 @@ impl<'a> HtmlToR3Transform<'a> {
916917 /// Security-schema name for a selectorless component, matching
917918 /// `calcPossibleSecurityContexts(component.tagName, ...)`.
918919 ///
919- /// A bare host tag that is not an HTML element is rewritten to its known
920- /// `:svg:`/`:math:` form (`animate` → `:svg:animate`). `tagName === null`
921- /// resolves over every known element upstream; the empty name reproduces
922- /// that through the `*|attr` fallback (`src` → `NONE`, `innerHTML` →
923- /// `HTML`).
924- fn component_security_name ( host_tag : Option < & str > ) -> String {
920+ /// On the namespaced schema a bare host tag that is not an HTML element is
921+ /// rewritten to its known `:svg:`/`:math:` form (`animate` →
922+ /// `:svg:animate`); pre-v22 versions look up the bare tag (`animate|to`).
923+ /// `tagName === null` resolves over every known element upstream; the empty
924+ /// name reproduces that through the `*|attr` fallback (`src` → `NONE`,
925+ /// `innerHTML` → `HTML`).
926+ fn component_security_name ( host_tag : Option < & str > , version : Option < AngularVersion > ) -> String {
925927 let Some ( tag) = host_tag else {
926928 return String :: new ( ) ;
927929 } ;
930+ if !uses_namespaced_schema ( version) {
931+ return tag. to_string ( ) ;
932+ }
928933 let lower = tag. to_ascii_lowercase ( ) ;
929934 let ( ns, local) = split_ns_name ( & lower) ;
930935 if ns. is_none ( ) && !is_known_element ( local) {
@@ -5373,4 +5378,41 @@ mod security_tests {
53735378 "{contexts:?}"
53745379 ) ;
53755380 }
5381+
5382+ #[ test]
5383+ fn selectorless_bare_host_stays_bare_before_the_namespaced_schema ( ) {
5384+ // Pre-v22 `calcPossibleSecurityContexts` looks the bare tag up:
5385+ // `animate|to` is `AttributeNoBinding` on 21.2.7 while `:svg:animate|to`
5386+ // is not a key.
5387+ let ( _, contexts, _) = compile_selectorless_at (
5388+ r#"<MyComp:animate [attr.to]="value"></MyComp:animate>"# ,
5389+ Some ( AngularVersion :: new ( 21 , 2 , 7 ) ) ,
5390+ ) ;
5391+ assert ! (
5392+ contexts
5393+ . iter( )
5394+ . any( |( name, ctx) | name == "to" && * ctx == SecurityContext :: AttributeNoBinding ) ,
5395+ "{contexts:?}"
5396+ ) ;
5397+
5398+ // Same for bare MathML hosts: `mi|href` is a bare URL key pre-v22.
5399+ let ( _, contexts, _) = compile_selectorless_at (
5400+ r#"<MyComp:mi [attr.href]="value"></MyComp:mi>"# ,
5401+ Some ( AngularVersion :: new ( 21 , 2 , 7 ) ) ,
5402+ ) ;
5403+ assert ! (
5404+ contexts. iter( ) . any( |( name, ctx) | name == "href" && * ctx == SecurityContext :: Url ) ,
5405+ "{contexts:?}"
5406+ ) ;
5407+
5408+ // v22 promotes the bare host to `:svg:animate` like upstream.
5409+ let ( _, contexts, _) =
5410+ compile_selectorless ( r#"<MyComp:animate [attr.to]="value"></MyComp:animate>"# ) ;
5411+ assert ! (
5412+ contexts
5413+ . iter( )
5414+ . any( |( name, ctx) | name == "to" && * ctx == SecurityContext :: AttributeNoBinding ) ,
5415+ "{contexts:?}"
5416+ ) ;
5417+ }
53765418}
0 commit comments