From ee5bb46dcd2bc01b0ee9d01dff9efe6d3cf1b5a6 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Thu, 2 Oct 2014 17:55:32 -0700 Subject: [PATCH] =?UTF-8?q?fix($browser):=20don=E2=80=99t=20use=20history?= =?UTF-8?q?=20api=20when=20only=20the=20hash=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IE10/11 have the following problem: When changing the url hash via `history.pushState()` and then reverting the hash via direct changes to `location.href` (or via a link) does not fire a `hashchange` nor `popstate` event. This commit changes the default behavior as follows: Uses `location.href`/`location.replace` if the new url differs from the previous url only in the hash fragment or the browser does not support history API. Use `history.pushState`/ `history.replaceState` otherwise. Fixes #9143 --- src/ng/browser.js | 9 ++++++++- src/ng/location.js | 3 +++ test/ng/browserSpecs.js | 22 ++++++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/ng/browser.js b/src/ng/browser.js index e83d34a69768..e0e00b9d55a3 100644 --- a/src/ng/browser.js +++ b/src/ng/browser.js @@ -153,8 +153,11 @@ function Browser(window, document, $log, $sniffer) { // setter if (url) { if (lastBrowserUrl == url) return; + var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); lastBrowserUrl = url; - if ($sniffer.history) { + // Don't use history API if only the hash changed + // due to a bug in IE10/IE11 (see #9143) + if (!sameBase && $sniffer.history) { if (replace) history.replaceState(null, '', url); else { history.pushState(null, '', url); @@ -190,6 +193,10 @@ function Browser(window, document, $log, $sniffer) { }); } + function stripHash(url) { + return url.replace(/#.*/, ''); + } + /** * @name $browser#onUrlChange * diff --git a/src/ng/location.js b/src/ng/location.js index 1fcbbf13de57..2b143c100269 100644 --- a/src/ng/location.js +++ b/src/ng/location.js @@ -720,6 +720,9 @@ function $LocationProvider(){ if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) { if ($location.$$parseLinkUrl(absHref, relHref)) { + // We do a preventDefault for all urls that are part of the angular application, + // in html5mode and also without, so that we are able to abort navigation without + // getting double entries in the location history. event.preventDefault(); // update location manually if ($location.absUrl() != $browser.url()) { diff --git a/test/ng/browserSpecs.js b/test/ng/browserSpecs.js index f5a4359625c2..e11b9ae6b25e 100755 --- a/test/ng/browserSpecs.js +++ b/test/ng/browserSpecs.js @@ -445,6 +445,17 @@ describe('browser', function() { expect(locationReplace).not.toHaveBeenCalled(); }); + it('should set location.href when the url only changed in the hash fragment', function() { + sniffer.history = true; + browser.url('http://server/#123'); + + expect(fakeWindow.location.href).toEqual('http://server/#123'); + + expect(pushState).not.toHaveBeenCalled(); + expect(replaceState).not.toHaveBeenCalled(); + expect(locationReplace).not.toHaveBeenCalled(); + }); + it('should use location.replace when history.replaceState not available', function() { sniffer.history = false; browser.url('http://new.org', true); @@ -456,6 +467,17 @@ describe('browser', function() { expect(fakeWindow.location.href).toEqual('http://server/'); }); + it('should use location.replace when the url only changed in the hash fragment', function() { + sniffer.history = true; + browser.url('http://server/#123', true); + + expect(locationReplace).toHaveBeenCalledWith('http://server/#123'); + + expect(pushState).not.toHaveBeenCalled(); + expect(replaceState).not.toHaveBeenCalled(); + expect(fakeWindow.location.href).toEqual('http://server/'); + }); + it('should return $browser to allow chaining', function() { expect(browser.url('http://any.com')).toBe(browser); });