fix(modules): allow modules to be loaded in any order when using angular-loader

Some modules used to assume that the angular helpers would always be available when their script was
executed. This could be a problem when using `angular-loader` and the module file happened to get
loaded before the core `angular.js` file.
This commit fixes the issue by delaying the access to angular helpers, until they are guaranteed to
be available.

Affected modules:
- `ngAnimate`
- `ngMessageFormat`
- `ngMessages`
- `ngRoute`
- `ngSanitize`

Fixes #9140

Closes #14794
This commit is contained in:
Georgios Kalpakas
2016-06-16 00:09:01 +03:00
parent 4585b939ea
commit d406a15e8c
19 changed files with 532 additions and 430 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ module.exports = function(config) {
sharedConfig(config, {testName: 'AngularJS: modules', logFile: 'karma-modules.log'});
config.set({
files: angularFiles.mergeFilesFor('karmaModules', 'angularSrcModules'),
files: angularFiles.mergeFilesFor('karmaModules'),
junitReporter: {
outputFile: 'test_out/modules.xml',
+67 -54
View File
@@ -6,68 +6,81 @@
"window": false,
"angular": false,
"noop": false,
"copy": false,
"forEach": false,
"extend": false,
"jqLite": false,
"isArray": false,
"isString": false,
"isObject": false,
"isUndefined": false,
"isDefined": false,
"isFunction": false,
"isElement": false,
/* ng helpers */
"copy": true,
"extend": true,
"forEach": true,
"isArray": true,
"isDefined": true,
"isElement": true,
"isFunction": true,
"isObject": true,
"isString": true,
"isUndefined": true,
"jqLite": true,
"noop": true,
"ELEMENT_NODE": false,
"COMMENT_NODE": false,
"NG_ANIMATE_CLASSNAME": false,
"NG_ANIMATE_CHILDREN_DATA": false,
/* ngAnimate constants */
"COMMENT_NODE": true,
"ELEMENT_NODE": true,
"NG_ANIMATE_CLASSNAME": true,
"NG_ANIMATE_CHILDREN_DATA": true,
"ADD_CLASS_SUFFIX": false,
"REMOVE_CLASS_SUFFIX": false,
"EVENT_CLASS_PREFIX": false,
"ACTIVE_CLASS_SUFFIX": false,
"PREPARE_CLASS_SUFFIX": false,
/* ngAnimate className constants */
"ADD_CLASS_SUFFIX": true,
"REMOVE_CLASS_SUFFIX": true,
"EVENT_CLASS_PREFIX": true,
"ACTIVE_CLASS_SUFFIX": true,
"PREPARE_CLASS_SUFFIX": true,
"TRANSITION_DURATION_PROP": false,
"TRANSITION_DELAY_PROP": false,
"TRANSITION_PROP": false,
"PROPERTY_KEY": false,
"DURATION_KEY": false,
"DELAY_KEY": false,
"TIMING_KEY": false,
"ANIMATION_DURATION_PROP": false,
"ANIMATION_DELAY_PROP": false,
"ANIMATION_PROP": false,
"ANIMATION_ITERATION_COUNT_KEY": false,
"SAFE_FAST_FORWARD_DURATION_VALUE": false,
"TRANSITIONEND_EVENT": false,
"ANIMATIONEND_EVENT": false,
/* ngAnimate CSS constants */
"ANIMATION_DELAY_PROP": true,
"ANIMATION_DURATION_PROP": true,
"ANIMATION_ITERATION_COUNT_KEY": true,
"ANIMATION_PROP": true,
"ANIMATIONEND_EVENT": true,
"DELAY_KEY": true,
"DURATION_KEY": true,
"PROPERTY_KEY": true,
"SAFE_FAST_FORWARD_DURATION_VALUE": true,
"TIMING_KEY": true,
"TRANSITION_DELAY_PROP": true,
"TRANSITION_DURATION_PROP": true,
"TRANSITION_PROP": true,
"TRANSITIONEND_EVENT": true,
"assertArg": false,
"isPromiseLike": false,
"mergeClasses": false,
"mergeAnimationDetails": false,
"prepareAnimationOptions": false,
"applyAnimationStyles": false,
"applyAnimationFromStyles": false,
"applyAnimationToStyles": false,
/* ngAnimate helpers */
"applyAnimationClassesFactory": false,
"pendClasses": false,
"normalizeCssProp": false,
"packageStyles": false,
"removeFromArray": false,
"stripCommentsFromElement": false,
"applyAnimationFromStyles": false,
"applyAnimationStyles": false,
"applyAnimationToStyles": false,
"applyGeneratedPreparationClasses": false,
"applyInlineStyle": false,
"assertArg": false,
"blockKeyframeAnimations": false,
"blockTransitions": false,
"clearGeneratedClasses": false,
"concatWithSpace": false,
"extractElementNode": false,
"getDomNode": false,
"mergeAnimationDetails": false,
"mergeClasses": false,
"packageStyles": false,
"pendClasses": false,
"prepareAnimationOptions": false,
"removeFromArray": false,
"stripCommentsFromElement": false,
"applyGeneratedPreparationClasses": false,
"clearGeneratedClasses": false,
"blockTransitions": false,
"blockKeyframeAnimations": false,
"applyInlineStyle": false,
"concatWithSpace": false
/* ngAnimate directives/services */
"ngAnimateSwapDirective": true,
"$$rAFSchedulerFactory": true,
"$$AnimateChildrenDirective": true,
"$$AnimateQueueProvider": true,
"$$AnimationProvider": true,
"$AnimateCssProvider": true,
"$$AnimateCssDriverProvider": true,
"$$AnimateJsProvider": true,
"$$AnimateJsDriverProvider": true
}
}
+1 -1
View File
@@ -82,7 +82,7 @@ var $$AnimateChildrenDirective = ['$interpolate', function($interpolate) {
return {
link: function(scope, element, attrs) {
var val = attrs.ngAnimateChildren;
if (angular.isString(val) && val.length === 0) { //empty attribute
if (isString(val) && val.length === 0) { //empty attribute
element.data(NG_ANIMATE_CHILDREN_DATA, true);
} else {
// Interpolate and set the value, so that it is available to
+1 -1
View File
@@ -241,7 +241,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
},
off: function(event, container, callback) {
if (arguments.length === 1 && !angular.isString(arguments[0])) {
if (arguments.length === 1 && !isString(arguments[0])) {
container = arguments[0];
for (var eventType in callbackRegistry) {
callbackRegistry[eventType] = filterFromRegistry(callbackRegistry[eventType], container);
+29 -15
View File
@@ -1,19 +1,5 @@
'use strict';
/* global angularAnimateModule: true,
ngAnimateSwapDirective,
$$AnimateAsyncRunFactory,
$$rAFSchedulerFactory,
$$AnimateChildrenDirective,
$$AnimateQueueProvider,
$$AnimationProvider,
$AnimateCssProvider,
$$AnimateCssDriverProvider,
$$AnimateJsProvider,
$$AnimateJsDriverProvider,
*/
/**
* @ngdoc module
* @name ngAnimate
@@ -730,6 +716,19 @@
* (Note that you will need to trigger a digest within the callback to get angular to notice any scope-related changes.)
*/
var copy;
var extend;
var forEach;
var isArray;
var isDefined;
var isElement;
var isFunction;
var isObject;
var isString;
var isUndefined;
var jqLite;
var noop;
/**
* @ngdoc service
* @name $animate
@@ -740,7 +739,22 @@
*
* Click here {@link ng.$animate to learn more about animations with `$animate`}.
*/
angular.module('ngAnimate', [])
angular.module('ngAnimate', [], function initAngularHelpers() {
// Access helpers from angular core.
// Do it inside a `config` block to ensure `window.angular` is available.
noop = angular.noop;
copy = angular.copy;
extend = angular.extend;
jqLite = angular.element;
forEach = angular.forEach;
isArray = angular.isArray;
isString = angular.isString;
isObject = angular.isObject;
isUndefined = angular.isUndefined;
isDefined = angular.isDefined;
isFunction = angular.isFunction;
isElement = angular.isElement;
})
.directive('ngAnimateSwap', ngAnimateSwapDirective)
.directive('ngAnimateChildren', $$AnimateChildrenDirective)
+4 -24
View File
@@ -1,19 +1,5 @@
'use strict';
/* jshint ignore:start */
var noop = angular.noop;
var copy = angular.copy;
var extend = angular.extend;
var jqLite = angular.element;
var forEach = angular.forEach;
var isArray = angular.isArray;
var isString = angular.isString;
var isObject = angular.isObject;
var isUndefined = angular.isUndefined;
var isDefined = angular.isDefined;
var isFunction = angular.isFunction;
var isElement = angular.isElement;
var ELEMENT_NODE = 1;
var COMMENT_NODE = 8;
@@ -38,7 +24,7 @@ var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMA
// Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
// therefore there is no reason to test anymore for other vendor prefixes:
// http://caniuse.com/#search=transition
if (isUndefined(window.ontransitionend) && isDefined(window.onwebkittransitionend)) {
if ((window.ontransitionend === void 0) && (window.onwebkittransitionend !== void 0)) {
CSS_PREFIX = '-webkit-';
TRANSITION_PROP = 'WebkitTransition';
TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
@@ -47,7 +33,7 @@ if (isUndefined(window.ontransitionend) && isDefined(window.onwebkittransitionen
TRANSITIONEND_EVENT = 'transitionend';
}
if (isUndefined(window.onanimationend) && isDefined(window.onwebkitanimationend)) {
if ((window.onanimationend === void 0) && (window.onwebkitanimationend !== void 0)) {
CSS_PREFIX = '-webkit-';
ANIMATION_PROP = 'WebkitAnimation';
ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
@@ -69,10 +55,6 @@ var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY;
var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY;
var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY;
var isPromiseLike = function(p) {
return p && p.then ? true : false;
};
var ngMinErr = angular.$$minErr('ng');
function assertArg(arg, name, reason) {
if (!arg) {
@@ -128,7 +110,6 @@ function stripCommentsFromElement(element) {
switch (element.length) {
case 0:
return element;
break;
case 1:
// there is no point of stripping anything if the element
@@ -141,7 +122,6 @@ function stripCommentsFromElement(element) {
default:
return jqLite(extractElementNode(element));
break;
}
}
@@ -182,7 +162,7 @@ function applyAnimationClassesFactory($$jqLite) {
$$removeClass($$jqLite, element, options.removeClass);
options.removeClass = null;
}
}
};
}
function prepareAnimationOptions(options) {
@@ -318,7 +298,7 @@ function resolveElementClasses(existing, toAdd, toRemove) {
}
function getDomNode(element) {
return (element instanceof angular.element) ? element[0] : element;
return (element instanceof jqLite) ? element[0] : element;
}
function applyGeneratedPreparationClasses(element, event, options) {
+3 -5
View File
@@ -5,11 +5,9 @@
// This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
// constructs incompatible with that mode.
var $interpolateMinErr = window['angular']['$interpolateMinErr'];
var noop = window['angular']['noop'],
isFunction = window['angular']['isFunction'],
toJson = window['angular']['toJson'];
/* global isFunction: false */
/* global noop: false */
/* global toJson: false */
function stringify(value) {
if (value == null /* null/undefined */) { return ''; }
+14 -1
View File
@@ -5,7 +5,10 @@
// This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
// constructs incompatible with that mode.
/* global $interpolateMinErr: false */
/* global $interpolateMinErr: true */
/* global isFunction: true */
/* global noop: true */
/* global toJson: true */
/* global MessageFormatParser: false */
/* global stringify: false */
@@ -207,8 +210,18 @@ var $$interpolateDecorator = ['$$messageFormat', '$delegate', function $$interpo
return interpolate;
}];
var $interpolateMinErr;
var isFunction;
var noop;
var toJson;
var module = window['angular']['module']('ngMessageFormat', ['ng']);
module['factory']('$$messageFormat', $$MessageFormatFactory);
module['config'](['$provide', function($provide) {
$interpolateMinErr = window['angular']['$interpolateMinErr'];
isFunction = window['angular']['isFunction'];
noop = window['angular']['noop'];
toJson = window['angular']['toJson'];
$provide['decorator']('$interpolate', $$interpolateDecorator);
}]);
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "../../.jshintrc-base",
"globals": {
"window": false,
"angular": false
}
}
+12 -8
View File
@@ -1,12 +1,9 @@
'use strict';
/* jshint ignore:start */
// this code is in the core, but not in angular-messages.js
var isArray = angular.isArray;
var forEach = angular.forEach;
var isString = angular.isString;
var jqLite = angular.element;
/* jshint ignore:end */
var forEach;
var isArray;
var isString;
var jqLite;
/**
* @ngdoc module
@@ -262,7 +259,14 @@ var jqLite = angular.element;
*
* {@link ngAnimate Click here} to learn how to use JavaScript animations or to learn more about ngAnimate.
*/
angular.module('ngMessages', [])
angular.module('ngMessages', [], function initAngularHelpers() {
// Access helpers from angular core.
// Do it inside a `config` block to ensure `window.angular` is available.
forEach = angular.forEach;
isArray = angular.isArray;
isString = angular.isString;
jqLite = angular.element;
})
/**
* @ngdoc directive
+7 -3
View File
@@ -2,9 +2,10 @@
/* global shallowCopy: false */
// There are necessary for `shallowCopy()` (included via `src/shallowCopy.js`)
var isArray = angular.isArray;
var isObject = angular.isObject;
// There are necessary for `shallowCopy()` (included via `src/shallowCopy.js`).
// They are initialized inside the `$RouteProvider`, to ensure `window.angular` is available.
var isArray;
var isObject;
/**
* @ngdoc module
@@ -41,6 +42,9 @@ var ngRouteModule = angular.module('ngRoute', ['ng']).
* Requires the {@link ngRoute `ngRoute`} module to be installed.
*/
function $RouteProvider() {
isArray = angular.isArray;
isObject = angular.isObject;
function inherit(parent, extra) {
return angular.extend(Object.create(parent), extra);
}
+1 -1
View File
@@ -4,6 +4,6 @@
"window": false,
"angular": false,
"htmlSanitizeWriter": false
"sanitizeText": false
}
}
+6 -5
View File
@@ -1,7 +1,5 @@
'use strict';
/* global sanitizeText: false */
/**
* @ngdoc filter
* @name linky
@@ -135,6 +133,9 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
MAILTO_REGEXP = /^mailto:/i;
var linkyMinErr = angular.$$minErr('linky');
var isDefined = angular.isDefined;
var isFunction = angular.isFunction;
var isObject = angular.isObject;
var isString = angular.isString;
return function(text, target, attributes) {
@@ -142,8 +143,8 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
if (!isString(text)) throw linkyMinErr('notstring', 'Expected string but received: {0}', text);
var attributesFn =
angular.isFunction(attributes) ? attributes :
angular.isObject(attributes) ? function getAttributesObject() {return attributes;} :
isFunction(attributes) ? attributes :
isObject(attributes) ? function getAttributesObject() {return attributes;} :
function getEmptyAttributesObject() {return {};};
var match;
@@ -181,7 +182,7 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
html.push(key + '="' + linkAttributes[key] + '" ');
}
if (angular.isDefined(target) && !('target' in linkAttributes)) {
if (isDefined(target) && !('target' in linkAttributes)) {
html.push('target="',
target,
'" ');
+329 -309
View File
@@ -12,6 +12,14 @@
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
var $sanitizeMinErr = angular.$$minErr('$sanitize');
var bind;
var extend;
var forEach;
var isDefined;
var lowercase;
var noop;
var htmlParser;
var htmlSanitizeWriter;
/**
* @ngdoc module
@@ -144,7 +152,7 @@ function $SanitizeProvider() {
this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
if (svgEnabled) {
angular.extend(validElements, svgElements);
extend(validElements, svgElements);
}
return function(html) {
var buf = [];
@@ -187,328 +195,340 @@ function $SanitizeProvider() {
* without an argument or self for chaining otherwise.
*/
this.enableSvg = function(enableSvg) {
if (angular.isDefined(enableSvg)) {
if (isDefined(enableSvg)) {
svgEnabled = enableSvg;
return this;
} else {
return svgEnabled;
}
};
//////////////////////////////////////////////////////////////////////////////////////////////////
// Private stuff
//////////////////////////////////////////////////////////////////////////////////////////////////
bind = angular.bind;
extend = angular.extend;
forEach = angular.forEach;
isDefined = angular.isDefined;
lowercase = angular.lowercase;
noop = angular.noop;
htmlParser = htmlParserImpl;
htmlSanitizeWriter = htmlSanitizeWriterImpl;
// Regular Expressions for parsing tags and attributes
var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
// Match everything outside of normal chars and " (quote character)
NON_ALPHANUMERIC_REGEXP = /([^\#-~ |!])/g;
// Good source of info about elements and attributes
// http://dev.w3.org/html5/spec/Overview.html#semantics
// http://simon.html5.org/html-elements
// Safe Void Elements - HTML5
// http://dev.w3.org/html5/spec/Overview.html#void-elements
var voidElements = toMap("area,br,col,hr,img,wbr");
// Elements that you can, intentionally, leave open (and which close themselves)
// http://dev.w3.org/html5/spec/Overview.html#optional-tags
var optionalEndTagBlockElements = toMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
optionalEndTagInlineElements = toMap("rp,rt"),
optionalEndTagElements = extend({},
optionalEndTagInlineElements,
optionalEndTagBlockElements);
// Safe Block Elements - HTML5
var blockElements = extend({}, optionalEndTagBlockElements, toMap("address,article," +
"aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," +
"h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul"));
// Inline Elements - HTML5
var inlineElements = extend({}, optionalEndTagInlineElements, toMap("a,abbr,acronym,b," +
"bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," +
"samp,small,span,strike,strong,sub,sup,time,tt,u,var"));
// SVG Elements
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements
// Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted.
// They can potentially allow for arbitrary javascript to be executed. See #11290
var svgElements = toMap("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph," +
"hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline," +
"radialGradient,rect,stop,svg,switch,text,title,tspan");
// Blocked Elements (will be stripped)
var blockedElements = toMap("script,style");
var validElements = extend({},
voidElements,
blockElements,
inlineElements,
optionalEndTagElements);
//Attributes that have href and hence need to be sanitized
var uriAttrs = toMap("background,cite,href,longdesc,src,xlink:href");
var htmlAttrs = toMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' +
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' +
'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' +
'valign,value,vspace,width');
// SVG attributes (without "id" and "name" attributes)
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes
var svgAttrs = toMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' +
'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' +
'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' +
'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' +
'height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,' +
'marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,' +
'max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,' +
'path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,' +
'requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,' +
'stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,' +
'stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,' +
'stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,' +
'underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,' +
'width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,' +
'xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan', true);
var validAttrs = extend({},
uriAttrs,
svgAttrs,
htmlAttrs);
function toMap(str, lowercaseKeys) {
var obj = {}, items = str.split(','), i;
for (i = 0; i < items.length; i++) {
obj[lowercaseKeys ? lowercase(items[i]) : items[i]] = true;
}
return obj;
}
var inertBodyElement;
(function(window) {
var doc;
if (window.document && window.document.implementation) {
doc = window.document.implementation.createHTMLDocument("inert");
} else {
throw $sanitizeMinErr('noinert', "Can't create an inert html document");
}
var docElement = doc.documentElement || doc.getDocumentElement();
var bodyElements = docElement.getElementsByTagName('body');
// usually there should be only one body element in the document, but IE doesn't have any, so we need to create one
if (bodyElements.length === 1) {
inertBodyElement = bodyElements[0];
} else {
var html = doc.createElement('html');
inertBodyElement = doc.createElement('body');
html.appendChild(inertBodyElement);
doc.appendChild(html);
}
})(window);
/**
* @example
* htmlParser(htmlString, {
* start: function(tag, attrs) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* });
*
* @param {string} html string
* @param {object} handler
*/
function htmlParserImpl(html, handler) {
if (html === null || html === undefined) {
html = '';
} else if (typeof html !== 'string') {
html = '' + html;
}
inertBodyElement.innerHTML = html;
//mXSS protection
var mXSSAttempts = 5;
do {
if (mXSSAttempts === 0) {
throw $sanitizeMinErr('uinput', "Failed to sanitize html because the input is unstable");
}
mXSSAttempts--;
// strip custom-namespaced attributes on IE<=11
if (window.document.documentMode) {
stripCustomNsAttrs(inertBodyElement);
}
html = inertBodyElement.innerHTML; //trigger mXSS
inertBodyElement.innerHTML = html;
} while (html !== inertBodyElement.innerHTML);
var node = inertBodyElement.firstChild;
while (node) {
switch (node.nodeType) {
case 1: // ELEMENT_NODE
handler.start(node.nodeName.toLowerCase(), attrToMap(node.attributes));
break;
case 3: // TEXT NODE
handler.chars(node.textContent);
break;
}
var nextNode;
if (!(nextNode = node.firstChild)) {
if (node.nodeType === 1) {
handler.end(node.nodeName.toLowerCase());
}
nextNode = node.nextSibling;
if (!nextNode) {
while (nextNode == null) {
node = node.parentNode;
if (node === inertBodyElement) break;
nextNode = node.nextSibling;
if (node.nodeType === 1) {
handler.end(node.nodeName.toLowerCase());
}
}
}
}
node = nextNode;
}
while (node = inertBodyElement.firstChild) {
inertBodyElement.removeChild(node);
}
}
function attrToMap(attrs) {
var map = {};
for (var i = 0, ii = attrs.length; i < ii; i++) {
var attr = attrs[i];
map[attr.name] = attr.value;
}
return map;
}
/**
* Escapes all potentially dangerous characters, so that the
* resulting string can be safely inserted into attribute or
* element text.
* @param value
* @returns {string} escaped text
*/
function encodeEntities(value) {
return value.
replace(/&/g, '&amp;').
replace(SURROGATE_PAIR_REGEXP, function(value) {
var hi = value.charCodeAt(0);
var low = value.charCodeAt(1);
return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
}).
replace(NON_ALPHANUMERIC_REGEXP, function(value) {
return '&#' + value.charCodeAt(0) + ';';
}).
replace(/</g, '&lt;').
replace(/>/g, '&gt;');
}
/**
* create an HTML/XML writer which writes to buffer
* @param {Array} buf use buf.join('') to get out sanitized html string
* @returns {object} in the form of {
* start: function(tag, attrs) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* }
*/
function htmlSanitizeWriterImpl(buf, uriValidator) {
var ignoreCurrentElement = false;
var out = bind(buf, buf.push);
return {
start: function(tag, attrs) {
tag = lowercase(tag);
if (!ignoreCurrentElement && blockedElements[tag]) {
ignoreCurrentElement = tag;
}
if (!ignoreCurrentElement && validElements[tag] === true) {
out('<');
out(tag);
forEach(attrs, function(value, key) {
var lkey = lowercase(key);
var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
if (validAttrs[lkey] === true &&
(uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
out(' ');
out(key);
out('="');
out(encodeEntities(value));
out('"');
}
});
out('>');
}
},
end: function(tag) {
tag = lowercase(tag);
if (!ignoreCurrentElement && validElements[tag] === true && voidElements[tag] !== true) {
out('</');
out(tag);
out('>');
}
if (tag == ignoreCurrentElement) {
ignoreCurrentElement = false;
}
},
chars: function(chars) {
if (!ignoreCurrentElement) {
out(encodeEntities(chars));
}
}
};
}
/**
* When IE9-11 comes across an unknown namespaced attribute e.g. 'xlink:foo' it adds 'xmlns:ns1' attribute to declare
* ns1 namespace and prefixes the attribute with 'ns1' (e.g. 'ns1:xlink:foo'). This is undesirable since we don't want
* to allow any of these custom attributes. This method strips them all.
*
* @param node Root element to process
*/
function stripCustomNsAttrs(node) {
if (node.nodeType === window.Node.ELEMENT_NODE) {
var attrs = node.attributes;
for (var i = 0, l = attrs.length; i < l; i++) {
var attrNode = attrs[i];
var attrName = attrNode.name.toLowerCase();
if (attrName === 'xmlns:ns1' || attrName.lastIndexOf('ns1:', 0) === 0) {
node.removeAttributeNode(attrNode);
i--;
l--;
}
}
}
var nextNode = node.firstChild;
if (nextNode) {
stripCustomNsAttrs(nextNode);
}
nextNode = node.nextSibling;
if (nextNode) {
stripCustomNsAttrs(nextNode);
}
}
}
function sanitizeText(chars) {
var buf = [];
var writer = htmlSanitizeWriter(buf, angular.noop);
var writer = htmlSanitizeWriter(buf, noop);
writer.chars(chars);
return buf.join('');
}
// Regular Expressions for parsing tags and attributes
var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
// Match everything outside of normal chars and " (quote character)
NON_ALPHANUMERIC_REGEXP = /([^\#-~ |!])/g;
// Good source of info about elements and attributes
// http://dev.w3.org/html5/spec/Overview.html#semantics
// http://simon.html5.org/html-elements
// Safe Void Elements - HTML5
// http://dev.w3.org/html5/spec/Overview.html#void-elements
var voidElements = toMap("area,br,col,hr,img,wbr");
// Elements that you can, intentionally, leave open (and which close themselves)
// http://dev.w3.org/html5/spec/Overview.html#optional-tags
var optionalEndTagBlockElements = toMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
optionalEndTagInlineElements = toMap("rp,rt"),
optionalEndTagElements = angular.extend({},
optionalEndTagInlineElements,
optionalEndTagBlockElements);
// Safe Block Elements - HTML5
var blockElements = angular.extend({}, optionalEndTagBlockElements, toMap("address,article," +
"aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," +
"h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul"));
// Inline Elements - HTML5
var inlineElements = angular.extend({}, optionalEndTagInlineElements, toMap("a,abbr,acronym,b," +
"bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," +
"samp,small,span,strike,strong,sub,sup,time,tt,u,var"));
// SVG Elements
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements
// Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted.
// They can potentially allow for arbitrary javascript to be executed. See #11290
var svgElements = toMap("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph," +
"hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline," +
"radialGradient,rect,stop,svg,switch,text,title,tspan");
// Blocked Elements (will be stripped)
var blockedElements = toMap("script,style");
var validElements = angular.extend({},
voidElements,
blockElements,
inlineElements,
optionalEndTagElements);
//Attributes that have href and hence need to be sanitized
var uriAttrs = toMap("background,cite,href,longdesc,src,xlink:href");
var htmlAttrs = toMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' +
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' +
'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' +
'valign,value,vspace,width');
// SVG attributes (without "id" and "name" attributes)
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes
var svgAttrs = toMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' +
'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' +
'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' +
'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' +
'height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,' +
'marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,' +
'max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,' +
'path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,' +
'requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,' +
'stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,' +
'stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,' +
'stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,' +
'underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,' +
'width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,' +
'xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan', true);
var validAttrs = angular.extend({},
uriAttrs,
svgAttrs,
htmlAttrs);
function toMap(str, lowercaseKeys) {
var obj = {}, items = str.split(','), i;
for (i = 0; i < items.length; i++) {
obj[lowercaseKeys ? angular.lowercase(items[i]) : items[i]] = true;
}
return obj;
}
var inertBodyElement;
(function(window) {
var doc;
if (window.document && window.document.implementation) {
doc = window.document.implementation.createHTMLDocument("inert");
} else {
throw $sanitizeMinErr('noinert', "Can't create an inert html document");
}
var docElement = doc.documentElement || doc.getDocumentElement();
var bodyElements = docElement.getElementsByTagName('body');
// usually there should be only one body element in the document, but IE doesn't have any, so we need to create one
if (bodyElements.length === 1) {
inertBodyElement = bodyElements[0];
} else {
var html = doc.createElement('html');
inertBodyElement = doc.createElement('body');
html.appendChild(inertBodyElement);
doc.appendChild(html);
}
})(window);
/**
* @example
* htmlParser(htmlString, {
* start: function(tag, attrs) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* });
*
* @param {string} html string
* @param {object} handler
*/
function htmlParser(html, handler) {
if (html === null || html === undefined) {
html = '';
} else if (typeof html !== 'string') {
html = '' + html;
}
inertBodyElement.innerHTML = html;
//mXSS protection
var mXSSAttempts = 5;
do {
if (mXSSAttempts === 0) {
throw $sanitizeMinErr('uinput', "Failed to sanitize html because the input is unstable");
}
mXSSAttempts--;
// strip custom-namespaced attributes on IE<=11
if (window.document.documentMode) {
stripCustomNsAttrs(inertBodyElement);
}
html = inertBodyElement.innerHTML; //trigger mXSS
inertBodyElement.innerHTML = html;
} while (html !== inertBodyElement.innerHTML);
var node = inertBodyElement.firstChild;
while (node) {
switch (node.nodeType) {
case 1: // ELEMENT_NODE
handler.start(node.nodeName.toLowerCase(), attrToMap(node.attributes));
break;
case 3: // TEXT NODE
handler.chars(node.textContent);
break;
}
var nextNode;
if (!(nextNode = node.firstChild)) {
if (node.nodeType === 1) {
handler.end(node.nodeName.toLowerCase());
}
nextNode = node.nextSibling;
if (!nextNode) {
while (nextNode == null) {
node = node.parentNode;
if (node === inertBodyElement) break;
nextNode = node.nextSibling;
if (node.nodeType === 1) {
handler.end(node.nodeName.toLowerCase());
}
}
}
}
node = nextNode;
}
while (node = inertBodyElement.firstChild) {
inertBodyElement.removeChild(node);
}
}
function attrToMap(attrs) {
var map = {};
for (var i = 0, ii = attrs.length; i < ii; i++) {
var attr = attrs[i];
map[attr.name] = attr.value;
}
return map;
}
/**
* Escapes all potentially dangerous characters, so that the
* resulting string can be safely inserted into attribute or
* element text.
* @param value
* @returns {string} escaped text
*/
function encodeEntities(value) {
return value.
replace(/&/g, '&amp;').
replace(SURROGATE_PAIR_REGEXP, function(value) {
var hi = value.charCodeAt(0);
var low = value.charCodeAt(1);
return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
}).
replace(NON_ALPHANUMERIC_REGEXP, function(value) {
return '&#' + value.charCodeAt(0) + ';';
}).
replace(/</g, '&lt;').
replace(/>/g, '&gt;');
}
/**
* create an HTML/XML writer which writes to buffer
* @param {Array} buf use buf.join('') to get out sanitized html string
* @returns {object} in the form of {
* start: function(tag, attrs) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* }
*/
function htmlSanitizeWriter(buf, uriValidator) {
var ignoreCurrentElement = false;
var out = angular.bind(buf, buf.push);
return {
start: function(tag, attrs) {
tag = angular.lowercase(tag);
if (!ignoreCurrentElement && blockedElements[tag]) {
ignoreCurrentElement = tag;
}
if (!ignoreCurrentElement && validElements[tag] === true) {
out('<');
out(tag);
angular.forEach(attrs, function(value, key) {
var lkey=angular.lowercase(key);
var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
if (validAttrs[lkey] === true &&
(uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
out(' ');
out(key);
out('="');
out(encodeEntities(value));
out('"');
}
});
out('>');
}
},
end: function(tag) {
tag = angular.lowercase(tag);
if (!ignoreCurrentElement && validElements[tag] === true && voidElements[tag] !== true) {
out('</');
out(tag);
out('>');
}
if (tag == ignoreCurrentElement) {
ignoreCurrentElement = false;
}
},
chars: function(chars) {
if (!ignoreCurrentElement) {
out(encodeEntities(chars));
}
}
};
}
/**
* When IE9-11 comes across an unknown namespaced attribute e.g. 'xlink:foo' it adds 'xmlns:ns1' attribute to declare
* ns1 namespace and prefixes the attribute with 'ns1' (e.g. 'ns1:xlink:foo'). This is undesirable since we don't want
* to allow any of these custom attributes. This method strips them all.
*
* @param node Root element to process
*/
function stripCustomNsAttrs(node) {
if (node.nodeType === window.Node.ELEMENT_NODE) {
var attrs = node.attributes;
for (var i = 0, l = attrs.length; i < l; i++) {
var attrNode = attrs[i];
var attrName = attrNode.name.toLowerCase();
if (attrName === 'xmlns:ns1' || attrName.lastIndexOf('ns1:', 0) === 0) {
node.removeAttributeNode(attrNode);
i--;
l--;
}
}
}
var nextNode = node.firstChild;
if (nextNode) {
stripCustomNsAttrs(nextNode);
}
nextNode = node.nextSibling;
if (nextNode) {
stripCustomNsAttrs(nextNode);
}
}
// define ngSanitize module and register $sanitize service
angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
+20
View File
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html ng-app="test">
<div ng-controller="TestCtrl">
<p>{{text}}</p>
</div>
<script src="angular-loader.js"></script>
<script src="angular-touch.js"></script>
<script src="angular-sanitize.js"></script>
<script src="angular-route.js"></script>
<script src="angular-resource.js"></script>
<script src="angular-parse-ext.js"></script>
<script src="angular-messages.js"></script>
<script src="angular-message-format.js"></script>
<script src="angular-cookies.js"></script>
<script src="angular-aria.js"></script>
<script src="angular-animate.js"></script>
<script src="angular.js"></script>
<script src="script.js"></script>
</html>
+16
View File
@@ -0,0 +1,16 @@
angular.
module('test', [
'ngTouch',
'ngSanitize',
'ngRoute',
'ngResource',
'ngParseExt',
'ngMessages',
'ngMessageFormat',
'ngCookies',
'ngAria',
'ngAnimate'
]).
controller('TestCtrl', function TestCtrl($scope) {
$scope.text = 'Hello, world!';
});
+9
View File
@@ -0,0 +1,9 @@
describe('loader', function() {
beforeEach(function() {
loadFixture("loader").andWaitForAngular();
});
it('should not be broken by loading the modules before core', function() {
expect(element(by.binding('text')).getText()).toBe('Hello, world!');
});
});
+4
View File
@@ -29,6 +29,10 @@ describe("ngAnimate $animateCss", function() {
{ timeStamp: Date.now() + ((delay || 1) * 1000), elapsedTime: duration });
}
function isPromiseLike(p) {
return !!(p && p.then);
}
var fakeStyle = {
color: 'blue'
};
-2
View File
@@ -19,7 +19,6 @@ describe('HTML', function() {
describe('htmlParser', function() {
/* global htmlParser */
if (angular.isUndefined(window.htmlParser)) return;
var handler, start, text, comment;
beforeEach(function() {
@@ -309,7 +308,6 @@ describe('HTML', function() {
describe('htmlSanitizerWriter', function() {
/* global htmlSanitizeWriter: false */
if (angular.isUndefined(window.htmlSanitizeWriter)) return;
var writer, html, uriValidator;
beforeEach(function() {