From 1a92be0240f106c7cb3802edff97ddb5bf09f326 Mon Sep 17 00:00:00 2001 From: Kin Blas Date: Tue, 13 Sep 2011 00:23:03 -0700 Subject: [PATCH] Fix for issue 2406 - Navigation from one page back to multi-page template - Make sure that our hashchange resolves non-path hashes against the documentBase. This prevents the resulting changePath() call from incorrectly resolving against the URL for the current active (external) page. - Fixed a problem in the push-state code. A hashchange event is *NOT* fired when navigating back (window.history.back()) from an external page to an internal page. This makes sense when you think about it since hashchange is only ever fired when the hash of the current document url changes, not when the document url itself changes. The fix was to make sure that the pushstate hashchange callback always sets a state object, even on embedded page URLs. This allows the hashchange callback to be triggered from within onPopState(). --- js/jquery.mobile.navigation.js | 7 +++++- js/jquery.mobile.navigation.pushstate.js | 32 ++++++++++++++++-------- tests/unit/navigation/navigation_core.js | 30 ++++++++++++++++++++++ 3 files changed, 57 insertions(+), 12 deletions(-) diff --git a/js/jquery.mobile.navigation.js b/js/jquery.mobile.navigation.js index 2b9e029a..afcb6d1f 100755 --- a/js/jquery.mobile.navigation.js +++ b/js/jquery.mobile.navigation.js @@ -1351,7 +1351,12 @@ //if to is defined, load it if ( to ) { - to = ( typeof to === "string" && !path.isPath( to ) ) ? ( '#' + to ) : to; + // At this point, 'to' can be one of 3 things, a cached page element from + // a history stack entry, an id, or site-relative/absolute URL. If 'to' is + // an id, we need to resolve it against the documentBase, not the location.href, + // since the hashchange could've been the result of a forward/backward navigation + // that crosses from an external page/dialog to an internal page/dialog. + to = ( typeof to === "string" && !path.isPath( to ) ) ? ( path.makeUrlAbsolute( '#' + to, documentBase ) ) : to; $.mobile.changePage( to, changePageOptions ); } else { //there's no hash, go to the first page in the dom diff --git a/js/jquery.mobile.navigation.pushstate.js b/js/jquery.mobile.navigation.pushstate.js index 03f3c1ff..0eb99bd6 100644 --- a/js/jquery.mobile.navigation.pushstate.js +++ b/js/jquery.mobile.navigation.pushstate.js @@ -52,24 +52,34 @@ // NOTE this takes place *after* the vanilla navigation hash change // handling has taken place and set the state of the DOM onHashChange: function( e ) { - var href, state; + var href, state, + hash = location.hash, + isPath = $.mobile.path.isPath( hash ); + hash = isPath ? hash.replace( "#", "" ) : hash; self.hashchangeFired = true; - // only replaceState when the hash doesn't represent an embeded page - if( $.mobile.path.isPath(location.hash) ) { + // propulate the hash when its not available + state = self.state(); - // propulate the hash when its not available - state = self.state(); - - // make the hash abolute with the current href - href = $.mobile.path.makeUrlAbsolute( state.hash.replace("#", ""), location.href ); + // make the hash abolute with the current href + href = $.mobile.path.makeUrlAbsolute( hash, location.href ); + if ( isPath ) { href = self.resetUIKeys( href ); - - // replace the current url with the new href and store the state - history.replaceState( state, document.title, href ); } + + // replace the current url with the new href and store the state + // Note that in some cases we might be replacing an url with the + // same url. We do this anyways because we need to make sure that + // all of our history entries have a state object associated with + // them. This allows us to work around the case where window.history.back() + // is called to transition from an external page to an embedded page. + // In that particular case, a hashchange event is *NOT* generated by the browser. + // Ensuring each history entry has a state object means that onPopState() + // will always trigger our hashchange callback even when a hashchange event + // is not fired. + history.replaceState( state, document.title, href ); }, // on popstate (ie back or forward) we need to replace the hash that was there previously diff --git a/tests/unit/navigation/navigation_core.js b/tests/unit/navigation/navigation_core.js index 2c5a87cf..37406ebb 100644 --- a/tests/unit/navigation/navigation_core.js +++ b/tests/unit/navigation/navigation_core.js @@ -43,6 +43,36 @@ } }); + asyncTest( "window.history.back() from external to internal page", function(){ + + $.testHelper.pageSequence([ + + // open our test page + function(){ + $.testHelper.openPage("#active-state-page1"); + }, + + function(){ + ok( $.mobile.activePage[0] === $( "#active-state-page1" )[ 0 ], "successful navigation to internal page." ); + + //location.hash = siteDirectory + "external.html"; + $.mobile.changePage("external.html"); + }, + + function(){ + ok( $.mobile.activePage[0] !== $( "#active-state-page1" )[ 0 ], "successful navigation to external page." ); + + window.history.back(); + }, + + function(){ + ok( $.mobile.activePage[0] === $( "#active-state-page1" )[ 0 ], "successful navigation back to internal page." ); + + start(); + } + ]); + }); + asyncTest( "external page is removed from the DOM after pagehide", function(){ $.testHelper.pageSequence([ navigateTestRoot,