fix($browser): don’t use history api when only the hash changes

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
Closes #9406
This commit is contained in:
Tobias Bosch
2014-10-02 17:55:32 -07:00
parent f3539f3cb5
commit 0656484d3e
3 changed files with 31 additions and 1 deletions

View File

@@ -153,8 +153,13 @@ 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 which leads
// to not firing a `hashchange` nor `popstate` event
// in some cases (see #9143).
if (!sameBase && $sniffer.history) {
if (replace) history.replaceState(null, '', url);
else {
history.pushState(null, '', url);

View File

@@ -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()) {

View File

@@ -445,6 +445,17 @@ describe('browser', function() {
expect(locationReplace).not.toHaveBeenCalled();
});
it('should set location.href and not use pushState when the url only changed in the hash fragment to please IE10/11', 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 and not use replaceState when the url only changed in the hash fragment to please IE10/11', 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);
});