Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 9e7cb3c

Browse files
committed
feat(jQuery): upgrade to jQuery to 2.1.1
The data jQuery method was re-implemented in 2.0 in a secure way. This made current hacky Angular solution to move data between elements via changing the value of the internal node[jQuery.expando] stop working. Instead, just copy the data from the first element to the other one. Testing cache leaks on jQuery 2.x is not possible in the same way as it's done in jqLite or in jQuery 1.x as there is no publicly exposed data storage. One way to test it would be to intercept all places where a jQuery object is created to save a reference to the underlaying node but there is no single place in the jQuery code through which all element creation passes (there are various shortcuts for performance reasons). Instead we rely on jqLite.cache testing to find potential data leaks. BREAKING CHANGE: Angular no longer supports jQuery versions below 2.1.1.
1 parent 38db2d4 commit 9e7cb3c

13 files changed

+102
-38
lines changed

Gruntfile.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ module.exports = function(grunt) {
161161
scenario: {
162162
dest: 'build/angular-scenario.js',
163163
src: [
164-
'bower_components/jquery/jquery.js',
164+
'bower_components/jquery/dist/jquery.js',
165165
util.wrap([files['angularSrc'], files['angularScenario']], 'ngScenario/angular')
166166
],
167167
styles: {

angularFiles.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ var angularFiles = {
143143
],
144144

145145
'karma': [
146-
'bower_components/jquery/jquery.js',
146+
'bower_components/jquery/dist/jquery.js',
147147
'test/jquery_remove.js',
148148
'@angularSrc',
149149
'src/publishExternalApis.js',
@@ -177,7 +177,7 @@ var angularFiles = {
177177
],
178178

179179
'karmaJquery': [
180-
'bower_components/jquery/jquery.js',
180+
'bower_components/jquery/dist/jquery.js',
181181
'test/jquery_alias.js',
182182
'@angularSrc',
183183
'src/publishExternalApis.js',

bower.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "AngularJS",
33
"devDependencies": {
4-
"jquery": "1.10.2",
4+
"jquery": "2.1.1",
55
"lunr.js": "0.4.3",
66
"open-sans-fontface": "1.0.4",
77
"google-code-prettify": "1.0.1",

docs/content/misc/faq.ngdoc

+3-2
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,15 @@ The size of the file is < 36KB compressed and minified.
7575
Yes, you can use widgets from the [Closure Library](https://developers.google.com/closure/library/)
7676
in Angular.
7777

78+
7879
### Does Angular use the jQuery library?
7980

8081
Yes, Angular can use [jQuery](http://jquery.com/) if it's present in your app when the
8182
application is being bootstrapped. If jQuery is not present in your script path, Angular falls back
8283
to its own implementation of the subset of jQuery that we call {@link angular.element jQLite}.
8384

84-
Due to a change to use `on()`/`off()` rather than `bind()`/`unbind()`, Angular 1.2 only operates with
85-
jQuery 1.7.1 or above. However, Angular does not currently support jQuery 2.x or above.
85+
Angular 1.3 only supports jQuery 2.1 or above. jQuery 1.7 and newer might work correctly with Angular
86+
but we don't guarantee that.
8687

8788

8889
### What is testability like in Angular?

docs/content/tutorial/step_12.ngdoc

+2-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ __`app/index.html`.__
104104
```
105105

106106
<div class="alert alert-error">
107-
**Important:** Be sure to use jQuery version `1.10.x`. AngularJS does not yet support jQuery `2.x`.
107+
**Important:** Be sure to use jQuery version 2.1 or newer when using Angular 1.3; jQuery 1.x is
108+
not officially supported.
108109
Be sure to load jQuery before all AngularJS scripts, otherwise AngularJS won't detect jQuery and
109110
animations will not work as expected.
110111
</div>

src/Angular.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -1459,7 +1459,9 @@ function bindJQuery() {
14591459
// bind to jQuery if present;
14601460
jQuery = window.jQuery;
14611461
// Use jQuery if it exists with proper functionality, otherwise default to us.
1462-
// Angular 1.2+ requires jQuery 1.7.1+ for on()/off() support.
1462+
// Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
1463+
// Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older
1464+
// versions. It will not work for sure with jQuery <1.7, though.
14631465
if (jQuery && jQuery.fn.on) {
14641466
jqLite = jQuery;
14651467
extend(jQuery.fn, {

src/ng/compile.js

+19-1
Original file line numberDiff line numberDiff line change
@@ -2031,7 +2031,25 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
20312031
}
20322032
var fragment = document.createDocumentFragment();
20332033
fragment.appendChild(firstElementToRemove);
2034-
newNode[jqLite.expando] = firstElementToRemove[jqLite.expando];
2034+
2035+
// Copy over user data (that includes Angular's $scope etc.). Don't copy private
2036+
// data here because there's no public interface in jQuery to do that and copying over
2037+
// event listeners (which is the main use of private data) wouldn't work anyway.
2038+
jqLite(newNode).data(jqLite(firstElementToRemove).data());
2039+
2040+
// Remove data of the replaced element. We cannot just call .remove()
2041+
// on the element it since that would deallocate scope and event handlers that are still needed
2042+
// for the new node. Instead, remove the data "manually".
2043+
if (!jQuery) {
2044+
delete jqLite.cache[firstElementToRemove[jqLite.expando]];
2045+
} else {
2046+
// jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after the replaced
2047+
// element. Note that we need to use the original method here and not the one monkey-patched by Angular
2048+
// since the patched method emits the $destroy event causing the scope to be trashed and we do need
2049+
// the very same scope to work with the new element.
2050+
jQuery.cleanData.$$original([firstElementToRemove]);
2051+
}
2052+
20352053
for (var k = 1, kk = elementsToRemove.length; k < kk; k++) {
20362054
var element = elementsToRemove[k];
20372055
jqLite(element).remove(); // must do this way to clean up expando

src/ngMock/.jshintrc

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"browser": true,
44
"globals": {
55
"angular": false,
6-
"expect": false
6+
"expect": false,
7+
"jQuery": false
78
}
89
}

src/ngMock/angular-mocks.js

+7
Original file line numberDiff line numberDiff line change
@@ -1956,6 +1956,13 @@ angular.mock.e2e.$httpBackendDecorator =
19561956

19571957

19581958
angular.mock.clearDataCache = function() {
1959+
// jQuery 2.x doesn't expose data attached to elements. We could use jQuery.cleanData
1960+
// to clean up after elements but we'd first need to know which elements to clean up after.
1961+
// Skip it then.
1962+
if (window.jQuery) {
1963+
return;
1964+
}
1965+
19591966
var key,
19601967
cache = angular.element.cache;
19611968

test/helpers/testabilityPatch.js

+23-18
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ beforeEach(function() {
3434
});
3535

3636
afterEach(function() {
37+
var count, cache;
38+
3739
if (this.$injector) {
3840
var $rootScope = this.$injector.get('$rootScope');
3941
var $rootElement = this.$injector.get('$rootElement');
@@ -46,25 +48,27 @@ afterEach(function() {
4648
$log.assertEmpty && $log.assertEmpty();
4749
}
4850

49-
// complain about uncleared jqCache references
50-
var count = 0;
51+
if (!window.jQuery) {
52+
// jQuery 2.x doesn't expose the cache storage.
5153

52-
// This line should be enabled as soon as this bug is fixed: http://bugs.jquery.com/ticket/11775
53-
//var cache = jqLite.cache;
54-
var cache = angular.element.cache;
54+
// complain about uncleared jqCache references
55+
count = 0;
5556

56-
forEachSorted(cache, function(expando, key){
57-
angular.forEach(expando.data, function(value, key){
58-
count ++;
59-
if (value && value.$element) {
60-
dump('LEAK', key, value.$id, sortedHtml(value.$element));
61-
} else {
62-
dump('LEAK', key, angular.toJson(value));
63-
}
57+
cache = angular.element.cache;
58+
59+
forEachSorted(cache, function (expando, key) {
60+
angular.forEach(expando.data, function (value, key) {
61+
count++;
62+
if (value && value.$element) {
63+
dump('LEAK', key, value.$id, sortedHtml(value.$element));
64+
} else {
65+
dump('LEAK', key, angular.toJson(value));
66+
}
67+
});
6468
});
65-
});
66-
if (count) {
67-
throw new Error('Found jqCache references that were not deallocated! count: ' + count);
69+
if (count) {
70+
throw new Error('Found jqCache references that were not deallocated! count: ' + count);
71+
}
6872
}
6973

7074

@@ -95,8 +99,9 @@ function dealoc(obj) {
9599
if (obj) {
96100
if (angular.isElement(obj)) {
97101
cleanup(angular.element(obj));
98-
} else {
99-
for(var key in jqCache) {
102+
} else if (!window.jQuery) {
103+
// jQuery 2.x doesn't expose the cache storage.
104+
for (var key in jqCache) {
100105
var value = jqCache[key];
101106
if (value.data && value.data.$scope == obj) {
102107
delete jqCache[key];

test/jqLiteSpec.js

-8
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,6 @@ describe('jqLite', function() {
130130
});
131131

132132
describe('_data', function() {
133-
it('should provide access to the data present on the element', function() {
134-
var element = jqLite('<i>foo</i>');
135-
var data = ['value'];
136-
element.data('val', data);
137-
expect(angular.element._data(element[0]).data.val).toBe(data);
138-
dealoc(element);
139-
});
140-
141133
it('should provide access to the events present on the element', function() {
142134
var element = jqLite('<i>foo</i>');
143135
expect(angular.element._data(element[0]).events).toBeUndefined();

test/ng/compileSpec.js

+34-2
Original file line numberDiff line numberDiff line change
@@ -4028,7 +4028,12 @@ describe('$compile', function() {
40284028

40294029

40304030

4031-
it('should not leak if two "element" transclusions are on the same element', function() {
4031+
it('should not leak if two "element" transclusions are on the same element', function () {
4032+
if (jQuery) {
4033+
// jQuery 2.x doesn't expose the cache storage.
4034+
return;
4035+
}
4036+
40324037
var calcCacheSize = function() {
40334038
var size = 0;
40344039
forEach(jqLite.cache, function(item, key) { size++; });
@@ -4056,7 +4061,11 @@ describe('$compile', function() {
40564061
});
40574062

40584063

4059-
it('should not leak if two "element" transclusions are on the same element', function() {
4064+
it('should not leak if two "element" transclusions are on the same element', function () {
4065+
if (jQuery) {
4066+
// jQuery 2.x doesn't expose the cache storage.
4067+
return;
4068+
}
40604069
var calcCacheSize = function() {
40614070
var size = 0;
40624071
forEach(jqLite.cache, function(item, key) { size++; });
@@ -4086,6 +4095,29 @@ describe('$compile', function() {
40864095
});
40874096
});
40884097

4098+
if (jQuery) {
4099+
it('should clean up after a replaced element', inject(function ($compile) {
4100+
var privateData, firstRepeatedElem;
4101+
4102+
element = $compile('<div><div ng-repeat="x in xs">{{x}}</div></div>')($rootScope);
4103+
4104+
$rootScope.$apply('xs = [0,1]');
4105+
firstRepeatedElem = element.children('.ng-scope').eq(0);
4106+
4107+
expect(firstRepeatedElem.data('$scope')).toBeDefined();
4108+
privateData = jQuery._data(firstRepeatedElem[0]);
4109+
expect(privateData.events).toBeDefined();
4110+
expect(privateData.events.$destroy).toBeDefined();
4111+
expect(privateData.events.$destroy[0]).toBeDefined();
4112+
4113+
$rootScope.$apply('xs = null');
4114+
4115+
expect(firstRepeatedElem.data('$scope')).not.toBeDefined();
4116+
privateData = jQuery._data(firstRepeatedElem[0]);
4117+
expect(privateData && privateData.events).not.toBeDefined();
4118+
}));
4119+
}
4120+
40894121

40904122
it('should remove transclusion scope, when the DOM is destroyed', function() {
40914123
module(function() {

test/ngMock/angular-mocksSpec.js

+5
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,11 @@ describe('ngMock', function() {
706706

707707

708708
describe('angular.mock.clearDataCache', function() {
709+
if (window.jQuery) {
710+
// jQuery 2.x doesn't expose the cache storage.
711+
return;
712+
}
713+
709714
function keys(obj) {
710715
var keysArr = [];
711716
for(var key in obj) {

0 commit comments

Comments
 (0)