Skip to content

Commit 9845cee

Browse files
petebacondarwinjeffbcross
authored andcommitted
fix($browser): prevent infinite digests when clearing the hash of a url
By using `location.hash` to update the current browser location when only the hash has changed, we prevent the browser from attempting to reload. Closes angular#9629 Closes angular#9635 Closes angular#10228 Closes angular#10308
1 parent 5f52957 commit 9845cee

File tree

2 files changed

+50
-5
lines changed

2 files changed

+50
-5
lines changed

src/ng/browser.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ function Browser(window, document, $log, $sniffer) {
6262
}
6363
}
6464

65+
function getHash(url) {
66+
var index = url.indexOf('#');
67+
return index === -1 ? '' : url.substr(index + 1);
68+
}
69+
6570
/**
6671
* @private
6772
* Note: this method is used only by scenario runner
@@ -173,8 +178,10 @@ function Browser(window, document, $log, $sniffer) {
173178
}
174179
if (replace) {
175180
location.replace(url);
176-
} else {
181+
} else if (!sameBase) {
177182
location.href = url;
183+
} else {
184+
location.hash = getHash(url);
178185
}
179186
}
180187
return self;

test/ng/browserSpecs.js

+42-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
'use strict';
22

3-
function MockWindow() {
3+
/* global getHash:true, stripHash:true */
4+
5+
var historyEntriesLength;
6+
var sniffer = {};
7+
8+
function MockWindow(options) {
9+
if (typeof options !== 'object') {
10+
options = {};
11+
}
412
var events = {};
513
var timeouts = this.timeouts = [];
14+
var locationHref = 'http://server/';
15+
var mockWindow = this;
616

717
this.setTimeout = function(fn) {
818
return timeouts.push(fn) - 1;
@@ -36,8 +46,24 @@ function MockWindow() {
3646
};
3747

3848
this.location = {
39-
href: 'http://server/',
40-
replace: noop
49+
get href() {
50+
return locationHref;
51+
},
52+
set href(value) {
53+
locationHref = value;
54+
mockWindow.history.state = null;
55+
historyEntriesLength++;
56+
},
57+
get hash() {
58+
return getHash(locationHref);
59+
},
60+
set hash(value) {
61+
locationHref = stripHash(locationHref) + '#' + value;
62+
},
63+
replace: function(url) {
64+
locationHref = url;
65+
mockWindow.history.state = null;
66+
}
4167
};
4268

4369
this.history = {
@@ -451,6 +477,17 @@ describe('browser', function() {
451477
expect(locationReplace).not.toHaveBeenCalled();
452478
});
453479

480+
it("should retain the # character when the only change is clearing the hash fragment, to prevent page reload", function() {
481+
sniffer.history = true;
482+
483+
browser.url('http://server/#123');
484+
expect(fakeWindow.location.href).toEqual('http://server/#123');
485+
486+
browser.url('http://server/');
487+
expect(fakeWindow.location.href).toEqual('http://server/#');
488+
489+
});
490+
454491
it('should use location.replace when history.replaceState not available', function() {
455492
sniffer.history = false;
456493
browser.url('http://new.org', true);
@@ -462,6 +499,7 @@ describe('browser', function() {
462499
expect(fakeWindow.location.href).toEqual('http://server/');
463500
});
464501

502+
465503
it('should use location.replace and not use replaceState when the url only changed in the hash fragment to please IE10/11', function() {
466504
sniffer.history = true;
467505
browser.url('http://server/#123', true);
@@ -473,6 +511,7 @@ describe('browser', function() {
473511
expect(fakeWindow.location.href).toEqual('http://server/');
474512
});
475513

514+
476515
it('should return $browser to allow chaining', function() {
477516
expect(browser.url('http://any.com')).toBe(browser);
478517
});
@@ -835,7 +874,6 @@ describe('browser', function() {
835874
expect(browser.url()).toBe(newUrl);
836875
$rootScope.$digest();
837876
expect(browser.url()).toBe(newUrl);
838-
expect(fakeWindow.location.href).toBe(current);
839877
});
840878
});
841879

0 commit comments

Comments
 (0)