parent
							
								
									81e4b2a4bf
								
							
						
					
					
						commit
						d6989c80d3
					
				| @ -260,8 +260,11 @@ export function injectAttribute(attrNameToInject: string): string|undefined { | ||||
|   const attrs = tElement.attrs; | ||||
|   if (attrs) { | ||||
|     for (let i = 0; i < attrs.length; i = i + 2) { | ||||
|       const attrName = attrs[i]; | ||||
|       let attrName = attrs[i]; | ||||
|       if (attrName === AttributeMarker.SELECT_ONLY) break; | ||||
|       if (attrName === 0) {  // NS.FULL
 | ||||
|         attrName = attrs[i += 2]; | ||||
|       } | ||||
|       if (attrName == attrNameToInject) { | ||||
|         return attrs[i + 1] as string; | ||||
|       } | ||||
|  | ||||
| @ -842,15 +842,28 @@ export function createTView( | ||||
| function setUpAttributes(native: RElement, attrs: TAttributes): void { | ||||
|   const isProc = isProceduralRenderer(renderer); | ||||
|   for (let i = 0; i < attrs.length; i += 2) { | ||||
|     const attrName = attrs[i]; | ||||
|     if (attrName === AttributeMarker.SELECT_ONLY) break; | ||||
|     if (attrName !== NG_PROJECT_AS_ATTR_NAME) { | ||||
|       const attrVal = attrs[i + 1]; | ||||
|       ngDevMode && ngDevMode.rendererSetAttribute++; | ||||
|       isProc ? | ||||
|           (renderer as ProceduralRenderer3) | ||||
|               .setAttribute(native, attrName as string, attrVal as string) : | ||||
|           native.setAttribute(attrName as string, attrVal as string); | ||||
|     let attrName = attrs[i]; | ||||
|     if (attrName === 0) {  // NS.FULL
 | ||||
|       // Namespaced attribute
 | ||||
|       const attrNS = attrs[i + 1] as string; | ||||
|       attrName = attrs[i + 2] as string; | ||||
|       const attrVal = attrs[i + 3] as string; | ||||
|       i += 2; | ||||
|       if (isProc) { | ||||
|         (renderer as ProceduralRenderer3).setAttribute(native, attrName, attrVal, attrNS); | ||||
|       } else { | ||||
|         native.setAttributeNS(attrNS, attrName, attrVal); | ||||
|       } | ||||
|     } else { | ||||
|       if (attrName === AttributeMarker.SELECT_ONLY) break; | ||||
|       if (attrName !== NG_PROJECT_AS_ATTR_NAME) { | ||||
|         const attrVal = attrs[i + 1]; | ||||
|         ngDevMode && ngDevMode.rendererSetAttribute++; | ||||
|         isProc ? | ||||
|             (renderer as ProceduralRenderer3) | ||||
|                 .setAttribute(native, attrName as string, attrVal as string) : | ||||
|             native.setAttribute(attrName as string, attrVal as string); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -1493,7 +1506,8 @@ function generateInitialInputs( | ||||
| 
 | ||||
|   const attrs = tNode.attrs !; | ||||
|   for (let i = 0; i < attrs.length; i += 2) { | ||||
|     const attrName = attrs[i]; | ||||
|     const first = attrs[i]; | ||||
|     const attrName = first === 0 ? attrs[i += 2] : first;  // 0 = NS.FULL
 | ||||
|     const minifiedInputName = inputs[attrName]; | ||||
|     const attrValue = attrs[i + 1]; | ||||
| 
 | ||||
| @ -1906,7 +1920,7 @@ function appendToProjectionNode( | ||||
|  *        - 1 based index of the selector from the {@link projectionDef} | ||||
|  */ | ||||
| export function projection( | ||||
|     nodeIndex: number, localIndex: number, selectorIndex: number = 0, attrs?: string[]): void { | ||||
|     nodeIndex: number, localIndex: number, selectorIndex: number = 0, attrs?: TAttributes): void { | ||||
|   const node = createLNode( | ||||
|       nodeIndex, TNodeType.Projection, null, null, attrs || null, {head: null, tail: null}); | ||||
| 
 | ||||
|  | ||||
| @ -5,7 +5,6 @@ | ||||
|  * Use of this source code is governed by an MIT-style license that can be | ||||
|  * found in the LICENSE file at https://angular.io/license
 | ||||
|  */ | ||||
| 
 | ||||
| import {LContainer} from './container'; | ||||
| import {LInjector} from './injector'; | ||||
| import {LProjection} from './projection'; | ||||
| @ -14,6 +13,16 @@ import {RElement, RNode, RText} from './renderer'; | ||||
| import {LView, TData, TView} from './view'; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Namespace attribute flags. | ||||
|  */ | ||||
| export const enum NS { | ||||
|   /** | ||||
|    * Use the next value as the full namespaces URI, the values after that | ||||
|    * are then the name and the value, respectively. | ||||
|    */ | ||||
|   FULL = 0, | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * TNodeType corresponds to the TNode.type property. It contains information | ||||
| @ -179,7 +188,7 @@ export const enum AttributeMarker { | ||||
|  * - attribute names and values | ||||
|  * - special markers acting as flags to alter attributes processing. | ||||
|  */ | ||||
| export type TAttributes = (string | AttributeMarker)[]; | ||||
| export type TAttributes = (string | AttributeMarker | NS)[]; | ||||
| 
 | ||||
| /** | ||||
|  * LNode binding data (flyweight) for a particular node that is shared between all templates | ||||
|  | ||||
| @ -106,8 +106,12 @@ function findAttrIndexInNode(name: string, attrs: TAttributes | null): number { | ||||
|   if (attrs === null) return -1; | ||||
|   for (let i = 0; i < attrs.length; i += step) { | ||||
|     const attrName = attrs[i]; | ||||
|     if (attrName === name) return i; | ||||
|     if (attrName === AttributeMarker.SELECT_ONLY) { | ||||
|     if (attrName === 0) { | ||||
|       // NS.FULL
 | ||||
|       step = 2; | ||||
|     } else if (attrName === name) { | ||||
|       return i; | ||||
|     } else if (attrName === AttributeMarker.SELECT_ONLY) { | ||||
|       step = 1; | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @ -11,7 +11,7 @@ import {NgForOfContext} from '@angular/common'; | ||||
| import {RenderFlags, directiveInject} from '../../src/render3'; | ||||
| import {defineComponent} from '../../src/render3/definition'; | ||||
| import {bind, container, elementAttribute, elementClass, elementEnd, elementProperty, elementStart, elementStyle, elementStyleNamed, interpolation1, renderTemplate, setHtmlNS, setSvgNS, text, textBinding} from '../../src/render3/instructions'; | ||||
| import {LElementNode, LNode} from '../../src/render3/interfaces/node'; | ||||
| import {LElementNode, LNode, NS} from '../../src/render3/interfaces/node'; | ||||
| import {RElement, domRendererFactory3} from '../../src/render3/interfaces/renderer'; | ||||
| import {TrustedString, bypassSanitizationTrustHtml, bypassSanitizationTrustResourceUrl, bypassSanitizationTrustScript, bypassSanitizationTrustStyle, bypassSanitizationTrustUrl, sanitizeHtml, sanitizeResourceUrl, sanitizeScript, sanitizeStyle, sanitizeUrl} from '../../src/sanitization/sanitization'; | ||||
| import {Sanitizer, SecurityContext} from '../../src/sanitization/security'; | ||||
| @ -91,6 +91,43 @@ describe('instructions', () => { | ||||
|         rendererSetAttribute: 2 | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     it('should use sanitizer function even on elements with namespaced attributes', () => { | ||||
|       const t = new TemplateFixture(() => { | ||||
|         elementStart(0, 'div', [ | ||||
|           NS.FULL, | ||||
|           'http://www.example.com/2004/test', | ||||
|           'whatever', | ||||
|           'abc', | ||||
|         ]); | ||||
|         elementEnd(); | ||||
|       }); | ||||
| 
 | ||||
|       t.update(() => elementAttribute(0, 'title', 'javascript:true', sanitizeUrl)); | ||||
| 
 | ||||
| 
 | ||||
|       let standardHTML = '<div whatever="abc" title="unsafe:javascript:true"></div>'; | ||||
|       let ieHTML = '<div title="unsafe:javascript:true" whatever="abc"></div>'; | ||||
| 
 | ||||
|       expect([standardHTML, ieHTML]).toContain(t.html); | ||||
| 
 | ||||
|       t.update( | ||||
|           () => elementAttribute( | ||||
|               0, 'title', bypassSanitizationTrustUrl('javascript:true'), sanitizeUrl)); | ||||
| 
 | ||||
|       standardHTML = '<div whatever="abc" title="javascript:true"></div>'; | ||||
|       ieHTML = '<div title="javascript:true" whatever="abc"></div>'; | ||||
| 
 | ||||
|       expect([standardHTML, ieHTML]).toContain(t.html); | ||||
| 
 | ||||
|       expect(ngDevMode).toHaveProperties({ | ||||
|         firstTemplatePass: 1, | ||||
|         tNode: 2, | ||||
|         tView: 1, | ||||
|         rendererCreateElement: 1, | ||||
|         rendererSetAttribute: 2 | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   describe('elementProperty', () => { | ||||
| @ -408,6 +445,11 @@ describe('instructions', () => { | ||||
|           // height="300"
 | ||||
|           'height', | ||||
|           '300', | ||||
|           // test:title="abc"
 | ||||
|           NS.FULL, | ||||
|           'http://www.example.com/2014/test', | ||||
|           'title', | ||||
|           'abc', | ||||
|         ]); | ||||
|         elementStart(2, 'circle', ['cx', '200', 'cy', '150', 'fill', '#0000ff']); | ||||
|         elementEnd(); | ||||
| @ -419,12 +461,71 @@ describe('instructions', () => { | ||||
| 
 | ||||
|       // Most browsers will print <circle></circle>, some will print <circle />, both are valid
 | ||||
|       const standardHTML = | ||||
|           '<div id="container"><svg id="display" width="400" height="300"><circle cx="200" cy="150" fill="#0000ff"></circle></svg></div>'; | ||||
|           '<div id="container"><svg id="display" width="400" height="300" title="abc"><circle cx="200" cy="150" fill="#0000ff"></circle></svg></div>'; | ||||
|       const ie11HTML = | ||||
|           '<div id="container"><svg xmlns="http://www.w3.org/2000/svg" id="display" width="400" height="300"><circle fill="#0000ff" cx="200" cy="150" /></svg></div>'; | ||||
|           '<div id="container"><svg xmlns="http://www.w3.org/2000/svg" xmlns:NS1="http://www.example.com/2014/test" NS1:title="abc" id="display" width="400" height="300"><circle fill="#0000ff" cx="200" cy="150" /></svg></div>'; | ||||
| 
 | ||||
|       expect([standardHTML, ie11HTML]).toContain(t.html); | ||||
|     }); | ||||
| 
 | ||||
|     it('should set an attribute with a namespace', () => { | ||||
|       const t = new TemplateFixture(() => { | ||||
|         elementStart(0, 'div', [ | ||||
|           'id', | ||||
|           'container', | ||||
|           // test:title="abc"
 | ||||
|           NS.FULL, | ||||
|           'http://www.example.com/2014/test', | ||||
|           'title', | ||||
|           'abc', | ||||
|         ]); | ||||
|         elementEnd(); | ||||
|       }); | ||||
| 
 | ||||
|       const standardHTML = '<div id="container" title="abc"></div>'; | ||||
|       const ie11HTML = | ||||
|           '<div id="container" xmlns:NS1="https://www.example.com/2014/test" NS1:title="abc"></div>'; | ||||
|       expect([standardHTML, ie11HTML]).toContain(t.html); | ||||
|     }); | ||||
| 
 | ||||
|     it('should set attributes including more than one namespaced attribute', () => { | ||||
|       const t = new TemplateFixture(() => { | ||||
|         elementStart(0, 'div', [ | ||||
|           'id', | ||||
|           'container', | ||||
| 
 | ||||
|           // NS1:title="abc"
 | ||||
|           NS.FULL, | ||||
|           'http://www.example.com/2014/test', | ||||
|           'title', | ||||
|           'abc', | ||||
| 
 | ||||
|           // style="background: #dead11"
 | ||||
|           'style', | ||||
|           'background: #dead11', | ||||
| 
 | ||||
|           // NS1:whatever="wee"
 | ||||
|           NS.FULL, | ||||
|           'http://www.example.com/2014/test', | ||||
|           'whatever', | ||||
|           'wee', | ||||
| 
 | ||||
|           // NS2:shazbot="wocka wocka"
 | ||||
|           NS.FULL, | ||||
|           'http://www.whatever.com/2016/blah', | ||||
|           'shazbot', | ||||
|           'wocka wocka', | ||||
|         ]); | ||||
|         elementEnd(); | ||||
|       }); | ||||
| 
 | ||||
|       const standardHTML = | ||||
|           '<div id="container" title="abc" style="background: #dead11" whatever="wee" shazbot="wocka wocka"></div>'; | ||||
|       const ieHTML = | ||||
|           '<div id="container" style="background: rgb(222, 173, 17);" title="abc" whatever="wee" shazbot="wocka wocka"></div>'; | ||||
| 
 | ||||
|       expect([standardHTML, ieHTML]).toContain(t.html); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
|  | ||||
| @ -28,23 +28,14 @@ import {NAMESPACE_URIS} from '../../src/dom/dom_renderer'; | ||||
| 
 | ||||
|     describe('setAttribute', () => { | ||||
|       describe('with namespace', () => { | ||||
|         it('xmlns', () => shouldSetAttributeWithNs('xmlns')); | ||||
|         it('xml', () => shouldSetAttributeWithNs('xml')); | ||||
|         it('svg', () => shouldSetAttributeWithNs('svg')); | ||||
|         it('xhtml', () => shouldSetAttributeWithNs('xhtml')); | ||||
|         it('xlink', () => shouldSetAttributeWithNs('xlink')); | ||||
| 
 | ||||
|         it('unknown', () => { | ||||
|           const div = document.createElement('div'); | ||||
|           expect(div.hasAttribute('unknown:name')).toBe(false); | ||||
| 
 | ||||
|           renderer.setAttribute(div, 'name', 'value', 'unknown'); | ||||
| 
 | ||||
|           expect(div.getAttribute('unknown:name')).toBe('value'); | ||||
|         }); | ||||
|         it('custom', () => shouldSetAttributeWithNs('custom')); | ||||
| 
 | ||||
|         function shouldSetAttributeWithNs(namespace: string): void { | ||||
|           const namespaceUri = NAMESPACE_URIS[namespace]; | ||||
|           const namespaceUri = NAMESPACE_URIS[namespace] || namespace; | ||||
|           const div = document.createElement('div'); | ||||
|           expect(div.hasAttributeNS(namespaceUri, 'name')).toBe(false); | ||||
| 
 | ||||
| @ -57,26 +48,16 @@ import {NAMESPACE_URIS} from '../../src/dom/dom_renderer'; | ||||
| 
 | ||||
|     describe('removeAttribute', () => { | ||||
|       describe('with namespace', () => { | ||||
|         it('xmlns', () => shouldRemoveAttributeWithNs('xmlns')); | ||||
|         it('xml', () => shouldRemoveAttributeWithNs('xml')); | ||||
|         it('svg', () => shouldRemoveAttributeWithNs('svg')); | ||||
|         it('xhtml', () => shouldRemoveAttributeWithNs('xhtml')); | ||||
|         it('xlink', () => shouldRemoveAttributeWithNs('xlink')); | ||||
| 
 | ||||
|         it('unknown', () => { | ||||
|           const div = document.createElement('div'); | ||||
|           div.setAttribute('unknown:name', 'value'); | ||||
|           expect(div.hasAttribute('unknown:name')).toBe(true); | ||||
| 
 | ||||
|           renderer.removeAttribute(div, 'name', 'unknown'); | ||||
| 
 | ||||
|           expect(div.hasAttribute('unknown:name')).toBe(false); | ||||
|         }); | ||||
|         it('custom', () => shouldRemoveAttributeWithNs('custom')); | ||||
| 
 | ||||
|         function shouldRemoveAttributeWithNs(namespace: string): void { | ||||
|           const namespaceUri = NAMESPACE_URIS[namespace]; | ||||
|           const namespaceUri = NAMESPACE_URIS[namespace] || namespace; | ||||
|           const div = document.createElement('div'); | ||||
|           div.setAttributeNS(namespaceUri, `${namespace}:name`, 'value'); | ||||
|           div.setAttributeNS(namespaceUri, 'name', 'value'); | ||||
|           expect(div.hasAttributeNS(namespaceUri, 'name')).toBe(true); | ||||
| 
 | ||||
|           renderer.removeAttribute(div, 'name', namespace); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user