@@ -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 }
0 commit comments