Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 8d39bd8

Browse files
fix($browser): handle async updates to location
Both browser reloads and iOS 9 bugs cause the window.location to report a different href that which we have just set. The change does not become available until the next tick. This change generalises previous work to deal with reloads to deal with the iOS 9 bug in the UIWebView component. Closes #12241 Closes #12819
1 parent 472d076 commit 8d39bd8

File tree

2 files changed

+59
-12
lines changed

2 files changed

+59
-12
lines changed

src/ng/browser.js

+11-6
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ function Browser(window, document, $log, $sniffer) {
8787
var cachedState, lastHistoryState,
8888
lastBrowserUrl = location.href,
8989
baseElement = document.find('base'),
90-
reloadLocation = null;
90+
pendingLocation = null;
9191

9292
cacheState();
9393
lastHistoryState = cachedState;
@@ -147,8 +147,8 @@ function Browser(window, document, $log, $sniffer) {
147147
// Do the assignment again so that those two variables are referentially identical.
148148
lastHistoryState = cachedState;
149149
} else {
150-
if (!sameBase || reloadLocation) {
151-
reloadLocation = url;
150+
if (!sameBase || pendingLocation) {
151+
pendingLocation = url;
152152
}
153153
if (replace) {
154154
location.replace(url);
@@ -157,14 +157,18 @@ function Browser(window, document, $log, $sniffer) {
157157
} else {
158158
location.hash = getHash(url);
159159
}
160+
if (location.href !== url) {
161+
pendingLocation = url;
162+
}
160163
}
161164
return self;
162165
// getter
163166
} else {
164-
// - reloadLocation is needed as browsers don't allow to read out
165-
// the new location.href if a reload happened.
167+
// - pendingLocation is needed as browsers don't allow to read out
168+
// the new location.href if a reload happened or if there is a bug like in iOS 9 (see
169+
// https://openradar.appspot.com/22186109).
166170
// - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
167-
return reloadLocation || location.href.replace(/%27/g,"'");
171+
return pendingLocation || location.href.replace(/%27/g,"'");
168172
}
169173
};
170174

@@ -186,6 +190,7 @@ function Browser(window, document, $log, $sniffer) {
186190
urlChangeInit = false;
187191

188192
function cacheStateAndFireUrlChange() {
193+
pendingLocation = null;
189194
cacheState();
190195
fireUrlChange();
191196
}

test/ng/browserSpecs.js

+48-6
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,20 @@ function MockWindow(options) {
1212
var events = {};
1313
var timeouts = this.timeouts = [];
1414
var locationHref = 'http://server/';
15+
var committedHref = 'http://server/';
1516
var mockWindow = this;
1617
var msie = options.msie;
1718
var ieState;
1819

1920
historyEntriesLength = 1;
2021

22+
function replaceHash(href, hash) {
23+
// replace the hash with the new one (stripping off a leading hash if there is one)
24+
// See hash setter spec: https://url.spec.whatwg.org/#urlutils-and-urlutilsreadonly-members
25+
return stripHash(href) + '#' + hash.replace(/^#/,'');
26+
}
27+
28+
2129
this.setTimeout = function(fn) {
2230
return timeouts.push(fn) - 1;
2331
};
@@ -46,24 +54,28 @@ function MockWindow(options) {
4654

4755
this.location = {
4856
get href() {
49-
return locationHref;
57+
return committedHref;
5058
},
5159
set href(value) {
5260
locationHref = value;
5361
mockWindow.history.state = null;
5462
historyEntriesLength++;
63+
if (!options.updateAsync) this.flushHref();
5564
},
5665
get hash() {
57-
return getHash(locationHref);
66+
return getHash(committedHref);
5867
},
5968
set hash(value) {
60-
// replace the hash with the new one (stripping off a leading hash if there is one)
61-
// See hash setter spec: https://url.spec.whatwg.org/#urlutils-and-urlutilsreadonly-members
62-
locationHref = stripHash(locationHref) + '#' + value.replace(/^#/,'');
69+
locationHref = replaceHash(locationHref, value);
70+
if (!options.updateAsync) this.flushHref();
6371
},
6472
replace: function(url) {
6573
locationHref = url;
6674
mockWindow.history.state = null;
75+
if (!options.updateAsync) this.flushHref();
76+
},
77+
flushHref: function() {
78+
committedHref = locationHref;
6779
}
6880
};
6981

@@ -132,7 +144,7 @@ describe('browser', function() {
132144

133145
logs = {log:[], warn:[], info:[], error:[]};
134146

135-
var fakeLog = {log: function() { logs.log.push(slice.call(arguments)); },
147+
fakeLog = {log: function() { logs.log.push(slice.call(arguments)); },
136148
warn: function() { logs.warn.push(slice.call(arguments)); },
137149
info: function() { logs.info.push(slice.call(arguments)); },
138150
error: function() { logs.error.push(slice.call(arguments)); }};
@@ -703,7 +715,11 @@ describe('browser', function() {
703715
describe('integration tests with $location', function() {
704716

705717
function setup(options) {
718+
fakeWindow = new MockWindow(options);
719+
browser = new Browser(fakeWindow, fakeDocument, fakeLog, sniffer);
720+
706721
module(function($provide, $locationProvider) {
722+
707723
spyOn(fakeWindow.history, 'pushState').andCallFake(function(stateObj, title, newUrl) {
708724
fakeWindow.location.href = newUrl;
709725
});
@@ -827,6 +843,32 @@ describe('browser', function() {
827843
});
828844

829845
});
846+
847+
// issue #12241
848+
it('should not infinite digest if the browser does not synchronously update the location properties', function() {
849+
setup({
850+
history: true,
851+
html5Mode: true,
852+
updateAsync: true // Simulate a browser that doesn't update the href synchronously
853+
});
854+
855+
inject(function($location, $rootScope) {
856+
857+
// Change the hash within Angular and check that we don't infinitely digest
858+
$location.hash('newHash');
859+
expect(function() { $rootScope.$digest(); }).not.toThrow();
860+
expect($location.absUrl()).toEqual('http://server/#newHash');
861+
862+
// Now change the hash from outside Angular and check that $location updates correctly
863+
fakeWindow.location.hash = '#otherHash';
864+
865+
// simulate next tick - since this browser doesn't update synchronously
866+
fakeWindow.location.flushHref();
867+
fakeWindow.fire('hashchange');
868+
869+
expect($location.absUrl()).toEqual('http://server/#otherHash');
870+
});
871+
});
830872
});
831873

832874
describe('integration test with $rootScope', function() {

0 commit comments

Comments
 (0)