diff --git a/src/ng/location.js b/src/ng/location.js index 09f08c09cdfe..aeb3a3f7ca9c 100644 --- a/src/ng/location.js +++ b/src/ng/location.js @@ -879,6 +879,13 @@ function $LocationProvider() { var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; + // Determine if two URLs are equal despite potentially having different encoding/normalizing + // such as $location.absUrl() vs $browser.url() + // See https://github.com/angular/angular.js/issues/16592 + function urlsEqual(a, b) { + return a === b || urlResolve(a).href === urlResolve(b).href; + } + function setBrowserUrlWithFallback(url, replace, state) { var oldUrl = $location.url(); var oldState = $location.$$state; @@ -996,7 +1003,7 @@ function $LocationProvider() { var newUrl = trimEmptyHash($location.absUrl()); var oldState = $browser.state(); var currentReplace = $location.$$replace; - var urlOrStateChanged = oldUrl !== newUrl || + var urlOrStateChanged = !urlsEqual(oldUrl, newUrl) || ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); if (initializing || urlOrStateChanged) { diff --git a/test/ng/locationSpec.js b/test/ng/locationSpec.js index 3dbfcd3af22c..5814b405b090 100644 --- a/test/ng/locationSpec.js +++ b/test/ng/locationSpec.js @@ -747,6 +747,58 @@ describe('$location', function() { }); + //https://github.com/angular/angular.js/issues/16592 + it('should not infinitely digest when initial params contain a quote', function() { + initService({html5Mode:true,supportHistory:true}); + mockUpBrowser({initialUrl:'http://localhost:9876/?q=\'', baseHref:'/'}); + inject(function($location, $browser, $rootScope) { + expect(function() { + $rootScope.$digest(); + }).not.toThrow(); + }); + }); + + + //https://github.com/angular/angular.js/issues/16592 + it('should not infinitely digest when initial params contain an escaped quote', function() { + initService({html5Mode:true,supportHistory:true}); + mockUpBrowser({initialUrl:'http://localhost:9876/?q=%27', baseHref:'/'}); + inject(function($location, $browser, $rootScope) { + expect(function() { + $rootScope.$digest(); + }).not.toThrow(); + }); + }); + + + //https://github.com/angular/angular.js/issues/16592 + it('should not infinitely digest when updating params containing a quote (via $browser.url)', function() { + initService({html5Mode:true,supportHistory:true}); + mockUpBrowser({initialUrl:'http://localhost:9876/', baseHref:'/'}); + inject(function($location, $browser, $rootScope) { + $rootScope.$digest(); + $browser.url('http://localhost:9876/?q=\''); + expect(function() { + $rootScope.$digest(); + }).not.toThrow(); + }); + }); + + + //https://github.com/angular/angular.js/issues/16592 + it('should not infinitely digest when updating params containing a quote (via window.location + popstate)', function() { + initService({html5Mode:true,supportHistory:true}); + mockUpBrowser({initialUrl:'http://localhost:9876/', baseHref:'/'}); + inject(function($window, $location, $browser, $rootScope) { + $rootScope.$digest(); + $window.location.href = 'http://localhost:9876/?q=\''; + expect(function() { + jqLite($window).triggerHandler('popstate'); + }).not.toThrow(); + }); + }); + + describe('when changing the browser URL/history directly during a `$digest`', function() { beforeEach(function() { @@ -804,10 +856,13 @@ describe('$location', function() { }); - function updatePathOnLocationChangeSuccessTo(newPath) { + function updatePathOnLocationChangeSuccessTo(newPath, newParams) { inject(function($rootScope, $location) { $rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) { $location.path(newPath); + if (newParams) { + $location.search(newParams); + } }); }); } @@ -950,6 +1005,24 @@ describe('$location', function() { expect($browserUrl).not.toHaveBeenCalled(); }); }); + + //https://github.com/angular/angular.js/issues/16592 + it('should not infinite $digest when going to base URL with trailing slash when $locationChangeSuccess watcher changes query params to contain quote', function() { + initService({html5Mode: true, supportHistory: true}); + mockUpBrowser({initialUrl:'http://server/app/', baseHref:'/app/'}); + inject(function($rootScope, $injector, $browser) { + var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').and.callThrough(); + + var $location = $injector.get('$location'); + updatePathOnLocationChangeSuccessTo('/', {q: '\''}); + + $rootScope.$digest(); + + expect($location.path()).toEqual('/'); + expect($location.search()).toEqual({q: '\''}); + expect($browserUrl).toHaveBeenCalledTimes(1); + }); + }); }); }); @@ -1140,6 +1213,40 @@ describe('$location', function() { }); }); + //https://github.com/angular/angular.js/issues/16592 + it('should not infinite $digest on pushState() with quote in param', function() { + initService({html5Mode: true, supportHistory: true}); + mockUpBrowser({initialUrl:'http://server/app/', baseHref:'/app/'}); + inject(function($rootScope, $injector, $window) { + var $location = $injector.get('$location'); + $rootScope.$digest(); //allow $location initialization to finish + + $window.history.pushState({}, null, 'http://server/app/Home?q=\''); + $rootScope.$digest(); + + expect($location.absUrl()).toEqual('http://server/app/Home?q=\''); + expect($location.path()).toEqual('/Home'); + expect($location.search()).toEqual({q: '\''}); + }); + }); + + //https://github.com/angular/angular.js/issues/16592 + it('should not infinite $digest on popstate event with quote in param', function() { + initService({html5Mode: true, supportHistory: true}); + mockUpBrowser({initialUrl:'http://server/app/', baseHref:'/app/'}); + inject(function($rootScope, $injector, $window) { + var $location = $injector.get('$location'); + $rootScope.$digest(); //allow $location initialization to finish + + $window.location.href = 'http://server/app/Home?q=\''; + jqLite($window).triggerHandler('popstate'); + + expect($location.absUrl()).toEqual('http://server/app/Home?q=\''); + expect($location.path()).toEqual('/Home'); + expect($location.search()).toEqual({q: '\''}); + }); + }); + it('should replace browser url & state when replace() was called at least once', function() { initService({html5Mode:true, supportHistory: true}); mockUpBrowser({initialUrl:'http://new.com/a/b/', baseHref:'/a/b/'});