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

Commit 1efaf3d

Browse files
mgolIgorMinar
authored andcommitted
fix($browser): account for IE deserializing history.state on each read
IE 10-11+ deserialize history.state on every read, causing simple comparisons against history.state always return false. Account for that caching `history.state` on every hashchange or popstate event. Also: 1. Prevent firing onUrlChange callbacks twice if both popstate and hashchange event were fired. 2. Fix the issue of routes sometimes not firing the URL change in all browsers. Closes #9587 Fixes #9545
1 parent 393c1c7 commit 1efaf3d

File tree

3 files changed

+243
-81
lines changed

3 files changed

+243
-81
lines changed

src/ng/browser.js

+35-10
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,14 @@ function Browser(window, document, $log, $sniffer) {
123123
// URL API
124124
//////////////////////////////////////////////////////////////
125125

126-
var lastBrowserUrl = location.href,
127-
lastHistoryState = history.state,
126+
var cachedState, lastHistoryState,
127+
lastBrowserUrl = location.href,
128128
baseElement = document.find('base'),
129129
reloadLocation = null;
130130

131+
cacheState();
132+
lastHistoryState = cachedState;
133+
131134
/**
132135
* @name $browser#url
133136
*
@@ -165,18 +168,20 @@ function Browser(window, document, $log, $sniffer) {
165168
// Don't change anything if previous and current URLs and states match. This also prevents
166169
// IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
167170
// See https://github.com/angular/angular.js/commit/ffb2701
168-
if (lastBrowserUrl === url && (!$sniffer.history || history.state === state)) {
171+
if (lastBrowserUrl === url && (!$sniffer.history || cachedState === state)) {
169172
return;
170173
}
171174
var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
172175
lastBrowserUrl = url;
176+
lastHistoryState = state;
173177
// Don't use history API if only the hash changed
174178
// due to a bug in IE10/IE11 which leads
175179
// to not firing a `hashchange` nor `popstate` event
176180
// in some cases (see #9143).
177-
if ($sniffer.history && (!sameBase || history.state !== state)) {
181+
if ($sniffer.history && (!sameBase || cachedState !== state)) {
178182
history[replace ? 'replaceState' : 'pushState'](state, '', url);
179-
lastHistoryState = history.state;
183+
cacheState();
184+
lastHistoryState = cachedState;
180185
} else {
181186
if (!sameBase) {
182187
reloadLocation = url;
@@ -208,20 +213,40 @@ function Browser(window, document, $log, $sniffer) {
208213
* @returns {object} state
209214
*/
210215
self.state = function() {
211-
return isUndefined(history.state) ? null : history.state;
216+
return cachedState;
212217
};
213218

214219
var urlChangeListeners = [],
215220
urlChangeInit = false;
216221

222+
function cacheStateAndFireUrlChange() {
223+
cacheState();
224+
fireUrlChange();
225+
}
226+
227+
// This variable should be used *only* inside the cacheState function.
228+
var lastCachedState = null;
229+
function cacheState() {
230+
// This should be the only place in $browser where `history.state` is read.
231+
cachedState = window.history.state;
232+
cachedState = isUndefined(cachedState) ? null : cachedState;
233+
234+
// Prevent callbacks fo fire twice if both hashchange & popstate were fired.
235+
if (equals(cachedState, lastCachedState)) {
236+
cachedState = lastCachedState;
237+
}
238+
lastCachedState = cachedState;
239+
}
240+
217241
function fireUrlChange() {
218-
if (lastBrowserUrl === self.url() && lastHistoryState === history.state) {
242+
if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) {
219243
return;
220244
}
221245

222246
lastBrowserUrl = self.url();
247+
lastHistoryState = cachedState;
223248
forEach(urlChangeListeners, function(listener) {
224-
listener(self.url(), history.state);
249+
listener(self.url(), cachedState);
225250
});
226251
}
227252

@@ -254,9 +279,9 @@ function Browser(window, document, $log, $sniffer) {
254279
// changed by push/replaceState
255280

256281
// html5 history api - popstate event
257-
if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange);
282+
if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange);
258283
// hashchange event
259-
jqLite(window).on('hashchange', fireUrlChange);
284+
jqLite(window).on('hashchange', cacheStateAndFireUrlChange);
260285

261286
urlChangeInit = true;
262287
}

src/ng/location.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -850,9 +850,10 @@ function $LocationProvider(){
850850
var oldUrl = $browser.url();
851851
var oldState = $browser.state();
852852
var currentReplace = $location.$$replace;
853+
var urlOrStateChanged = oldUrl !== $location.absUrl() ||
854+
($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
853855

854-
if (initializing || oldUrl !== $location.absUrl() ||
855-
($location.$$html5 && $sniffer.history && oldState !== $location.$$state)) {
856+
if (initializing || urlOrStateChanged) {
856857
initializing = false;
857858

858859
$rootScope.$evalAsync(function() {
@@ -861,8 +862,10 @@ function $LocationProvider(){
861862
$location.$$parse(oldUrl);
862863
$location.$$state = oldState;
863864
} else {
864-
setBrowserUrlWithFallback($location.absUrl(), currentReplace,
865-
oldState === $location.$$state ? null : $location.$$state);
865+
if (urlOrStateChanged) {
866+
setBrowserUrlWithFallback($location.absUrl(), currentReplace,
867+
oldState === $location.$$state ? null : $location.$$state);
868+
}
866869
afterLocationChange(oldUrl, oldState);
867870
}
868871
});

0 commit comments

Comments
 (0)