2011-07-17 08:05:43 +00:00
|
|
|
'use strict';
|
|
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
|
|
|
|
|
*
|
|
|
|
|
* DOM-related variables:
|
|
|
|
|
*
|
|
|
|
|
* - "node" - DOM Node
|
|
|
|
|
* - "element" - DOM Element or Node
|
|
|
|
|
* - "$node" or "$element" - jqLite-wrapped node or element
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* Compiler related stuff:
|
|
|
|
|
*
|
|
|
|
|
* - "linkFn" - linking fn of a single directive
|
|
|
|
|
* - "nodeLinkFn" - function that aggregates all linking fns for a particular node
|
|
|
|
|
* - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node
|
|
|
|
|
* - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList)
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
/**
|
|
|
|
|
* @ngdoc function
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.$compile
|
2011-11-29 20:11:32 +00:00
|
|
|
* @function
|
|
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* Compiles a piece of HTML string or DOM into a template and produces a template function, which
|
2012-06-12 06:49:24 +00:00
|
|
|
* can then be used to link {@link ng.$rootScope.Scope scope} and the template together.
|
2011-11-29 20:11:32 +00:00
|
|
|
*
|
|
|
|
|
* The compilation is a process of walking the DOM tree and trying to match DOM elements to
|
2012-08-12 17:36:09 +00:00
|
|
|
* {@link ng.$compileProvider#directive directives}. For each match it
|
2011-11-29 20:11:32 +00:00
|
|
|
* executes corresponding template function and collects the
|
|
|
|
|
* instance functions into a single template function which is then returned.
|
|
|
|
|
*
|
|
|
|
|
* The template function can then be used once to produce the view or as it is the case with
|
2012-06-12 06:49:24 +00:00
|
|
|
* {@link ng.directive:ngRepeat repeater} many-times, in which
|
2011-11-29 20:11:32 +00:00
|
|
|
* case each call results in a view that is a DOM clone of the original template.
|
|
|
|
|
*
|
|
|
|
|
<doc:example module="compile">
|
|
|
|
|
<doc:source>
|
|
|
|
|
<script>
|
|
|
|
|
// declare a new module, and inject the $compileProvider
|
|
|
|
|
angular.module('compile', [], function($compileProvider) {
|
|
|
|
|
// configure new 'compile' directive by passing a directive
|
|
|
|
|
// factory function. The factory function injects the '$compile'
|
|
|
|
|
$compileProvider.directive('compile', function($compile) {
|
|
|
|
|
// directive factory creates a link function
|
|
|
|
|
return function(scope, element, attrs) {
|
|
|
|
|
scope.$watch(
|
|
|
|
|
function(scope) {
|
|
|
|
|
// watch the 'compile' expression for changes
|
|
|
|
|
return scope.$eval(attrs.compile);
|
|
|
|
|
},
|
2011-11-23 05:28:39 +00:00
|
|
|
function(value) {
|
2011-11-29 20:11:32 +00:00
|
|
|
// when the 'compile' expression changes
|
|
|
|
|
// assign it into the current DOM
|
|
|
|
|
element.html(value);
|
2010-03-19 23:41:02 +00:00
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
// compile the new DOM and link it to the current
|
|
|
|
|
// scope.
|
|
|
|
|
// NOTE: we only compile .childNodes so that
|
|
|
|
|
// we don't get into infinite loop compiling ourselves
|
|
|
|
|
$compile(element.contents())(scope);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
|
2011-12-14 01:55:31 +00:00
|
|
|
function Ctrl($scope) {
|
|
|
|
|
$scope.name = 'Angular';
|
|
|
|
|
$scope.html = 'Hello {{name}}';
|
2011-10-26 05:21:21 +00:00
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
</script>
|
|
|
|
|
<div ng-controller="Ctrl">
|
2012-03-09 08:00:05 +00:00
|
|
|
<input ng-model="name"> <br>
|
|
|
|
|
<textarea ng-model="html"></textarea> <br>
|
2011-11-29 20:11:32 +00:00
|
|
|
<div compile="html"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</doc:source>
|
|
|
|
|
<doc:scenario>
|
|
|
|
|
it('should auto compile', function() {
|
|
|
|
|
expect(element('div[compile]').text()).toBe('Hello Angular');
|
|
|
|
|
input('html').enter('{{name}}!');
|
|
|
|
|
expect(element('div[compile]').text()).toBe('Angular!');
|
|
|
|
|
});
|
|
|
|
|
</doc:scenario>
|
|
|
|
|
</doc:example>
|
2010-03-20 05:18:39 +00:00
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* @param {string|DOMElement} element Element or HTML string to compile into a template function.
|
2012-01-28 00:18:16 +00:00
|
|
|
* @param {function(angular.Scope[, cloneAttachFn]} transclude function available to directives.
|
|
|
|
|
* @param {number} maxPriority only apply directives lower then given priority (Only effects the
|
|
|
|
|
* root element(s), not their children)
|
2011-11-29 20:11:32 +00:00
|
|
|
* @returns {function(scope[, cloneAttachFn])} a link function which is used to bind template
|
|
|
|
|
* (a DOM element/tree) to a scope. Where:
|
|
|
|
|
*
|
2012-06-12 06:49:24 +00:00
|
|
|
* * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
|
2011-11-29 20:11:32 +00:00
|
|
|
* * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
|
|
|
|
|
* `template` and call the `cloneAttachFn` function allowing the caller to attach the
|
|
|
|
|
* cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
|
|
|
|
|
* called as: <br> `cloneAttachFn(clonedElement, scope)` where:
|
|
|
|
|
*
|
|
|
|
|
* * `clonedElement` - is a clone of the original `element` passed into the compiler.
|
|
|
|
|
* * `scope` - is the current scope with which the linking function is working with.
|
|
|
|
|
*
|
|
|
|
|
* Calling the linking function returns the element of the template. It is either the original element
|
|
|
|
|
* passed in, or the clone of the element if the `cloneAttachFn` is provided.
|
|
|
|
|
*
|
2012-04-17 21:35:00 +00:00
|
|
|
* After linking the view is not updated until after a call to $digest which typically is done by
|
2011-11-29 20:11:32 +00:00
|
|
|
* Angular automatically.
|
|
|
|
|
*
|
|
|
|
|
* If you need access to the bound view, there are two ways to do it:
|
|
|
|
|
*
|
|
|
|
|
* - If you are not asking the linking function to clone the template, create the DOM element(s)
|
|
|
|
|
* before you send them to the compiler and keep this reference around.
|
|
|
|
|
* <pre>
|
|
|
|
|
* var element = $compile('<p>{{total}}</p>')(scope);
|
|
|
|
|
* </pre>
|
|
|
|
|
*
|
|
|
|
|
* - if on the other hand, you need the element to be cloned, the view reference from the original
|
|
|
|
|
* example would not point to the clone, but rather to the original template that was cloned. In
|
|
|
|
|
* this case, you can access the clone via the cloneAttachFn:
|
|
|
|
|
* <pre>
|
|
|
|
|
* var templateHTML = angular.element('<p>{{total}}</p>'),
|
|
|
|
|
* scope = ....;
|
|
|
|
|
*
|
|
|
|
|
* var clonedElement = $compile(templateHTML)(scope, function(clonedElement, scope) {
|
|
|
|
|
* //attach the clone to DOM document at the right place
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* //now we have reference to the cloned DOM via `clone`
|
|
|
|
|
* </pre>
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* For information on how the compiler works, see the
|
2012-03-01 19:28:50 +00:00
|
|
|
* {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
|
2011-11-29 20:11:32 +00:00
|
|
|
*/
|
|
|
|
|
|
2013-06-08 01:24:30 +00:00
|
|
|
var $compileMinErr = minErr('$compile');
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-05-03 23:30:36 +00:00
|
|
|
/**
|
|
|
|
|
* @ngdoc service
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.$compileProvider
|
2012-05-03 23:30:36 +00:00
|
|
|
* @function
|
|
|
|
|
*
|
|
|
|
|
* @description
|
2012-03-01 19:28:50 +00:00
|
|
|
*/
|
2011-11-29 20:11:32 +00:00
|
|
|
$CompileProvider.$inject = ['$provide'];
|
|
|
|
|
function $CompileProvider($provide) {
|
|
|
|
|
var hasDirectives = {},
|
|
|
|
|
Suffix = 'Directive',
|
|
|
|
|
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
|
|
|
|
|
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
|
2013-07-16 19:48:29 +00:00
|
|
|
aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|file):/,
|
|
|
|
|
imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|data:image\//;
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2013-06-21 20:03:56 +00:00
|
|
|
// Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
|
|
|
|
|
// The assumption is that future DOM event attribute names will begin with
|
|
|
|
|
// 'on' and be composed of only English letters.
|
|
|
|
|
var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]*|formaction)$/;
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-05-03 23:30:36 +00:00
|
|
|
/**
|
|
|
|
|
* @ngdoc function
|
2012-08-12 17:45:14 +00:00
|
|
|
* @name ng.$compileProvider#directive
|
2012-06-12 06:49:24 +00:00
|
|
|
* @methodOf ng.$compileProvider
|
2012-05-03 23:30:36 +00:00
|
|
|
* @function
|
|
|
|
|
*
|
|
|
|
|
* @description
|
2012-08-12 17:45:14 +00:00
|
|
|
* Register a new directives with the compiler.
|
2012-05-03 23:30:36 +00:00
|
|
|
*
|
|
|
|
|
* @param {string} name Name of the directive in camel-case. (ie <code>ngBind</code> which will match as
|
|
|
|
|
* <code>ng-bind</code>).
|
2013-03-21 19:09:47 +00:00
|
|
|
* @param {function} directiveFactory An injectable directive factory function. See {@link guide/directive} for more
|
2012-05-03 23:30:36 +00:00
|
|
|
* info.
|
2012-08-12 17:45:14 +00:00
|
|
|
* @returns {ng.$compileProvider} Self for chaining.
|
2012-05-03 23:30:36 +00:00
|
|
|
*/
|
|
|
|
|
this.directive = function registerDirective(name, directiveFactory) {
|
2011-11-29 20:11:32 +00:00
|
|
|
if (isString(name)) {
|
2013-06-05 22:30:31 +00:00
|
|
|
assertArg(directiveFactory, 'directiveFactory');
|
2011-11-29 20:11:32 +00:00
|
|
|
if (!hasDirectives.hasOwnProperty(name)) {
|
|
|
|
|
hasDirectives[name] = [];
|
|
|
|
|
$provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
|
|
|
|
|
function($injector, $exceptionHandler) {
|
|
|
|
|
var directives = [];
|
|
|
|
|
forEach(hasDirectives[name], function(directiveFactory) {
|
|
|
|
|
try {
|
|
|
|
|
var directive = $injector.invoke(directiveFactory);
|
|
|
|
|
if (isFunction(directive)) {
|
|
|
|
|
directive = { compile: valueFn(directive) };
|
|
|
|
|
} else if (!directive.compile && directive.link) {
|
|
|
|
|
directive.compile = valueFn(directive.link);
|
|
|
|
|
}
|
|
|
|
|
directive.priority = directive.priority || 0;
|
2012-01-28 00:18:16 +00:00
|
|
|
directive.name = directive.name || name;
|
|
|
|
|
directive.require = directive.require || (directive.controller && directive.name);
|
2012-03-08 06:47:01 +00:00
|
|
|
directive.restrict = directive.restrict || 'A';
|
2011-11-29 20:11:32 +00:00
|
|
|
directives.push(directive);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
$exceptionHandler(e);
|
2011-11-09 01:40:03 +00:00
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
});
|
|
|
|
|
return directives;
|
|
|
|
|
}]);
|
|
|
|
|
}
|
|
|
|
|
hasDirectives[name].push(directiveFactory);
|
|
|
|
|
} else {
|
|
|
|
|
forEach(name, reverseParams(registerDirective));
|
|
|
|
|
}
|
|
|
|
|
return this;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2013-02-19 17:55:05 +00:00
|
|
|
/**
|
|
|
|
|
* @ngdoc function
|
2013-07-16 19:48:29 +00:00
|
|
|
* @name ng.$compileProvider#aHrefSanitizationWhitelist
|
2013-02-19 17:55:05 +00:00
|
|
|
* @methodOf ng.$compileProvider
|
|
|
|
|
* @function
|
|
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
|
2013-07-16 19:48:29 +00:00
|
|
|
* urls during a[href] sanitization.
|
2013-02-19 17:55:05 +00:00
|
|
|
*
|
|
|
|
|
* The sanitization is a security measure aimed at prevent XSS attacks via html links.
|
|
|
|
|
*
|
2013-07-16 19:48:29 +00:00
|
|
|
* Any url about to be assigned to a[href] via data-binding is first normalized and turned into
|
|
|
|
|
* an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
|
|
|
|
|
* regular expression. If a match is found, the original url is written into the dom. Otherwise,
|
|
|
|
|
* the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
|
2013-02-19 17:55:05 +00:00
|
|
|
*
|
|
|
|
|
* @param {RegExp=} regexp New regexp to whitelist urls with.
|
|
|
|
|
* @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
|
|
|
|
|
* chaining otherwise.
|
|
|
|
|
*/
|
2013-07-16 19:48:29 +00:00
|
|
|
this.aHrefSanitizationWhitelist = function(regexp) {
|
2013-02-19 17:55:05 +00:00
|
|
|
if (isDefined(regexp)) {
|
2013-07-16 19:48:29 +00:00
|
|
|
aHrefSanitizationWhitelist = regexp;
|
2013-02-19 17:55:05 +00:00
|
|
|
return this;
|
|
|
|
|
}
|
2013-07-16 19:48:29 +00:00
|
|
|
return aHrefSanitizationWhitelist;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @ngdoc function
|
|
|
|
|
* @name ng.$compileProvider#imgSrcSanitizationWhitelist
|
|
|
|
|
* @methodOf ng.$compileProvider
|
|
|
|
|
* @function
|
|
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
|
|
|
|
|
* urls during img[src] sanitization.
|
|
|
|
|
*
|
|
|
|
|
* The sanitization is a security measure aimed at prevent XSS attacks via html links.
|
|
|
|
|
*
|
|
|
|
|
* Any url about to be assigned to img[src] via data-binding is first normalized and turned into an
|
|
|
|
|
* absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` regular
|
|
|
|
|
* expression. If a match is found, the original url is written into the dom. Otherwise, the
|
|
|
|
|
* absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
|
|
|
|
|
*
|
|
|
|
|
* @param {RegExp=} regexp New regexp to whitelist urls with.
|
|
|
|
|
* @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
|
|
|
|
|
* chaining otherwise.
|
|
|
|
|
*/
|
|
|
|
|
this.imgSrcSanitizationWhitelist = function(regexp) {
|
|
|
|
|
if (isDefined(regexp)) {
|
|
|
|
|
imgSrcSanitizationWhitelist = regexp;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
return imgSrcSanitizationWhitelist;
|
2013-02-19 17:55:05 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2012-01-28 00:18:16 +00:00
|
|
|
this.$get = [
|
|
|
|
|
'$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
|
feat($sce): new $sce service for Strict Contextual Escaping.
$sce is a service that provides Strict Contextual Escaping services to AngularJS.
Strict Contextual Escaping
--------------------------
Strict Contextual Escaping (SCE) is a mode in which AngularJS requires
bindings in certain contexts to result in a value that is marked as safe
to use for that context One example of such a context is binding
arbitrary html controlled by the user via ng-bind-html-unsafe. We
refer to these contexts as privileged or SCE contexts.
As of version 1.2, Angular ships with SCE enabled by default.
Note: When enabled (the default), IE8 in quirks mode is not supported.
In this mode, IE8 allows one to execute arbitrary javascript by the use
of the expression() syntax. Refer
http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx
to learn more about them. You can ensure your document is in standards
mode and not quirks mode by adding <!doctype html> to the top of your
HTML document.
SCE assists in writing code in way that (a) is secure by default and (b)
makes auditing for security vulnerabilities such as XSS, clickjacking,
etc. a lot easier.
Here's an example of a binding in a privileged context:
<input ng-model="userHtml">
<div ng-bind-html-unsafe="{{userHtml}}">
Notice that ng-bind-html-unsafe is bound to {{userHtml}} controlled by
the user. With SCE disabled, this application allows the user to render
arbitrary HTML into the DIV. In a more realistic example, one may be
rendering user comments, blog articles, etc. via bindings. (HTML is
just one example of a context where rendering user controlled input
creates security vulnerabilities.)
For the case of HTML, you might use a library, either on the client side, or on the server side,
to sanitize unsafe HTML before binding to the value and rendering it in the document.
How would you ensure that every place that used these types of bindings was bound to a value that
was sanitized by your library (or returned as safe for rendering by your server?) How can you
ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
properties/fields and forgot to update the binding to the sanitized value?
To be secure by default, you want to ensure that any such bindings are disallowed unless you can
determine that something explicitly says it's safe to use a value for binding in that
context. You can then audit your code (a simple grep would do) to ensure that this is only done
for those values that you can easily tell are safe - because they were received from your server,
sanitized by your library, etc. You can organize your codebase to help with this - perhaps
allowing only the files in a specific directory to do this. Ensuring that the internal API
exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task.
In the case of AngularJS' SCE service, one uses $sce.trustAs (and
shorthand methods such as $sce.trustAsHtml, etc.) to obtain values that
will be accepted by SCE / privileged contexts.
In privileged contexts, directives and code will bind to the result of
$sce.getTrusted(context, value) rather than to the value directly.
Directives use $sce.parseAs rather than $parse to watch attribute
bindings, which performs the $sce.getTrusted behind the scenes on
non-constant literals.
As an example, ngBindHtmlUnsafe uses $sce.parseAsHtml(binding
expression). Here's the actual code (slightly simplified):
var ngBindHtmlUnsafeDirective = ['$sce', function($sce) {
return function(scope, element, attr) {
scope.$watch($sce.parseAsHtml(attr.ngBindHtmlUnsafe), function(value) {
element.html(value || '');
});
};
}];
Impact on loading templates
---------------------------
This applies both to the ng-include directive as well as templateUrl's
specified by directives.
By default, Angular only loads templates from the same domain and
protocol as the application document. This is done by calling
$sce.getTrustedResourceUrl on the template URL. To load templates from
other domains and/or protocols, you may either either whitelist them or
wrap it into a trusted value.
*Please note*:
The browser's Same Origin Policy and Cross-Origin Resource Sharing
(CORS) policy apply in addition to this and may further restrict whether
the template is successfully loaded. This means that without the right
CORS policy, loading templates from a different domain won't work on all
browsers. Also, loading templates from file:// URL does not work on
some browsers.
This feels like too much overhead for the developer?
----------------------------------------------------
It's important to remember that SCE only applies to interpolation expressions.
If your expressions are constant literals, they're automatically trusted
and you don't need to call $sce.trustAs on them.
e.g. <div ng-html-bind-unsafe="'<b>implicitly trusted</b>'"></div> just works.
Additionally, a[href] and img[src] automatically sanitize their URLs and
do not pass them through $sce.getTrusted. SCE doesn't play a role here.
The included $sceDelegate comes with sane defaults to allow you to load
templates in ng-include from your application's domain without having to
even know about SCE. It blocks loading templates from other domains or
loading templates over http from an https served document. You can
change these by setting your own custom whitelists and blacklists for
matching such URLs.
This significantly reduces the overhead. It is far easier to pay the
small overhead and have an application that's secure and can be audited
to verify that with much more ease than bolting security onto an
application later.
2013-05-14 21:51:39 +00:00
|
|
|
'$controller', '$rootScope', '$document', '$sce', '$$urlUtils',
|
2012-01-28 00:18:16 +00:00
|
|
|
function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
|
feat($sce): new $sce service for Strict Contextual Escaping.
$sce is a service that provides Strict Contextual Escaping services to AngularJS.
Strict Contextual Escaping
--------------------------
Strict Contextual Escaping (SCE) is a mode in which AngularJS requires
bindings in certain contexts to result in a value that is marked as safe
to use for that context One example of such a context is binding
arbitrary html controlled by the user via ng-bind-html-unsafe. We
refer to these contexts as privileged or SCE contexts.
As of version 1.2, Angular ships with SCE enabled by default.
Note: When enabled (the default), IE8 in quirks mode is not supported.
In this mode, IE8 allows one to execute arbitrary javascript by the use
of the expression() syntax. Refer
http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx
to learn more about them. You can ensure your document is in standards
mode and not quirks mode by adding <!doctype html> to the top of your
HTML document.
SCE assists in writing code in way that (a) is secure by default and (b)
makes auditing for security vulnerabilities such as XSS, clickjacking,
etc. a lot easier.
Here's an example of a binding in a privileged context:
<input ng-model="userHtml">
<div ng-bind-html-unsafe="{{userHtml}}">
Notice that ng-bind-html-unsafe is bound to {{userHtml}} controlled by
the user. With SCE disabled, this application allows the user to render
arbitrary HTML into the DIV. In a more realistic example, one may be
rendering user comments, blog articles, etc. via bindings. (HTML is
just one example of a context where rendering user controlled input
creates security vulnerabilities.)
For the case of HTML, you might use a library, either on the client side, or on the server side,
to sanitize unsafe HTML before binding to the value and rendering it in the document.
How would you ensure that every place that used these types of bindings was bound to a value that
was sanitized by your library (or returned as safe for rendering by your server?) How can you
ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
properties/fields and forgot to update the binding to the sanitized value?
To be secure by default, you want to ensure that any such bindings are disallowed unless you can
determine that something explicitly says it's safe to use a value for binding in that
context. You can then audit your code (a simple grep would do) to ensure that this is only done
for those values that you can easily tell are safe - because they were received from your server,
sanitized by your library, etc. You can organize your codebase to help with this - perhaps
allowing only the files in a specific directory to do this. Ensuring that the internal API
exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task.
In the case of AngularJS' SCE service, one uses $sce.trustAs (and
shorthand methods such as $sce.trustAsHtml, etc.) to obtain values that
will be accepted by SCE / privileged contexts.
In privileged contexts, directives and code will bind to the result of
$sce.getTrusted(context, value) rather than to the value directly.
Directives use $sce.parseAs rather than $parse to watch attribute
bindings, which performs the $sce.getTrusted behind the scenes on
non-constant literals.
As an example, ngBindHtmlUnsafe uses $sce.parseAsHtml(binding
expression). Here's the actual code (slightly simplified):
var ngBindHtmlUnsafeDirective = ['$sce', function($sce) {
return function(scope, element, attr) {
scope.$watch($sce.parseAsHtml(attr.ngBindHtmlUnsafe), function(value) {
element.html(value || '');
});
};
}];
Impact on loading templates
---------------------------
This applies both to the ng-include directive as well as templateUrl's
specified by directives.
By default, Angular only loads templates from the same domain and
protocol as the application document. This is done by calling
$sce.getTrustedResourceUrl on the template URL. To load templates from
other domains and/or protocols, you may either either whitelist them or
wrap it into a trusted value.
*Please note*:
The browser's Same Origin Policy and Cross-Origin Resource Sharing
(CORS) policy apply in addition to this and may further restrict whether
the template is successfully loaded. This means that without the right
CORS policy, loading templates from a different domain won't work on all
browsers. Also, loading templates from file:// URL does not work on
some browsers.
This feels like too much overhead for the developer?
----------------------------------------------------
It's important to remember that SCE only applies to interpolation expressions.
If your expressions are constant literals, they're automatically trusted
and you don't need to call $sce.trustAs on them.
e.g. <div ng-html-bind-unsafe="'<b>implicitly trusted</b>'"></div> just works.
Additionally, a[href] and img[src] automatically sanitize their URLs and
do not pass them through $sce.getTrusted. SCE doesn't play a role here.
The included $sceDelegate comes with sane defaults to allow you to load
templates in ng-include from your application's domain without having to
even know about SCE. It blocks loading templates from other domains or
loading templates over http from an https served document. You can
change these by setting your own custom whitelists and blacklists for
matching such URLs.
This significantly reduces the overhead. It is far easier to pay the
small overhead and have an application that's secure and can be audited
to verify that with much more ease than bolting security onto an
application later.
2013-05-14 21:51:39 +00:00
|
|
|
$controller, $rootScope, $document, $sce, $$urlUtils) {
|
2012-01-28 00:18:16 +00:00
|
|
|
|
2012-03-28 23:03:59 +00:00
|
|
|
var Attributes = function(element, attr) {
|
|
|
|
|
this.$$element = element;
|
|
|
|
|
this.$attr = attr || {};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Attributes.prototype = {
|
|
|
|
|
$normalize: directiveNormalize,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set a normalized attribute on the element in a way such that all directives
|
|
|
|
|
* can share the attribute. This function properly handles boolean attributes.
|
|
|
|
|
* @param {string} key Normalized key. (ie ngAttribute)
|
|
|
|
|
* @param {string|boolean} value The value to set. If `null` attribute will be deleted.
|
|
|
|
|
* @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
|
|
|
|
|
* Defaults to true.
|
|
|
|
|
* @param {string=} attrName Optional none normalized name. Defaults to key.
|
|
|
|
|
*/
|
|
|
|
|
$set: function(key, value, writeAttr, attrName) {
|
2012-06-04 22:06:02 +00:00
|
|
|
var booleanKey = getBooleanAttrName(this.$$element[0], key),
|
2013-02-19 17:55:05 +00:00
|
|
|
$$observers = this.$$observers,
|
2013-06-21 19:33:03 +00:00
|
|
|
normalizedVal,
|
|
|
|
|
nodeName;
|
2012-03-28 23:03:59 +00:00
|
|
|
|
|
|
|
|
if (booleanKey) {
|
|
|
|
|
this.$$element.prop(key, value);
|
|
|
|
|
attrName = booleanKey;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this[key] = value;
|
|
|
|
|
|
|
|
|
|
// translate normalized key to actual key
|
|
|
|
|
if (attrName) {
|
|
|
|
|
this.$attr[key] = attrName;
|
|
|
|
|
} else {
|
|
|
|
|
attrName = this.$attr[key];
|
|
|
|
|
if (!attrName) {
|
|
|
|
|
this.$attr[key] = attrName = snake_case(key, '-');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-15 19:26:46 +00:00
|
|
|
nodeName = nodeName_(this.$$element);
|
2013-02-19 17:55:05 +00:00
|
|
|
|
2013-06-21 19:33:03 +00:00
|
|
|
// sanitize a[href] and img[src] values
|
|
|
|
|
if ((nodeName === 'A' && key === 'href') ||
|
2013-07-15 19:26:46 +00:00
|
|
|
(nodeName === 'IMG' && key === 'src')) {
|
|
|
|
|
// NOTE: $$urlUtils.resolve() doesn't support IE < 8 so we don't sanitize for that case.
|
|
|
|
|
if (!msie || msie >= 8 ) {
|
|
|
|
|
normalizedVal = $$urlUtils.resolve(value);
|
|
|
|
|
if (normalizedVal !== '') {
|
|
|
|
|
if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) ||
|
|
|
|
|
(key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) {
|
|
|
|
|
this[key] = value = 'unsafe:' + normalizedVal;
|
|
|
|
|
}
|
2013-07-16 19:48:29 +00:00
|
|
|
}
|
2013-02-19 17:55:05 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-28 23:03:59 +00:00
|
|
|
if (writeAttr !== false) {
|
|
|
|
|
if (value === null || value === undefined) {
|
|
|
|
|
this.$$element.removeAttr(attrName);
|
|
|
|
|
} else {
|
|
|
|
|
this.$$element.attr(attrName, value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// fire observers
|
2012-06-04 22:06:02 +00:00
|
|
|
$$observers && forEach($$observers[key], function(fn) {
|
2012-03-28 23:03:59 +00:00
|
|
|
try {
|
|
|
|
|
fn(value);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
$exceptionHandler(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Observe an interpolated attribute.
|
|
|
|
|
* The observer will never be called, if given attribute is not interpolated.
|
|
|
|
|
*
|
|
|
|
|
* @param {string} key Normalized key. (ie ngAttribute) .
|
|
|
|
|
* @param {function(*)} fn Function that will be called whenever the attribute value changes.
|
2012-05-03 23:30:36 +00:00
|
|
|
* @returns {function(*)} the `fn` Function passed in.
|
2012-03-28 23:03:59 +00:00
|
|
|
*/
|
|
|
|
|
$observe: function(key, fn) {
|
2012-06-04 22:06:02 +00:00
|
|
|
var attrs = this,
|
|
|
|
|
$$observers = (attrs.$$observers || (attrs.$$observers = {})),
|
|
|
|
|
listeners = ($$observers[key] || ($$observers[key] = []));
|
|
|
|
|
|
|
|
|
|
listeners.push(fn);
|
|
|
|
|
$rootScope.$evalAsync(function() {
|
|
|
|
|
if (!listeners.$$inter) {
|
|
|
|
|
// no one registered attribute interpolation function, so lets call it manually
|
|
|
|
|
fn(attrs[key]);
|
|
|
|
|
}
|
|
|
|
|
});
|
2012-05-03 23:30:36 +00:00
|
|
|
return fn;
|
2012-03-28 23:03:59 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2013-02-19 17:55:05 +00:00
|
|
|
var urlSanitizationNode = $document[0].createElement('a'),
|
|
|
|
|
startSymbol = $interpolate.startSymbol(),
|
2012-08-11 07:13:10 +00:00
|
|
|
endSymbol = $interpolate.endSymbol(),
|
|
|
|
|
denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
|
|
|
|
|
? identity
|
|
|
|
|
: function denormalizeTemplate(template) {
|
|
|
|
|
return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
|
2013-02-25 23:00:47 +00:00
|
|
|
},
|
|
|
|
|
NG_ATTR_BINDING = /^ngAttr[A-Z]/;
|
2012-08-11 07:13:10 +00:00
|
|
|
|
|
|
|
|
|
2012-01-28 00:18:16 +00:00
|
|
|
return compile;
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-01-28 00:18:16 +00:00
|
|
|
//================================
|
|
|
|
|
|
2013-07-03 00:23:51 +00:00
|
|
|
function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective) {
|
2012-10-25 07:56:34 +00:00
|
|
|
if (!($compileNodes instanceof jqLite)) {
|
2013-01-22 15:59:09 +00:00
|
|
|
// jquery always rewraps, whereas we need to preserve the original selector so that we can modify it.
|
2012-10-25 07:56:34 +00:00
|
|
|
$compileNodes = jqLite($compileNodes);
|
2012-03-16 05:18:06 +00:00
|
|
|
}
|
2012-02-04 00:20:24 +00:00
|
|
|
// We can not compile top level text elements since text nodes can be merged and we will
|
|
|
|
|
// not be able to attach scope data to them, so we will wrap them in <span>
|
2012-10-25 07:56:34 +00:00
|
|
|
forEach($compileNodes, function(node, index){
|
2013-01-09 22:23:50 +00:00
|
|
|
if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) {
|
2013-05-24 19:41:38 +00:00
|
|
|
$compileNodes[index] = node = jqLite(node).wrap('<span></span>').parent()[0];
|
2012-02-04 00:20:24 +00:00
|
|
|
}
|
|
|
|
|
});
|
2013-07-03 00:23:51 +00:00
|
|
|
var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority, ignoreDirective);
|
2012-10-25 07:56:34 +00:00
|
|
|
return function publicLinkFn(scope, cloneConnectFn){
|
2011-11-29 20:11:32 +00:00
|
|
|
assertArg(scope, 'scope');
|
|
|
|
|
// important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
|
|
|
|
|
// and sometimes changes the structure of the DOM.
|
2012-05-03 07:15:07 +00:00
|
|
|
var $linkNode = cloneConnectFn
|
2012-10-25 07:56:34 +00:00
|
|
|
? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!!
|
|
|
|
|
: $compileNodes;
|
2013-02-18 12:05:16 +00:00
|
|
|
|
|
|
|
|
// Attach scope only to non-text nodes.
|
|
|
|
|
for(var i = 0, ii = $linkNode.length; i<ii; i++) {
|
|
|
|
|
var node = $linkNode[i];
|
|
|
|
|
if (node.nodeType == 1 /* element */ || node.nodeType == 9 /* document */) {
|
|
|
|
|
$linkNode.eq(i).data('$scope', scope);
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-05-03 07:15:07 +00:00
|
|
|
safeAddClass($linkNode, 'ng-scope');
|
|
|
|
|
if (cloneConnectFn) cloneConnectFn($linkNode, scope);
|
|
|
|
|
if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode);
|
|
|
|
|
return $linkNode;
|
2011-11-29 20:11:32 +00:00
|
|
|
};
|
2012-01-28 00:18:16 +00:00
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
function safeAddClass($element, className) {
|
2012-03-12 23:49:28 +00:00
|
|
|
try {
|
2012-05-03 07:15:07 +00:00
|
|
|
$element.addClass(className);
|
2012-03-12 23:49:28 +00:00
|
|
|
} catch(e) {
|
|
|
|
|
// ignore, since it means that we are trying to set class on
|
|
|
|
|
// SVG element, where class name is read-only.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
/**
|
|
|
|
|
* Compile function matches each node in nodeList against the directives. Once all directives
|
|
|
|
|
* for a particular node are collected their compile functions are executed. The compile
|
|
|
|
|
* functions return values - the linking functions - are combined into a composite linking
|
|
|
|
|
* function, which is the a linking function for the node.
|
|
|
|
|
*
|
2013-01-22 15:59:09 +00:00
|
|
|
* @param {NodeList} nodeList an array of nodes or NodeList to compile
|
2012-01-28 00:18:16 +00:00
|
|
|
* @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the
|
|
|
|
|
* scope argument is auto-generated to the new child of the transcluded parent scope.
|
2012-05-03 07:15:07 +00:00
|
|
|
* @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then the
|
2011-11-29 20:11:32 +00:00
|
|
|
* rootElement must be set the jqLite collection of the compile root. This is
|
|
|
|
|
* needed so that the jqLite collection items can be replaced with widgets.
|
2012-01-28 00:18:16 +00:00
|
|
|
* @param {number=} max directive priority
|
2011-11-29 20:11:32 +00:00
|
|
|
* @returns {?function} A composite linking function of all of the matched directives or null.
|
|
|
|
|
*/
|
2013-07-03 00:23:51 +00:00
|
|
|
function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective) {
|
2013-01-10 00:54:29 +00:00
|
|
|
var linkFns = [],
|
|
|
|
|
nodeLinkFn, childLinkFn, directives, attrs, linkFnFound;
|
|
|
|
|
|
|
|
|
|
for(var i = 0; i < nodeList.length; i++) {
|
|
|
|
|
attrs = new Attributes();
|
|
|
|
|
|
|
|
|
|
// we must always refer to nodeList[i] since the nodes can be replaced underneath us.
|
2013-07-03 00:23:51 +00:00
|
|
|
directives = collectDirectives(nodeList[i], [], attrs, i == 0 ? maxPriority : undefined, ignoreDirective);
|
2013-01-10 00:54:29 +00:00
|
|
|
|
|
|
|
|
nodeLinkFn = (directives.length)
|
|
|
|
|
? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement)
|
|
|
|
|
: null;
|
|
|
|
|
|
2013-02-24 06:54:35 +00:00
|
|
|
childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || !nodeList[i].childNodes || !nodeList[i].childNodes.length)
|
2013-01-10 00:54:29 +00:00
|
|
|
? null
|
|
|
|
|
: compileNodes(nodeList[i].childNodes,
|
|
|
|
|
nodeLinkFn ? nodeLinkFn.transclude : transcludeFn);
|
|
|
|
|
|
|
|
|
|
linkFns.push(nodeLinkFn);
|
|
|
|
|
linkFns.push(childLinkFn);
|
|
|
|
|
linkFnFound = (linkFnFound || nodeLinkFn || childLinkFn);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// return a linking function if we have found anything, null otherwise
|
|
|
|
|
return linkFnFound ? compositeLinkFn : null;
|
|
|
|
|
|
|
|
|
|
function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) {
|
2013-01-08 19:11:51 +00:00
|
|
|
var nodeLinkFn, childLinkFn, node, childScope, childTranscludeFn, i, ii, n;
|
2013-01-10 00:54:29 +00:00
|
|
|
|
2013-01-08 19:11:51 +00:00
|
|
|
// copy nodeList so that linking doesn't break due to live list updates.
|
|
|
|
|
var stableNodeList = [];
|
|
|
|
|
for (i = 0, ii = nodeList.length; i < ii; i++) {
|
|
|
|
|
stableNodeList.push(nodeList[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for(i = 0, n = 0, ii = linkFns.length; i < ii; n++) {
|
|
|
|
|
node = stableNodeList[n];
|
2013-01-10 00:54:29 +00:00
|
|
|
nodeLinkFn = linkFns[i++];
|
|
|
|
|
childLinkFn = linkFns[i++];
|
|
|
|
|
|
|
|
|
|
if (nodeLinkFn) {
|
|
|
|
|
if (nodeLinkFn.scope) {
|
|
|
|
|
childScope = scope.$new(isObject(nodeLinkFn.scope));
|
|
|
|
|
jqLite(node).data('$scope', childScope);
|
|
|
|
|
} else {
|
|
|
|
|
childScope = scope;
|
|
|
|
|
}
|
|
|
|
|
childTranscludeFn = nodeLinkFn.transclude;
|
|
|
|
|
if (childTranscludeFn || (!boundTranscludeFn && transcludeFn)) {
|
|
|
|
|
nodeLinkFn(childLinkFn, childScope, node, $rootElement,
|
|
|
|
|
(function(transcludeFn) {
|
|
|
|
|
return function(cloneFn) {
|
|
|
|
|
var transcludeScope = scope.$new();
|
2013-01-23 05:01:13 +00:00
|
|
|
transcludeScope.$$transcluded = true;
|
2013-01-10 00:54:29 +00:00
|
|
|
|
|
|
|
|
return transcludeFn(transcludeScope, cloneFn).
|
2013-06-19 19:52:50 +00:00
|
|
|
on('$destroy', bind(transcludeScope, transcludeScope.$destroy));
|
2012-01-28 00:18:16 +00:00
|
|
|
};
|
2012-05-03 07:15:07 +00:00
|
|
|
})(childTranscludeFn || transcludeFn)
|
2013-01-10 00:54:29 +00:00
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
nodeLinkFn(childLinkFn, childScope, node, undefined, boundTranscludeFn);
|
|
|
|
|
}
|
|
|
|
|
} else if (childLinkFn) {
|
|
|
|
|
childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2012-10-25 07:56:34 +00:00
|
|
|
* Looks for directives on the given node and adds them to the directive collection which is
|
|
|
|
|
* sorted.
|
2011-11-29 20:11:32 +00:00
|
|
|
*
|
2012-10-25 07:56:34 +00:00
|
|
|
* @param node Node to search.
|
|
|
|
|
* @param directives An array to which the directives are added to. This array is sorted before
|
2011-11-29 20:11:32 +00:00
|
|
|
* the function returns.
|
2012-10-25 07:56:34 +00:00
|
|
|
* @param attrs The shared attrs object which is used to populate the normalized attributes.
|
|
|
|
|
* @param {number=} maxPriority Max directive priority.
|
2011-11-29 20:11:32 +00:00
|
|
|
*/
|
2013-07-03 00:23:51 +00:00
|
|
|
function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
|
2011-11-29 20:11:32 +00:00
|
|
|
var nodeType = node.nodeType,
|
|
|
|
|
attrsMap = attrs.$attr,
|
|
|
|
|
match,
|
|
|
|
|
className;
|
|
|
|
|
|
|
|
|
|
switch(nodeType) {
|
|
|
|
|
case 1: /* Element */
|
|
|
|
|
// use the node name: <directive>
|
2012-01-28 00:18:16 +00:00
|
|
|
addDirective(directives,
|
2013-07-03 00:23:51 +00:00
|
|
|
directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective);
|
2011-11-29 20:11:32 +00:00
|
|
|
|
|
|
|
|
// iterate over the attributes
|
2013-02-25 23:00:47 +00:00
|
|
|
for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes,
|
2011-11-29 20:11:32 +00:00
|
|
|
j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
|
2013-05-24 19:41:38 +00:00
|
|
|
var attrStartName;
|
|
|
|
|
var attrEndName;
|
|
|
|
|
var index;
|
|
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
attr = nAttrs[j];
|
2013-07-25 20:24:58 +00:00
|
|
|
if (!msie || msie >= 8 || attr.specified) {
|
2012-03-19 19:20:57 +00:00
|
|
|
name = attr.name;
|
2013-02-25 23:00:47 +00:00
|
|
|
// support ngAttr attribute binding
|
|
|
|
|
ngAttrName = directiveNormalize(name);
|
|
|
|
|
if (NG_ATTR_BINDING.test(ngAttrName)) {
|
|
|
|
|
name = ngAttrName.substr(6).toLowerCase();
|
|
|
|
|
}
|
2013-05-24 19:41:38 +00:00
|
|
|
if ((index = ngAttrName.lastIndexOf('Start')) != -1 && index == ngAttrName.length - 5) {
|
|
|
|
|
attrStartName = name;
|
|
|
|
|
attrEndName = name.substr(0, name.length - 5) + 'end';
|
|
|
|
|
name = name.substr(0, name.length - 6);
|
|
|
|
|
}
|
2012-03-19 19:20:57 +00:00
|
|
|
nName = directiveNormalize(name.toLowerCase());
|
|
|
|
|
attrsMap[nName] = name;
|
|
|
|
|
attrs[nName] = value = trim((msie && name == 'href')
|
2011-11-29 20:11:32 +00:00
|
|
|
? decodeURIComponent(node.getAttribute(name, 2))
|
|
|
|
|
: attr.value);
|
2012-06-05 04:11:35 +00:00
|
|
|
if (getBooleanAttrName(node, nName)) {
|
2012-03-19 19:20:57 +00:00
|
|
|
attrs[nName] = true; // presence means true
|
|
|
|
|
}
|
2012-04-10 21:29:49 +00:00
|
|
|
addAttrInterpolateDirective(node, directives, value, nName);
|
2013-07-03 00:23:51 +00:00
|
|
|
addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, attrEndName);
|
2011-11-02 23:32:46 +00:00
|
|
|
}
|
|
|
|
|
}
|
2011-10-26 05:21:21 +00:00
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
// use class as directive
|
|
|
|
|
className = node.className;
|
2012-10-25 08:00:21 +00:00
|
|
|
if (isString(className) && className !== '') {
|
2012-03-12 23:49:28 +00:00
|
|
|
while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
|
|
|
|
|
nName = directiveNormalize(match[2]);
|
2013-07-03 00:23:51 +00:00
|
|
|
if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) {
|
2012-03-12 23:49:28 +00:00
|
|
|
attrs[nName] = trim(match[3]);
|
|
|
|
|
}
|
|
|
|
|
className = className.substr(match.index + match[0].length);
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
2011-11-02 23:32:46 +00:00
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
break;
|
|
|
|
|
case 3: /* Text Node */
|
|
|
|
|
addTextInterpolateDirective(directives, node.nodeValue);
|
|
|
|
|
break;
|
|
|
|
|
case 8: /* Comment */
|
2012-04-17 20:58:25 +00:00
|
|
|
try {
|
|
|
|
|
match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
|
|
|
|
|
if (match) {
|
|
|
|
|
nName = directiveNormalize(match[1]);
|
2013-07-03 00:23:51 +00:00
|
|
|
if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) {
|
2012-04-17 20:58:25 +00:00
|
|
|
attrs[nName] = trim(match[2]);
|
|
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
2012-04-17 20:58:25 +00:00
|
|
|
} catch (e) {
|
|
|
|
|
// turns out that under some circumstances IE9 throws errors when one attempts to read comment's node value.
|
|
|
|
|
// Just ignore it and continue. (Can't seem to reproduce in test case.)
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
directives.sort(byPriority);
|
|
|
|
|
return directives;
|
|
|
|
|
}
|
2010-03-19 23:41:02 +00:00
|
|
|
|
2013-05-24 19:41:38 +00:00
|
|
|
/**
|
|
|
|
|
* Given a node with an directive-start it collects all of the siblings until it find directive-end.
|
|
|
|
|
* @param node
|
|
|
|
|
* @param attrStart
|
|
|
|
|
* @param attrEnd
|
|
|
|
|
* @returns {*}
|
|
|
|
|
*/
|
|
|
|
|
function groupScan(node, attrStart, attrEnd) {
|
|
|
|
|
var nodes = [];
|
|
|
|
|
var depth = 0;
|
|
|
|
|
if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
|
|
|
|
|
var startNode = node;
|
|
|
|
|
do {
|
|
|
|
|
if (!node) {
|
2013-06-08 01:24:30 +00:00
|
|
|
throw $compileMinErr('utrat', "Unterminated attribute, found '{0}' but no matching '{1}' found.", attrStart, attrEnd);
|
2013-05-24 19:41:38 +00:00
|
|
|
}
|
2013-06-11 20:08:21 +00:00
|
|
|
if (node.nodeType == 1 /** Element **/) {
|
|
|
|
|
if (node.hasAttribute(attrStart)) depth++;
|
|
|
|
|
if (node.hasAttribute(attrEnd)) depth--;
|
|
|
|
|
}
|
2013-05-24 19:41:38 +00:00
|
|
|
nodes.push(node);
|
|
|
|
|
node = node.nextSibling;
|
|
|
|
|
} while (depth > 0);
|
|
|
|
|
} else {
|
|
|
|
|
nodes.push(node);
|
|
|
|
|
}
|
|
|
|
|
return jqLite(nodes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Wrapper for linking function which converts normal linking function into a grouped
|
|
|
|
|
* linking function.
|
|
|
|
|
* @param linkFn
|
|
|
|
|
* @param attrStart
|
|
|
|
|
* @param attrEnd
|
|
|
|
|
* @returns {Function}
|
|
|
|
|
*/
|
|
|
|
|
function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
|
|
|
|
|
return function(scope, element, attrs, controllers) {
|
|
|
|
|
element = groupScan(element[0], attrStart, attrEnd);
|
|
|
|
|
return linkFn(scope, element, attrs, controllers);
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-03-20 05:18:39 +00:00
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
/**
|
2013-04-02 21:38:23 +00:00
|
|
|
* Once the directives have been collected, their compile functions are executed. This method
|
2012-03-13 04:30:03 +00:00
|
|
|
* is responsible for inlining directive templates as well as terminating the application
|
2013-04-02 21:38:23 +00:00
|
|
|
* of the directives if the terminal directive has been reached.
|
2011-11-29 20:11:32 +00:00
|
|
|
*
|
|
|
|
|
* @param {Array} directives Array of collected directives to execute their compile function.
|
|
|
|
|
* this needs to be pre-sorted by priority order.
|
2012-05-03 07:15:07 +00:00
|
|
|
* @param {Node} compileNode The raw DOM node to apply the compile functions to
|
2011-11-29 20:11:32 +00:00
|
|
|
* @param {Object} templateAttrs The shared attribute function
|
2012-01-28 00:18:16 +00:00
|
|
|
* @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the
|
|
|
|
|
* scope argument is auto-generated to the new child of the transcluded parent scope.
|
2012-12-03 19:03:24 +00:00
|
|
|
* @param {JQLite} jqCollection If we are working on the root of the compile tree then this
|
|
|
|
|
* argument has the root jqLite array so that we can replace nodes on it.
|
2012-05-03 07:15:07 +00:00
|
|
|
* @returns linkFn
|
2011-11-29 20:11:32 +00:00
|
|
|
*/
|
2013-07-03 00:23:51 +00:00
|
|
|
function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, jqCollection, originalReplaceDirective) {
|
2011-11-29 20:11:32 +00:00
|
|
|
var terminalPriority = -Number.MAX_VALUE,
|
2012-05-03 07:15:07 +00:00
|
|
|
preLinkFns = [],
|
|
|
|
|
postLinkFns = [],
|
2011-11-29 20:11:32 +00:00
|
|
|
newScopeDirective = null,
|
2012-10-25 07:56:34 +00:00
|
|
|
newIsolateScopeDirective = null,
|
2011-11-29 20:11:32 +00:00
|
|
|
templateDirective = null,
|
2012-05-03 07:15:07 +00:00
|
|
|
$compileNode = templateAttrs.$$element = jqLite(compileNode),
|
2012-01-28 00:18:16 +00:00
|
|
|
directive,
|
|
|
|
|
directiveName,
|
2012-05-03 07:15:07 +00:00
|
|
|
$template,
|
2012-01-28 00:18:16 +00:00
|
|
|
transcludeDirective,
|
2013-07-03 00:23:51 +00:00
|
|
|
replaceDirective = originalReplaceDirective,
|
2012-01-28 00:18:16 +00:00
|
|
|
childTranscludeFn = transcludeFn,
|
|
|
|
|
controllerDirectives,
|
2012-05-03 07:15:07 +00:00
|
|
|
linkFn,
|
2012-01-28 00:18:16 +00:00
|
|
|
directiveValue;
|
2011-11-29 20:11:32 +00:00
|
|
|
|
|
|
|
|
// executes all directives on the current element
|
|
|
|
|
for(var i = 0, ii = directives.length; i < ii; i++) {
|
|
|
|
|
directive = directives[i];
|
2013-05-24 19:41:38 +00:00
|
|
|
var attrStart = directive.$$start;
|
|
|
|
|
var attrEnd = directive.$$end;
|
|
|
|
|
|
|
|
|
|
// collect multiblock sections
|
|
|
|
|
if (attrStart) {
|
|
|
|
|
$compileNode = groupScan(compileNode, attrStart, attrEnd)
|
|
|
|
|
}
|
2012-05-03 07:15:07 +00:00
|
|
|
$template = undefined;
|
2011-11-29 20:11:32 +00:00
|
|
|
|
|
|
|
|
if (terminalPriority > directive.priority) {
|
|
|
|
|
break; // prevent further processing of directives
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-28 00:18:16 +00:00
|
|
|
if (directiveValue = directive.scope) {
|
2012-10-25 07:56:34 +00:00
|
|
|
assertNoDuplicate('isolated scope', newIsolateScopeDirective, directive, $compileNode);
|
2012-01-28 00:18:16 +00:00
|
|
|
if (isObject(directiveValue)) {
|
2012-05-03 07:15:07 +00:00
|
|
|
safeAddClass($compileNode, 'ng-isolate-scope');
|
2012-10-25 07:56:34 +00:00
|
|
|
newIsolateScopeDirective = directive;
|
2012-01-28 00:18:16 +00:00
|
|
|
}
|
2012-05-03 07:15:07 +00:00
|
|
|
safeAddClass($compileNode, 'ng-scope');
|
2012-01-28 00:18:16 +00:00
|
|
|
newScopeDirective = newScopeDirective || directive;
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
|
2012-01-28 00:18:16 +00:00
|
|
|
directiveName = directive.name;
|
|
|
|
|
|
|
|
|
|
if (directiveValue = directive.controller) {
|
|
|
|
|
controllerDirectives = controllerDirectives || {};
|
|
|
|
|
assertNoDuplicate("'" + directiveName + "' controller",
|
2012-05-03 07:15:07 +00:00
|
|
|
controllerDirectives[directiveName], directive, $compileNode);
|
2012-01-28 00:18:16 +00:00
|
|
|
controllerDirectives[directiveName] = directive;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (directiveValue = directive.transclude) {
|
2012-05-03 07:15:07 +00:00
|
|
|
assertNoDuplicate('transclusion', transcludeDirective, directive, $compileNode);
|
2012-01-28 00:18:16 +00:00
|
|
|
transcludeDirective = directive;
|
|
|
|
|
terminalPriority = directive.priority;
|
|
|
|
|
if (directiveValue == 'element') {
|
2013-05-24 19:41:38 +00:00
|
|
|
$template = groupScan(compileNode, attrStart, attrEnd)
|
2012-05-03 07:15:07 +00:00
|
|
|
$compileNode = templateAttrs.$$element =
|
2013-01-10 06:07:33 +00:00
|
|
|
jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' '));
|
2012-05-03 07:15:07 +00:00
|
|
|
compileNode = $compileNode[0];
|
2013-05-24 19:41:38 +00:00
|
|
|
replaceWith(jqCollection, jqLite(sliceArgs($template)), compileNode);
|
2013-07-03 00:23:51 +00:00
|
|
|
|
|
|
|
|
childTranscludeFn = compile($template, transcludeFn, terminalPriority,
|
|
|
|
|
replaceDirective && replaceDirective.name);
|
2012-01-28 00:18:16 +00:00
|
|
|
} else {
|
2012-05-03 07:15:07 +00:00
|
|
|
$template = jqLite(JQLiteClone(compileNode)).contents();
|
|
|
|
|
$compileNode.html(''); // clear contents
|
|
|
|
|
childTranscludeFn = compile($template, transcludeFn);
|
2012-01-28 00:18:16 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-21 20:56:40 +00:00
|
|
|
if (directive.template) {
|
2012-05-03 07:15:07 +00:00
|
|
|
assertNoDuplicate('template', templateDirective, directive, $compileNode);
|
2011-11-29 20:11:32 +00:00
|
|
|
templateDirective = directive;
|
2013-02-21 20:56:40 +00:00
|
|
|
|
|
|
|
|
directiveValue = (isFunction(directive.template))
|
|
|
|
|
? directive.template($compileNode, templateAttrs)
|
|
|
|
|
: directive.template;
|
|
|
|
|
|
2012-08-11 07:13:10 +00:00
|
|
|
directiveValue = denormalizeTemplate(directiveValue);
|
2011-11-29 20:11:32 +00:00
|
|
|
|
|
|
|
|
if (directive.replace) {
|
2013-07-03 00:23:51 +00:00
|
|
|
replaceDirective = directive;
|
2012-08-11 06:46:42 +00:00
|
|
|
$template = jqLite('<div>' +
|
|
|
|
|
trim(directiveValue) +
|
|
|
|
|
'</div>').contents();
|
|
|
|
|
compileNode = $template[0];
|
|
|
|
|
|
2012-05-04 08:24:46 +00:00
|
|
|
if ($template.length != 1 || compileNode.nodeType !== 1) {
|
2013-06-08 01:24:30 +00:00
|
|
|
throw $compileMinErr('tplrt', "Template for directive '{0}' must have exactly one root element. {1}", directiveName, '');
|
2012-05-04 08:24:46 +00:00
|
|
|
}
|
|
|
|
|
|
2012-12-03 19:03:24 +00:00
|
|
|
replaceWith(jqCollection, $compileNode, compileNode);
|
2011-11-29 20:11:32 +00:00
|
|
|
|
|
|
|
|
var newTemplateAttrs = {$attr: {}};
|
|
|
|
|
|
|
|
|
|
// combine directives from the original node and from the template:
|
|
|
|
|
// - take the array of directives for this element
|
|
|
|
|
// - split it into two parts, those that were already applied and those that weren't
|
|
|
|
|
// - collect directives from the template, add them to the second group and sort them
|
|
|
|
|
// - append the second group with new directives to the first group
|
|
|
|
|
directives = directives.concat(
|
|
|
|
|
collectDirectives(
|
2012-05-03 07:15:07 +00:00
|
|
|
compileNode,
|
2011-11-29 20:11:32 +00:00
|
|
|
directives.splice(i + 1, directives.length - (i + 1)),
|
|
|
|
|
newTemplateAttrs
|
|
|
|
|
)
|
|
|
|
|
);
|
2013-07-03 00:15:24 +00:00
|
|
|
mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
|
2011-11-29 20:11:32 +00:00
|
|
|
|
|
|
|
|
ii = directives.length;
|
|
|
|
|
} else {
|
2012-05-03 07:15:07 +00:00
|
|
|
$compileNode.html(directiveValue);
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (directive.templateUrl) {
|
2012-05-03 07:15:07 +00:00
|
|
|
assertNoDuplicate('template', templateDirective, directive, $compileNode);
|
2011-11-29 20:11:32 +00:00
|
|
|
templateDirective = directive;
|
2013-07-03 00:23:51 +00:00
|
|
|
|
|
|
|
|
if (directive.replace) {
|
|
|
|
|
replaceDirective = directive;
|
|
|
|
|
}
|
2012-05-03 07:15:07 +00:00
|
|
|
nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i),
|
2013-07-03 00:23:51 +00:00
|
|
|
nodeLinkFn, $compileNode, templateAttrs, jqCollection, childTranscludeFn);
|
2011-11-29 20:11:32 +00:00
|
|
|
ii = directives.length;
|
|
|
|
|
} else if (directive.compile) {
|
|
|
|
|
try {
|
2012-05-03 07:15:07 +00:00
|
|
|
linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
|
|
|
|
|
if (isFunction(linkFn)) {
|
2013-05-24 19:41:38 +00:00
|
|
|
addLinkFns(null, linkFn, attrStart, attrEnd);
|
2012-05-03 07:15:07 +00:00
|
|
|
} else if (linkFn) {
|
2013-05-24 19:41:38 +00:00
|
|
|
addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd);
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
} catch (e) {
|
2012-05-03 07:15:07 +00:00
|
|
|
$exceptionHandler(e, startingTag($compileNode));
|
2011-11-02 23:32:46 +00:00
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
2010-03-19 23:41:02 +00:00
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
if (directive.terminal) {
|
2012-05-03 07:15:07 +00:00
|
|
|
nodeLinkFn.terminal = true;
|
2011-11-29 20:11:32 +00:00
|
|
|
terminalPriority = Math.max(terminalPriority, directive.priority);
|
2011-11-02 23:32:46 +00:00
|
|
|
}
|
2011-02-25 22:03:02 +00:00
|
|
|
|
2010-04-30 19:22:07 +00:00
|
|
|
}
|
2012-01-28 00:18:16 +00:00
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope;
|
|
|
|
|
nodeLinkFn.transclude = transcludeDirective && childTranscludeFn;
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
// might be normal or delayed nodeLinkFn depending on if templateUrl is present
|
|
|
|
|
return nodeLinkFn;
|
2010-03-19 23:41:02 +00:00
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
////////////////////
|
|
|
|
|
|
2013-05-24 19:41:38 +00:00
|
|
|
function addLinkFns(pre, post, attrStart, attrEnd) {
|
2012-01-28 00:18:16 +00:00
|
|
|
if (pre) {
|
2013-05-24 19:41:38 +00:00
|
|
|
if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd);
|
2012-01-28 00:18:16 +00:00
|
|
|
pre.require = directive.require;
|
2012-05-03 07:15:07 +00:00
|
|
|
preLinkFns.push(pre);
|
2012-01-28 00:18:16 +00:00
|
|
|
}
|
|
|
|
|
if (post) {
|
2013-05-24 19:41:38 +00:00
|
|
|
if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd);
|
2012-01-28 00:18:16 +00:00
|
|
|
post.require = directive.require;
|
2012-05-03 07:15:07 +00:00
|
|
|
postLinkFns.push(post);
|
2012-01-28 00:18:16 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
function getControllers(require, $element) {
|
2012-01-28 00:18:16 +00:00
|
|
|
var value, retrievalMethod = 'data', optional = false;
|
|
|
|
|
if (isString(require)) {
|
|
|
|
|
while((value = require.charAt(0)) == '^' || value == '?') {
|
|
|
|
|
require = require.substr(1);
|
|
|
|
|
if (value == '^') {
|
|
|
|
|
retrievalMethod = 'inheritedData';
|
|
|
|
|
}
|
|
|
|
|
optional = optional || value == '?';
|
|
|
|
|
}
|
2012-05-03 07:15:07 +00:00
|
|
|
value = $element[retrievalMethod]('$' + require + 'Controller');
|
2012-01-28 00:18:16 +00:00
|
|
|
if (!value && !optional) {
|
2013-06-08 01:24:30 +00:00
|
|
|
throw $compileMinErr('ctreq', "Controller '{0}', required by directive '{1}', can't be found!", require, directiveName);
|
2012-01-28 00:18:16 +00:00
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
} else if (isArray(require)) {
|
|
|
|
|
value = [];
|
|
|
|
|
forEach(require, function(require) {
|
2012-05-03 07:15:07 +00:00
|
|
|
value.push(getControllers(require, $element));
|
2012-01-28 00:18:16 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
|
|
|
|
|
var attrs, $element, i, ii, linkFn, controller;
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
if (compileNode === linkNode) {
|
2011-11-29 20:11:32 +00:00
|
|
|
attrs = templateAttrs;
|
|
|
|
|
} else {
|
2012-03-28 23:03:59 +00:00
|
|
|
attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr));
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
2012-05-03 07:15:07 +00:00
|
|
|
$element = attrs.$$element;
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-10-25 07:56:34 +00:00
|
|
|
if (newIsolateScopeDirective) {
|
2013-01-26 19:15:06 +00:00
|
|
|
var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/;
|
2012-06-06 20:58:10 +00:00
|
|
|
|
|
|
|
|
var parentScope = scope.$parent || scope;
|
|
|
|
|
|
2013-05-24 18:00:14 +00:00
|
|
|
forEach(newIsolateScopeDirective.scope, function(definition, scopeName) {
|
|
|
|
|
var match = definition.match(LOCAL_REGEXP) || [],
|
2013-01-26 19:15:06 +00:00
|
|
|
attrName = match[3] || scopeName,
|
|
|
|
|
optional = (match[2] == '?'),
|
2012-06-06 20:58:10 +00:00
|
|
|
mode = match[1], // @, =, or &
|
|
|
|
|
lastValue,
|
|
|
|
|
parentGet, parentSet;
|
|
|
|
|
|
2013-01-23 05:01:13 +00:00
|
|
|
scope.$$isolateBindings[scopeName] = mode + attrName;
|
|
|
|
|
|
2012-06-06 20:58:10 +00:00
|
|
|
switch (mode) {
|
|
|
|
|
|
|
|
|
|
case '@': {
|
|
|
|
|
attrs.$observe(attrName, function(value) {
|
|
|
|
|
scope[scopeName] = value;
|
|
|
|
|
});
|
|
|
|
|
attrs.$$observers[attrName].$$scope = parentScope;
|
2012-10-24 10:06:36 +00:00
|
|
|
if( attrs[attrName] ) {
|
|
|
|
|
// If the attribute has been provided then we trigger an interpolation to ensure the value is there for use in the link fn
|
|
|
|
|
scope[scopeName] = $interpolate(attrs[attrName])(parentScope);
|
|
|
|
|
}
|
2012-06-06 20:58:10 +00:00
|
|
|
break;
|
|
|
|
|
}
|
2013-02-19 17:55:05 +00:00
|
|
|
|
2012-06-06 20:58:10 +00:00
|
|
|
case '=': {
|
2013-01-26 19:15:06 +00:00
|
|
|
if (optional && !attrs[attrName]) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2012-06-06 20:58:10 +00:00
|
|
|
parentGet = $parse(attrs[attrName]);
|
|
|
|
|
parentSet = parentGet.assign || function() {
|
|
|
|
|
// reset the change, or we will throw this exception on every $digest
|
|
|
|
|
lastValue = scope[scopeName] = parentGet(parentScope);
|
2013-06-08 01:24:30 +00:00
|
|
|
throw $compileMinErr('noass', "Expression '{0}' used with directive '{1}' is non-assignable!",
|
2013-05-24 18:00:14 +00:00
|
|
|
attrs[attrName], newIsolateScopeDirective.name);
|
2012-06-06 20:58:10 +00:00
|
|
|
};
|
|
|
|
|
lastValue = scope[scopeName] = parentGet(parentScope);
|
2012-07-06 09:53:40 +00:00
|
|
|
scope.$watch(function parentValueWatch() {
|
2012-06-06 20:58:10 +00:00
|
|
|
var parentValue = parentGet(parentScope);
|
|
|
|
|
|
|
|
|
|
if (parentValue !== scope[scopeName]) {
|
|
|
|
|
// we are out of sync and need to copy
|
|
|
|
|
if (parentValue !== lastValue) {
|
|
|
|
|
// parent changed and it has precedence
|
|
|
|
|
lastValue = scope[scopeName] = parentValue;
|
|
|
|
|
} else {
|
|
|
|
|
// if the parent can be assigned then do so
|
2012-09-13 09:40:00 +00:00
|
|
|
parentSet(parentScope, parentValue = lastValue = scope[scopeName]);
|
2012-06-06 20:58:10 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return parentValue;
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case '&': {
|
|
|
|
|
parentGet = $parse(attrs[attrName]);
|
|
|
|
|
scope[scopeName] = function(locals) {
|
|
|
|
|
return parentGet(parentScope, locals);
|
2013-05-07 04:56:51 +00:00
|
|
|
};
|
2012-06-06 20:58:10 +00:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
default: {
|
2013-06-08 01:24:30 +00:00
|
|
|
throw $compileMinErr('iscp', "Invalid isolate scope definition for directive '{0}'. Definition: {... {1}: '{2}' ...}",
|
2013-05-24 18:00:14 +00:00
|
|
|
newIsolateScopeDirective.name, scopeName, definition);
|
2012-06-06 20:58:10 +00:00
|
|
|
}
|
|
|
|
|
}
|
2012-01-28 00:18:16 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (controllerDirectives) {
|
|
|
|
|
forEach(controllerDirectives, function(directive) {
|
|
|
|
|
var locals = {
|
|
|
|
|
$scope: scope,
|
2012-05-03 07:15:07 +00:00
|
|
|
$element: $element,
|
2012-01-28 00:18:16 +00:00
|
|
|
$attrs: attrs,
|
|
|
|
|
$transclude: boundTranscludeFn
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
controller = directive.controller;
|
|
|
|
|
if (controller == '@') {
|
|
|
|
|
controller = attrs[directive.name];
|
|
|
|
|
}
|
|
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
$element.data(
|
2012-01-28 00:18:16 +00:00
|
|
|
'$' + directive.name + 'Controller',
|
|
|
|
|
$controller(controller, locals));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
// PRELINKING
|
2012-05-03 07:15:07 +00:00
|
|
|
for(i = 0, ii = preLinkFns.length; i < ii; i++) {
|
2011-11-29 20:11:32 +00:00
|
|
|
try {
|
2012-05-03 07:15:07 +00:00
|
|
|
linkFn = preLinkFns[i];
|
|
|
|
|
linkFn(scope, $element, attrs,
|
|
|
|
|
linkFn.require && getControllers(linkFn.require, $element));
|
2011-11-29 20:11:32 +00:00
|
|
|
} catch (e) {
|
2012-05-03 07:15:07 +00:00
|
|
|
$exceptionHandler(e, startingTag($element));
|
2011-10-26 05:21:21 +00:00
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RECURSION
|
2012-05-03 07:15:07 +00:00
|
|
|
childLinkFn && childLinkFn(scope, linkNode.childNodes, undefined, boundTranscludeFn);
|
2011-11-29 20:11:32 +00:00
|
|
|
|
|
|
|
|
// POSTLINKING
|
2012-05-03 07:15:07 +00:00
|
|
|
for(i = 0, ii = postLinkFns.length; i < ii; i++) {
|
2011-11-29 20:11:32 +00:00
|
|
|
try {
|
2012-05-03 07:15:07 +00:00
|
|
|
linkFn = postLinkFns[i];
|
|
|
|
|
linkFn(scope, $element, attrs,
|
|
|
|
|
linkFn.require && getControllers(linkFn.require, $element));
|
2011-11-29 20:11:32 +00:00
|
|
|
} catch (e) {
|
2012-05-03 07:15:07 +00:00
|
|
|
$exceptionHandler(e, startingTag($element));
|
2011-10-26 05:21:21 +00:00
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* looks up the directive and decorates it with exception handling and proper parameters. We
|
|
|
|
|
* call this the boundDirective.
|
|
|
|
|
*
|
|
|
|
|
* @param {string} name name of the directive to look up.
|
|
|
|
|
* @param {string} location The directive must be found in specific format.
|
|
|
|
|
* String containing any of theses characters:
|
|
|
|
|
*
|
|
|
|
|
* * `E`: element name
|
|
|
|
|
* * `A': attribute
|
|
|
|
|
* * `C`: class
|
|
|
|
|
* * `M`: comment
|
|
|
|
|
* @returns true if directive was added.
|
|
|
|
|
*/
|
2013-07-03 00:23:51 +00:00
|
|
|
function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, endAttrName) {
|
|
|
|
|
if (name === ignoreDirective) return null;
|
2013-05-24 19:41:38 +00:00
|
|
|
var match = null;
|
2011-11-29 20:11:32 +00:00
|
|
|
if (hasDirectives.hasOwnProperty(name)) {
|
|
|
|
|
for(var directive, directives = $injector.get(name + Suffix),
|
2012-05-03 07:15:07 +00:00
|
|
|
i = 0, ii = directives.length; i<ii; i++) {
|
2011-11-29 20:11:32 +00:00
|
|
|
try {
|
|
|
|
|
directive = directives[i];
|
2012-01-28 00:18:16 +00:00
|
|
|
if ( (maxPriority === undefined || maxPriority > directive.priority) &&
|
|
|
|
|
directive.restrict.indexOf(location) != -1) {
|
2013-05-24 19:41:38 +00:00
|
|
|
if (startAttrName) {
|
|
|
|
|
directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
|
|
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
tDirectives.push(directive);
|
2013-05-24 19:41:38 +00:00
|
|
|
match = directive;
|
2011-11-02 23:32:46 +00:00
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
} catch(e) { $exceptionHandler(e); }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return match;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* When the element is replaced with HTML template then the new attributes
|
|
|
|
|
* on the template need to be merged with the existing attributes in the DOM.
|
|
|
|
|
* The desired effect is to have both of the attributes present.
|
|
|
|
|
*
|
|
|
|
|
* @param {object} dst destination attributes (original DOM)
|
|
|
|
|
* @param {object} src source attributes (from the directive template)
|
|
|
|
|
*/
|
2013-07-03 00:15:24 +00:00
|
|
|
function mergeTemplateAttributes(dst, src) {
|
2011-11-29 20:11:32 +00:00
|
|
|
var srcAttr = src.$attr,
|
|
|
|
|
dstAttr = dst.$attr,
|
2012-05-03 07:15:07 +00:00
|
|
|
$element = dst.$$element;
|
2012-06-06 14:23:07 +00:00
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
// reapply the old attributes to the new element
|
|
|
|
|
forEach(dst, function(value, key) {
|
2013-07-03 00:15:24 +00:00
|
|
|
if (key.charAt(0) != '$') {
|
2012-03-20 00:26:05 +00:00
|
|
|
if (src[key]) {
|
|
|
|
|
value += (key === 'style' ? ';' : ' ') + src[key];
|
|
|
|
|
}
|
2012-03-23 20:04:52 +00:00
|
|
|
dst.$set(key, value, true, srcAttr[key]);
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
});
|
2012-06-06 14:23:07 +00:00
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
// copy the new attributes on the old attrs object
|
|
|
|
|
forEach(src, function(value, key) {
|
|
|
|
|
if (key == 'class') {
|
2012-05-03 07:15:07 +00:00
|
|
|
safeAddClass($element, value);
|
2012-06-06 14:23:07 +00:00
|
|
|
dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value;
|
2011-11-29 20:11:32 +00:00
|
|
|
} else if (key == 'style') {
|
2012-05-03 07:15:07 +00:00
|
|
|
$element.attr('style', $element.attr('style') + ';' + value);
|
2013-07-03 00:15:24 +00:00
|
|
|
} else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) {
|
2011-11-29 20:11:32 +00:00
|
|
|
dst[key] = value;
|
|
|
|
|
dstAttr[key] = srcAttr[key];
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
function compileTemplateUrl(directives, beforeTemplateNodeLinkFn, $compileNode, tAttrs,
|
2013-07-03 00:23:51 +00:00
|
|
|
$rootElement, childTranscludeFn) {
|
2011-11-29 20:11:32 +00:00
|
|
|
var linkQueue = [],
|
2012-05-03 07:15:07 +00:00
|
|
|
afterTemplateNodeLinkFn,
|
|
|
|
|
afterTemplateChildLinkFn,
|
2012-05-04 04:50:55 +00:00
|
|
|
beforeTemplateCompileNode = $compileNode[0],
|
2012-05-03 07:15:07 +00:00
|
|
|
origAsyncDirective = directives.shift(),
|
2011-11-29 20:11:32 +00:00
|
|
|
// The fact that we have to copy and patch the directive seems wrong!
|
2012-05-03 07:15:07 +00:00
|
|
|
derivedSyncDirective = extend({}, origAsyncDirective, {
|
2013-07-03 00:23:51 +00:00
|
|
|
controller: null, templateUrl: null, transclude: null, scope: null, replace: null
|
2013-02-21 20:56:40 +00:00
|
|
|
}),
|
|
|
|
|
templateUrl = (isFunction(origAsyncDirective.templateUrl))
|
|
|
|
|
? origAsyncDirective.templateUrl($compileNode, tAttrs)
|
|
|
|
|
: origAsyncDirective.templateUrl;
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
$compileNode.html('');
|
2011-11-29 20:11:32 +00:00
|
|
|
|
feat($sce): new $sce service for Strict Contextual Escaping.
$sce is a service that provides Strict Contextual Escaping services to AngularJS.
Strict Contextual Escaping
--------------------------
Strict Contextual Escaping (SCE) is a mode in which AngularJS requires
bindings in certain contexts to result in a value that is marked as safe
to use for that context One example of such a context is binding
arbitrary html controlled by the user via ng-bind-html-unsafe. We
refer to these contexts as privileged or SCE contexts.
As of version 1.2, Angular ships with SCE enabled by default.
Note: When enabled (the default), IE8 in quirks mode is not supported.
In this mode, IE8 allows one to execute arbitrary javascript by the use
of the expression() syntax. Refer
http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx
to learn more about them. You can ensure your document is in standards
mode and not quirks mode by adding <!doctype html> to the top of your
HTML document.
SCE assists in writing code in way that (a) is secure by default and (b)
makes auditing for security vulnerabilities such as XSS, clickjacking,
etc. a lot easier.
Here's an example of a binding in a privileged context:
<input ng-model="userHtml">
<div ng-bind-html-unsafe="{{userHtml}}">
Notice that ng-bind-html-unsafe is bound to {{userHtml}} controlled by
the user. With SCE disabled, this application allows the user to render
arbitrary HTML into the DIV. In a more realistic example, one may be
rendering user comments, blog articles, etc. via bindings. (HTML is
just one example of a context where rendering user controlled input
creates security vulnerabilities.)
For the case of HTML, you might use a library, either on the client side, or on the server side,
to sanitize unsafe HTML before binding to the value and rendering it in the document.
How would you ensure that every place that used these types of bindings was bound to a value that
was sanitized by your library (or returned as safe for rendering by your server?) How can you
ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
properties/fields and forgot to update the binding to the sanitized value?
To be secure by default, you want to ensure that any such bindings are disallowed unless you can
determine that something explicitly says it's safe to use a value for binding in that
context. You can then audit your code (a simple grep would do) to ensure that this is only done
for those values that you can easily tell are safe - because they were received from your server,
sanitized by your library, etc. You can organize your codebase to help with this - perhaps
allowing only the files in a specific directory to do this. Ensuring that the internal API
exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task.
In the case of AngularJS' SCE service, one uses $sce.trustAs (and
shorthand methods such as $sce.trustAsHtml, etc.) to obtain values that
will be accepted by SCE / privileged contexts.
In privileged contexts, directives and code will bind to the result of
$sce.getTrusted(context, value) rather than to the value directly.
Directives use $sce.parseAs rather than $parse to watch attribute
bindings, which performs the $sce.getTrusted behind the scenes on
non-constant literals.
As an example, ngBindHtmlUnsafe uses $sce.parseAsHtml(binding
expression). Here's the actual code (slightly simplified):
var ngBindHtmlUnsafeDirective = ['$sce', function($sce) {
return function(scope, element, attr) {
scope.$watch($sce.parseAsHtml(attr.ngBindHtmlUnsafe), function(value) {
element.html(value || '');
});
};
}];
Impact on loading templates
---------------------------
This applies both to the ng-include directive as well as templateUrl's
specified by directives.
By default, Angular only loads templates from the same domain and
protocol as the application document. This is done by calling
$sce.getTrustedResourceUrl on the template URL. To load templates from
other domains and/or protocols, you may either either whitelist them or
wrap it into a trusted value.
*Please note*:
The browser's Same Origin Policy and Cross-Origin Resource Sharing
(CORS) policy apply in addition to this and may further restrict whether
the template is successfully loaded. This means that without the right
CORS policy, loading templates from a different domain won't work on all
browsers. Also, loading templates from file:// URL does not work on
some browsers.
This feels like too much overhead for the developer?
----------------------------------------------------
It's important to remember that SCE only applies to interpolation expressions.
If your expressions are constant literals, they're automatically trusted
and you don't need to call $sce.trustAs on them.
e.g. <div ng-html-bind-unsafe="'<b>implicitly trusted</b>'"></div> just works.
Additionally, a[href] and img[src] automatically sanitize their URLs and
do not pass them through $sce.getTrusted. SCE doesn't play a role here.
The included $sceDelegate comes with sane defaults to allow you to load
templates in ng-include from your application's domain without having to
even know about SCE. It blocks loading templates from other domains or
loading templates over http from an https served document. You can
change these by setting your own custom whitelists and blacklists for
matching such URLs.
This significantly reduces the overhead. It is far easier to pay the
small overhead and have an application that's secure and can be audited
to verify that with much more ease than bolting security onto an
application later.
2013-05-14 21:51:39 +00:00
|
|
|
$http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}).
|
2011-11-29 20:11:32 +00:00
|
|
|
success(function(content) {
|
2012-05-04 08:24:46 +00:00
|
|
|
var compileNode, tempTemplateAttrs, $template;
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-08-11 07:13:10 +00:00
|
|
|
content = denormalizeTemplate(content);
|
|
|
|
|
|
2013-07-03 00:23:51 +00:00
|
|
|
if (origAsyncDirective.replace) {
|
2012-05-04 08:24:46 +00:00
|
|
|
$template = jqLite('<div>' + trim(content) + '</div>').contents();
|
|
|
|
|
compileNode = $template[0];
|
|
|
|
|
|
|
|
|
|
if ($template.length != 1 || compileNode.nodeType !== 1) {
|
2013-06-08 01:24:30 +00:00
|
|
|
throw $compileMinErr('tplrt', "Template for directive '{0}' must have exactly one root element. {1}",
|
2013-05-24 18:00:14 +00:00
|
|
|
origAsyncDirective.name, templateUrl);
|
2012-05-04 08:24:46 +00:00
|
|
|
}
|
|
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
tempTemplateAttrs = {$attr: {}};
|
2012-05-03 07:15:07 +00:00
|
|
|
replaceWith($rootElement, $compileNode, compileNode);
|
|
|
|
|
collectDirectives(compileNode, directives, tempTemplateAttrs);
|
2013-07-03 00:15:24 +00:00
|
|
|
mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
|
2011-11-29 20:11:32 +00:00
|
|
|
} else {
|
2012-05-04 04:50:55 +00:00
|
|
|
compileNode = beforeTemplateCompileNode;
|
2012-05-03 07:15:07 +00:00
|
|
|
$compileNode.html(content);
|
2011-11-02 23:32:46 +00:00
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
directives.unshift(derivedSyncDirective);
|
2013-07-03 00:23:51 +00:00
|
|
|
|
|
|
|
|
afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, childTranscludeFn, $compileNode, origAsyncDirective);
|
2013-03-13 23:29:26 +00:00
|
|
|
forEach($rootElement, function(node, i) {
|
|
|
|
|
if (node == compileNode) {
|
|
|
|
|
$rootElement[i] = $compileNode[0];
|
|
|
|
|
}
|
|
|
|
|
});
|
2013-01-22 15:59:09 +00:00
|
|
|
afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
|
2011-11-29 20:11:32 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
while(linkQueue.length) {
|
2013-02-26 09:22:12 +00:00
|
|
|
var scope = linkQueue.shift(),
|
|
|
|
|
beforeTemplateLinkNode = linkQueue.shift(),
|
|
|
|
|
linkRootElement = linkQueue.shift(),
|
|
|
|
|
controller = linkQueue.shift(),
|
2013-03-13 23:29:26 +00:00
|
|
|
linkNode = $compileNode[0];
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-05-04 04:50:55 +00:00
|
|
|
if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
|
2011-11-29 20:11:32 +00:00
|
|
|
// it was cloned therefore we have to clone as well.
|
2012-05-03 07:15:07 +00:00
|
|
|
linkNode = JQLiteClone(compileNode);
|
2012-05-04 04:50:55 +00:00
|
|
|
replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
|
2012-05-03 04:08:02 +00:00
|
|
|
}
|
|
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
afterTemplateNodeLinkFn(function() {
|
|
|
|
|
beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, controller);
|
|
|
|
|
}, scope, linkNode, $rootElement, controller);
|
2011-11-02 23:32:46 +00:00
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
linkQueue = null;
|
|
|
|
|
}).
|
|
|
|
|
error(function(response, code, headers, config) {
|
2013-06-08 01:24:30 +00:00
|
|
|
throw $compileMinErr('tpload', 'Failed to load template: {0}', config.url);
|
2011-11-29 20:11:32 +00:00
|
|
|
});
|
2011-11-02 23:32:46 +00:00
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, controller) {
|
2011-11-29 20:11:32 +00:00
|
|
|
if (linkQueue) {
|
|
|
|
|
linkQueue.push(scope);
|
|
|
|
|
linkQueue.push(node);
|
|
|
|
|
linkQueue.push(rootElement);
|
2012-01-28 00:18:16 +00:00
|
|
|
linkQueue.push(controller);
|
2011-11-29 20:11:32 +00:00
|
|
|
} else {
|
2012-05-03 07:15:07 +00:00
|
|
|
afterTemplateNodeLinkFn(function() {
|
|
|
|
|
beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, controller);
|
2012-01-28 00:18:16 +00:00
|
|
|
}, scope, node, rootElement, controller);
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sorting function for bound directives.
|
|
|
|
|
*/
|
|
|
|
|
function byPriority(a, b) {
|
|
|
|
|
return b.priority - a.priority;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function assertNoDuplicate(what, previousDirective, directive, element) {
|
|
|
|
|
if (previousDirective) {
|
2013-06-08 01:24:30 +00:00
|
|
|
throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}',
|
2013-05-24 18:00:14 +00:00
|
|
|
previousDirective.name, directive.name, what, startingTag(element));
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function addTextInterpolateDirective(directives, text) {
|
|
|
|
|
var interpolateFn = $interpolate(text, true);
|
|
|
|
|
if (interpolateFn) {
|
|
|
|
|
directives.push({
|
|
|
|
|
priority: 0,
|
2012-10-25 07:56:34 +00:00
|
|
|
compile: valueFn(function textInterpolateLinkFn(scope, node) {
|
2011-11-29 20:11:32 +00:00
|
|
|
var parent = node.parent(),
|
|
|
|
|
bindings = parent.data('$binding') || [];
|
|
|
|
|
bindings.push(interpolateFn);
|
2012-03-12 23:49:28 +00:00
|
|
|
safeAddClass(parent.data('$binding', bindings), 'ng-binding');
|
2012-07-06 09:53:40 +00:00
|
|
|
scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
|
2011-11-29 20:11:32 +00:00
|
|
|
node[0].nodeValue = value;
|
2011-11-02 23:32:46 +00:00
|
|
|
});
|
2011-11-29 20:11:32 +00:00
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
feat($sce): new $sce service for Strict Contextual Escaping.
$sce is a service that provides Strict Contextual Escaping services to AngularJS.
Strict Contextual Escaping
--------------------------
Strict Contextual Escaping (SCE) is a mode in which AngularJS requires
bindings in certain contexts to result in a value that is marked as safe
to use for that context One example of such a context is binding
arbitrary html controlled by the user via ng-bind-html-unsafe. We
refer to these contexts as privileged or SCE contexts.
As of version 1.2, Angular ships with SCE enabled by default.
Note: When enabled (the default), IE8 in quirks mode is not supported.
In this mode, IE8 allows one to execute arbitrary javascript by the use
of the expression() syntax. Refer
http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx
to learn more about them. You can ensure your document is in standards
mode and not quirks mode by adding <!doctype html> to the top of your
HTML document.
SCE assists in writing code in way that (a) is secure by default and (b)
makes auditing for security vulnerabilities such as XSS, clickjacking,
etc. a lot easier.
Here's an example of a binding in a privileged context:
<input ng-model="userHtml">
<div ng-bind-html-unsafe="{{userHtml}}">
Notice that ng-bind-html-unsafe is bound to {{userHtml}} controlled by
the user. With SCE disabled, this application allows the user to render
arbitrary HTML into the DIV. In a more realistic example, one may be
rendering user comments, blog articles, etc. via bindings. (HTML is
just one example of a context where rendering user controlled input
creates security vulnerabilities.)
For the case of HTML, you might use a library, either on the client side, or on the server side,
to sanitize unsafe HTML before binding to the value and rendering it in the document.
How would you ensure that every place that used these types of bindings was bound to a value that
was sanitized by your library (or returned as safe for rendering by your server?) How can you
ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
properties/fields and forgot to update the binding to the sanitized value?
To be secure by default, you want to ensure that any such bindings are disallowed unless you can
determine that something explicitly says it's safe to use a value for binding in that
context. You can then audit your code (a simple grep would do) to ensure that this is only done
for those values that you can easily tell are safe - because they were received from your server,
sanitized by your library, etc. You can organize your codebase to help with this - perhaps
allowing only the files in a specific directory to do this. Ensuring that the internal API
exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task.
In the case of AngularJS' SCE service, one uses $sce.trustAs (and
shorthand methods such as $sce.trustAsHtml, etc.) to obtain values that
will be accepted by SCE / privileged contexts.
In privileged contexts, directives and code will bind to the result of
$sce.getTrusted(context, value) rather than to the value directly.
Directives use $sce.parseAs rather than $parse to watch attribute
bindings, which performs the $sce.getTrusted behind the scenes on
non-constant literals.
As an example, ngBindHtmlUnsafe uses $sce.parseAsHtml(binding
expression). Here's the actual code (slightly simplified):
var ngBindHtmlUnsafeDirective = ['$sce', function($sce) {
return function(scope, element, attr) {
scope.$watch($sce.parseAsHtml(attr.ngBindHtmlUnsafe), function(value) {
element.html(value || '');
});
};
}];
Impact on loading templates
---------------------------
This applies both to the ng-include directive as well as templateUrl's
specified by directives.
By default, Angular only loads templates from the same domain and
protocol as the application document. This is done by calling
$sce.getTrustedResourceUrl on the template URL. To load templates from
other domains and/or protocols, you may either either whitelist them or
wrap it into a trusted value.
*Please note*:
The browser's Same Origin Policy and Cross-Origin Resource Sharing
(CORS) policy apply in addition to this and may further restrict whether
the template is successfully loaded. This means that without the right
CORS policy, loading templates from a different domain won't work on all
browsers. Also, loading templates from file:// URL does not work on
some browsers.
This feels like too much overhead for the developer?
----------------------------------------------------
It's important to remember that SCE only applies to interpolation expressions.
If your expressions are constant literals, they're automatically trusted
and you don't need to call $sce.trustAs on them.
e.g. <div ng-html-bind-unsafe="'<b>implicitly trusted</b>'"></div> just works.
Additionally, a[href] and img[src] automatically sanitize their URLs and
do not pass them through $sce.getTrusted. SCE doesn't play a role here.
The included $sceDelegate comes with sane defaults to allow you to load
templates in ng-include from your application's domain without having to
even know about SCE. It blocks loading templates from other domains or
loading templates over http from an https served document. You can
change these by setting your own custom whitelists and blacklists for
matching such URLs.
This significantly reduces the overhead. It is far easier to pay the
small overhead and have an application that's secure and can be audited
to verify that with much more ease than bolting security onto an
application later.
2013-05-14 21:51:39 +00:00
|
|
|
function getTrustedContext(node, attrNormalizedName) {
|
2013-06-22 02:00:45 +00:00
|
|
|
// maction[xlink:href] can source SVG. It's not limited to <maction>.
|
|
|
|
|
if (attrNormalizedName == "xlinkHref" ||
|
|
|
|
|
(nodeName_(node) != "IMG" && (attrNormalizedName == "src" ||
|
|
|
|
|
attrNormalizedName == "ngSrc"))) {
|
feat($sce): new $sce service for Strict Contextual Escaping.
$sce is a service that provides Strict Contextual Escaping services to AngularJS.
Strict Contextual Escaping
--------------------------
Strict Contextual Escaping (SCE) is a mode in which AngularJS requires
bindings in certain contexts to result in a value that is marked as safe
to use for that context One example of such a context is binding
arbitrary html controlled by the user via ng-bind-html-unsafe. We
refer to these contexts as privileged or SCE contexts.
As of version 1.2, Angular ships with SCE enabled by default.
Note: When enabled (the default), IE8 in quirks mode is not supported.
In this mode, IE8 allows one to execute arbitrary javascript by the use
of the expression() syntax. Refer
http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx
to learn more about them. You can ensure your document is in standards
mode and not quirks mode by adding <!doctype html> to the top of your
HTML document.
SCE assists in writing code in way that (a) is secure by default and (b)
makes auditing for security vulnerabilities such as XSS, clickjacking,
etc. a lot easier.
Here's an example of a binding in a privileged context:
<input ng-model="userHtml">
<div ng-bind-html-unsafe="{{userHtml}}">
Notice that ng-bind-html-unsafe is bound to {{userHtml}} controlled by
the user. With SCE disabled, this application allows the user to render
arbitrary HTML into the DIV. In a more realistic example, one may be
rendering user comments, blog articles, etc. via bindings. (HTML is
just one example of a context where rendering user controlled input
creates security vulnerabilities.)
For the case of HTML, you might use a library, either on the client side, or on the server side,
to sanitize unsafe HTML before binding to the value and rendering it in the document.
How would you ensure that every place that used these types of bindings was bound to a value that
was sanitized by your library (or returned as safe for rendering by your server?) How can you
ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
properties/fields and forgot to update the binding to the sanitized value?
To be secure by default, you want to ensure that any such bindings are disallowed unless you can
determine that something explicitly says it's safe to use a value for binding in that
context. You can then audit your code (a simple grep would do) to ensure that this is only done
for those values that you can easily tell are safe - because they were received from your server,
sanitized by your library, etc. You can organize your codebase to help with this - perhaps
allowing only the files in a specific directory to do this. Ensuring that the internal API
exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task.
In the case of AngularJS' SCE service, one uses $sce.trustAs (and
shorthand methods such as $sce.trustAsHtml, etc.) to obtain values that
will be accepted by SCE / privileged contexts.
In privileged contexts, directives and code will bind to the result of
$sce.getTrusted(context, value) rather than to the value directly.
Directives use $sce.parseAs rather than $parse to watch attribute
bindings, which performs the $sce.getTrusted behind the scenes on
non-constant literals.
As an example, ngBindHtmlUnsafe uses $sce.parseAsHtml(binding
expression). Here's the actual code (slightly simplified):
var ngBindHtmlUnsafeDirective = ['$sce', function($sce) {
return function(scope, element, attr) {
scope.$watch($sce.parseAsHtml(attr.ngBindHtmlUnsafe), function(value) {
element.html(value || '');
});
};
}];
Impact on loading templates
---------------------------
This applies both to the ng-include directive as well as templateUrl's
specified by directives.
By default, Angular only loads templates from the same domain and
protocol as the application document. This is done by calling
$sce.getTrustedResourceUrl on the template URL. To load templates from
other domains and/or protocols, you may either either whitelist them or
wrap it into a trusted value.
*Please note*:
The browser's Same Origin Policy and Cross-Origin Resource Sharing
(CORS) policy apply in addition to this and may further restrict whether
the template is successfully loaded. This means that without the right
CORS policy, loading templates from a different domain won't work on all
browsers. Also, loading templates from file:// URL does not work on
some browsers.
This feels like too much overhead for the developer?
----------------------------------------------------
It's important to remember that SCE only applies to interpolation expressions.
If your expressions are constant literals, they're automatically trusted
and you don't need to call $sce.trustAs on them.
e.g. <div ng-html-bind-unsafe="'<b>implicitly trusted</b>'"></div> just works.
Additionally, a[href] and img[src] automatically sanitize their URLs and
do not pass them through $sce.getTrusted. SCE doesn't play a role here.
The included $sceDelegate comes with sane defaults to allow you to load
templates in ng-include from your application's domain without having to
even know about SCE. It blocks loading templates from other domains or
loading templates over http from an https served document. You can
change these by setting your own custom whitelists and blacklists for
matching such URLs.
This significantly reduces the overhead. It is far easier to pay the
small overhead and have an application that's secure and can be audited
to verify that with much more ease than bolting security onto an
application later.
2013-05-14 21:51:39 +00:00
|
|
|
return $sce.RESOURCE_URL;
|
2013-06-22 02:00:45 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-03-13 21:42:26 +00:00
|
|
|
function addAttrInterpolateDirective(node, directives, value, name) {
|
2012-03-23 22:53:04 +00:00
|
|
|
var interpolateFn = $interpolate(value, true);
|
2012-03-20 07:00:09 +00:00
|
|
|
|
2012-03-23 22:53:04 +00:00
|
|
|
// no interpolation found -> ignore
|
|
|
|
|
if (!interpolateFn) return;
|
2012-03-20 07:00:09 +00:00
|
|
|
|
2013-02-19 17:55:05 +00:00
|
|
|
|
2013-07-24 23:42:52 +00:00
|
|
|
if (name === "multiple" && nodeName_(node) === "SELECT") {
|
2013-07-26 22:53:45 +00:00
|
|
|
throw $compileMinErr("selmulti", "Binding to the multiple attribute is not supported. Element: {0}",
|
2013-07-24 23:42:52 +00:00
|
|
|
startingTag(node));
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
directives.push({
|
|
|
|
|
priority: 100,
|
2012-10-25 07:56:34 +00:00
|
|
|
compile: valueFn(function attrInterpolateLinkFn(scope, element, attr) {
|
2012-06-04 22:06:02 +00:00
|
|
|
var $$observers = (attr.$$observers || (attr.$$observers = {}));
|
|
|
|
|
|
2013-06-21 20:03:56 +00:00
|
|
|
if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
|
|
|
|
|
throw $compileMinErr('nodomevents',
|
|
|
|
|
"Interpolations for HTML DOM event attributes are disallowed. Please use the ng- " +
|
|
|
|
|
"versions (such as ng-click instead of onclick) instead.");
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-26 10:14:27 +00:00
|
|
|
// we need to interpolate again, in case the attribute value has been updated
|
|
|
|
|
// (e.g. by another directive's compile function)
|
feat($sce): new $sce service for Strict Contextual Escaping.
$sce is a service that provides Strict Contextual Escaping services to AngularJS.
Strict Contextual Escaping
--------------------------
Strict Contextual Escaping (SCE) is a mode in which AngularJS requires
bindings in certain contexts to result in a value that is marked as safe
to use for that context One example of such a context is binding
arbitrary html controlled by the user via ng-bind-html-unsafe. We
refer to these contexts as privileged or SCE contexts.
As of version 1.2, Angular ships with SCE enabled by default.
Note: When enabled (the default), IE8 in quirks mode is not supported.
In this mode, IE8 allows one to execute arbitrary javascript by the use
of the expression() syntax. Refer
http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx
to learn more about them. You can ensure your document is in standards
mode and not quirks mode by adding <!doctype html> to the top of your
HTML document.
SCE assists in writing code in way that (a) is secure by default and (b)
makes auditing for security vulnerabilities such as XSS, clickjacking,
etc. a lot easier.
Here's an example of a binding in a privileged context:
<input ng-model="userHtml">
<div ng-bind-html-unsafe="{{userHtml}}">
Notice that ng-bind-html-unsafe is bound to {{userHtml}} controlled by
the user. With SCE disabled, this application allows the user to render
arbitrary HTML into the DIV. In a more realistic example, one may be
rendering user comments, blog articles, etc. via bindings. (HTML is
just one example of a context where rendering user controlled input
creates security vulnerabilities.)
For the case of HTML, you might use a library, either on the client side, or on the server side,
to sanitize unsafe HTML before binding to the value and rendering it in the document.
How would you ensure that every place that used these types of bindings was bound to a value that
was sanitized by your library (or returned as safe for rendering by your server?) How can you
ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
properties/fields and forgot to update the binding to the sanitized value?
To be secure by default, you want to ensure that any such bindings are disallowed unless you can
determine that something explicitly says it's safe to use a value for binding in that
context. You can then audit your code (a simple grep would do) to ensure that this is only done
for those values that you can easily tell are safe - because they were received from your server,
sanitized by your library, etc. You can organize your codebase to help with this - perhaps
allowing only the files in a specific directory to do this. Ensuring that the internal API
exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task.
In the case of AngularJS' SCE service, one uses $sce.trustAs (and
shorthand methods such as $sce.trustAsHtml, etc.) to obtain values that
will be accepted by SCE / privileged contexts.
In privileged contexts, directives and code will bind to the result of
$sce.getTrusted(context, value) rather than to the value directly.
Directives use $sce.parseAs rather than $parse to watch attribute
bindings, which performs the $sce.getTrusted behind the scenes on
non-constant literals.
As an example, ngBindHtmlUnsafe uses $sce.parseAsHtml(binding
expression). Here's the actual code (slightly simplified):
var ngBindHtmlUnsafeDirective = ['$sce', function($sce) {
return function(scope, element, attr) {
scope.$watch($sce.parseAsHtml(attr.ngBindHtmlUnsafe), function(value) {
element.html(value || '');
});
};
}];
Impact on loading templates
---------------------------
This applies both to the ng-include directive as well as templateUrl's
specified by directives.
By default, Angular only loads templates from the same domain and
protocol as the application document. This is done by calling
$sce.getTrustedResourceUrl on the template URL. To load templates from
other domains and/or protocols, you may either either whitelist them or
wrap it into a trusted value.
*Please note*:
The browser's Same Origin Policy and Cross-Origin Resource Sharing
(CORS) policy apply in addition to this and may further restrict whether
the template is successfully loaded. This means that without the right
CORS policy, loading templates from a different domain won't work on all
browsers. Also, loading templates from file:// URL does not work on
some browsers.
This feels like too much overhead for the developer?
----------------------------------------------------
It's important to remember that SCE only applies to interpolation expressions.
If your expressions are constant literals, they're automatically trusted
and you don't need to call $sce.trustAs on them.
e.g. <div ng-html-bind-unsafe="'<b>implicitly trusted</b>'"></div> just works.
Additionally, a[href] and img[src] automatically sanitize their URLs and
do not pass them through $sce.getTrusted. SCE doesn't play a role here.
The included $sceDelegate comes with sane defaults to allow you to load
templates in ng-include from your application's domain without having to
even know about SCE. It blocks loading templates from other domains or
loading templates over http from an https served document. You can
change these by setting your own custom whitelists and blacklists for
matching such URLs.
This significantly reduces the overhead. It is far easier to pay the
small overhead and have an application that's secure and can be audited
to verify that with much more ease than bolting security onto an
application later.
2013-05-14 21:51:39 +00:00
|
|
|
interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name));
|
2013-02-26 10:14:27 +00:00
|
|
|
|
|
|
|
|
// if attribute was updated so that there is no interpolation going on we don't want to
|
|
|
|
|
// register any observers
|
|
|
|
|
if (!interpolateFn) return;
|
2012-03-23 22:53:04 +00:00
|
|
|
|
2012-11-19 10:01:53 +00:00
|
|
|
attr[name] = interpolateFn(scope);
|
2012-06-04 22:06:02 +00:00
|
|
|
($$observers[name] || ($$observers[name] = [])).$$inter = true;
|
2012-06-06 20:58:10 +00:00
|
|
|
(attr.$$observers && attr.$$observers[name].$$scope || scope).
|
2012-07-06 09:53:40 +00:00
|
|
|
$watch(interpolateFn, function interpolateFnWatchAction(value) {
|
2012-06-06 20:58:10 +00:00
|
|
|
attr.$set(name, value);
|
|
|
|
|
});
|
2012-03-23 22:53:04 +00:00
|
|
|
})
|
2011-11-29 20:11:32 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This is a special jqLite.replaceWith, which can replace items which
|
|
|
|
|
* have no parents, provided that the containing jqLite collection is provided.
|
|
|
|
|
*
|
2012-05-03 07:15:07 +00:00
|
|
|
* @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
|
2011-11-29 20:11:32 +00:00
|
|
|
* in the root of the tree.
|
2013-05-24 19:41:38 +00:00
|
|
|
* @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep the shell,
|
2011-11-29 20:11:32 +00:00
|
|
|
* but replace its DOM node reference.
|
|
|
|
|
* @param {Node} newNode The new DOM node.
|
|
|
|
|
*/
|
2013-05-24 19:41:38 +00:00
|
|
|
function replaceWith($rootElement, elementsToRemove, newNode) {
|
|
|
|
|
var firstElementToRemove = elementsToRemove[0],
|
|
|
|
|
removeCount = elementsToRemove.length,
|
|
|
|
|
parent = firstElementToRemove.parentNode,
|
2011-11-29 20:11:32 +00:00
|
|
|
i, ii;
|
|
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
if ($rootElement) {
|
|
|
|
|
for(i = 0, ii = $rootElement.length; i < ii; i++) {
|
2013-05-24 19:41:38 +00:00
|
|
|
if ($rootElement[i] == firstElementToRemove) {
|
|
|
|
|
$rootElement[i++] = newNode;
|
|
|
|
|
for (var j = i, j2 = j + removeCount - 1,
|
|
|
|
|
jj = $rootElement.length;
|
|
|
|
|
j < jj; j++, j2++) {
|
|
|
|
|
if (j2 < jj) {
|
|
|
|
|
$rootElement[j] = $rootElement[j2];
|
|
|
|
|
} else {
|
|
|
|
|
delete $rootElement[j];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$rootElement.length -= removeCount - 1;
|
2012-05-04 04:49:06 +00:00
|
|
|
break;
|
2011-11-02 23:32:46 +00:00
|
|
|
}
|
|
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
2012-05-04 04:49:06 +00:00
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
if (parent) {
|
2013-05-24 19:41:38 +00:00
|
|
|
parent.replaceChild(newNode, firstElementToRemove);
|
|
|
|
|
}
|
|
|
|
|
var fragment = document.createDocumentFragment();
|
|
|
|
|
fragment.appendChild(firstElementToRemove);
|
|
|
|
|
newNode[jqLite.expando] = firstElementToRemove[jqLite.expando];
|
|
|
|
|
for (var k = 1, kk = elementsToRemove.length; k < kk; k++) {
|
|
|
|
|
var element = elementsToRemove[k];
|
|
|
|
|
jqLite(element).remove(); // must do this way to clean up expando
|
|
|
|
|
fragment.appendChild(element);
|
|
|
|
|
delete elementsToRemove[k];
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
2012-05-04 04:49:06 +00:00
|
|
|
|
2013-05-24 19:41:38 +00:00
|
|
|
elementsToRemove[0] = newNode;
|
|
|
|
|
elementsToRemove.length = 1
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
2012-02-15 19:34:56 +00:00
|
|
|
}];
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i;
|
|
|
|
|
/**
|
|
|
|
|
* Converts all accepted directives format into proper directive name.
|
|
|
|
|
* All of these will become 'myDirective':
|
|
|
|
|
* my:DiRective
|
|
|
|
|
* my-directive
|
|
|
|
|
* x-my-directive
|
|
|
|
|
* data-my:directive
|
|
|
|
|
*
|
|
|
|
|
* Also there is special case for Moz prefix starting with upper case letter.
|
|
|
|
|
* @param name Name to normalize
|
|
|
|
|
*/
|
|
|
|
|
function directiveNormalize(name) {
|
|
|
|
|
return camelCase(name.replace(PREFIX_REGEXP, ''));
|
2010-03-30 21:55:04 +00:00
|
|
|
}
|
2012-01-28 00:18:16 +00:00
|
|
|
|
2012-03-01 19:28:50 +00:00
|
|
|
/**
|
|
|
|
|
* @ngdoc object
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.$compile.directive.Attributes
|
2012-03-01 19:28:50 +00:00
|
|
|
* @description
|
|
|
|
|
*
|
|
|
|
|
* A shared object between directive compile / linking functions which contains normalized DOM element
|
|
|
|
|
* attributes. The the values reflect current binding state `{{ }}`. The normalization is needed
|
|
|
|
|
* since all of these are treated as equivalent in Angular:
|
|
|
|
|
*
|
|
|
|
|
* <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @ngdoc property
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.$compile.directive.Attributes#$attr
|
|
|
|
|
* @propertyOf ng.$compile.directive.Attributes
|
2012-03-01 19:28:50 +00:00
|
|
|
* @returns {object} A map of DOM element attribute names to the normalized name. This is
|
|
|
|
|
* needed to do reverse lookup from normalized name back to actual name.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @ngdoc function
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.$compile.directive.Attributes#$set
|
|
|
|
|
* @methodOf ng.$compile.directive.Attributes
|
2012-03-01 19:28:50 +00:00
|
|
|
* @function
|
|
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* Set DOM element attribute value.
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* @param {string} name Normalized element attribute name of the property to modify. The name is
|
2012-06-12 06:49:24 +00:00
|
|
|
* revers translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
|
2012-03-01 19:28:50 +00:00
|
|
|
* property to the original name.
|
2013-02-26 10:14:27 +00:00
|
|
|
* @param {string} value Value to set the attribute to. The value can be an interpolated string.
|
2012-03-01 19:28:50 +00:00
|
|
|
*/
|
|
|
|
|
|
2012-01-28 00:18:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Closure compiler type information
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
function nodesetLinkingFn(
|
|
|
|
|
/* angular.Scope */ scope,
|
|
|
|
|
/* NodeList */ nodeList,
|
|
|
|
|
/* Element */ rootElement,
|
|
|
|
|
/* function(Function) */ boundTranscludeFn
|
|
|
|
|
){}
|
|
|
|
|
|
|
|
|
|
function directiveLinkingFn(
|
|
|
|
|
/* nodesetLinkingFn */ nodesetLinkingFn,
|
|
|
|
|
/* angular.Scope */ scope,
|
|
|
|
|
/* Node */ node,
|
|
|
|
|
/* Element */ rootElement,
|
|
|
|
|
/* function(Function) */ boundTranscludeFn
|
|
|
|
|
){}
|