Make WordPress Core

Changeset 26169


Ignore:
Timestamp:
11/14/2013 06:40:03 PM (11 years ago)
Author:
azaozz
Message:

Heartbeat: clean up code style, better naming, better code structure. Props carldanley, props evansolomon.
Changes:

  • Add connectNow() public method to trigger a connection immediately.
  • Remove the "skipping" when no data to send.
  • Change the default interval to 60 sec.
  • Fix resetting the next connection time when changing the interval.

See #25073.

Location:
trunk/src
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/includes/post.php

    r25868 r26169  
    11861186    $user = isset( $lock[1] ) ? $lock[1] : get_post_meta( $post->ID, '_edit_last', true );
    11871187
    1188     $time_window = apply_filters( 'wp_check_post_lock_window', 120 );
     1188    $time_window = apply_filters( 'wp_check_post_lock_window', 150 );
    11891189
    11901190    if ( $time && $time > time() - $time_window && $user != get_current_user_id() )
  • trunk/src/wp-admin/js/inline-edit-post.js

    r26123 r26169  
    326326    if ( check.length )
    327327        data['wp-check-locked-posts'] = check;
     328}).ready( function() {
     329    // Set the heartbeat interval to 15 sec.
     330    if ( typeof wp !== 'undefined' && wp.heartbeat ) {
     331        wp.heartbeat.setInterval( 15 );
     332    }
    328333});
    329334
  • trunk/src/wp-admin/js/post.js

    r24528 r26169  
    371371        }
    372372    }).filter(':visible').find('.wp-tab-first').focus();
     373   
     374    // Set the heartbeat interval to 15 sec. if post lock dialogs are enabled
     375    if ( typeof wp !== 'undefined' && wp.heartbeat && $('#post-lock-dialog').length ) {
     376        wp.heartbeat.setInterval( 15 );
     377    }
    373378
    374379    // multi-taxonomies
  • trunk/src/wp-includes/js/heartbeat.js

    r25874 r26169  
    77 *
    88 * Heartbeat is a simple server polling API that sends XHR requests to
    9  * the server every 15 seconds and triggers events (or callbacks) upon
     9 * the server every 15 - 60 seconds and triggers events (or callbacks) upon
    1010 * receiving data. Currently these 'ticks' handle transports for post locking,
    1111 * login-expiration warnings, and related tasks while a user is logged in.
    1212 *
    13  * Available filters in ajax-actions.php:
     13 * Available PHP filters (in ajax-actions.php):
    1414 * - heartbeat_received
    1515 * - heartbeat_send
     
    2020 * @see wp_ajax_nopriv_heartbeat(), wp_ajax_heartbeat()
    2121 *
     22 * Custom jQuery events:
     23 * - heartbeat-send
     24 * - heartbeat-tick
     25 * - heartbeat-error
     26 * - heartbeat-connection-lost
     27 * - heartbeat-connection-restored
     28 * - heartbeat-nonces-expired
     29 *
    2230 * @since 3.6.0
    2331 */
    2432
    25  // Ensure the global `wp` object exists.
    26 window.wp = window.wp || {};
    27 
    28 (function($){
     33( function( $, window, undefined ) {
    2934    var Heartbeat = function() {
    30         var self = this,
    31             running,
    32             beat,
    33             screenId = typeof pagenow != 'undefined' ? pagenow : '',
    34             url = typeof ajaxurl != 'undefined' ? ajaxurl : '',
    35             settings,
    36             tick = 0,
    37             queue = {},
    38             interval,
    39             connecting,
    40             countdown = 0,
    41             errorcount = 0,
    42             tempInterval,
    43             hasFocus = true,
    44             isUserActive,
    45             userActiveEvents,
    46             winBlurTimeout,
    47             frameBlurTimeout = -1,
    48             hasConnectionError = null;
    49 
    50         /**
    51          * Returns a boolean that's indicative of whether or not there is a connection error
    52          *
    53          * @returns boolean
    54          */
    55         this.hasConnectionError = function() {
    56             return !! hasConnectionError;
    57         };
    58 
    59         if ( typeof( window.heartbeatSettings ) == 'object' ) {
    60             settings = $.extend( {}, window.heartbeatSettings );
    61 
    62             // Add private vars
    63             url = settings.ajaxurl || url;
    64             delete settings.ajaxurl;
    65             delete settings.nonce;
    66 
    67             interval = settings.interval || 15; // default interval
    68             delete settings.interval;
    69             // The interval can be from 15 to 60 sec. and can be set temporarily to 5 sec.
    70             if ( interval < 15 )
    71                 interval = 15;
    72             else if ( interval > 60 )
    73                 interval = 60;
    74 
    75             interval = interval * 1000;
    76 
    77             // 'screenId' can be added from settings on the front-end where the JS global 'pagenow' is not set
    78             screenId = screenId || settings.screenId || 'front';
    79             delete settings.screenId;
    80 
    81             // Add or overwrite public vars
    82             $.extend( this, settings );
    83         }
    84 
    85         function time(s) {
    86             if ( s )
    87                 return parseInt( (new Date()).getTime() / 1000 );
    88 
     35        var $document = $(document),
     36            settings = {
     37                // Used to stop the "beat"
     38                isRunning: true,
     39
     40                // Current screen id, defaults to the JS global 'pagenow' when present (in the admin) or 'front'
     41                screenId: '',
     42
     43                // XHR request URL, defaults to the JS global 'ajaxurl' when present
     44                url: '',
     45
     46                // Timestamp, start of the last connection request
     47                lastTick: 0,
     48
     49                // Container for the enqueued items
     50                queue: {},
     51
     52                // Connect interval (in seconds)
     53                mainInterval: 60,
     54
     55                // Used when the interval is set to 5 sec. temporarily
     56                tempInterval: 0,
     57
     58                // Used when the interval is reset
     59                originalInterval: 0,
     60
     61                // Used together with tempInterval
     62                countdown: 0,
     63
     64                // Whether a connection is currently in progress
     65                connecting: false,
     66
     67                // Whether a connection error occured
     68                connectionError: false,
     69
     70                // Used to track non-critical errors
     71                errorcount: 0,
     72
     73                // Whether at least one connection has completed successfully
     74                hasConnected: false,
     75
     76                // Whether the current browser window is in focus and the user is active
     77                hasFocus: true,
     78
     79                // Timestamp, last time the user was active. Checked every 30 sec.
     80                userActivity: 0,
     81
     82                // Flags whether events tracking user activity were set
     83                userActivityEvents: false,
     84
     85                // References to various timeouts
     86                beatTimer: 0,
     87                winBlurTimer: 0,
     88                frameBlurTimer: 0
     89            };
     90
     91        /**
     92         * Set local vars and events, then start
     93         *
     94         * @access private
     95         *
     96         * @return void
     97         */
     98        function initialize() {
     99            if ( typeof window.pagenow === 'string' ) {
     100                settings.screenId = window.pagenow;
     101            }
     102
     103            if ( typeof window.ajaxurl === 'string' ) {
     104                settings.url = window.ajaxurl;
     105            }
     106
     107            // Pull in options passed from PHP
     108            if ( typeof window.heartbeatSettings === 'object' ) {
     109                var options = window.heartbeatSettings;
     110
     111                // The XHR URL can be passed as option when window.ajaxurl is not set
     112                if ( ! settings.url && options.ajaxurl ) {
     113                    settings.url = options.ajaxurl;
     114                }
     115
     116                // The interval can be from 15 to 60 sec. and can be set temporarily to 5 sec.
     117                if ( options.interval ) {
     118                    settings.mainInterval = options.interval;
     119
     120                    if ( settings.mainInterval < 15 ) {
     121                        settings.mainInterval = 15;
     122                    } else if ( settings.mainInterval > 60 ) {
     123                        settings.mainInterval = 60;
     124                    }
     125                }
     126
     127                // 'screenId' can be added from settings on the front-end where the JS global 'pagenow' is not set
     128                if ( ! settings.screenId ) {
     129                    settings.screenId = options.screenId || 'front';
     130                }
     131            }
     132
     133            // Convert to milliseconds
     134            settings.mainInterval = settings.mainInterval * 1000;
     135            settings.originalInterval = settings.mainInterval;
     136
     137            // Set focus/blur events on the window
     138            $(window).on( 'blur.wp-heartbeat-focus', function() {
     139                setFrameFocusEvents();
     140                // We don't know why the 'blur' was fired. Either the user clicked in an iframe or outside the browser.
     141                // Running blurred() after some timeout lets us cancel it if the user clicked in an iframe.
     142                settings.winBlurTimer = window.setTimeout( function(){ blurred(); }, 500 );
     143            }).on( 'focus.wp-heartbeat-focus', function() {
     144                removeFrameFocusEvents();
     145                focused();
     146            }).on( 'unload.wp-heartbeat', function() {
     147                // Don't connect any more
     148                settings.isRunning = false;
     149            });
     150
     151            // Check for user activity every 30 seconds.
     152            window.setInterval( function(){ checkUserActivity(); }, 30000 );
     153
     154            // Start one tick after DOM ready
     155            $document.ready( function() {
     156                settings.lastTick = time();
     157                scheduleNextTick();
     158            });
     159        }
     160
     161        /**
     162         * Return the current time according to the browser
     163         *
     164         * @access private
     165         *
     166         * @return int
     167         */
     168        function time() {
    89169            return (new Date()).getTime();
    90170        }
    91171
     172        /**
     173         * Check if the iframe is from the same origin
     174         *
     175         * @access private
     176         *
     177         * @return bool
     178         */
    92179        function isLocalFrame( frame ) {
    93180            var origin, src = frame.src;
    94181
     182            // Need to compare strings as WebKit doesn't throw JS errors when iframes have different origin.
     183            // It throws uncatchable exceptions.
    95184            if ( src && /^https?:\/\//.test( src ) ) {
    96185                origin = window.location.origin ? window.location.origin : window.location.protocol + '//' + window.location.host;
    97186
    98                 if ( src.indexOf( origin ) !== 0 )
     187                if ( src.indexOf( origin ) !== 0 ) {
    99188                    return false;
     189                }
    100190            }
    101191
    102192            try {
    103                 if ( frame.contentWindow.document )
     193                if ( frame.contentWindow.document ) {
    104194                    return true;
     195                }
    105196            } catch(e) {}
    106197
     
    108199        }
    109200
    110         // Set error state and fire an event on XHR errors or timeout
    111         function errorstate( error, status ) {
     201        /**
     202         * Set error state and fire an event on XHR errors or timeout
     203         *
     204         * @access private
     205         *
     206         * @param string error The error type passed from the XHR
     207         * @param int status The HTTP status code passed from jqXHR (200, 404, 500, etc.)
     208         * @return void
     209         */
     210        function setErrorState( error, status ) {
    112211            var trigger;
    113212
     
    121220                        trigger = true;
    122221                        break;
     222                    case 'error':
     223                        if ( 503 === status && settings.hasConnected ) {
     224                            trigger = true;
     225                            break;
     226                        }
     227                        // Pass through other error statuses
    123228                    case 'parsererror':
    124                     case 'error':
    125229                    case 'empty':
    126230                    case 'unknown':
    127                         errorcount++;
    128 
    129                         if ( errorcount > 2 )
     231                        settings.errorcount++;
     232
     233                        if ( settings.errorcount > 2 && settings.hasConnected ) {
    130234                            trigger = true;
     235                        }
    131236
    132237                        break;
    133238                }
    134239
    135                 if ( 503 == status && false === hasConnectionError ) {
    136                     trigger = true;
    137                 }
    138 
    139                 if ( trigger && ! self.hasConnectionError() ) {
    140                     hasConnectionError = true;
     240                if ( trigger && ! hasConnectionError() ) {
     241                    settings.connectionError = true;
    141242                    $(document).trigger( 'heartbeat-connection-lost', [error, status] );
    142243                }
    143             } else if ( self.hasConnectionError() ) {
    144                 errorcount = 0;
    145                 hasConnectionError = false;
    146                 $(document).trigger( 'heartbeat-connection-restored' );
    147             } else if ( null === hasConnectionError ) {
    148                 hasConnectionError = false;
    149             }
    150         }
    151 
     244            }
     245        }
     246
     247        /**
     248         * Clear the error state and fire an event
     249         *
     250         * @access private
     251         *
     252         * @return void
     253         */
     254        function clearErrorState() {
     255            // Has connected successfully
     256            settings.hasConnected = true;
     257
     258            if ( hasConnectionError() ) {
     259                settings.errorcount = 0;
     260                settings.connectionError = false;
     261                $document.trigger( 'heartbeat-connection-restored' );
     262            }
     263        }
     264
     265        /**
     266         * Gather the data and connect to the server
     267         *
     268         * @access private
     269         *
     270         * @return void
     271         */
    152272        function connect() {
    153             var send = {}, data, i, empty = true,
    154             nonce = typeof window.heartbeatSettings == 'object' ? window.heartbeatSettings.nonce : '';
    155             tick = time();
    156 
    157             data = $.extend( {}, queue );
     273            var ajaxData, heartbeatData;
     274
     275            // If the connection to the server is slower than the interval,
     276            // heartbeat connects as soon as the previous connection's response is received.
     277            if ( settings.connecting ) {
     278                return;
     279            }
     280
     281            settings.lastTick = time();
     282
     283            heartbeatData = $.extend( {}, settings.queue );
    158284            // Clear the data queue, anything added after this point will be send on the next tick
    159             queue = {};
    160 
    161             $(document).trigger( 'heartbeat-send', [data] );
    162 
    163             for ( i in data ) {
    164                 if ( data.hasOwnProperty( i ) ) {
    165                     empty = false;
    166                     break;
    167                 }
    168             }
    169 
    170             // If nothing to send (nothing is expecting a response),
    171             // schedule the next tick and bail
    172             if ( empty && ! self.hasConnectionError() ) {
    173                 connecting = false;
    174                 next();
    175                 return;
    176             }
    177 
    178             send.data = data;
    179             send.interval = interval / 1000;
    180             send._nonce = nonce;
    181             send.action = 'heartbeat';
    182             send.screen_id = screenId;
    183             send.has_focus = hasFocus;
    184 
    185             connecting = true;
    186             self.xhr = $.ajax({
    187                 url: url,
     285            settings.queue = {};
     286
     287            $document.trigger( 'heartbeat-send', [ heartbeatData ] );
     288
     289            ajaxData = {
     290                data: heartbeatData,
     291                interval: settings.tempInterval ? settings.tempInterval / 1000 : settings.mainInterval / 1000,
     292                _nonce: typeof window.heartbeatSettings === 'object' ? window.heartbeatSettings.nonce : '',
     293                action: 'heartbeat',
     294                screen_id: settings.screenId,
     295                has_focus: settings.hasFocus
     296            };
     297
     298            settings.connecting = true;
     299            settings.xhr = $.ajax({
     300                url: settings.url,
    188301                type: 'post',
    189302                timeout: 30000, // throw an error if not completed after 30 sec.
    190                 data: send,
     303                data: ajaxData,
    191304                dataType: 'json'
     305            }).always( function() {
     306                settings.connecting = false;
     307                scheduleNextTick();
    192308            }).done( function( response, textStatus, jqXHR ) {
    193                 var new_interval;
    194 
    195                 if ( ! response )
    196                     return errorstate( 'empty' );
    197 
    198                 // Clear error state
    199                 if ( self.hasConnectionError() )
    200                     errorstate();
     309                var newInterval;
     310
     311                if ( ! response ) {
     312                    setErrorState( 'empty' );
     313                    return;
     314                }
     315
     316                clearErrorState();
    201317
    202318                if ( response.nonces_expired ) {
    203                     $(document).trigger( 'heartbeat-nonces-expired' );
     319                    $document.trigger( 'heartbeat-nonces-expired' );
    204320                    return;
    205321                }
     
    207323                // Change the interval from PHP
    208324                if ( response.heartbeat_interval ) {
    209                     new_interval = response.heartbeat_interval;
     325                    newInterval = response.heartbeat_interval;
    210326                    delete response.heartbeat_interval;
    211327                }
    212328
    213                 self.tick( response, textStatus, jqXHR );
    214 
    215                 // do this last, can trigger the next XHR if connection time > 5 sec. and new_interval == 'fast'
    216                 if ( new_interval )
    217                     self.interval.call( self, new_interval );
    218             }).always( function() {
    219                 connecting = false;
    220                 next();
     329                $document.trigger( 'heartbeat-tick', [response, textStatus, jqXHR] );
     330
     331                // Do this last, can trigger the next XHR if connection time > 5 sec. and newInterval == 'fast'
     332                if ( newInterval ) {
     333                    setInterval( newInterval );
     334                }
    221335            }).fail( function( jqXHR, textStatus, error ) {
    222                 errorstate( textStatus || 'unknown', jqXHR.status );
    223                 self.error( jqXHR, textStatus, error );
     336                setErrorState( textStatus || 'unknown', jqXHR.status );
     337                $document.trigger( 'heartbeat-error', [jqXHR, textStatus, error] );
    224338            });
    225339        }
    226340
    227         function next() {
    228             var delta = time() - tick, t = interval;
    229 
    230             if ( ! running )
     341        /**
     342         * Schedule the next connection
     343         *
     344         * Fires immediately if the connection time is longer than the interval.
     345         *
     346         * @access private
     347         *
     348         * @return void
     349         */
     350        function scheduleNextTick() {
     351            var delta = time() - settings.lastTick,
     352                interval = settings.mainInterval;
     353
     354            if ( ! settings.isRunning ) {
    231355                return;
    232 
    233             if ( ! hasFocus ) {
    234                 t = 100000; // 100 sec. Post locks expire after 120 sec.
    235             } else if ( countdown > 0 && tempInterval ) {
    236                 t = tempInterval;
    237                 countdown--;
    238             }
    239 
    240             window.clearTimeout(beat);
    241 
    242             if ( delta < t ) {
    243                 beat = window.setTimeout(
    244                     function(){
    245                         if ( running )
     356            }
     357
     358            if ( ! settings.hasFocus ) {
     359                interval = 120000; // 120 sec. Post locks expire after 150 sec.
     360            } else if ( settings.countdown > 0 && settings.tempInterval ) {
     361                interval = settings.tempInterval;
     362                settings.countdown--;
     363
     364                if ( settings.countdown < 1 ) {
     365                    settings.tempInterval = 0;
     366                }
     367            }
     368
     369            window.clearTimeout( settings.beatTimer );
     370
     371            if ( delta < interval ) {
     372                settings.beatTimer = window.setTimeout(
     373                    function() {
    246374                            connect();
    247375                    },
    248                     t - delta
     376                    interval - delta
    249377                );
    250378            } else {
     
    253381        }
    254382
     383        /**
     384         * Set the internal state when the browser window looses focus
     385         *
     386         * @access private
     387         *
     388         * @return void
     389         */
    255390        function blurred() {
    256             window.clearTimeout(winBlurTimeout);
    257             window.clearTimeout(frameBlurTimeout);
    258             winBlurTimeout = frameBlurTimeout = 0;
    259 
    260             hasFocus = false;
    261         }
    262 
     391            clearFocusTimers();
     392            settings.hasFocus = false;
     393        }
     394
     395        /**
     396         * Set the internal state when the browser window is focused
     397         *
     398         * @access private
     399         *
     400         * @return void
     401         */
    263402        function focused() {
    264             window.clearTimeout(winBlurTimeout);
    265             window.clearTimeout(frameBlurTimeout);
    266             winBlurTimeout = frameBlurTimeout = 0;
    267 
    268             isUserActive = time();
    269 
    270             if ( hasFocus )
    271                 return;
    272 
    273             hasFocus = true;
    274             window.clearTimeout(beat);
    275 
    276             if ( ! connecting )
    277                 next();
    278         }
    279 
    280         function setFrameEvents() {
    281             $('iframe').each( function( i, frame ){
    282                 if ( ! isLocalFrame( frame ) )
     403            clearFocusTimers();
     404            settings.userActivity = time();
     405
     406            if ( ! settings.hasFocus ) {
     407                settings.hasFocus = true;
     408                scheduleNextTick();
     409            }
     410        }
     411
     412        /**
     413         * Add focus/blur events to all local iframes
     414         *
     415         * Used to detect when focus is moved from the main window to an iframe
     416         *
     417         * @access private
     418         *
     419         * @return void
     420         */
     421        function setFrameFocusEvents() {
     422            $('iframe').each( function( i, frame ) {
     423                if ( ! isLocalFrame( frame ) ) {
    283424                    return;
    284 
    285                 if ( $.data( frame, 'wp-heartbeat-focus' ) )
     425                }
     426
     427                if ( $.data( frame, 'wp-heartbeat-focus' ) ) {
    286428                    return;
     429                }
    287430
    288431                $.data( frame, 'wp-heartbeat-focus', 1 );
    289432
    290                 $( frame.contentWindow ).on( 'focus.wp-heartbeat-focus', function(e) {
     433                $( frame.contentWindow ).on( 'focus.wp-heartbeat-focus', function() {
    291434                    focused();
    292                 }).on('blur.wp-heartbeat-focus', function(e) {
    293                     setFrameEvents();
    294                     frameBlurTimeout = window.setTimeout( function(){ blurred(); }, 500 );
     435                }).on('blur.wp-heartbeat-focus', function() {
     436                    setFrameFocusEvents();
     437                    // We don't know why 'blur' was fired. Either the user clicked in the main window or outside the browser.
     438                    // Running blurred() after some timeout lets us cancel it if the user clicked in the main window.
     439                    settings.frameBlurTimer = window.setTimeout( function(){ blurred(); }, 500 );
    295440                });
    296441            });
    297442        }
    298443
    299         $(window).on( 'blur.wp-heartbeat-focus', function(e) {
    300             setFrameEvents();
    301             winBlurTimeout = window.setTimeout( function(){ blurred(); }, 500 );
    302         }).on( 'focus.wp-heartbeat-focus', function() {
     444        /**
     445         * Remove the focus/blur events to all local iframes
     446         *
     447         * @access private
     448         *
     449         * @return void
     450         */
     451        function removeFrameFocusEvents() {
    303452            $('iframe').each( function( i, frame ) {
    304                 if ( !isLocalFrame( frame ) )
     453                if ( ! isLocalFrame( frame ) ) {
    305454                    return;
     455                }
    306456
    307457                $.removeData( frame, 'wp-heartbeat-focus' );
    308458                $( frame.contentWindow ).off( '.wp-heartbeat-focus' );
    309459            });
    310 
    311             focused();
    312         });
    313 
     460        }
     461
     462        /**
     463         * Clear the reset timers for focus/blur events on the window and iframes
     464         *
     465         * @access private
     466         *
     467         * @return void
     468         */
     469        function clearFocusTimers() {
     470            window.clearTimeout( settings.winBlurTimer );
     471            window.clearTimeout( settings.frameBlurTimer );
     472        }
     473
     474        /**
     475         * Runs when the user becomes active after a period of inactivity
     476         *
     477         * @access private
     478         *
     479         * @return void
     480         */
    314481        function userIsActive() {
    315             userActiveEvents = false;
    316             $(document).off( '.wp-heartbeat-active' );
     482            settings.userActivityEvents = false;
     483            $document.off( '.wp-heartbeat-active' );
     484
    317485            $('iframe').each( function( i, frame ) {
    318                 if ( ! isLocalFrame( frame ) )
     486                if ( ! isLocalFrame( frame ) ) {
    319487                    return;
     488                }
    320489
    321490                $( frame.contentWindow ).off( '.wp-heartbeat-active' );
     
    325494        }
    326495
    327         // Set 'hasFocus = true' if user is active and the window is in the background.
    328         // Set 'hasFocus = false' if the user has been inactive (no mouse or keyboard activity) for 5 min. even when the window has focus.
    329         function checkUserActive() {
    330             var lastActive = isUserActive ? time() - isUserActive : 0;
    331 
    332             // Throttle down when no mouse or keyboard activity for 5 min
    333             if ( lastActive > 300000 && hasFocus )
    334                  blurred();
    335 
    336             if ( ! userActiveEvents ) {
    337                 $(document).on( 'mouseover.wp-heartbeat-active keyup.wp-heartbeat-active', function(){ userIsActive(); } );
     496        /**
     497         * Check for user activity
     498         *
     499         * Runs every 30 sec.
     500         * Sets 'hasFocus = true' if user is active and the window is in the background.
     501         * Set 'hasFocus = false' if the user has been inactive (no mouse or keyboard activity)
     502         * for 5 min. even when the window has focus.
     503         *
     504         * @access private
     505         *
     506         * @return void
     507         */
     508        function checkUserActivity() {
     509            var lastActive = settings.userActivity ? time() - settings.userActivity : 0;
     510
     511            if ( lastActive > 300000 && settings.hasFocus ) {
     512                // Throttle down when no mouse or keyboard activity for 5 min
     513                blurred();
     514            }
     515
     516            if ( ! settings.userActivityEvents ) {
     517                $document.on( 'mouseover.wp-heartbeat-active keyup.wp-heartbeat-active', function(){ userIsActive(); } );
    338518
    339519                $('iframe').each( function( i, frame ) {
    340                     if ( ! isLocalFrame( frame ) )
     520                    if ( ! isLocalFrame( frame ) ) {
    341521                        return;
     522                    }
    342523
    343524                    $( frame.contentWindow ).on( 'mouseover.wp-heartbeat-active keyup.wp-heartbeat-active', function(){ userIsActive(); } );
    344525                });
    345526
    346                 userActiveEvents = true;
    347             }
    348         }
    349 
    350         // Check for user activity every 30 seconds.
    351         window.setInterval( function(){ checkUserActive(); }, 30000 );
    352         $(document).ready( function() {
    353             // Start one tick (15 sec) after DOM ready
    354             running = true;
    355             tick = time();
    356             next();
    357         });
    358 
    359         this.hasFocus = function() {
    360             return hasFocus;
    361         };
     527                settings.userActivityEvents = true;
     528            }
     529        }
     530
     531        // Public methods
     532
     533        /**
     534         * Whether the window (or any local iframe in it) has focus, or the user is active
     535         *
     536         * @return bool
     537         */
     538        function hasFocus() {
     539            return settings.hasFocus;
     540        }
     541
     542        /**
     543         * Whether there is a connection error
     544         *
     545         * @return bool
     546         */
     547        function hasConnectionError() {
     548            return settings.connectionError;
     549        }
     550
     551        /**
     552         * Connect asap regardless of 'hasFocus'
     553         *
     554         * Will not open two concurrent connections. If a connection is in progress,
     555         * will connect again immediately after the current connection completes.
     556         *
     557         * @return void
     558         */
     559        function connectNow() {
     560            settings.lastTick = 0;
     561            scheduleNextTick();
     562        }
    362563
    363564        /**
    364565         * Get/Set the interval
    365566         *
    366          * When setting to 'fast', the interval is 5 sec. for the next 30 ticks (for 2 min and 30 sec).
     567         * When setting to 'fast' or 5, by default interval is 5 sec. for the next 30 ticks (for 2 min and 30 sec).
     568         * In this case the number of 'ticks' can be passed as second argument.
    367569         * If the window doesn't have focus, the interval slows down to 2 min.
    368570         *
    369          * @param string speed Interval speed: 'fast' (5sec), 'standard' (15sec) default, 'slow' (60sec)
    370          * @param string ticks Used with speed = 'fast', how many ticks before the speed reverts back
     571         * @param mixed speed Interval: 'fast' or 5, 15, 30, 60
     572         * @param string ticks Used with speed = 'fast' or 5, how many ticks before the interval reverts back
    371573         * @return int Current interval in seconds
    372574         */
    373         this.interval = function( speed, ticks ) {
    374             var reset, seconds;
    375             ticks = parseInt( ticks, 10 ) || 30;
    376             ticks = ticks < 1 || ticks > 30 ? 30 : ticks;
     575        function setInterval( speed, ticks ) {
     576            var interval, oldInerval = settings.tempInterval ? settings.tempInterval : settings.mainInterval;
    377577
    378578            if ( speed ) {
    379579                switch ( speed ) {
    380580                    case 'fast':
    381                         seconds = 5;
    382                         countdown = ticks;
     581                    case 5:
     582                        interval = 5000;
    383583                        break;
    384                     case 'slow':
    385                         seconds = 60;
    386                         countdown = 0;
     584                    case 15:
     585                        interval = 15000;
     586                        break;
     587                    case 30:
     588                        interval = 30000;
     589                        break;
     590                    case 60:
     591                        interval = 60000;
    387592                        break;
    388593                    case 'long-polling':
    389594                        // Allow long polling, (experimental)
    390                         interval = 0;
     595                        settings.mainInterval = 0;
    391596                        return 0;
    392597                        break;
    393598                    default:
    394                         seconds = 15;
    395                         countdown = 0;
    396                 }
    397 
    398                 // Reset when the new interval value is lower than the current one
    399                 reset = seconds * 1000 < interval;
    400 
    401                 if ( countdown > 0 ) {
    402                     tempInterval = seconds * 1000;
     599                        interval = settings.originalInterval;
     600                }
     601
     602                if ( 5000 === interval ) {
     603                    ticks = parseInt( ticks, 10 ) || 30;
     604                    ticks = ticks < 1 || ticks > 30 ? 30 : ticks;
     605
     606                    settings.countdown = ticks;
     607                    settings.tempInterval = interval;
    403608                } else {
    404                     interval = seconds * 1000;
    405                     tempInterval = 0;
    406                 }
    407 
    408                 if ( reset )
    409                     next();
    410             }
    411 
    412             if ( ! hasFocus )
    413                 return 120;
    414 
    415             return tempInterval ? tempInterval / 1000 : interval / 1000;
    416         };
     609                    settings.countdown = 0;
     610                    settings.tempInterval = 0;
     611                    settings.mainInterval = interval;
     612                }
     613
     614                // Change the next connection time if new interval has been set.
     615                // Will connect immediately if the time since the last connection
     616                // is greater than the new interval.
     617                if ( interval !== oldInerval ) {
     618                    scheduleNextTick();
     619                }
     620            }
     621
     622            return settings.tempInterval ? settings.tempInterval / 1000 : settings.mainInterval / 1000;
     623        }
    417624
    418625        /**
    419626         * Enqueue data to send with the next XHR
    420627         *
    421          * As the data is sent later, this function doesn't return the XHR response.
     628         * As the data is send asynchronously, this function doesn't return the XHR response.
    422629         * To see the response, use the custom jQuery event 'heartbeat-tick' on the document, example:
    423630         *      $(document).on( 'heartbeat-tick.myname', function( event, data, textStatus, jqXHR ) {
     
    429636         * $param string handle Unique handle for the data. The handle is used in PHP to receive the data.
    430637         * $param mixed data The data to send.
    431          * $param bool dont_overwrite Whether to overwrite existing data in the queue.
     638         * $param bool noOverwrite Whether to overwrite existing data in the queue.
    432639         * $return bool Whether the data was queued or not.
    433640         */
    434         this.enqueue = function( handle, data, dont_overwrite ) {
     641        function enqueue( handle, data, noOverwrite ) {
    435642            if ( handle ) {
    436                 if ( dont_overwrite && this.isQueued( handle ) )
     643                if ( noOverwrite && this.isQueued( handle ) ) {
    437644                    return false;
    438 
    439                 queue[handle] = data;
     645                }
     646
     647                settings.queue[handle] = data;
    440648                return true;
    441649            }
    442650            return false;
    443         };
     651        }
    444652
    445653        /**
     
    449657         * $return bool Whether some data is queued with this handle
    450658         */
    451         this.isQueued = function( handle ) {
    452             if ( handle )
    453                 return queue.hasOwnProperty( handle );
    454         };
     659        function isQueued( handle ) {
     660            if ( handle ) {
     661                return settings.queue.hasOwnProperty( handle );
     662            }
     663        }
    455664
    456665        /**
     
    460669         * $return void
    461670         */
    462         this.dequeue = function( handle ) {
    463             if ( handle )
    464                 delete queue[handle];
    465         };
     671        function dequeue( handle ) {
     672            if ( handle ) {
     673                delete settings.queue[handle];
     674            }
     675        }
    466676
    467677        /**
     
    471681         * $return mixed The data or undefined
    472682         */
    473         this.getQueuedItem = function( handle ) {
    474             if ( handle )
    475                 return this.isQueued( handle ) ? queue[handle] : undefined;
     683        function getQueuedItem( handle ) {
     684            if ( handle ) {
     685                return this.isQueued( handle ) ? settings.queue[handle] : undefined;
     686            }
     687        }
     688
     689        initialize();
     690
     691        // Expose public methods
     692        return {
     693            hasFocus: hasFocus,
     694            connectNow: connectNow,
     695            setInterval: setInterval,
     696            hasConnectionError: hasConnectionError,
     697            enqueue: enqueue,
     698            dequeue: dequeue,
     699            isQueued: isQueued,
     700            getQueuedItem: getQueuedItem
    476701        };
    477702    };
    478703
    479     $.extend( Heartbeat.prototype, {
    480         tick: function( data, textStatus, jqXHR ) {
    481             $(document).trigger( 'heartbeat-tick', [data, textStatus, jqXHR] );
    482         },
    483         error: function( jqXHR, textStatus, error ) {
    484             $(document).trigger( 'heartbeat-error', [jqXHR, textStatus, error] );
    485         }
    486     });
    487 
    488     wp.heartbeat = new Heartbeat();
    489 
    490 }(jQuery));
     704    // Ensure the global `wp` object exists.
     705    window.wp = window.wp || {};
     706    window.wp.heartbeat = new Heartbeat();
     707
     708}( jQuery, window ));
Note: See TracChangeset for help on using the changeset viewer.