Roo/History.js
[roojs1] / Roo / History.js
1 /**
2  * Originally based of this code... - refactored for Roo...
3  *
4  * History.js Core
5  * @author Benjamin Arthur Lupton <contact@balupton.com>
6  * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
7  * @license New BSD License <http://creativecommons.org/licenses/BSD/>
8  */
9
10 (function(window,undefined){
11         "use strict";
12
13         // ========================================================================
14         // Initialise
15
16         // Localise Globals
17         var
18                 console = window.console||undefined, // Prevent a JSLint complain
19                 document = window.document, // Make sure we are using the correct document
20                 navigator = window.navigator, // Make sure we are using the correct navigator
21                 sessionStorage = false, // sessionStorage
22                 setTimeout = window.setTimeout,
23                 clearTimeout = window.clearTimeout,
24                 setInterval = window.setInterval,
25                 clearInterval = window.clearInterval,
26                 JSON = window.JSON,
27                 alert = window.alert,
28                 History = window.History = window.History||{}, // Public History Object
29                 history = window.history; // Old History Object
30
31         try {
32                 sessionStorage = window.sessionStorage; // This will throw an exception in some browsers when cookies/localStorage are explicitly disabled (i.e. Chrome)
33                 sessionStorage.setItem('TEST', '1');
34                 sessionStorage.removeItem('TEST');
35         } catch(e) {
36                 sessionStorage = false;
37         }
38
39         // MooTools Compatibility
40         JSON.stringify = JSON.stringify||JSON.encode;
41         JSON.parse = JSON.parse||JSON.decode;
42
43         // Check Existence
44         if ( typeof History.init !== 'undefined' ) {
45                 throw new Error('History.js Core has already been loaded...');
46         }
47
48         // Initialise History
49         History.init = function(options){
50                 // Check Load Status of Adapter
51                 if ( typeof History.Adapter === 'undefined' ) {
52                         return false;
53                 }
54
55                 // Check Load Status of Core
56                 if ( typeof History.initCore !== 'undefined' ) {
57                         History.initCore();
58                 }
59
60                 // Check Load Status of HTML4 Support
61                 if ( typeof History.initHtml4 !== 'undefined' ) {
62                         History.initHtml4();
63                 }
64
65                 // Return true
66                 return true;
67         };
68
69
70         // ========================================================================
71         // Initialise Core
72
73         // Initialise Core
74         History.initCore = function(options){
75                 // Initialise
76                 if ( typeof History.initCore.initialized !== 'undefined' ) {
77                         // Already Loaded
78                         return false;
79                 }
80                 else {
81                         History.initCore.initialized = true;
82                 }
83
84
85                 // ====================================================================
86                 // Options
87
88                 /**
89                  * History.options
90                  * Configurable options
91                  */
92                 History.options = History.options||{};
93
94                 /**
95                  * History.options.hashChangeInterval
96                  * How long should the interval be before hashchange checks
97                  */
98                 History.options.hashChangeInterval = History.options.hashChangeInterval || 100;
99
100                 /**
101                  * History.options.safariPollInterval
102                  * How long should the interval be before safari poll checks
103                  */
104                 History.options.safariPollInterval = History.options.safariPollInterval || 500;
105
106                 /**
107                  * History.options.doubleCheckInterval
108                  * How long should the interval be before we perform a double check
109                  */
110                 History.options.doubleCheckInterval = History.options.doubleCheckInterval || 500;
111
112                 /**
113                  * History.options.disableSuid
114                  * Force History not to append suid
115                  */
116                 History.options.disableSuid = History.options.disableSuid || false;
117
118                 /**
119                  * History.options.storeInterval
120                  * How long should we wait between store calls
121                  */
122                 History.options.storeInterval = History.options.storeInterval || 1000;
123
124                 /**
125                  * History.options.busyDelay
126                  * How long should we wait between busy events
127                  */
128                 History.options.busyDelay = History.options.busyDelay || 250;
129
130                 /**
131                  * History.options.debug
132                  * If true will enable debug messages to be logged
133                  */
134                 History.options.debug = History.options.debug || false;
135
136                 /**
137                  * History.options.initialTitle
138                  * What is the title of the initial state
139                  */
140                 History.options.initialTitle = History.options.initialTitle || document.title;
141
142                 /**
143                  * History.options.html4Mode
144                  * If true, will force HTMl4 mode (hashtags)
145                  */
146                 History.options.html4Mode = History.options.html4Mode || false;
147
148                 /**
149                  * History.options.delayInit
150                  * Want to override default options and call init manually.
151                  */
152                 History.options.delayInit = History.options.delayInit || false;
153
154
155                 // ====================================================================
156                 // Interval record
157
158                 /**
159                  * History.intervalList
160                  * List of intervals set, to be cleared when document is unloaded.
161                  */
162                 History.intervalList = [];
163
164                 /**
165                  * History.clearAllIntervals
166                  * Clears all setInterval instances.
167                  */
168                 History.clearAllIntervals = function(){
169                         var i, il = History.intervalList;
170                         if (typeof il !== "undefined" && il !== null) {
171                                 for (i = 0; i < il.length; i++) {
172                                         clearInterval(il[i]);
173                                 }
174                                 History.intervalList = null;
175                         }
176                 };
177
178
179                 // ====================================================================
180                 // Debug
181
182                 /**
183                  * History.debug(message,...)
184                  * Logs the passed arguments if debug enabled
185                  */
186                 History.debug = function(){
187                         if ( (History.options.debug||false) ) {
188                                 Roo.log.apply(History,arguments);
189                         }
190                 };
191
192                  
193
194                 // ====================================================================
195                 // Emulated Status
196
197                 /**
198                  * History.getInternetExplorerMajorVersion()
199                  * Get's the major version of Internet Explorer
200                  * @return {integer}
201                  * @license Public Domain
202                  * @author Benjamin Arthur Lupton <contact@balupton.com>
203                  * @author James Padolsey <https://gist.github.com/527683>
204                  */
205                 History.getInternetExplorerMajorVersion = function(){
206                         var result = History.getInternetExplorerMajorVersion.cached =
207                                         (typeof History.getInternetExplorerMajorVersion.cached !== 'undefined')
208                                 ?       History.getInternetExplorerMajorVersion.cached
209                                 :       (function(){
210                                                 var v = 3,
211                                                                 div = document.createElement('div'),
212                                                                 all = div.getElementsByTagName('i');
213                                                 while ( (div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->') && all[0] ) {}
214                                                 return (v > 4) ? v : false;
215                                         })()
216                                 ;
217                         return result;
218                 };
219
220                 /**
221                  * History.isInternetExplorer()
222                  * Are we using Internet Explorer?
223                  * @return {boolean}
224                  * @license Public Domain
225                  * @author Benjamin Arthur Lupton <contact@balupton.com>
226                  */
227                 History.isInternetExplorer = function(){
228                         var result =
229                                 History.isInternetExplorer.cached =
230                                 (typeof History.isInternetExplorer.cached !== 'undefined')
231                                         ?       History.isInternetExplorer.cached
232                                         :       Boolean(History.getInternetExplorerMajorVersion())
233                                 ;
234                         return result;
235                 };
236
237                 /**
238                  * History.emulated
239                  * Which features require emulating?
240                  */
241
242                 if (History.options.html4Mode) {
243                         History.emulated = {
244                                 pushState : true,
245                                 hashChange: true
246                         };
247                 }
248
249                 else {
250
251                         History.emulated = {
252                                 pushState: !Boolean(
253                                         window.history && window.history.pushState && window.history.replaceState
254                                         && !(
255                                                 (/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent) /* disable for versions of iOS before version 4.3 (8F190) */
256                                                 || (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent) /* disable for the mercury iOS browser, or at least older versions of the webkit engine */
257                                         )
258                                 ),
259                                 hashChange: Boolean(
260                                         !(('onhashchange' in window) || ('onhashchange' in document))
261                                         ||
262                                         (History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8)
263                                 )
264                         };
265                 }
266
267                 /**
268                  * History.enabled
269                  * Is History enabled?
270                  */
271                 History.enabled = !History.emulated.pushState;
272
273                 /**
274                  * History.bugs
275                  * Which bugs are present
276                  */
277                 History.bugs = {
278                         /**
279                          * Safari 5 and Safari iOS 4 fail to return to the correct state once a hash is replaced by a `replaceState` call
280                          * https://bugs.webkit.org/show_bug.cgi?id=56249
281                          */
282                         setHash: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
283
284                         /**
285                          * Safari 5 and Safari iOS 4 sometimes fail to apply the state change under busy conditions
286                          * https://bugs.webkit.org/show_bug.cgi?id=42940
287                          */
288                         safariPoll: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
289
290                         /**
291                          * MSIE 6 and 7 sometimes do not apply a hash even it was told to (requiring a second call to the apply function)
292                          */
293                         ieDoubleCheck: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8),
294
295                         /**
296                          * MSIE 6 requires the entire hash to be encoded for the hashes to trigger the onHashChange event
297                          */
298                         hashEscape: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 7)
299                 };
300
301                 /**
302                  * History.isEmptyObject(obj)
303                  * Checks to see if the Object is Empty
304                  * @param {Object} obj
305                  * @return {boolean}
306                  */
307                 History.isEmptyObject = function(obj) {
308                         for ( var name in obj ) {
309                                 if ( obj.hasOwnProperty(name) ) {
310                                         return false;
311                                 }
312                         }
313                         return true;
314                 };
315
316                 /**
317                  * History.cloneObject(obj)
318                  * Clones a object and eliminate all references to the original contexts
319                  * @param {Object} obj
320                  * @return {Object}
321                  */
322                 History.cloneObject = function(obj) {
323                         var hash,newObj;
324                         if ( obj ) {
325                                 hash = JSON.stringify(obj);
326                                 newObj = JSON.parse(hash);
327                         }
328                         else {
329                                 newObj = {};
330                         }
331                         return newObj;
332                 };
333
334
335                 // ====================================================================
336                 // URL Helpers
337
338                 /**
339                  * History.getRootUrl()
340                  * Turns "http://mysite.com/dir/page.html?asd" into "http://mysite.com"
341                  * @return {String} rootUrl
342                  */
343                 History.getRootUrl = function(){
344                         // Create
345                         var rootUrl = document.location.protocol+'//'+(document.location.hostname||document.location.host);
346                         if ( document.location.port||false ) {
347                                 rootUrl += ':'+document.location.port;
348                         }
349                         rootUrl += '/';
350
351                         // Return
352                         return rootUrl;
353                 };
354
355                 /**
356                  * History.getBaseHref()
357                  * Fetches the `href` attribute of the `<base href="...">` element if it exists
358                  * @return {String} baseHref
359                  */
360                 History.getBaseHref = function(){
361                         // Create
362                         var
363                                 baseElements = document.getElementsByTagName('base'),
364                                 baseElement = null,
365                                 baseHref = '';
366
367                         // Test for Base Element
368                         if ( baseElements.length === 1 ) {
369                                 // Prepare for Base Element
370                                 baseElement = baseElements[0];
371                                 baseHref = baseElement.href.replace(/[^\/]+$/,'');
372                         }
373
374                         // Adjust trailing slash
375                         baseHref = baseHref.replace(/\/+$/,'');
376                         if ( baseHref ) baseHref += '/';
377
378                         // Return
379                         return baseHref;
380                 };
381
382                 /**
383                  * History.getBaseUrl()
384                  * Fetches the baseHref or basePageUrl or rootUrl (whichever one exists first)
385                  * @return {String} baseUrl
386                  */
387                 History.getBaseUrl = function(){
388                         // Create
389                         var baseUrl = History.getBaseHref()||History.getBasePageUrl()||History.getRootUrl();
390
391                         // Return
392                         return baseUrl;
393                 };
394
395                 /**
396                  * History.getPageUrl()
397                  * Fetches the URL of the current page
398                  * @return {String} pageUrl
399                  */
400                 History.getPageUrl = function(){
401                         // Fetch
402                         var
403                                 State = History.getState(false,false),
404                                 stateUrl = (State||{}).url||History.getLocationHref(),
405                                 pageUrl;
406
407                         // Create
408                         pageUrl = stateUrl.replace(/\/+$/,'').replace(/[^\/]+$/,function(part,index,string){
409                                 return (/\./).test(part) ? part : part+'/';
410                         });
411
412                         // Return
413                         return pageUrl;
414                 };
415
416                 /**
417                  * History.getBasePageUrl()
418                  * Fetches the Url of the directory of the current page
419                  * @return {String} basePageUrl
420                  */
421                 History.getBasePageUrl = function(){
422                         // Create
423                         var basePageUrl = (History.getLocationHref()).replace(/[#\?].*/,'').replace(/[^\/]+$/,function(part,index,string){
424                                 return (/[^\/]$/).test(part) ? '' : part;
425                         }).replace(/\/+$/,'')+'/';
426
427                         // Return
428                         return basePageUrl;
429                 };
430
431                 /**
432                  * History.getFullUrl(url)
433                  * Ensures that we have an absolute URL and not a relative URL
434                  * @param {string} url
435                  * @param {Boolean} allowBaseHref
436                  * @return {string} fullUrl
437                  */
438                 History.getFullUrl = function(url,allowBaseHref){
439                         // Prepare
440                         var fullUrl = url, firstChar = url.substring(0,1);
441                         allowBaseHref = (typeof allowBaseHref === 'undefined') ? true : allowBaseHref;
442
443                         // Check
444                         if ( /[a-z]+\:\/\//.test(url) ) {
445                                 // Full URL
446                         }
447                         else if ( firstChar === '/' ) {
448                                 // Root URL
449                                 fullUrl = History.getRootUrl()+url.replace(/^\/+/,'');
450                         }
451                         else if ( firstChar === '#' ) {
452                                 // Anchor URL
453                                 fullUrl = History.getPageUrl().replace(/#.*/,'')+url;
454                         }
455                         else if ( firstChar === '?' ) {
456                                 // Query URL
457                                 fullUrl = History.getPageUrl().replace(/[\?#].*/,'')+url;
458                         }
459                         else {
460                                 // Relative URL
461                                 if ( allowBaseHref ) {
462                                         fullUrl = History.getBaseUrl()+url.replace(/^(\.\/)+/,'');
463                                 } else {
464                                         fullUrl = History.getBasePageUrl()+url.replace(/^(\.\/)+/,'');
465                                 }
466                                 // We have an if condition above as we do not want hashes
467                                 // which are relative to the baseHref in our URLs
468                                 // as if the baseHref changes, then all our bookmarks
469                                 // would now point to different locations
470                                 // whereas the basePageUrl will always stay the same
471                         }
472
473                         // Return
474                         return fullUrl.replace(/\#$/,'');
475                 };
476
477                 /**
478                  * History.getShortUrl(url)
479                  * Ensures that we have a relative URL and not a absolute URL
480                  * @param {string} url
481                  * @return {string} url
482                  */
483                 History.getShortUrl = function(url){
484                         // Prepare
485                         var shortUrl = url, baseUrl = History.getBaseUrl(), rootUrl = History.getRootUrl();
486
487                         // Trim baseUrl
488                         if ( History.emulated.pushState ) {
489                                 // We are in a if statement as when pushState is not emulated
490                                 // The actual url these short urls are relative to can change
491                                 // So within the same session, we the url may end up somewhere different
492                                 shortUrl = shortUrl.replace(baseUrl,'');
493                         }
494
495                         // Trim rootUrl
496                         shortUrl = shortUrl.replace(rootUrl,'/');
497
498                         // Ensure we can still detect it as a state
499                         if ( History.isTraditionalAnchor(shortUrl) ) {
500                                 shortUrl = './'+shortUrl;
501                         }
502
503                         // Clean It
504                         shortUrl = shortUrl.replace(/^(\.\/)+/g,'./').replace(/\#$/,'');
505
506                         // Return
507                         return shortUrl;
508                 };
509
510                 /**
511                  * History.getLocationHref(document)
512                  * Returns a normalized version of document.location.href
513                  * accounting for browser inconsistencies, etc.
514                  *
515                  * This URL will be URI-encoded and will include the hash
516                  *
517                  * @param {object} document
518                  * @return {string} url
519                  */
520                 History.getLocationHref = function(doc) {
521                         doc = doc || document;
522
523                         // most of the time, this will be true
524                         if (doc.URL === doc.location.href)
525                                 return doc.location.href;
526
527                         // some versions of webkit URI-decode document.location.href
528                         // but they leave document.URL in an encoded state
529                         if (doc.location.href === decodeURIComponent(doc.URL))
530                                 return doc.URL;
531
532                         // FF 3.6 only updates document.URL when a page is reloaded
533                         // document.location.href is updated correctly
534                         if (doc.location.hash && decodeURIComponent(doc.location.href.replace(/^[^#]+/, "")) === doc.location.hash)
535                                 return doc.location.href;
536
537                         if (doc.URL.indexOf('#') == -1 && doc.location.href.indexOf('#') != -1)
538                                 return doc.location.href;
539                         
540                         return doc.URL || doc.location.href;
541                 };
542
543
544                 // ====================================================================
545                 // State Storage
546
547                 /**
548                  * History.store
549                  * The store for all session specific data
550                  */
551                 History.store = {};
552
553                 /**
554                  * History.idToState
555                  * 1-1: State ID to State Object
556                  */
557                 History.idToState = History.idToState||{};
558
559                 /**
560                  * History.stateToId
561                  * 1-1: State String to State ID
562                  */
563                 History.stateToId = History.stateToId||{};
564
565                 /**
566                  * History.urlToId
567                  * 1-1: State URL to State ID
568                  */
569                 History.urlToId = History.urlToId||{};
570
571                 /**
572                  * History.storedStates
573                  * Store the states in an array
574                  */
575                 History.storedStates = History.storedStates||[];
576
577                 /**
578                  * History.savedStates
579                  * Saved the states in an array
580                  */
581                 History.savedStates = History.savedStates||[];
582
583                 /**
584                  * History.noramlizeStore()
585                  * Noramlize the store by adding necessary values
586                  */
587                 History.normalizeStore = function(){
588                         History.store.idToState = History.store.idToState||{};
589                         History.store.urlToId = History.store.urlToId||{};
590                         History.store.stateToId = History.store.stateToId||{};
591                 };
592
593                 /**
594                  * History.getState()
595                  * Get an object containing the data, title and url of the current state
596                  * @param {Boolean} friendly
597                  * @param {Boolean} create
598                  * @return {Object} State
599                  */
600                 History.getState = function(friendly,create){
601                         // Prepare
602                         if ( typeof friendly === 'undefined' ) { friendly = true; }
603                         if ( typeof create === 'undefined' ) { create = true; }
604
605                         // Fetch
606                         var State = History.getLastSavedState();
607
608                         // Create
609                         if ( !State && create ) {
610                                 State = History.createStateObject();
611                         }
612
613                         // Adjust
614                         if ( friendly ) {
615                                 State = History.cloneObject(State);
616                                 State.url = State.cleanUrl||State.url;
617                         }
618
619                         // Return
620                         return State;
621                 };
622
623                 /**
624                  * History.getIdByState(State)
625                  * Gets a ID for a State
626                  * @param {State} newState
627                  * @return {String} id
628                  */
629                 History.getIdByState = function(newState){
630
631                         // Fetch ID
632                         var id = History.extractId(newState.url),
633                                 str;
634
635                         if ( !id ) {
636                                 // Find ID via State String
637                                 str = History.getStateString(newState);
638                                 if ( typeof History.stateToId[str] !== 'undefined' ) {
639                                         id = History.stateToId[str];
640                                 }
641                                 else if ( typeof History.store.stateToId[str] !== 'undefined' ) {
642                                         id = History.store.stateToId[str];
643                                 }
644                                 else {
645                                         // Generate a new ID
646                                         while ( true ) {
647                                                 id = (new Date()).getTime() + String(Math.random()).replace(/\D/g,'');
648                                                 if ( typeof History.idToState[id] === 'undefined' && typeof History.store.idToState[id] === 'undefined' ) {
649                                                         break;
650                                                 }
651                                         }
652
653                                         // Apply the new State to the ID
654                                         History.stateToId[str] = id;
655                                         History.idToState[id] = newState;
656                                 }
657                         }
658
659                         // Return ID
660                         return id;
661                 };
662
663                 /**
664                  * History.normalizeState(State)
665                  * Expands a State Object
666                  * @param {object} State
667                  * @return {object}
668                  */
669                 History.normalizeState = function(oldState){
670                         // Variables
671                         var newState, dataNotEmpty;
672
673                         // Prepare
674                         if ( !oldState || (typeof oldState !== 'object') ) {
675                                 oldState = {};
676                         }
677
678                         // Check
679                         if ( typeof oldState.normalized !== 'undefined' ) {
680                                 return oldState;
681                         }
682
683                         // Adjust
684                         if ( !oldState.data || (typeof oldState.data !== 'object') ) {
685                                 oldState.data = {};
686                         }
687
688                         // ----------------------------------------------------------------
689
690                         // Create
691                         newState = {};
692                         newState.normalized = true;
693                         newState.title = oldState.title||'';
694                         newState.url = History.getFullUrl(oldState.url?oldState.url:(History.getLocationHref()));
695                         newState.hash = History.getShortUrl(newState.url);
696                         newState.data = History.cloneObject(oldState.data);
697
698                         // Fetch ID
699                         newState.id = History.getIdByState(newState);
700
701                         // ----------------------------------------------------------------
702
703                         // Clean the URL
704                         newState.cleanUrl = newState.url.replace(/\??\&_suid.*/,'');
705                         newState.url = newState.cleanUrl;
706
707                         // Check to see if we have more than just a url
708                         dataNotEmpty = !History.isEmptyObject(newState.data);
709
710                         // Apply
711                         if ( (newState.title || dataNotEmpty) && History.options.disableSuid !== true ) {
712                                 // Add ID to Hash
713                                 newState.hash = History.getShortUrl(newState.url).replace(/\??\&_suid.*/,'');
714                                 if ( !/\?/.test(newState.hash) ) {
715                                         newState.hash += '?';
716                                 }
717                                 newState.hash += '&_suid='+newState.id;
718                         }
719
720                         // Create the Hashed URL
721                         newState.hashedUrl = History.getFullUrl(newState.hash);
722
723                         // ----------------------------------------------------------------
724
725                         // Update the URL if we have a duplicate
726                         if ( (History.emulated.pushState || History.bugs.safariPoll) && History.hasUrlDuplicate(newState) ) {
727                                 newState.url = newState.hashedUrl;
728                         }
729
730                         // ----------------------------------------------------------------
731
732                         // Return
733                         return newState;
734                 };
735
736                 /**
737                  * History.createStateObject(data,title,url)
738                  * Creates a object based on the data, title and url state params
739                  * @param {object} data
740                  * @param {string} title
741                  * @param {string} url
742                  * @return {object}
743                  */
744                 History.createStateObject = function(data,title,url){
745                         // Hashify
746                         var State = {
747                                 'data': data,
748                                 'title': title,
749                                 'url': url
750                         };
751
752                         // Expand the State
753                         State = History.normalizeState(State);
754
755                         // Return object
756                         return State;
757                 };
758
759                 /**
760                  * History.getStateById(id)
761                  * Get a state by it's UID
762                  * @param {String} id
763                  */
764                 History.getStateById = function(id){
765                         // Prepare
766                         id = String(id);
767
768                         // Retrieve
769                         var State = History.idToState[id] || History.store.idToState[id] || undefined;
770
771                         // Return State
772                         return State;
773                 };
774
775                 /**
776                  * Get a State's String
777                  * @param {State} passedState
778                  */
779                 History.getStateString = function(passedState){
780                         // Prepare
781                         var State, cleanedState, str;
782
783                         // Fetch
784                         State = History.normalizeState(passedState);
785
786                         // Clean
787                         cleanedState = {
788                                 data: State.data,
789                                 title: passedState.title,
790                                 url: passedState.url
791                         };
792
793                         // Fetch
794                         str = JSON.stringify(cleanedState);
795
796                         // Return
797                         return str;
798                 };
799
800                 /**
801                  * Get a State's ID
802                  * @param {State} passedState
803                  * @return {String} id
804                  */
805                 History.getStateId = function(passedState){
806                         // Prepare
807                         var State, id;
808
809                         // Fetch
810                         State = History.normalizeState(passedState);
811
812                         // Fetch
813                         id = State.id;
814
815                         // Return
816                         return id;
817                 };
818
819                 /**
820                  * History.getHashByState(State)
821                  * Creates a Hash for the State Object
822                  * @param {State} passedState
823                  * @return {String} hash
824                  */
825                 History.getHashByState = function(passedState){
826                         // Prepare
827                         var State, hash;
828
829                         // Fetch
830                         State = History.normalizeState(passedState);
831
832                         // Hash
833                         hash = State.hash;
834
835                         // Return
836                         return hash;
837                 };
838
839                 /**
840                  * History.extractId(url_or_hash)
841                  * Get a State ID by it's URL or Hash
842                  * @param {string} url_or_hash
843                  * @return {string} id
844                  */
845                 History.extractId = function ( url_or_hash ) {
846                         // Prepare
847                         var id,parts,url, tmp;
848
849                         // Extract
850                         
851                         // If the URL has a #, use the id from before the #
852                         if (url_or_hash.indexOf('#') != -1)
853                         {
854                                 tmp = url_or_hash.split("#")[0];
855                         }
856                         else
857                         {
858                                 tmp = url_or_hash;
859                         }
860                         
861                         parts = /(.*)\&_suid=([0-9]+)$/.exec(tmp);
862                         url = parts ? (parts[1]||url_or_hash) : url_or_hash;
863                         id = parts ? String(parts[2]||'') : '';
864
865                         // Return
866                         return id||false;
867                 };
868
869                 /**
870                  * History.isTraditionalAnchor
871                  * Checks to see if the url is a traditional anchor or not
872                  * @param {String} url_or_hash
873                  * @return {Boolean}
874                  */
875                 History.isTraditionalAnchor = function(url_or_hash){
876                         // Check
877                         var isTraditional = !(/[\/\?\.]/.test(url_or_hash));
878
879                         // Return
880                         return isTraditional;
881                 };
882
883                 /**
884                  * History.extractState
885                  * Get a State by it's URL or Hash
886                  * @param {String} url_or_hash
887                  * @return {State|null}
888                  */
889                 History.extractState = function(url_or_hash,create){
890                         // Prepare
891                         var State = null, id, url;
892                         create = create||false;
893
894                         // Fetch SUID
895                         id = History.extractId(url_or_hash);
896                         if ( id ) {
897                                 State = History.getStateById(id);
898                         }
899
900                         // Fetch SUID returned no State
901                         if ( !State ) {
902                                 // Fetch URL
903                                 url = History.getFullUrl(url_or_hash);
904
905                                 // Check URL
906                                 id = History.getIdByUrl(url)||false;
907                                 if ( id ) {
908                                         State = History.getStateById(id);
909                                 }
910
911                                 // Create State
912                                 if ( !State && create && !History.isTraditionalAnchor(url_or_hash) ) {
913                                         State = History.createStateObject(null,null,url);
914                                 }
915                         }
916
917                         // Return
918                         return State;
919                 };
920
921                 /**
922                  * History.getIdByUrl()
923                  * Get a State ID by a State URL
924                  */
925                 History.getIdByUrl = function(url){
926                         // Fetch
927                         var id = History.urlToId[url] || History.store.urlToId[url] || undefined;
928
929                         // Return
930                         return id;
931                 };
932
933                 /**
934                  * History.getLastSavedState()
935                  * Get an object containing the data, title and url of the current state
936                  * @return {Object} State
937                  */
938                 History.getLastSavedState = function(){
939                         return History.savedStates[History.savedStates.length-1]||undefined;
940                 };
941
942                 /**
943                  * History.getLastStoredState()
944                  * Get an object containing the data, title and url of the current state
945                  * @return {Object} State
946                  */
947                 History.getLastStoredState = function(){
948                         return History.storedStates[History.storedStates.length-1]||undefined;
949                 };
950
951                 /**
952                  * History.hasUrlDuplicate
953                  * Checks if a Url will have a url conflict
954                  * @param {Object} newState
955                  * @return {Boolean} hasDuplicate
956                  */
957                 History.hasUrlDuplicate = function(newState) {
958                         // Prepare
959                         var hasDuplicate = false,
960                                 oldState;
961
962                         // Fetch
963                         oldState = History.extractState(newState.url);
964
965                         // Check
966                         hasDuplicate = oldState && oldState.id !== newState.id;
967
968                         // Return
969                         return hasDuplicate;
970                 };
971
972                 /**
973                  * History.storeState
974                  * Store a State
975                  * @param {Object} newState
976                  * @return {Object} newState
977                  */
978                 History.storeState = function(newState){
979                         // Store the State
980                         History.urlToId[newState.url] = newState.id;
981
982                         // Push the State
983                         History.storedStates.push(History.cloneObject(newState));
984
985                         // Return newState
986                         return newState;
987                 };
988
989                 /**
990                  * History.isLastSavedState(newState)
991                  * Tests to see if the state is the last state
992                  * @param {Object} newState
993                  * @return {boolean} isLast
994                  */
995                 History.isLastSavedState = function(newState){
996                         // Prepare
997                         var isLast = false,
998                                 newId, oldState, oldId;
999
1000                         // Check
1001                         if ( History.savedStates.length ) {
1002                                 newId = newState.id;
1003                                 oldState = History.getLastSavedState();
1004                                 oldId = oldState.id;
1005
1006                                 // Check
1007                                 isLast = (newId === oldId);
1008                         }
1009
1010                         // Return
1011                         return isLast;
1012                 };
1013
1014                 /**
1015                  * History.saveState
1016                  * Push a State
1017                  * @param {Object} newState
1018                  * @return {boolean} changed
1019                  */
1020                 History.saveState = function(newState){
1021                         // Check Hash
1022                         if ( History.isLastSavedState(newState) ) {
1023                                 return false;
1024                         }
1025
1026                         // Push the State
1027                         History.savedStates.push(History.cloneObject(newState));
1028
1029                         // Return true
1030                         return true;
1031                 };
1032
1033                 /**
1034                  * History.getStateByIndex()
1035                  * Gets a state by the index
1036                  * @param {integer} index
1037                  * @return {Object}
1038                  */
1039                 History.getStateByIndex = function(index){
1040                         // Prepare
1041                         var State = null;
1042
1043                         // Handle
1044                         if ( typeof index === 'undefined' ) {
1045                                 // Get the last inserted
1046                                 State = History.savedStates[History.savedStates.length-1];
1047                         }
1048                         else if ( index < 0 ) {
1049                                 // Get from the end
1050                                 State = History.savedStates[History.savedStates.length+index];
1051                         }
1052                         else {
1053                                 // Get from the beginning
1054                                 State = History.savedStates[index];
1055                         }
1056
1057                         // Return State
1058                         return State;
1059                 };
1060                 
1061                 /**
1062                  * History.getCurrentIndex()
1063                  * Gets the current index
1064                  * @return (integer)
1065                 */
1066                 History.getCurrentIndex = function(){
1067                         // Prepare
1068                         var index = null;
1069                         
1070                         // No states saved
1071                         if(History.savedStates.length < 1) {
1072                                 index = 0;
1073                         }
1074                         else {
1075                                 index = History.savedStates.length-1;
1076                         }
1077                         return index;
1078                 };
1079
1080                 // ====================================================================
1081                 // Hash Helpers
1082
1083                 /**
1084                  * History.getHash()
1085                  * @param {Location=} location
1086                  * Gets the current document hash
1087                  * Note: unlike location.hash, this is guaranteed to return the escaped hash in all browsers
1088                  * @return {string}
1089                  */
1090                 History.getHash = function(doc){
1091                         var url = History.getLocationHref(doc),
1092                                 hash;
1093                         hash = History.getHashByUrl(url);
1094                         return hash;
1095                 };
1096
1097                 /**
1098                  * History.unescapeHash()
1099                  * normalize and Unescape a Hash
1100                  * @param {String} hash
1101                  * @return {string}
1102                  */
1103                 History.unescapeHash = function(hash){
1104                         // Prepare
1105                         var result = History.normalizeHash(hash);
1106
1107                         // Unescape hash
1108                         result = decodeURIComponent(result);
1109
1110                         // Return result
1111                         return result;
1112                 };
1113
1114                 /**
1115                  * History.normalizeHash()
1116                  * normalize a hash across browsers
1117                  * @return {string}
1118                  */
1119                 History.normalizeHash = function(hash){
1120                         // Prepare
1121                         var result = hash.replace(/[^#]*#/,'').replace(/#.*/, '');
1122
1123                         // Return result
1124                         return result;
1125                 };
1126
1127                 /**
1128                  * History.setHash(hash)
1129                  * Sets the document hash
1130                  * @param {string} hash
1131                  * @return {History}
1132                  */
1133                 History.setHash = function(hash,queue){
1134                         // Prepare
1135                         var State, pageUrl;
1136
1137                         // Handle Queueing
1138                         if ( queue !== false && History.busy() ) {
1139                                 // Wait + Push to Queue
1140                                 //History.debug('History.setHash: we must wait', arguments);
1141                                 History.pushQueue({
1142                                         scope: History,
1143                                         callback: History.setHash,
1144                                         args: arguments,
1145                                         queue: queue
1146                                 });
1147                                 return false;
1148                         }
1149
1150                         // Log
1151                         //History.debug('History.setHash: called',hash);
1152
1153                         // Make Busy + Continue
1154                         History.busy(true);
1155
1156                         // Check if hash is a state
1157                         State = History.extractState(hash,true);
1158                         if ( State && !History.emulated.pushState ) {
1159                                 // Hash is a state so skip the setHash
1160                                 //History.debug('History.setHash: Hash is a state so skipping the hash set with a direct pushState call',arguments);
1161
1162                                 // PushState
1163                                 History.pushState(State.data,State.title,State.url,false);
1164                         }
1165                         else if ( History.getHash() !== hash ) {
1166                                 // Hash is a proper hash, so apply it
1167
1168                                 // Handle browser bugs
1169                                 if ( History.bugs.setHash ) {
1170                                         // Fix Safari Bug https://bugs.webkit.org/show_bug.cgi?id=56249
1171
1172                                         // Fetch the base page
1173                                         pageUrl = History.getPageUrl();
1174
1175                                         // Safari hash apply
1176                                         History.pushState(null,null,pageUrl+'#'+hash,false);
1177                                 }
1178                                 else {
1179                                         // Normal hash apply
1180                                         document.location.hash = hash;
1181                                 }
1182                         }
1183
1184                         // Chain
1185                         return History;
1186                 };
1187
1188                 /**
1189                  * History.escape()
1190                  * normalize and Escape a Hash
1191                  * @return {string}
1192                  */
1193                 History.escapeHash = function(hash){
1194                         // Prepare
1195                         var result = History.normalizeHash(hash);
1196
1197                         // Escape hash
1198                         result = window.encodeURIComponent(result);
1199
1200                         // IE6 Escape Bug
1201                         if ( !History.bugs.hashEscape ) {
1202                                 // Restore common parts
1203                                 result = result
1204                                         .replace(/\%21/g,'!')
1205                                         .replace(/\%26/g,'&')
1206                                         .replace(/\%3D/g,'=')
1207                                         .replace(/\%3F/g,'?');
1208                         }
1209
1210                         // Return result
1211                         return result;
1212                 };
1213
1214                 /**
1215                  * History.getHashByUrl(url)
1216                  * Extracts the Hash from a URL
1217                  * @param {string} url
1218                  * @return {string} url
1219                  */
1220                 History.getHashByUrl = function(url){
1221                         // Extract the hash
1222                         var hash = String(url)
1223                                 .replace(/([^#]*)#?([^#]*)#?(.*)/, '$2')
1224                                 ;
1225
1226                         // Unescape hash
1227                         hash = History.unescapeHash(hash);
1228
1229                         // Return hash
1230                         return hash;
1231                 };
1232
1233                 /**
1234                  * History.setTitle(title)
1235                  * Applies the title to the document
1236                  * @param {State} newState
1237                  * @return {Boolean}
1238                  */
1239                 History.setTitle = function(newState){
1240                         // Prepare
1241                         var title = newState.title,
1242                                 firstState;
1243
1244                         // Initial
1245                         if ( !title ) {
1246                                 firstState = History.getStateByIndex(0);
1247                                 if ( firstState && firstState.url === newState.url ) {
1248                                         title = firstState.title||History.options.initialTitle;
1249                                 }
1250                         }
1251
1252                         // Apply
1253                         try {
1254                                 document.getElementsByTagName('title')[0].innerHTML = title.replace('<','&lt;').replace('>','&gt;').replace(' & ',' &amp; ');
1255                         }
1256                         catch ( Exception ) { }
1257                         document.title = title;
1258
1259                         // Chain
1260                         return History;
1261                 };
1262
1263
1264                 // ====================================================================
1265                 // Queueing
1266
1267                 /**
1268                  * History.queues
1269                  * The list of queues to use
1270                  * First In, First Out
1271                  */
1272                 History.queues = [];
1273
1274                 /**
1275                  * History.busy(value)
1276                  * @param {boolean} value [optional]
1277                  * @return {boolean} busy
1278                  */
1279                 History.busy = function(value){
1280                         // Apply
1281                         if ( typeof value !== 'undefined' ) {
1282                                 //History.debug('History.busy: changing ['+(History.busy.flag||false)+'] to ['+(value||false)+']', History.queues.length);
1283                                 History.busy.flag = value;
1284                         }
1285                         // Default
1286                         else if ( typeof History.busy.flag === 'undefined' ) {
1287                                 History.busy.flag = false;
1288                         }
1289
1290                         // Queue
1291                         if ( !History.busy.flag ) {
1292                                 // Execute the next item in the queue
1293                                 clearTimeout(History.busy.timeout);
1294                                 var fireNext = function(){
1295                                         var i, queue, item;
1296                                         if ( History.busy.flag ) return;
1297                                         for ( i=History.queues.length-1; i >= 0; --i ) {
1298                                                 queue = History.queues[i];
1299                                                 if ( queue.length === 0 ) continue;
1300                                                 item = queue.shift();
1301                                                 History.fireQueueItem(item);
1302                                                 History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
1303                                         }
1304                                 };
1305                                 History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
1306                         }
1307
1308                         // Return
1309                         return History.busy.flag;
1310                 };
1311
1312                 /**
1313                  * History.busy.flag
1314                  */
1315                 History.busy.flag = false;
1316
1317                 /**
1318                  * History.fireQueueItem(item)
1319                  * Fire a Queue Item
1320                  * @param {Object} item
1321                  * @return {Mixed} result
1322                  */
1323                 History.fireQueueItem = function(item){
1324                         return item.callback.apply(item.scope||History,item.args||[]);
1325                 };
1326
1327                 /**
1328                  * History.pushQueue(callback,args)
1329                  * Add an item to the queue
1330                  * @param {Object} item [scope,callback,args,queue]
1331                  */
1332                 History.pushQueue = function(item){
1333                         // Prepare the queue
1334                         History.queues[item.queue||0] = History.queues[item.queue||0]||[];
1335
1336                         // Add to the queue
1337                         History.queues[item.queue||0].push(item);
1338
1339                         // Chain
1340                         return History;
1341                 };
1342
1343                 /**
1344                  * History.queue (item,queue), (func,queue), (func), (item)
1345                  * Either firs the item now if not busy, or adds it to the queue
1346                  */
1347                 History.queue = function(item,queue){
1348                         // Prepare
1349                         if ( typeof item === 'function' ) {
1350                                 item = {
1351                                         callback: item
1352                                 };
1353                         }
1354                         if ( typeof queue !== 'undefined' ) {
1355                                 item.queue = queue;
1356                         }
1357
1358                         // Handle
1359                         if ( History.busy() ) {
1360                                 History.pushQueue(item);
1361                         } else {
1362                                 History.fireQueueItem(item);
1363                         }
1364
1365                         // Chain
1366                         return History;
1367                 };
1368
1369                 /**
1370                  * History.clearQueue()
1371                  * Clears the Queue
1372                  */
1373                 History.clearQueue = function(){
1374                         History.busy.flag = false;
1375                         History.queues = [];
1376                         return History;
1377                 };
1378
1379
1380                 // ====================================================================
1381                 // IE Bug Fix
1382
1383                 /**
1384                  * History.stateChanged
1385                  * States whether or not the state has changed since the last double check was initialised
1386                  */
1387                 History.stateChanged = false;
1388
1389                 /**
1390                  * History.doubleChecker
1391                  * Contains the timeout used for the double checks
1392                  */
1393                 History.doubleChecker = false;
1394
1395                 /**
1396                  * History.doubleCheckComplete()
1397                  * Complete a double check
1398                  * @return {History}
1399                  */
1400                 History.doubleCheckComplete = function(){
1401                         // Update
1402                         History.stateChanged = true;
1403
1404                         // Clear
1405                         History.doubleCheckClear();
1406
1407                         // Chain
1408                         return History;
1409                 };
1410
1411                 /**
1412                  * History.doubleCheckClear()
1413                  * Clear a double check
1414                  * @return {History}
1415                  */
1416                 History.doubleCheckClear = function(){
1417                         // Clear
1418                         if ( History.doubleChecker ) {
1419                                 clearTimeout(History.doubleChecker);
1420                                 History.doubleChecker = false;
1421                         }
1422
1423                         // Chain
1424                         return History;
1425                 };
1426
1427                 /**
1428                  * History.doubleCheck()
1429                  * Create a double check
1430                  * @return {History}
1431                  */
1432                 History.doubleCheck = function(tryAgain){
1433                         // Reset
1434                         History.stateChanged = false;
1435                         History.doubleCheckClear();
1436
1437                         // Fix IE6,IE7 bug where calling history.back or history.forward does not actually change the hash (whereas doing it manually does)
1438                         // Fix Safari 5 bug where sometimes the state does not change: https://bugs.webkit.org/show_bug.cgi?id=42940
1439                         if ( History.bugs.ieDoubleCheck ) {
1440                                 // Apply Check
1441                                 History.doubleChecker = setTimeout(
1442                                         function(){
1443                                                 History.doubleCheckClear();
1444                                                 if ( !History.stateChanged ) {
1445                                                         //History.debug('History.doubleCheck: State has not yet changed, trying again', arguments);
1446                                                         // Re-Attempt
1447                                                         tryAgain();
1448                                                 }
1449                                                 return true;
1450                                         },
1451                                         History.options.doubleCheckInterval
1452                                 );
1453                         }
1454
1455                         // Chain
1456                         return History;
1457                 };
1458
1459
1460                 // ====================================================================
1461                 // Safari Bug Fix
1462
1463                 /**
1464                  * History.safariStatePoll()
1465                  * Poll the current state
1466                  * @return {History}
1467                  */
1468                 History.safariStatePoll = function(){
1469                         // Poll the URL
1470
1471                         // Get the Last State which has the new URL
1472                         var
1473                                 urlState = History.extractState(History.getLocationHref()),
1474                                 newState;
1475
1476                         // Check for a difference
1477                         if ( !History.isLastSavedState(urlState) ) {
1478                                 newState = urlState;
1479                         }
1480                         else {
1481                                 return;
1482                         }
1483
1484                         // Check if we have a state with that url
1485                         // If not create it
1486                         if ( !newState ) {
1487                                 //History.debug('History.safariStatePoll: new');
1488                                 newState = History.createStateObject();
1489                         }
1490
1491                         // Apply the New State
1492                         //History.debug('History.safariStatePoll: trigger');
1493                         History.Adapter.trigger(window,'popstate');
1494
1495                         // Chain
1496                         return History;
1497                 };
1498
1499
1500                 // ====================================================================
1501                 // State Aliases
1502
1503                 /**
1504                  * History.back(queue)
1505                  * Send the browser history back one item
1506                  * @param {Integer} queue [optional]
1507                  */
1508                 History.back = function(queue){
1509                         //History.debug('History.back: called', arguments);
1510
1511                         // Handle Queueing
1512                         if ( queue !== false && History.busy() ) {
1513                                 // Wait + Push to Queue
1514                                 //History.debug('History.back: we must wait', arguments);
1515                                 History.pushQueue({
1516                                         scope: History,
1517                                         callback: History.back,
1518                                         args: arguments,
1519                                         queue: queue
1520                                 });
1521                                 return false;
1522                         }
1523
1524                         // Make Busy + Continue
1525                         History.busy(true);
1526
1527                         // Fix certain browser bugs that prevent the state from changing
1528                         History.doubleCheck(function(){
1529                                 History.back(false);
1530                         });
1531
1532                         // Go back
1533                         history.go(-1);
1534
1535                         // End back closure
1536                         return true;
1537                 };
1538
1539                 /**
1540                  * History.forward(queue)
1541                  * Send the browser history forward one item
1542                  * @param {Integer} queue [optional]
1543                  */
1544                 History.forward = function(queue){
1545                         //History.debug('History.forward: called', arguments);
1546
1547                         // Handle Queueing
1548                         if ( queue !== false && History.busy() ) {
1549                                 // Wait + Push to Queue
1550                                 //History.debug('History.forward: we must wait', arguments);
1551                                 History.pushQueue({
1552                                         scope: History,
1553                                         callback: History.forward,
1554                                         args: arguments,
1555                                         queue: queue
1556                                 });
1557                                 return false;
1558                         }
1559
1560                         // Make Busy + Continue
1561                         History.busy(true);
1562
1563                         // Fix certain browser bugs that prevent the state from changing
1564                         History.doubleCheck(function(){
1565                                 History.forward(false);
1566                         });
1567
1568                         // Go forward
1569                         history.go(1);
1570
1571                         // End forward closure
1572                         return true;
1573                 };
1574
1575                 /**
1576                  * History.go(index,queue)
1577                  * Send the browser history back or forward index times
1578                  * @param {Integer} queue [optional]
1579                  */
1580                 History.go = function(index,queue){
1581                         //History.debug('History.go: called', arguments);
1582
1583                         // Prepare
1584                         var i;
1585
1586                         // Handle
1587                         if ( index > 0 ) {
1588                                 // Forward
1589                                 for ( i=1; i<=index; ++i ) {
1590                                         History.forward(queue);
1591                                 }
1592                         }
1593                         else if ( index < 0 ) {
1594                                 // Backward
1595                                 for ( i=-1; i>=index; --i ) {
1596                                         History.back(queue);
1597                                 }
1598                         }
1599                         else {
1600                                 throw new Error('History.go: History.go requires a positive or negative integer passed.');
1601                         }
1602
1603                         // Chain
1604                         return History;
1605                 };
1606
1607
1608                 // ====================================================================
1609                 // HTML5 State Support
1610
1611                 // Non-Native pushState Implementation
1612                 if ( History.emulated.pushState ) {
1613                         /*
1614                          * Provide Skeleton for HTML4 Browsers
1615                          */
1616
1617                         // Prepare
1618                         var emptyFunction = function(){};
1619                         History.pushState = History.pushState||emptyFunction;
1620                         History.replaceState = History.replaceState||emptyFunction;
1621                 } // History.emulated.pushState
1622
1623                 // Native pushState Implementation
1624                 else {
1625                         /*
1626                          * Use native HTML5 History API Implementation
1627                          */
1628
1629                         /**
1630                          * History.onPopState(event,extra)
1631                          * Refresh the Current State
1632                          */
1633                         History.onPopState = function(event,extra){
1634                                 // Prepare
1635                                 var stateId = false, newState = false, currentHash, currentState;
1636
1637                                 // Reset the double check
1638                                 History.doubleCheckComplete();
1639
1640                                 // Check for a Hash, and handle apporiatly
1641                                 currentHash = History.getHash();
1642                                 if ( currentHash ) {
1643                                         // Expand Hash
1644                                         currentState = History.extractState(currentHash||History.getLocationHref(),true);
1645                                         if ( currentState ) {
1646                                                 // We were able to parse it, it must be a State!
1647                                                 // Let's forward to replaceState
1648                                                 //History.debug('History.onPopState: state anchor', currentHash, currentState);
1649                                                 History.replaceState(currentState.data, currentState.title, currentState.url, false);
1650                                         }
1651                                         else {
1652                                                 // Traditional Anchor
1653                                                 //History.debug('History.onPopState: traditional anchor', currentHash);
1654                                                 History.Adapter.trigger(window,'anchorchange');
1655                                                 History.busy(false);
1656                                         }
1657
1658                                         // We don't care for hashes
1659                                         History.expectedStateId = false;
1660                                         return false;
1661                                 }
1662
1663                                 // Ensure
1664                                 stateId = History.Adapter.extractEventData('state',event,extra) || false;
1665
1666                                 // Fetch State
1667                                 if ( stateId ) {
1668                                         // Vanilla: Back/forward button was used
1669                                         newState = History.getStateById(stateId);
1670                                 }
1671                                 else if ( History.expectedStateId ) {
1672                                         // Vanilla: A new state was pushed, and popstate was called manually
1673                                         newState = History.getStateById(History.expectedStateId);
1674                                 }
1675                                 else {
1676                                         // Initial State
1677                                         newState = History.extractState(History.getLocationHref());
1678                                 }
1679
1680                                 // The State did not exist in our store
1681                                 if ( !newState ) {
1682                                         // Regenerate the State
1683                                         newState = History.createStateObject(null,null,History.getLocationHref());
1684                                 }
1685
1686                                 // Clean
1687                                 History.expectedStateId = false;
1688
1689                                 // Check if we are the same state
1690                                 if ( History.isLastSavedState(newState) ) {
1691                                         // There has been no change (just the page's hash has finally propagated)
1692                                         //History.debug('History.onPopState: no change', newState, History.savedStates);
1693                                         History.busy(false);
1694                                         return false;
1695                                 }
1696
1697                                 // Store the State
1698                                 History.storeState(newState);
1699                                 History.saveState(newState);
1700
1701                                 // Force update of the title
1702                                 History.setTitle(newState);
1703
1704                                 // Fire Our Event
1705                                 History.Adapter.trigger(window,'statechange');
1706                                 History.busy(false);
1707
1708                                 // Return true
1709                                 return true;
1710                         };
1711                         History.Adapter.bind(window,'popstate',History.onPopState);
1712
1713                         /**
1714                          * History.pushState(data,title,url)
1715                          * Add a new State to the history object, become it, and trigger onpopstate
1716                          * We have to trigger for HTML4 compatibility
1717                          * @param {object} data
1718                          * @param {string} title
1719                          * @param {string} url
1720                          * @return {true}
1721                          */
1722                         History.pushState = function(data,title,url,queue){
1723                                 //History.debug('History.pushState: called', arguments);
1724
1725                                 // Check the State
1726                                 if ( History.getHashByUrl(url) && History.emulated.pushState ) {
1727                                         throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
1728                                 }
1729
1730                                 // Handle Queueing
1731                                 if ( queue !== false && History.busy() ) {
1732                                         // Wait + Push to Queue
1733                                         //History.debug('History.pushState: we must wait', arguments);
1734                                         History.pushQueue({
1735                                                 scope: History,
1736                                                 callback: History.pushState,
1737                                                 args: arguments,
1738                                                 queue: queue
1739                                         });
1740                                         return false;
1741                                 }
1742
1743                                 // Make Busy + Continue
1744                                 History.busy(true);
1745
1746                                 // Create the newState
1747                                 var newState = History.createStateObject(data,title,url);
1748
1749                                 // Check it
1750                                 if ( History.isLastSavedState(newState) ) {
1751                                         // Won't be a change
1752                                         History.busy(false);
1753                                 }
1754                                 else {
1755                                         // Store the newState
1756                                         History.storeState(newState);
1757                                         History.expectedStateId = newState.id;
1758
1759                                         // Push the newState
1760                                         history.pushState(newState.id,newState.title,newState.url);
1761
1762                                         // Fire HTML5 Event
1763                                         History.Adapter.trigger(window,'popstate');
1764                                 }
1765
1766                                 // End pushState closure
1767                                 return true;
1768                         };
1769
1770                         /**
1771                          * History.replaceState(data,title,url)
1772                          * Replace the State and trigger onpopstate
1773                          * We have to trigger for HTML4 compatibility
1774                          * @param {object} data
1775                          * @param {string} title
1776                          * @param {string} url
1777                          * @return {true}
1778                          */
1779                         History.replaceState = function(data,title,url,queue){
1780                                 //History.debug('History.replaceState: called', arguments);
1781
1782                                 // Check the State
1783                                 if ( History.getHashByUrl(url) && History.emulated.pushState ) {
1784                                         throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
1785                                 }
1786
1787                                 // Handle Queueing
1788                                 if ( queue !== false && History.busy() ) {
1789                                         // Wait + Push to Queue
1790                                         //History.debug('History.replaceState: we must wait', arguments);
1791                                         History.pushQueue({
1792                                                 scope: History,
1793                                                 callback: History.replaceState,
1794                                                 args: arguments,
1795                                                 queue: queue
1796                                         });
1797                                         return false;
1798                                 }
1799
1800                                 // Make Busy + Continue
1801                                 History.busy(true);
1802
1803                                 // Create the newState
1804                                 var newState = History.createStateObject(data,title,url);
1805
1806                                 // Check it
1807                                 if ( History.isLastSavedState(newState) ) {
1808                                         // Won't be a change
1809                                         History.busy(false);
1810                                 }
1811                                 else {
1812                                         // Store the newState
1813                                         History.storeState(newState);
1814                                         History.expectedStateId = newState.id;
1815
1816                                         // Push the newState
1817                                         history.replaceState(newState.id,newState.title,newState.url);
1818
1819                                         // Fire HTML5 Event
1820                                         History.Adapter.trigger(window,'popstate');
1821                                 }
1822
1823                                 // End replaceState closure
1824                                 return true;
1825                         };
1826
1827                 } // !History.emulated.pushState
1828
1829
1830                 // ====================================================================
1831                 // Initialise
1832
1833                 /**
1834                  * Load the Store
1835                  */
1836                 if ( sessionStorage ) {
1837                         // Fetch
1838                         try {
1839                                 History.store = JSON.parse(sessionStorage.getItem('History.store'))||{};
1840                         }
1841                         catch ( err ) {
1842                                 History.store = {};
1843                         }
1844
1845                         // Normalize
1846                         History.normalizeStore();
1847                 }
1848                 else {
1849                         // Default Load
1850                         History.store = {};
1851                         History.normalizeStore();
1852                 }
1853
1854                 /**
1855                  * Clear Intervals on exit to prevent memory leaks
1856                  */
1857                 History.Adapter.bind(window,"unload",History.clearAllIntervals);
1858
1859                 /**
1860                  * Create the initial State
1861                  */
1862                 History.saveState(History.storeState(History.extractState(History.getLocationHref(),true)));
1863
1864                 /**
1865                  * Bind for Saving Store
1866                  */
1867                 if ( sessionStorage ) {
1868                         // When the page is closed
1869                         History.onUnload = function(){
1870                                 // Prepare
1871                                 var     currentStore, item, currentStoreString;
1872
1873                                 // Fetch
1874                                 try {
1875                                         currentStore = JSON.parse(sessionStorage.getItem('History.store'))||{};
1876                                 }
1877                                 catch ( err ) {
1878                                         currentStore = {};
1879                                 }
1880
1881                                 // Ensure
1882                                 currentStore.idToState = currentStore.idToState || {};
1883                                 currentStore.urlToId = currentStore.urlToId || {};
1884                                 currentStore.stateToId = currentStore.stateToId || {};
1885
1886                                 // Sync
1887                                 for ( item in History.idToState ) {
1888                                         if ( !History.idToState.hasOwnProperty(item) ) {
1889                                                 continue;
1890                                         }
1891                                         currentStore.idToState[item] = History.idToState[item];
1892                                 }
1893                                 for ( item in History.urlToId ) {
1894                                         if ( !History.urlToId.hasOwnProperty(item) ) {
1895                                                 continue;
1896                                         }
1897                                         currentStore.urlToId[item] = History.urlToId[item];
1898                                 }
1899                                 for ( item in History.stateToId ) {
1900                                         if ( !History.stateToId.hasOwnProperty(item) ) {
1901                                                 continue;
1902                                         }
1903                                         currentStore.stateToId[item] = History.stateToId[item];
1904                                 }
1905
1906                                 // Update
1907                                 History.store = currentStore;
1908                                 History.normalizeStore();
1909
1910                                 // In Safari, going into Private Browsing mode causes the
1911                                 // Session Storage object to still exist but if you try and use
1912                                 // or set any property/function of it it throws the exception
1913                                 // "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to
1914                                 // add something to storage that exceeded the quota." infinitely
1915                                 // every second.
1916                                 currentStoreString = JSON.stringify(currentStore);
1917                                 try {
1918                                         // Store
1919                                         sessionStorage.setItem('History.store', currentStoreString);
1920                                 }
1921                                 catch (e) {
1922                                         if (e.code === DOMException.QUOTA_EXCEEDED_ERR) {
1923                                                 if (sessionStorage.length) {
1924                                                         // Workaround for a bug seen on iPads. Sometimes the quota exceeded error comes up and simply
1925                                                         // removing/resetting the storage can work.
1926                                                         sessionStorage.removeItem('History.store');
1927                                                         sessionStorage.setItem('History.store', currentStoreString);
1928                                                 } else {
1929                                                         // Otherwise, we're probably private browsing in Safari, so we'll ignore the exception.
1930                                                 }
1931                                         } else {
1932                                                 throw e;
1933                                         }
1934                                 }
1935                         };
1936
1937                         // For Internet Explorer
1938                         History.intervalList.push(setInterval(History.onUnload,History.options.storeInterval));
1939
1940                         // For Other Browsers
1941                         History.Adapter.bind(window,'beforeunload',History.onUnload);
1942                         History.Adapter.bind(window,'unload',History.onUnload);
1943
1944                         // Both are enabled for consistency
1945                 }
1946
1947                 // Non-Native pushState Implementation
1948                 if ( !History.emulated.pushState ) {
1949                         // Be aware, the following is only for native pushState implementations
1950                         // If you are wanting to include something for all browsers
1951                         // Then include it above this if block
1952
1953                         /**
1954                          * Setup Safari Fix
1955                          */
1956                         if ( History.bugs.safariPoll ) {
1957                                 History.intervalList.push(setInterval(History.safariStatePoll, History.options.safariPollInterval));
1958                         }
1959
1960                         /**
1961                          * Ensure Cross Browser Compatibility
1962                          */
1963                         if ( navigator.vendor === 'Apple Computer, Inc.' || (navigator.appCodeName||'') === 'Mozilla' ) {
1964                                 /**
1965                                  * Fix Safari HashChange Issue
1966                                  */
1967
1968                                 // Setup Alias
1969                                 History.Adapter.bind(window,'hashchange',function(){
1970                                         History.Adapter.trigger(window,'popstate');
1971                                 });
1972
1973                                 // Initialise Alias
1974                                 if ( History.getHash() ) {
1975                                         History.Adapter.onDomLoad(function(){
1976                                                 History.Adapter.trigger(window,'hashchange');
1977                                         });
1978                                 }
1979                         }
1980
1981                 } // !History.emulated.pushState
1982
1983
1984         }; // History.initCore
1985
1986         // Try to Initialise History
1987         if (!History.options || !History.options.delayInit) {
1988                 History.init();
1989         }
1990
1991 })(window);