@@ -1586,6 +1586,91 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
1586
1586
return cssClassDirectivesEnabledConfig ;
1587
1587
} ;
1588
1588
1589
+
1590
+ /**
1591
+ * The security context of DOM Properties.
1592
+ * @private
1593
+ */
1594
+ var PROP_CONTEXTS = createMap ( ) ;
1595
+
1596
+ /**
1597
+ * @ngdoc method
1598
+ * @name $compileProvider#addPropertySecurityContext
1599
+ * @description
1600
+ *
1601
+ * Defines the security context for DOM properties bound by ng-prop-*.
1602
+ *
1603
+ * @param {string } elementName The element name or '*' to match any element.
1604
+ * @param {string } propertyName The DOM property name.
1605
+ * @param {string } ctx The {@link $sce} security context in which this value is safe for use, e.g. `$sce.URL`
1606
+ * @returns {object } `this` for chaining
1607
+ */
1608
+ this . addPropertySecurityContext = function ( elementName , propertyName , ctx ) {
1609
+ var key = ( elementName . toLowerCase ( ) + '|' + propertyName . toLowerCase ( ) ) ;
1610
+
1611
+ if ( key in PROP_CONTEXTS && PROP_CONTEXTS [ key ] !== ctx ) {
1612
+ throw $compileMinErr ( 'ctxoverride' , 'Property context \'{0}.{1}\' already set to \'{2}\', cannot override to \'{3}\'.' , elementName , propertyName , PROP_CONTEXTS [ key ] , ctx ) ;
1613
+ }
1614
+
1615
+ PROP_CONTEXTS [ key ] = ctx ;
1616
+ return this ;
1617
+ } ;
1618
+
1619
+ /* Default property contexts.
1620
+ *
1621
+ * Copy of https://github.com/angular/angular/blob/6.0.6/packages/compiler/src/schema/dom_security_schema.ts#L31-L58
1622
+ * Changing:
1623
+ * - SecurityContext.* => SCE_CONTEXTS/$sce.*
1624
+ * - STYLE => CSS
1625
+ * - various URL => MEDIA_URL
1626
+ * - *|formAction, form|action URL => RESOURCE_URL (like the attribute)
1627
+ */
1628
+ ( function registerNativePropertyContexts ( ) {
1629
+ function registerContext ( ctx , values ) {
1630
+ forEach ( values , function ( v ) { PROP_CONTEXTS [ v . toLowerCase ( ) ] = ctx ; } ) ;
1631
+ }
1632
+
1633
+ registerContext ( SCE_CONTEXTS . HTML , [
1634
+ 'iframe|srcdoc' ,
1635
+ '*|innerHTML' ,
1636
+ '*|outerHTML'
1637
+ ] ) ;
1638
+ registerContext ( SCE_CONTEXTS . CSS , [ '*|style' ] ) ;
1639
+ registerContext ( SCE_CONTEXTS . URL , [
1640
+ 'area|href' , 'area|ping' ,
1641
+ 'a|href' , 'a|ping' ,
1642
+ 'blockquote|cite' ,
1643
+ 'body|background' ,
1644
+ 'del|cite' ,
1645
+ 'input|src' ,
1646
+ 'ins|cite' ,
1647
+ 'q|cite'
1648
+ ] ) ;
1649
+ registerContext ( SCE_CONTEXTS . MEDIA_URL , [
1650
+ 'audio|src' ,
1651
+ 'img|src' , 'img|srcset' ,
1652
+ 'source|src' , 'source|srcset' ,
1653
+ 'track|src' ,
1654
+ 'video|src' , 'video|poster'
1655
+ ] ) ;
1656
+ registerContext ( SCE_CONTEXTS . RESOURCE_URL , [
1657
+ '*|formAction' ,
1658
+ 'applet|code' , 'applet|codebase' ,
1659
+ 'base|href' ,
1660
+ 'embed|src' ,
1661
+ 'frame|src' ,
1662
+ 'form|action' ,
1663
+ 'head|profile' ,
1664
+ 'html|manifest' ,
1665
+ 'iframe|src' ,
1666
+ 'link|href' ,
1667
+ 'media|src' ,
1668
+ 'object|codebase' , 'object|data' ,
1669
+ 'script|src'
1670
+ ] ) ;
1671
+ } ) ( ) ;
1672
+
1673
+
1589
1674
this . $get = [
1590
1675
'$injector' , '$interpolate' , '$exceptionHandler' , '$templateRequest' , '$parse' ,
1591
1676
'$controller' , '$rootScope' , '$sce' , '$animate' ,
@@ -1631,12 +1716,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
1631
1716
}
1632
1717
1633
1718
1634
- function sanitizeSrcset ( value ) {
1719
+ function sanitizeSrcset ( value , invokeType ) {
1635
1720
if ( ! value ) {
1636
1721
return value ;
1637
1722
}
1638
1723
if ( ! isString ( value ) ) {
1639
- throw $compileMinErr ( 'srcset' , 'Can\'t pass trusted values to `$set(\'srcset\', value) `: "{0 }"' , value . toString ( ) ) ;
1724
+ throw $compileMinErr ( 'srcset' , 'Can\'t pass trusted values to `{0} `: "{1 }"' , invokeType , value . toString ( ) ) ;
1640
1725
}
1641
1726
1642
1727
// Such values are a bit too complex to handle automatically inside $sce.
@@ -1916,7 +2001,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
1916
2001
: function denormalizeTemplate ( template ) {
1917
2002
return template . replace ( / \{ \{ / g, startSymbol ) . replace ( / } } / g, endSymbol ) ;
1918
2003
} ,
1919
- NG_ATTR_BINDING = / ^ n g A t t r [ A - Z ] / ;
2004
+ NG_PREFIX_BINDING = / ^ n g ( A t t r | P r o p | O n ) ( [ A - Z ] . * ) $ / ;
1920
2005
var MULTI_ELEMENT_DIR_RE = / ^ ( .+ ) S t a r t $ / ;
1921
2006
1922
2007
compile . $$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo ( $element , binding ) {
@@ -2252,43 +2337,66 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
2252
2337
directiveNormalize ( nodeName ) , 'E' , maxPriority , ignoreDirective ) ;
2253
2338
2254
2339
// iterate over the attributes
2255
- for ( var attr , name , nName , ngAttrName , value , isNgAttr , nAttrs = node . attributes ,
2340
+ for ( var attr , name , nName , value , ngPrefixMatch , nAttrs = node . attributes ,
2256
2341
j = 0 , jj = nAttrs && nAttrs . length ; j < jj ; j ++ ) {
2257
2342
var attrStartName = false ;
2258
2343
var attrEndName = false ;
2259
2344
2345
+ var isNgAttr = false , isNgProp = false , isNgEvent = false ;
2346
+ var multiElementMatch ;
2347
+
2260
2348
attr = nAttrs [ j ] ;
2261
2349
name = attr . name ;
2262
2350
value = attr . value ;
2263
2351
2264
- // support ngAttr attribute binding
2265
- ngAttrName = directiveNormalize ( name ) ;
2266
- isNgAttr = NG_ATTR_BINDING . test ( ngAttrName ) ;
2267
- if ( isNgAttr ) {
2352
+ nName = directiveNormalize ( name . toLowerCase ( ) ) ;
2353
+
2354
+ // Support ng-attr-*, ng-prop-* and ng-on-*
2355
+ if ( ( ngPrefixMatch = nName . match ( NG_PREFIX_BINDING ) ) ) {
2356
+ isNgAttr = ngPrefixMatch [ 1 ] === 'Attr' ;
2357
+ isNgProp = ngPrefixMatch [ 1 ] === 'Prop' ;
2358
+ isNgEvent = ngPrefixMatch [ 1 ] === 'On' ;
2359
+
2360
+ // Normalize the non-prefixed name
2268
2361
name = name . replace ( PREFIX_REGEXP , '' )
2269
- . substr ( 8 ) . replace ( / _ ( .) / g, function ( match , letter ) {
2362
+ . toLowerCase ( )
2363
+ . substr ( 4 + ngPrefixMatch [ 1 ] . length ) . replace ( / _ ( .) / g, function ( match , letter ) {
2270
2364
return letter . toUpperCase ( ) ;
2271
2365
} ) ;
2272
- }
2273
2366
2274
- var multiElementMatch = ngAttrName . match ( MULTI_ELEMENT_DIR_RE ) ;
2275
- if ( multiElementMatch && directiveIsMultiElement ( multiElementMatch [ 1 ] ) ) {
2367
+ // Support *-start / *-end multi element directives
2368
+ } else if ( ( multiElementMatch = nName . match ( MULTI_ELEMENT_DIR_RE ) ) && directiveIsMultiElement ( multiElementMatch [ 1 ] ) ) {
2276
2369
attrStartName = name ;
2277
2370
attrEndName = name . substr ( 0 , name . length - 5 ) + 'end' ;
2278
2371
name = name . substr ( 0 , name . length - 6 ) ;
2279
2372
}
2280
2373
2281
- nName = directiveNormalize ( name . toLowerCase ( ) ) ;
2282
- attrsMap [ nName ] = name ;
2283
- if ( isNgAttr || ! attrs . hasOwnProperty ( nName ) ) {
2374
+ if ( isNgProp || isNgEvent ) {
2375
+ attrs [ nName ] = value ;
2376
+ attrsMap [ nName ] = attr . name ;
2377
+
2378
+ if ( isNgProp ) {
2379
+ addPropertyDirective ( node , directives , nName , name ) ;
2380
+ } else {
2381
+ addEventDirective ( directives , nName , name ) ;
2382
+ }
2383
+ } else {
2384
+ // Update nName for cases where a prefix was removed
2385
+ // NOTE: the .toLowerCase() is unnecessary and causes https://github.com/angular/angular.js/issues/16624 for ng-attr-*
2386
+ nName = directiveNormalize ( name . toLowerCase ( ) ) ;
2387
+ attrsMap [ nName ] = name ;
2388
+
2389
+ if ( isNgAttr || ! attrs . hasOwnProperty ( nName ) ) {
2284
2390
attrs [ nName ] = value ;
2285
2391
if ( getBooleanAttrName ( node , nName ) ) {
2286
2392
attrs [ nName ] = true ; // presence means true
2287
2393
}
2394
+ }
2395
+
2396
+ addAttrInterpolateDirective ( node , directives , value , nName , isNgAttr ) ;
2397
+ addDirective ( directives , nName , 'A' , maxPriority , ignoreDirective , attrStartName ,
2398
+ attrEndName ) ;
2288
2399
}
2289
- addAttrInterpolateDirective ( node , directives , value , nName , isNgAttr ) ;
2290
- addDirective ( directives , nName , 'A' , maxPriority , ignoreDirective , attrStartName ,
2291
- attrEndName ) ;
2292
2400
}
2293
2401
2294
2402
if ( nodeName === 'input' && node . getAttribute ( 'type' ) === 'hidden' ) {
@@ -3325,42 +3433,95 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
3325
3433
}
3326
3434
3327
3435
3328
- function getTrustedContext ( node , attrNormalizedName ) {
3436
+ function getTrustedAttrContext ( nodeName , attrNormalizedName ) {
3329
3437
if ( attrNormalizedName === 'srcdoc' ) {
3330
3438
return $sce . HTML ;
3331
3439
}
3332
- var tag = nodeName_ ( node ) ;
3333
- // All tags with src attributes require a RESOURCE_URL value, except for
3334
- // img and various html5 media tags, which require the MEDIA_URL context.
3440
+ // All nodes with src attributes require a RESOURCE_URL value, except for
3441
+ // img and various html5 media nodes, which require the MEDIA_URL context.
3335
3442
if ( attrNormalizedName === 'src' || attrNormalizedName === 'ngSrc' ) {
3336
- if ( [ 'img' , 'video' , 'audio' , 'source' , 'track' ] . indexOf ( tag ) === - 1 ) {
3443
+ if ( [ 'img' , 'video' , 'audio' , 'source' , 'track' ] . indexOf ( nodeName ) === - 1 ) {
3337
3444
return $sce . RESOURCE_URL ;
3338
3445
}
3339
3446
return $sce . MEDIA_URL ;
3340
3447
} else if ( attrNormalizedName === 'xlinkHref' ) {
3341
3448
// Some xlink:href are okay, most aren't
3342
- if ( tag === 'image' ) return $sce . MEDIA_URL ;
3343
- if ( tag === 'a' ) return $sce . URL ;
3449
+ if ( nodeName === 'image' ) return $sce . MEDIA_URL ;
3450
+ if ( nodeName === 'a' ) return $sce . URL ;
3344
3451
return $sce . RESOURCE_URL ;
3345
3452
} else if (
3346
3453
// Formaction
3347
- ( tag === 'form' && attrNormalizedName === 'action' ) ||
3454
+ ( nodeName === 'form' && attrNormalizedName === 'action' ) ||
3348
3455
// If relative URLs can go where they are not expected to, then
3349
3456
// all sorts of trust issues can arise.
3350
- ( tag === 'base' && attrNormalizedName === 'href' ) ||
3457
+ ( nodeName === 'base' && attrNormalizedName === 'href' ) ||
3351
3458
// links can be stylesheets or imports, which can run script in the current origin
3352
- ( tag === 'link' && attrNormalizedName === 'href' )
3459
+ ( nodeName === 'link' && attrNormalizedName === 'href' )
3353
3460
) {
3354
3461
return $sce . RESOURCE_URL ;
3355
- } else if ( tag === 'a' && ( attrNormalizedName === 'href' ||
3462
+ } else if ( nodeName === 'a' && ( attrNormalizedName === 'href' ||
3356
3463
attrNormalizedName === 'ngHref' ) ) {
3357
3464
return $sce . URL ;
3358
3465
}
3359
3466
}
3360
3467
3468
+ function getTrustedPropContext ( nodeName , propNormalizedName ) {
3469
+ var prop = propNormalizedName . toLowerCase ( ) ;
3470
+ return PROP_CONTEXTS [ nodeName + '|' + prop ] || PROP_CONTEXTS [ '*|' + prop ] ;
3471
+ }
3472
+
3473
+ function sanitizeSrcsetPropertyValue ( value ) {
3474
+ return sanitizeSrcset ( $sce . valueOf ( value ) , 'ng-prop-srcset' ) ;
3475
+ }
3476
+ function addPropertyDirective ( node , directives , attrName , propName ) {
3477
+ if ( EVENT_HANDLER_ATTR_REGEXP . test ( propName ) ) {
3478
+ throw $compileMinErr ( 'nodomevents' , 'Property bindings for HTML DOM event properties are disallowed' ) ;
3479
+ }
3480
+
3481
+ var nodeName = nodeName_ ( node ) ;
3482
+ var trustedContext = getTrustedPropContext ( nodeName , propName ) ;
3483
+
3484
+ var sanitizer = identity ;
3485
+ // Sanitize img[srcset] + source[srcset] values.
3486
+ if ( propName === 'srcset' && ( nodeName === 'img' || nodeName === 'source' ) ) {
3487
+ sanitizer = sanitizeSrcsetPropertyValue ;
3488
+ } else if ( trustedContext ) {
3489
+ sanitizer = $sce . getTrusted . bind ( $sce , trustedContext ) ;
3490
+ }
3491
+
3492
+ directives . push ( {
3493
+ priority : 100 ,
3494
+ compile : function ngPropCompileFn ( _ , attr ) {
3495
+ var ngPropGetter = $parse ( attr [ attrName ] ) ;
3496
+ var ngPropWatch = $parse ( attr [ attrName ] , function sceValueOf ( val ) {
3497
+ // Unwrap the value to compare the actual inner safe value, not the wrapper object.
3498
+ return $sce . valueOf ( val ) ;
3499
+ } ) ;
3500
+
3501
+ return {
3502
+ pre : function ngPropPreLinkFn ( scope , $element ) {
3503
+ function applyPropValue ( ) {
3504
+ var propValue = ngPropGetter ( scope ) ;
3505
+ $element . prop ( propName , sanitizer ( propValue ) ) ;
3506
+ }
3507
+
3508
+ applyPropValue ( ) ;
3509
+ scope . $watch ( ngPropWatch , applyPropValue ) ;
3510
+ }
3511
+ } ;
3512
+ }
3513
+ } ) ;
3514
+ }
3515
+
3516
+ function addEventDirective ( directives , attrName , eventName ) {
3517
+ directives . push (
3518
+ createEventDirective ( $parse , $rootScope , $exceptionHandler , attrName , eventName , /*forceAsync=*/ false )
3519
+ ) ;
3520
+ }
3361
3521
3362
3522
function addAttrInterpolateDirective ( node , directives , value , name , isNgAttr ) {
3363
- var trustedContext = getTrustedContext ( node , name ) ;
3523
+ var nodeName = nodeName_ ( node ) ;
3524
+ var trustedContext = getTrustedAttrContext ( nodeName , name ) ;
3364
3525
var mustHaveExpression = ! isNgAttr ;
3365
3526
var allOrNothing = ALL_OR_NOTHING_ATTRS [ name ] || isNgAttr ;
3366
3527
@@ -3369,16 +3530,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
3369
3530
// no interpolation found -> ignore
3370
3531
if ( ! interpolateFn ) return ;
3371
3532
3372
- if ( name === 'multiple' && nodeName_ ( node ) === 'select' ) {
3533
+ if ( name === 'multiple' && nodeName === 'select' ) {
3373
3534
throw $compileMinErr ( 'selmulti' ,
3374
3535
'Binding to the \'multiple\' attribute is not supported. Element: {0}' ,
3375
3536
startingTag ( node ) ) ;
3376
3537
}
3377
3538
3378
3539
if ( EVENT_HANDLER_ATTR_REGEXP . test ( name ) ) {
3379
- throw $compileMinErr ( 'nodomevents' ,
3380
- 'Interpolations for HTML DOM event attributes are disallowed. Please use the ' +
3381
- 'ng- versions (such as ng-click instead of onclick) instead.' ) ;
3540
+ throw $compileMinErr ( 'nodomevents' , 'Interpolations for HTML DOM event attributes are disallowed' ) ;
3382
3541
}
3383
3542
3384
3543
directives . push ( {
0 commit comments