@@ -123,11 +123,13 @@ 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+
131133 /**
132134 * @name $browser#url
133135 *
@@ -165,7 +167,7 @@ function Browser(window, document, $log, $sniffer) {
165167 // Don't change anything if previous and current URLs and states match. This also prevents
166168 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
167169 // See https://github.com/angular/angular.js/commit/ffb2701
168- if ( lastBrowserUrl === url && ( ! $sniffer . history || history . state === state ) ) {
170+ if ( lastBrowserUrl === url && ( ! $sniffer . history || cachedState === state ) ) {
169171 return ;
170172 }
171173 var sameBase = lastBrowserUrl && stripHash ( lastBrowserUrl ) === stripHash ( url ) ;
@@ -174,9 +176,10 @@ function Browser(window, document, $log, $sniffer) {
174176 // due to a bug in IE10/IE11 which leads
175177 // to not firing a `hashchange` nor `popstate` event
176178 // in some cases (see #9143).
177- if ( $sniffer . history && ( ! sameBase || history . state !== state ) ) {
179+ if ( $sniffer . history && ( ! sameBase || cachedState !== state ) ) {
178180 history [ replace ? 'replaceState' : 'pushState' ] ( state , '' , url ) ;
179- lastHistoryState = history . state ;
181+ lastHistoryState = cachedState ;
182+ cachedState = state ;
180183 } else {
181184 if ( ! sameBase ) {
182185 reloadLocation = url ;
@@ -208,20 +211,40 @@ function Browser(window, document, $log, $sniffer) {
208211 * @returns {object } state
209212 */
210213 self . state = function ( ) {
211- return isUndefined ( history . state ) ? null : history . state ;
214+ return cachedState ;
212215 } ;
213216
214217 var urlChangeListeners = [ ] ,
215218 urlChangeInit = false ;
216219
220+ function cacheStateAndFireUrlChange ( ) {
221+ cacheState ( ) ;
222+ fireUrlChange ( ) ;
223+ }
224+
225+ // This variable should be used *only* inside the cacheState function.
226+ var lastCachedState = null ;
227+ function cacheState ( ) {
228+ // This should be the only place in $browser where `history.state` is read.
229+ cachedState = window . history . state ;
230+ cachedState = isUndefined ( cachedState ) ? null : cachedState ;
231+
232+ // Prevent callbacks fo fire twice if both hashchange & popstate were fired.
233+ if ( equals ( cachedState , lastCachedState ) ) {
234+ cachedState = lastCachedState ;
235+ }
236+ lastCachedState = cachedState ;
237+ }
238+
217239 function fireUrlChange ( ) {
218- if ( lastBrowserUrl === self . url ( ) && lastHistoryState === history . state ) {
240+ if ( lastBrowserUrl === self . url ( ) && lastHistoryState === cachedState ) {
219241 return ;
220242 }
221243
222244 lastBrowserUrl = self . url ( ) ;
245+ lastHistoryState = cachedState ;
223246 forEach ( urlChangeListeners , function ( listener ) {
224- listener ( self . url ( ) , history . state ) ;
247+ listener ( self . url ( ) , cachedState ) ;
225248 } ) ;
226249 }
227250
@@ -254,9 +277,9 @@ function Browser(window, document, $log, $sniffer) {
254277 // changed by push/replaceState
255278
256279 // html5 history api - popstate event
257- if ( $sniffer . history ) jqLite ( window ) . on ( 'popstate' , fireUrlChange ) ;
280+ if ( $sniffer . history ) jqLite ( window ) . on ( 'popstate' , cacheStateAndFireUrlChange ) ;
258281 // hashchange event
259- jqLite ( window ) . on ( 'hashchange' , fireUrlChange ) ;
282+ jqLite ( window ) . on ( 'hashchange' , cacheStateAndFireUrlChange ) ;
260283
261284 urlChangeInit = true ;
262285 }
0 commit comments