@@ -123,11 +123,14 @@ function Browser(window, document, $log, $sniffer) {
123
123
// URL API
124
124
//////////////////////////////////////////////////////////////
125
125
126
- var lastBrowserUrl = location . href ,
127
- lastHistoryState = history . state ,
126
+ var cachedState , lastHistoryState ,
127
+ lastBrowserUrl = location . href ,
128
128
baseElement = document . find ( 'base' ) ,
129
129
reloadLocation = null ;
130
130
131
+ cacheState ( ) ;
132
+ lastHistoryState = cachedState ;
133
+
131
134
/**
132
135
* @name $browser#url
133
136
*
@@ -165,18 +168,20 @@ function Browser(window, document, $log, $sniffer) {
165
168
// Don't change anything if previous and current URLs and states match. This also prevents
166
169
// IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
167
170
// 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 ) ) {
169
172
return ;
170
173
}
171
174
var sameBase = lastBrowserUrl && stripHash ( lastBrowserUrl ) === stripHash ( url ) ;
172
175
lastBrowserUrl = url ;
176
+ lastHistoryState = state ;
173
177
// Don't use history API if only the hash changed
174
178
// due to a bug in IE10/IE11 which leads
175
179
// to not firing a `hashchange` nor `popstate` event
176
180
// in some cases (see #9143).
177
- if ( $sniffer . history && ( ! sameBase || history . state !== state ) ) {
181
+ if ( $sniffer . history && ( ! sameBase || cachedState !== state ) ) {
178
182
history [ replace ? 'replaceState' : 'pushState' ] ( state , '' , url ) ;
179
- lastHistoryState = history . state ;
183
+ cacheState ( ) ;
184
+ lastHistoryState = cachedState ;
180
185
} else {
181
186
if ( ! sameBase ) {
182
187
reloadLocation = url ;
@@ -208,20 +213,40 @@ function Browser(window, document, $log, $sniffer) {
208
213
* @returns {object } state
209
214
*/
210
215
self . state = function ( ) {
211
- return isUndefined ( history . state ) ? null : history . state ;
216
+ return cachedState ;
212
217
} ;
213
218
214
219
var urlChangeListeners = [ ] ,
215
220
urlChangeInit = false ;
216
221
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
+
217
241
function fireUrlChange ( ) {
218
- if ( lastBrowserUrl === self . url ( ) && lastHistoryState === history . state ) {
242
+ if ( lastBrowserUrl === self . url ( ) && lastHistoryState === cachedState ) {
219
243
return ;
220
244
}
221
245
222
246
lastBrowserUrl = self . url ( ) ;
247
+ lastHistoryState = cachedState ;
223
248
forEach ( urlChangeListeners , function ( listener ) {
224
- listener ( self . url ( ) , history . state ) ;
249
+ listener ( self . url ( ) , cachedState ) ;
225
250
} ) ;
226
251
}
227
252
@@ -254,9 +279,9 @@ function Browser(window, document, $log, $sniffer) {
254
279
// changed by push/replaceState
255
280
256
281
// html5 history api - popstate event
257
- if ( $sniffer . history ) jqLite ( window ) . on ( 'popstate' , fireUrlChange ) ;
282
+ if ( $sniffer . history ) jqLite ( window ) . on ( 'popstate' , cacheStateAndFireUrlChange ) ;
258
283
// hashchange event
259
- jqLite ( window ) . on ( 'hashchange' , fireUrlChange ) ;
284
+ jqLite ( window ) . on ( 'hashchange' , cacheStateAndFireUrlChange ) ;
260
285
261
286
urlChangeInit = true ;
262
287
}
0 commit comments