perf($interpolate): do not keep empty separators

Do not keep empty separators and keep references to where
each expression goes
This commit is contained in:
Lucas Galfaso
2014-08-18 12:02:38 +01:00
parent 5b77e30c1a
commit 94b5c9f00e
2 changed files with 23 additions and 47 deletions

View File

@@ -188,68 +188,53 @@ function $InterpolateProvider() {
var startIndex,
endIndex,
index = 0,
separators = [],
expressions = [],
parseFns = [],
textLength = text.length,
hasInterpolation = false,
hasText = false,
exp,
concat = [];
concat = [],
expressionPositions = [];
while(index < textLength) {
if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) &&
((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) {
if (index !== startIndex) hasText = true;
separators.push(text.substring(index, startIndex));
if (index !== startIndex) {
concat.push(unescapeText(text.substring(index, startIndex)));
}
exp = text.substring(startIndex + startSymbolLength, endIndex);
expressions.push(exp);
parseFns.push($parse(exp, parseStringifyInterceptor));
index = endIndex + endSymbolLength;
hasInterpolation = true;
expressionPositions.push(concat.length);
concat.push('');
} else {
// we did not find an interpolation, so we have to add the remainder to the separators array
if (index !== textLength) {
hasText = true;
separators.push(text.substring(index));
concat.push(unescapeText(text.substring(index)));
}
break;
}
}
forEach(separators, function(key, i) {
separators[i] = separators[i].
replace(escapedStartRegexp, startSymbol).
replace(escapedEndRegexp, endSymbol);
});
if (separators.length === expressions.length) {
separators.push('');
}
// Concatenating expressions makes it hard to reason about whether some combination of
// concatenated values are unsafe to use and could easily lead to XSS. By requiring that a
// single expression be used for iframe[src], object[src], etc., we ensure that the value
// that's used is assigned or constructed by some JS code somewhere that is more testable or
// make it obvious that you bound the value to some user controlled value. This helps reduce
// the load when auditing for XSS issues.
if (trustedContext && hasInterpolation && (hasText || expressions.length > 1)) {
if (trustedContext && concat.length > 1) {
throw $interpolateMinErr('noconcat',
"Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
"interpolations that concatenate multiple expressions when a trusted value is " +
"required. See http://docs.angularjs.org/api/ng.$sce", text);
}
if (!mustHaveExpression || hasInterpolation) {
concat.length = separators.length + expressions.length;
if (!mustHaveExpression || expressions.length) {
var compute = function(values) {
for(var i = 0, ii = expressions.length; i < ii; i++) {
if (allOrNothing && isUndefined(values[i])) return;
concat[2*i] = separators[i];
concat[(2*i)+1] = values[i];
concat[expressionPositions[i]] = values[i];
}
concat[2*ii] = separators[ii];
return concat.join('');
};
@@ -299,7 +284,6 @@ function $InterpolateProvider() {
}, {
// all of these properties are undocumented for now
exp: text, //just for compatibility with regular watchers created via $watch
separators: separators,
expressions: expressions,
$$watchDelegate: function (scope, listener, objectEquality) {
var lastValue;
@@ -314,6 +298,11 @@ function $InterpolateProvider() {
});
}
function unescapeText(text) {
return text.replace(escapedStartRegexp, startSymbol).
replace(escapedEndRegexp, endSymbol);
}
function parseStringifyInterceptor(value) {
try {
return stringify(getValue(value));

View File

@@ -7,7 +7,6 @@ describe('$interpolate', function() {
var interpolateFn = $interpolate('some text');
expect(interpolateFn.exp).toBe('some text');
expect(interpolateFn.separators).toEqual(['some text']);
expect(interpolateFn.expressions).toEqual([]);
expect(interpolateFn({})).toBe('some text');
@@ -41,7 +40,6 @@ describe('$interpolate', function() {
var interpolateFn = $interpolate('Hello {{name}}!');
expect(interpolateFn.exp).toBe('Hello {{name}}!');
expect(interpolateFn.separators).toEqual(['Hello ', '!']);
expect(interpolateFn.expressions).toEqual(['name']);
var scope = $rootScope.$new();
@@ -185,7 +183,6 @@ describe('$interpolate', function() {
}));
it('should not get confused with same markers', inject(function($interpolate) {
expect($interpolate('---').separators).toEqual(['---']);
expect($interpolate('---').expressions).toEqual([]);
expect($interpolate('----')({})).toEqual('');
expect($interpolate('--1--')({})).toEqual('1');
@@ -194,67 +191,58 @@ describe('$interpolate', function() {
describe('parseBindings', function() {
it('should Parse Text With No Bindings', inject(function($interpolate) {
expect($interpolate("a").separators).toEqual(['a']);
expect($interpolate("a").expressions).toEqual([]);
}));
it('should Parse Empty Text', inject(function($interpolate) {
expect($interpolate("").separators).toEqual(['']);
expect($interpolate("").expressions).toEqual([]);
}));
it('should Parse Inner Binding', inject(function($interpolate) {
var interpolateFn = $interpolate("a{{b}}C"),
separators = interpolateFn.separators, expressions = interpolateFn.expressions;
expect(separators).toEqual(['a', 'C']);
expressions = interpolateFn.expressions;
expect(expressions).toEqual(['b']);
expect(interpolateFn({b: 123})).toEqual('a123C');
}));
it('should Parse Ending Binding', inject(function($interpolate) {
var interpolateFn = $interpolate("a{{b}}"),
separators = interpolateFn.separators, expressions = interpolateFn.expressions;
expect(separators).toEqual(['a', '']);
expressions = interpolateFn.expressions;
expect(expressions).toEqual(['b']);
expect(interpolateFn({b: 123})).toEqual('a123');
}));
it('should Parse Begging Binding', inject(function($interpolate) {
var interpolateFn = $interpolate("{{b}}c"),
separators = interpolateFn.separators, expressions = interpolateFn.expressions;
expect(separators).toEqual(['', 'c']);
expressions = interpolateFn.expressions;
expect(expressions).toEqual(['b']);
expect(interpolateFn({b: 123})).toEqual('123c');
}));
it('should Parse Loan Binding', inject(function($interpolate) {
var interpolateFn = $interpolate("{{b}}"),
separators = interpolateFn.separators, expressions = interpolateFn.expressions;
expect(separators).toEqual(['', '']);
expressions = interpolateFn.expressions;
expect(expressions).toEqual(['b']);
expect(interpolateFn({b: 123})).toEqual('123');
}));
it('should Parse Two Bindings', inject(function($interpolate) {
var interpolateFn = $interpolate("{{b}}{{c}}"),
separators = interpolateFn.separators, expressions = interpolateFn.expressions;
expect(separators).toEqual(['', '', '']);
expressions = interpolateFn.expressions;
expect(expressions).toEqual(['b', 'c']);
expect(interpolateFn({b: 111, c: 222})).toEqual('111222');
}));
it('should Parse Two Bindings With Text In Middle', inject(function($interpolate) {
var interpolateFn = $interpolate("{{b}}x{{c}}"),
separators = interpolateFn.separators, expressions = interpolateFn.expressions;
expect(separators).toEqual(['', 'x', '']);
expressions = interpolateFn.expressions;
expect(expressions).toEqual(['b', 'c']);
expect(interpolateFn({b: 111, c: 222})).toEqual('111x222');
}));
it('should Parse Multiline', inject(function($interpolate) {
var interpolateFn = $interpolate('"X\nY{{A\n+B}}C\nD"'),
separators = interpolateFn.separators, expressions = interpolateFn.expressions;
expect(separators).toEqual(['"X\nY', 'C\nD"']);
expressions = interpolateFn.expressions;
expect(expressions).toEqual(['A\n+B']);
expect(interpolateFn({'A': 'aa', 'B': 'bb'})).toEqual('"X\nYaabbC\nD"');
}));
@@ -317,7 +305,6 @@ describe('$interpolate', function() {
});
inject(function($interpolate) {
expect($interpolate('---').separators).toEqual(['---']);
expect($interpolate('---').expressions).toEqual([]);
expect($interpolate('----')({})).toEqual('');
expect($interpolate('--1--')({})).toEqual('1');