diff --git a/src/ng/browser.js b/src/ng/browser.js index fa63b8719151..044a3530288d 100644 --- a/src/ng/browser.js +++ b/src/ng/browser.js @@ -165,10 +165,12 @@ function Browser(window, document, $log, $sniffer) { // setter if (url) { + var sameState = lastHistoryState === state; + // Don't change anything if previous and current URLs and states match. This also prevents // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. // See https://github.com/angular/angular.js/commit/ffb2701 - if (lastBrowserUrl === url && (!$sniffer.history || cachedState === state)) { + if (lastBrowserUrl === url && (!$sniffer.history || sameState)) { return; } var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); @@ -178,9 +180,10 @@ function Browser(window, document, $log, $sniffer) { // due to a bug in IE10/IE11 which leads // to not firing a `hashchange` nor `popstate` event // in some cases (see #9143). - if ($sniffer.history && (!sameBase || cachedState !== state)) { + if ($sniffer.history && (!sameBase || !sameState)) { history[replace ? 'replaceState' : 'pushState'](state, '', url); cacheState(); + // Do the assignment again so that those two variables are referentially identical. lastHistoryState = cachedState; } else { if (!sameBase) { diff --git a/test/ng/browserSpecs.js b/test/ng/browserSpecs.js index c94a9c17d745..a42731362ed5 100755 --- a/test/ng/browserSpecs.js +++ b/test/ng/browserSpecs.js @@ -641,11 +641,9 @@ describe('browser', function() { }); describe('url (when state passed)', function() { - var currentHref; + var currentHref, pushState, replaceState, locationReplace; beforeEach(function() { - sniffer = {history: true}; - currentHref = fakeWindow.location.href; }); describe('in IE', runTests({msie: true})); @@ -654,7 +652,14 @@ describe('browser', function() { function runTests(options) { return function() { beforeEach(function() { + sniffer = {history: true}; + fakeWindow = new MockWindow({msie: options.msie}); + currentHref = fakeWindow.location.href; + pushState = spyOn(fakeWindow.history, 'pushState').andCallThrough(); + replaceState = spyOn(fakeWindow.history, 'replaceState').andCallThrough(); + locationReplace = spyOn(fakeWindow.location, 'replace').andCallThrough(); + browser = new Browser(fakeWindow, fakeDocument, fakeLog, sniffer); browser.onUrlChange(function() {}); }); @@ -703,20 +708,27 @@ describe('browser', function() { expect(fakeWindow.history.state).toEqual({prop: 'val3'}); }); - it('should do pushState with the same URL and null state', function() { - fakeWindow.history.state = {prop: 'val1'}; + it('should do pushState with the same URL and deep equal but referentially different state', function() { + fakeWindow.history.state = {prop: 'val'}; fakeWindow.fire('popstate'); + expect(historyEntriesLength).toBe(1); - browser.url(currentHref, false, null); - expect(fakeWindow.history.state).toEqual(null); + browser.url(currentHref, false, {prop: 'val'}); + expect(fakeWindow.history.state).toEqual({prop: 'val'}); + expect(historyEntriesLength).toBe(2); }); - it('should do pushState with the same URL and the same non-null state', function() { - fakeWindow.history.state = null; - fakeWindow.fire('popstate'); + it('should not do pushState with the same URL and state from $browser.state()', function() { + browser.url(currentHref, false, {prop: 'val'}); - browser.url(currentHref, false, {prop: 'val2'}); - expect(fakeWindow.history.state).toEqual({prop: 'val2'}); + pushState.reset(); + replaceState.reset(); + locationReplace.reset(); + + browser.url(currentHref, false, browser.state()); + expect(pushState).not.toHaveBeenCalled(); + expect(replaceState).not.toHaveBeenCalled(); + expect(locationReplace).not.toHaveBeenCalled(); }); }; }