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