diff --git a/wp-includes/js/heartbeat.js b/wp-includes/js/heartbeat.js index 63ee4f523c..b4c8316780 100644 --- a/wp-includes/js/heartbeat.js +++ b/wp-includes/js/heartbeat.js @@ -57,6 +57,9 @@ // Used when the interval is reset originalInterval: 0, + // Used to limit the number of AJAX requests. + minimalInterval: 0, + // Used together with tempInterval countdown: 0, @@ -81,10 +84,8 @@ // Flags whether events tracking user activity were set userActivityEvents: false, - // References to various timeouts - beatTimer: 0, - winBlurTimer: 0, - frameBlurTimer: 0 + checkFocusTimer: 0, + beatTimer: 0 }; /** @@ -95,6 +96,8 @@ * @return void */ function initialize() { + var options, hidden, visibilityState, visibilitychange; + if ( typeof window.pagenow === 'string' ) { settings.screenId = window.pagenow; } @@ -105,24 +108,39 @@ // Pull in options passed from PHP if ( typeof window.heartbeatSettings === 'object' ) { - var options = window.heartbeatSettings; + options = window.heartbeatSettings; // The XHR URL can be passed as option when window.ajaxurl is not set if ( ! settings.url && options.ajaxurl ) { settings.url = options.ajaxurl; } - // The interval can be from 15 to 60 sec. and can be set temporarily to 5 sec. + // The interval can be from 15 to 120 sec. and can be set temporarily to 5 sec. + // It can be set in the initial options or changed later from JS and/or from PHP. if ( options.interval ) { settings.mainInterval = options.interval; if ( settings.mainInterval < 15 ) { settings.mainInterval = 15; - } else if ( settings.mainInterval > 60 ) { - settings.mainInterval = 60; + } else if ( settings.mainInterval > 120 ) { + settings.mainInterval = 120; } } + // Used to limit the number of AJAX requests. Overrides all other intervals if they are shorter. + // Needed for some hosts that cannot handle frequent requests and the user may exceed the allocated server CPU time, etc. + // The minimal interval can be up to 600 sec. however setting it to longer than 120 sec. will limit or disable + // some of the functionality (like post locks). + // Once set at initialization, minimalInterval cannot be changed/overriden. + if ( options.minimalInterval ) { + options.minimalInterval = parseInt( options.minimalInterval, 10 ); + settings.minimalInterval = options.minimalInterval > 0 && options.minimalInterval <= 600 ? options.minimalInterval * 1000 : 0; + } + + if ( settings.minimalInterval && settings.mainInterval < settings.minimalInterval ) { + settings.mainInterval = settings.minimalInterval; + } + // 'screenId' can be added from settings on the front-end where the JS global 'pagenow' is not set if ( ! settings.screenId ) { settings.screenId = options.screenId || 'front'; @@ -137,16 +155,47 @@ settings.mainInterval = settings.mainInterval * 1000; settings.originalInterval = settings.mainInterval; - // Set focus/blur events on the window - $(window).on( 'blur.wp-heartbeat-focus', function() { - setFrameFocusEvents(); - // We don't know why the 'blur' was fired. Either the user clicked in an iframe or outside the browser. - // Running blurred() after some timeout lets us cancel it if the user clicked in an iframe. - settings.winBlurTimer = window.setTimeout( function(){ blurred(); }, 500 ); - }).on( 'focus.wp-heartbeat-focus', function() { - removeFrameFocusEvents(); - focused(); - }).on( 'unload.wp-heartbeat', function() { + // Switch the interval to 120 sec. by using the Page Visibility API. + // If the browser doesn't support it (Safari < 7, Android < 4.4, IE < 10), the interval + // will be increased to 120 sec. after 5 min. of mouse and keyboard inactivity. + if ( typeof document.hidden !== 'undefined' ) { + hidden = 'hidden'; + visibilitychange = 'visibilitychange'; + visibilityState = 'visibilityState'; + } else if ( typeof document.msHidden !== 'undefined' ) { // IE10 + hidden = 'msHidden'; + visibilitychange = 'msvisibilitychange'; + visibilityState = 'msVisibilityState'; + } else if ( typeof document.webkitHidden !== 'undefined' ) { // Android + hidden = 'webkitHidden'; + visibilitychange = 'webkitvisibilitychange'; + visibilityState = 'webkitVisibilityState'; + } + + if ( hidden ) { + if ( document[hidden] ) { + settings.hasFocus = false; + } + + $document.on( visibilitychange + '.wp-heartbeat', function() { + if ( document[visibilityState] === 'hidden' ) { + blurred(); + window.clearInterval( settings.checkFocusTimer ); + } else { + focused(); + if ( document.hasFocus ) { + settings.checkFocusTimer = window.setInterval( checkFocus, 10000 ); + } + } + }); + } + + // Use document.hasFocus() if available. + if ( document.hasFocus ) { + settings.checkFocusTimer = window.setInterval( checkFocus, 10000 ); + } + + $(window).on( 'unload.wp-heartbeat', function() { // Don't connect any more settings.suspend = true; @@ -157,7 +206,7 @@ }); // Check for user activity every 30 seconds. - window.setInterval( function(){ checkUserActivity(); }, 30000 ); + window.setInterval( checkUserActivity, 30000 ); // Start one tick after DOM ready $document.ready( function() { @@ -206,6 +255,21 @@ return false; } + /** + * Check if the document's focus has changed + * + * @access private + * + * @return void + */ + function checkFocus() { + if ( settings.hasFocus && ! document.hasFocus() ) { + blurred(); + } else if ( ! settings.hasFocus && document.hasFocus() ) { + focused(); + } + } + /** * Set error state and fire an event on XHR errors or timeout * @@ -374,12 +438,16 @@ } } + if ( settings.minimalInterval && interval < settings.minimalInterval ) { + interval = settings.minimalInterval; + } + window.clearTimeout( settings.beatTimer ); if ( delta < interval ) { settings.beatTimer = window.setTimeout( function() { - connect(); + connect(); }, interval - delta ); @@ -389,26 +457,24 @@ } /** - * Set the internal state when the browser window looses focus + * Set the internal state when the browser window becomes hidden or loses focus * * @access private * * @return void */ function blurred() { - clearFocusTimers(); settings.hasFocus = false; } /** - * Set the internal state when the browser window is focused + * Set the internal state when the browser window becomes visible or is in focus * * @access private * * @return void */ function focused() { - clearFocusTimers(); settings.userActivity = time(); // Resume if suspended @@ -420,68 +486,6 @@ } } - /** - * Add focus/blur events to all local iframes - * - * Used to detect when focus is moved from the main window to an iframe - * - * @access private - * - * @return void - */ - function setFrameFocusEvents() { - $('iframe').each( function( i, frame ) { - if ( ! isLocalFrame( frame ) ) { - return; - } - - if ( $.data( frame, 'wp-heartbeat-focus' ) ) { - return; - } - - $.data( frame, 'wp-heartbeat-focus', 1 ); - - $( frame.contentWindow ).on( 'focus.wp-heartbeat-focus', function() { - focused(); - }).on('blur.wp-heartbeat-focus', function() { - setFrameFocusEvents(); - // We don't know why 'blur' was fired. Either the user clicked in the main window or outside the browser. - // Running blurred() after some timeout lets us cancel it if the user clicked in the main window. - settings.frameBlurTimer = window.setTimeout( function(){ blurred(); }, 500 ); - }); - }); - } - - /** - * Remove the focus/blur events to all local iframes - * - * @access private - * - * @return void - */ - function removeFrameFocusEvents() { - $('iframe').each( function( i, frame ) { - if ( ! isLocalFrame( frame ) ) { - return; - } - - $.removeData( frame, 'wp-heartbeat-focus' ); - $( frame.contentWindow ).off( '.wp-heartbeat-focus' ); - }); - } - - /** - * Clear the reset timers for focus/blur events on the window and iframes - * - * @access private - * - * @return void - */ - function clearFocusTimers() { - window.clearTimeout( settings.winBlurTimer ); - window.clearTimeout( settings.frameBlurTimer ); - } - /** * Runs when the user becomes active after a period of inactivity * @@ -494,11 +498,9 @@ $document.off( '.wp-heartbeat-active' ); $('iframe').each( function( i, frame ) { - if ( ! isLocalFrame( frame ) ) { - return; + if ( isLocalFrame( frame ) ) { + $( frame.contentWindow ).off( '.wp-heartbeat-active' ); } - - $( frame.contentWindow ).off( '.wp-heartbeat-active' ); }); focused(); @@ -519,25 +521,28 @@ function checkUserActivity() { var lastActive = settings.userActivity ? time() - settings.userActivity : 0; + // Throttle down when no mouse or keyboard activity for 5 min. if ( lastActive > 300000 && settings.hasFocus ) { - // Throttle down when no mouse or keyboard activity for 5 min blurred(); } - if ( settings.suspendEnabled && lastActive > 1200000 ) { - // Suspend after 20 min. of inactivity + // Suspend after 10 min. of inactivity when suspending is enabled. + // Always suspend after 60 min. of inactivity. This will release the post lock, etc. + if ( ( settings.suspendEnabled && lastActive > 600000 ) || lastActive > 3600000 ) { settings.suspend = true; } if ( ! settings.userActivityEvents ) { - $document.on( 'mouseover.wp-heartbeat-active keyup.wp-heartbeat-active', function(){ userIsActive(); } ); + $document.on( 'mouseover.wp-heartbeat-active keyup.wp-heartbeat-active touchend.wp-heartbeat-active', function() { + userIsActive(); + }); $('iframe').each( function( i, frame ) { - if ( ! isLocalFrame( frame ) ) { - return; + if ( isLocalFrame( frame ) ) { + $( frame.contentWindow ).on( 'mouseover.wp-heartbeat-active keyup.wp-heartbeat-active touchend.wp-heartbeat-active', function() { + userIsActive(); + }); } - - $( frame.contentWindow ).on( 'mouseover.wp-heartbeat-active keyup.wp-heartbeat-active', function(){ userIsActive(); } ); }); settings.userActivityEvents = true; @@ -597,7 +602,7 @@ * In this case the number of 'ticks' can be passed as second argument. * If the window doesn't have focus, the interval slows down to 2 min. * - * @param mixed speed Interval: 'fast' or 5, 15, 30, 60 + * @param mixed speed Interval: 'fast' or 5, 15, 30, 60, 120 * @param string ticks Used with speed = 'fast' or 5, how many ticks before the interval reverts back * @return int Current interval in seconds */ @@ -620,6 +625,9 @@ case 60: newInterval = 60000; break; + case 120: + newInterval = 120000; + break; case 'long-polling': // Allow long polling, (experimental) settings.mainInterval = 0; @@ -628,6 +636,10 @@ newInterval = settings.originalInterval; } + if ( settings.minimalInterval && newInterval < settings.minimalInterval ) { + newInterval = settings.minimalInterval; + } + if ( 5000 === newInterval ) { ticks = parseInt( ticks, 10 ) || 30; ticks = ticks < 1 || ticks > 30 ? 30 : ticks; diff --git a/wp-includes/js/heartbeat.min.js b/wp-includes/js/heartbeat.min.js index d2f17f3ba4..29d0b4a0fa 100644 --- a/wp-includes/js/heartbeat.min.js +++ b/wp-includes/js/heartbeat.min.js @@ -1 +1 @@ -!function(a,b,c){var d=function(){function d(){if("string"==typeof b.pagenow&&(B.screenId=b.pagenow),"string"==typeof b.ajaxurl&&(B.url=b.ajaxurl),"object"==typeof b.heartbeatSettings){var c=b.heartbeatSettings;!B.url&&c.ajaxurl&&(B.url=c.ajaxurl),c.interval&&(B.mainInterval=c.interval,B.mainInterval<15?B.mainInterval=15:B.mainInterval>60&&(B.mainInterval=60)),B.screenId||(B.screenId=c.screenId||"front"),"disable"===c.suspension&&(B.suspendEnabled=!1)}B.mainInterval=1e3*B.mainInterval,B.originalInterval=B.mainInterval,a(b).on("blur.wp-heartbeat-focus",function(){m(),B.winBlurTimer=b.setTimeout(function(){k()},500)}).on("focus.wp-heartbeat-focus",function(){n(),l()}).on("unload.wp-heartbeat",function(){B.suspend=!0,B.xhr&&4!==B.xhr.readyState&&B.xhr.abort()}),b.setInterval(function(){q()},3e4),A.ready(function(){B.lastTick=e(),j()})}function e(){return(new Date).getTime()}function f(a){var c,d=a.src;if(d&&/^https?:\/\//.test(d)&&(c=b.location.origin?b.location.origin:b.location.protocol+"//"+b.location.host,0!==d.indexOf(c)))return!1;try{if(a.contentWindow.document)return!0}catch(e){}return!1}function g(a,b){var c;if(a){switch(a){case"abort":break;case"timeout":c=!0;break;case"error":if(503===b&&B.hasConnected){c=!0;break}case"parsererror":case"empty":case"unknown":B.errorcount++,B.errorcount>2&&B.hasConnected&&(c=!0)}c&&!s()&&(B.connectionError=!0,A.trigger("heartbeat-connection-lost",[a,b]))}}function h(){B.hasConnected=!0,s()&&(B.errorcount=0,B.connectionError=!1,A.trigger("heartbeat-connection-restored"))}function i(){var c,d;B.connecting||B.suspend||(B.lastTick=e(),d=a.extend({},B.queue),B.queue={},A.trigger("heartbeat-send",[d]),c={data:d,interval:B.tempInterval?B.tempInterval/1e3:B.mainInterval/1e3,_nonce:"object"==typeof b.heartbeatSettings?b.heartbeatSettings.nonce:"",action:"heartbeat",screen_id:B.screenId,has_focus:B.hasFocus},B.connecting=!0,B.xhr=a.ajax({url:B.url,type:"post",timeout:3e4,data:c,dataType:"json"}).always(function(){B.connecting=!1,j()}).done(function(a,b,c){var d;return a?(h(),a.nonces_expired?void A.trigger("heartbeat-nonces-expired"):(a.heartbeat_interval&&(d=a.heartbeat_interval,delete a.heartbeat_interval),A.trigger("heartbeat-tick",[a,b,c]),void(d&&v(d)))):void g("empty")}).fail(function(a,b,c){g(b||"unknown",a.status),A.trigger("heartbeat-error",[a,b,c])}))}function j(){var a=e()-B.lastTick,c=B.mainInterval;B.suspend||(B.hasFocus?B.countdown>0&&B.tempInterval&&(c=B.tempInterval,B.countdown--,B.countdown<1&&(B.tempInterval=0)):c=12e4,b.clearTimeout(B.beatTimer),c>a?B.beatTimer=b.setTimeout(function(){i()},c-a):i())}function k(){o(),B.hasFocus=!1}function l(){o(),B.userActivity=e(),B.suspend=!1,B.hasFocus||(B.hasFocus=!0,j())}function m(){a("iframe").each(function(c,d){f(d)&&(a.data(d,"wp-heartbeat-focus")||(a.data(d,"wp-heartbeat-focus",1),a(d.contentWindow).on("focus.wp-heartbeat-focus",function(){l()}).on("blur.wp-heartbeat-focus",function(){m(),B.frameBlurTimer=b.setTimeout(function(){k()},500)})))})}function n(){a("iframe").each(function(b,c){f(c)&&(a.removeData(c,"wp-heartbeat-focus"),a(c.contentWindow).off(".wp-heartbeat-focus"))})}function o(){b.clearTimeout(B.winBlurTimer),b.clearTimeout(B.frameBlurTimer)}function p(){B.userActivityEvents=!1,A.off(".wp-heartbeat-active"),a("iframe").each(function(b,c){f(c)&&a(c.contentWindow).off(".wp-heartbeat-active")}),l()}function q(){var b=B.userActivity?e()-B.userActivity:0;b>3e5&&B.hasFocus&&k(),B.suspendEnabled&&b>12e5&&(B.suspend=!0),B.userActivityEvents||(A.on("mouseover.wp-heartbeat-active keyup.wp-heartbeat-active",function(){p()}),a("iframe").each(function(b,c){f(c)&&a(c.contentWindow).on("mouseover.wp-heartbeat-active keyup.wp-heartbeat-active",function(){p()})}),B.userActivityEvents=!0)}function r(){return B.hasFocus}function s(){return B.connectionError}function t(){B.lastTick=0,j()}function u(){B.suspendEnabled=!1}function v(a,b){var c,d=B.tempInterval?B.tempInterval:B.mainInterval;if(a){switch(a){case"fast":case 5:c=5e3;break;case 15:c=15e3;break;case 30:c=3e4;break;case 60:c=6e4;break;case"long-polling":return B.mainInterval=0,0;default:c=B.originalInterval}5e3===c?(b=parseInt(b,10)||30,b=1>b||b>30?30:b,B.countdown=b,B.tempInterval=c):(B.countdown=0,B.tempInterval=0,B.mainInterval=c),c!==d&&j()}return B.tempInterval?B.tempInterval/1e3:B.mainInterval/1e3}function w(a,b,c){return a?c&&this.isQueued(a)?!1:(B.queue[a]=b,!0):!1}function x(a){return a?B.queue.hasOwnProperty(a):void 0}function y(a){a&&delete B.queue[a]}function z(a){return a?this.isQueued(a)?B.queue[a]:c:void 0}var A=a(document),B={suspend:!1,suspendEnabled:!0,screenId:"",url:"",lastTick:0,queue:{},mainInterval:60,tempInterval:0,originalInterval:0,countdown:0,connecting:!1,connectionError:!1,errorcount:0,hasConnected:!1,hasFocus:!0,userActivity:0,userActivityEvents:!1,beatTimer:0,winBlurTimer:0,frameBlurTimer:0};return d(),{hasFocus:r,connectNow:t,disableSuspend:u,interval:v,hasConnectionError:s,enqueue:w,dequeue:y,isQueued:x,getQueuedItem:z}};b.wp=b.wp||{},b.wp.heartbeat=new d}(jQuery,window); \ No newline at end of file +!function(a,b,c){var d=function(){function d(){var c,d,f,h;"string"==typeof b.pagenow&&(z.screenId=b.pagenow),"string"==typeof b.ajaxurl&&(z.url=b.ajaxurl),"object"==typeof b.heartbeatSettings&&(c=b.heartbeatSettings,!z.url&&c.ajaxurl&&(z.url=c.ajaxurl),c.interval&&(z.mainInterval=c.interval,z.mainInterval<15?z.mainInterval=15:z.mainInterval>120&&(z.mainInterval=120)),c.minimalInterval&&(c.minimalInterval=parseInt(c.minimalInterval,10),z.minimalInterval=c.minimalInterval>0&&c.minimalInterval<=600?1e3*c.minimalInterval:0),z.minimalInterval&&z.mainInterval2&&z.hasConnected&&(c=!0)}c&&!q()&&(z.connectionError=!0,y.trigger("heartbeat-connection-lost",[a,b]))}}function i(){z.hasConnected=!0,q()&&(z.errorcount=0,z.connectionError=!1,y.trigger("heartbeat-connection-restored"))}function j(){var c,d;z.connecting||z.suspend||(z.lastTick=e(),d=a.extend({},z.queue),z.queue={},y.trigger("heartbeat-send",[d]),c={data:d,interval:z.tempInterval?z.tempInterval/1e3:z.mainInterval/1e3,_nonce:"object"==typeof b.heartbeatSettings?b.heartbeatSettings.nonce:"",action:"heartbeat",screen_id:z.screenId,has_focus:z.hasFocus},z.connecting=!0,z.xhr=a.ajax({url:z.url,type:"post",timeout:3e4,data:c,dataType:"json"}).always(function(){z.connecting=!1,k()}).done(function(a,b,c){var d;return a?(i(),a.nonces_expired?void y.trigger("heartbeat-nonces-expired"):(a.heartbeat_interval&&(d=a.heartbeat_interval,delete a.heartbeat_interval),y.trigger("heartbeat-tick",[a,b,c]),void(d&&t(d)))):void h("empty")}).fail(function(a,b,c){h(b||"unknown",a.status),y.trigger("heartbeat-error",[a,b,c])}))}function k(){var a=e()-z.lastTick,c=z.mainInterval;z.suspend||(z.hasFocus?z.countdown>0&&z.tempInterval&&(c=z.tempInterval,z.countdown--,z.countdown<1&&(z.tempInterval=0)):c=12e4,z.minimalInterval&&ca?z.beatTimer=b.setTimeout(function(){j()},c-a):j())}function l(){z.hasFocus=!1}function m(){z.userActivity=e(),z.suspend=!1,z.hasFocus||(z.hasFocus=!0,k())}function n(){z.userActivityEvents=!1,y.off(".wp-heartbeat-active"),a("iframe").each(function(b,c){f(c)&&a(c.contentWindow).off(".wp-heartbeat-active")}),m()}function o(){var b=z.userActivity?e()-z.userActivity:0;b>3e5&&z.hasFocus&&l(),(z.suspendEnabled&&b>6e5||b>36e5)&&(z.suspend=!0),z.userActivityEvents||(y.on("mouseover.wp-heartbeat-active keyup.wp-heartbeat-active touchend.wp-heartbeat-active",function(){n()}),a("iframe").each(function(b,c){f(c)&&a(c.contentWindow).on("mouseover.wp-heartbeat-active keyup.wp-heartbeat-active touchend.wp-heartbeat-active",function(){n()})}),z.userActivityEvents=!0)}function p(){return z.hasFocus}function q(){return z.connectionError}function r(){z.lastTick=0,k()}function s(){z.suspendEnabled=!1}function t(a,b){var c,d=z.tempInterval?z.tempInterval:z.mainInterval;if(a){switch(a){case"fast":case 5:c=5e3;break;case 15:c=15e3;break;case 30:c=3e4;break;case 60:c=6e4;break;case 120:c=12e4;break;case"long-polling":return z.mainInterval=0,0;default:c=z.originalInterval}z.minimalInterval&&cb||b>30?30:b,z.countdown=b,z.tempInterval=c):(z.countdown=0,z.tempInterval=0,z.mainInterval=c),c!==d&&k()}return z.tempInterval?z.tempInterval/1e3:z.mainInterval/1e3}function u(a,b,c){return a?c&&this.isQueued(a)?!1:(z.queue[a]=b,!0):!1}function v(a){return a?z.queue.hasOwnProperty(a):void 0}function w(a){a&&delete z.queue[a]}function x(a){return a?this.isQueued(a)?z.queue[a]:c:void 0}var y=a(document),z={suspend:!1,suspendEnabled:!0,screenId:"",url:"",lastTick:0,queue:{},mainInterval:60,tempInterval:0,originalInterval:0,minimalInterval:0,countdown:0,connecting:!1,connectionError:!1,errorcount:0,hasConnected:!1,hasFocus:!0,userActivity:0,userActivityEvents:!1,checkFocusTimer:0,beatTimer:0};return d(),{hasFocus:p,connectNow:r,disableSuspend:s,interval:t,hasConnectionError:q,enqueue:u,dequeue:w,isQueued:v,getQueuedItem:x}};b.wp=b.wp||{},b.wp.heartbeat=new d}(jQuery,window); \ No newline at end of file diff --git a/wp-includes/version.php b/wp-includes/version.php index 2025ce6e5c..3a4ee6177f 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -4,7 +4,7 @@ * * @global string $wp_version */ -$wp_version = '4.1-alpha-30292'; +$wp_version = '4.1-alpha-30293'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.