diff --git a/karma-modules.conf.js b/karma-modules.conf.js index aecc0bf865cb..81ae2a069459 100644 --- a/karma-modules.conf.js +++ b/karma-modules.conf.js @@ -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', diff --git a/src/ngAnimate/.jshintrc b/src/ngAnimate/.jshintrc index e5f2ae1dd552..bf7ccdb0eec3 100644 --- a/src/ngAnimate/.jshintrc +++ b/src/ngAnimate/.jshintrc @@ -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, + /* ngAnimate helpers */ + "applyAnimationClassesFactory": false, "applyAnimationFromStyles": false, + "applyAnimationStyles": false, "applyAnimationToStyles": false, - "applyAnimationClassesFactory": false, - "pendClasses": false, - "normalizeCssProp": 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, - "extractElementNode": false, - "getDomNode": 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 } } diff --git a/src/ngAnimate/animateChildrenDirective.js b/src/ngAnimate/animateChildrenDirective.js index 129fe5bad1fe..02fa86bfb4cb 100644 --- a/src/ngAnimate/animateChildrenDirective.js +++ b/src/ngAnimate/animateChildrenDirective.js @@ -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 diff --git a/src/ngAnimate/animateQueue.js b/src/ngAnimate/animateQueue.js index c17dfdb89ce6..344f2b5d278b 100644 --- a/src/ngAnimate/animateQueue.js +++ b/src/ngAnimate/animateQueue.js @@ -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); diff --git a/src/ngAnimate/module.js b/src/ngAnimate/module.js index 61144b1a9026..eebab4664a1d 100644 --- a/src/ngAnimate/module.js +++ b/src/ngAnimate/module.js @@ -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) diff --git a/src/ngAnimate/shared.js b/src/ngAnimate/shared.js index 54d3c95412ae..0fbeec50a54f 100644 --- a/src/ngAnimate/shared.js +++ b/src/ngAnimate/shared.js @@ -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) { diff --git a/src/ngMessageFormat/messageFormatCommon.js b/src/ngMessageFormat/messageFormatCommon.js index 19af9d30f208..3082bef30978 100644 --- a/src/ngMessageFormat/messageFormatCommon.js +++ b/src/ngMessageFormat/messageFormatCommon.js @@ -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 ''; } diff --git a/src/ngMessageFormat/messageFormatService.js b/src/ngMessageFormat/messageFormatService.js index c8520fc1c8fa..b6b7999c44e2 100644 --- a/src/ngMessageFormat/messageFormatService.js +++ b/src/ngMessageFormat/messageFormatService.js @@ -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); }]); diff --git a/src/ngMessages/.jshintrc b/src/ngMessages/.jshintrc new file mode 100644 index 000000000000..1c700a8719e3 --- /dev/null +++ b/src/ngMessages/.jshintrc @@ -0,0 +1,8 @@ +{ + "extends": "../../.jshintrc-base", + "globals": { + "window": false, + + "angular": false + } +} diff --git a/src/ngMessages/messages.js b/src/ngMessages/messages.js index 6ed575da879c..45ef64b520f6 100644 --- a/src/ngMessages/messages.js +++ b/src/ngMessages/messages.js @@ -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 diff --git a/src/ngRoute/route.js b/src/ngRoute/route.js index 3fee882cd09c..4ead323e8baf 100644 --- a/src/ngRoute/route.js +++ b/src/ngRoute/route.js @@ -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); } diff --git a/src/ngSanitize/.jshintrc b/src/ngSanitize/.jshintrc index d82f0bd267ca..73c34918e2bf 100644 --- a/src/ngSanitize/.jshintrc +++ b/src/ngSanitize/.jshintrc @@ -4,6 +4,6 @@ "window": false, "angular": false, - "htmlSanitizeWriter": false + "sanitizeText": false } } diff --git a/src/ngSanitize/filter/linky.js b/src/ngSanitize/filter/linky.js index a38d6767978b..cdc9af0bb9d5 100644 --- a/src/ngSanitize/filter/linky.js +++ b/src/ngSanitize/filter/linky.js @@ -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, '" '); diff --git a/src/ngSanitize/sanitize.js b/src/ngSanitize/sanitize.js index d1f752827caa..0c41331c42e5 100644 --- a/src/ngSanitize/sanitize.js +++ b/src/ngSanitize/sanitize.js @@ -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,327 +195,339 @@ 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; } }; -} - -function sanitizeText(chars) { - var buf = []; - var writer = htmlSanitizeWriter(buf, angular.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); + ////////////////////////////////////////////////////////////////////////////////////////////////// + // 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; } -})(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; + 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'); - //mXSS protection - var mXSSAttempts = 5; - do { - if (mXSSAttempts === 0) { - throw $sanitizeMinErr('uinput', "Failed to sanitize html because the input is unstable"); + // 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); } - mXSSAttempts--; + })(window); - // strip custom-namespaced attributes on IE<=11 - if (window.document.documentMode) { - stripCustomNsAttrs(inertBodyElement); + /** + * @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; } - 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()); + //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); } - 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()); + 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; } - node = nextNode; - } - while (node = inertBodyElement.firstChild) { - inertBodyElement.removeChild(node); + 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; + 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; } - 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, '&'). - 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, '>'); -} + /** + * 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, '&'). + 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, '>'); + } -/** - * 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(''); - } - if (tag == ignoreCurrentElement) { - ignoreCurrentElement = false; - } - }, - chars: function(chars) { - if (!ignoreCurrentElement) { - out(encodeEntities(chars)); + /** + * 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(''); + } + 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--; + /** + * 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); - } + var nextNode = node.firstChild; + if (nextNode) { + stripCustomNsAttrs(nextNode); + } - nextNode = node.nextSibling; - if (nextNode) { - stripCustomNsAttrs(nextNode); + nextNode = node.nextSibling; + if (nextNode) { + stripCustomNsAttrs(nextNode); + } } } +function sanitizeText(chars) { + var buf = []; + var writer = htmlSanitizeWriter(buf, noop); + writer.chars(chars); + return buf.join(''); +} // define ngSanitize module and register $sanitize service diff --git a/test/e2e/fixtures/loader/index.html b/test/e2e/fixtures/loader/index.html new file mode 100644 index 000000000000..3df9ae87a5d4 --- /dev/null +++ b/test/e2e/fixtures/loader/index.html @@ -0,0 +1,20 @@ + + +
+

{{text}}

+
+ + + + + + + + + + + + + + + diff --git a/test/e2e/fixtures/loader/script.js b/test/e2e/fixtures/loader/script.js new file mode 100644 index 000000000000..9dd6ec9919a7 --- /dev/null +++ b/test/e2e/fixtures/loader/script.js @@ -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!'; + }); diff --git a/test/e2e/tests/loaderSpec.js b/test/e2e/tests/loaderSpec.js new file mode 100644 index 000000000000..bf63b5f064bd --- /dev/null +++ b/test/e2e/tests/loaderSpec.js @@ -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!'); + }); +}); diff --git a/test/ngAnimate/animateCssSpec.js b/test/ngAnimate/animateCssSpec.js index b9ae98d0c3d8..6cc78e1e2f7e 100644 --- a/test/ngAnimate/animateCssSpec.js +++ b/test/ngAnimate/animateCssSpec.js @@ -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' }; diff --git a/test/ngSanitize/sanitizeSpec.js b/test/ngSanitize/sanitizeSpec.js index da1f50cc0cc0..cb3bbce07aac 100644 --- a/test/ngSanitize/sanitizeSpec.js +++ b/test/ngSanitize/sanitizeSpec.js @@ -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() {