/* globals infiniteScroll, _wpmejsSettings, ga, _gaq, WPCOM_sharing_counts, MediaElementPlayer */
var Scroller, stats, type, text, totop, loading_text;
// IE requires special handling
var isIE = -1 !== navigator.userAgent.search( 'MSIE' );
var IEVersion = navigator.userAgent.match( /MSIE\s?(\d+)\.?\d*;/ );
IEVersion = parseInt( IEVersion[ 1 ] );
// HTTP ajaxurl when site is HTTPS causes Access-Control-Allow-Origin failure in Desktop and iOS Safari
if ( 'https:' === document.location.protocol ) {
infiniteScroll.settings.ajaxurl = infiniteScroll.settings.ajaxurl.replace(
* Loads new posts when users scroll near the bottom of the page.
Scroller = function ( settings ) {
// Initialize our variables
this.body = document.body;
this.element = document.getElementById( settings.id );
this.wrapperClass = settings.wrapper_class;
this.offset = settings.offset;
this.currentday = settings.currentday;
this.order = settings.order;
this.click_handle = settings.click_handle;
this.google_analytics = settings.google_analytics;
this.history = settings.history;
this.origURL = window.location.href;
this.handle = document.createElement( 'div' );
this.handle.setAttribute( 'id', 'infinite-handle' );
this.handle.innerHTML = '<span><button>' + text.replace( '\\', '' ) + '</button></span>';
el: document.getElementById( 'infinite-footer' ),
// Bind methods used as callbacks
this.checkViewportOnLoadBound = self.checkViewportOnLoad.bind( this );
// Core's native MediaElement.js implementation needs special handling
this.wpMediaelement = null;
// We have two type of infinite scroll
// cases 'scroll' and 'click'
if ( type === 'scroll' ) {
// Bind refresh to the scroll event
// Throttle to check for such case every 300ms
// On event the case becomes a fact
this.window.addEventListener( 'scroll', function () {
setInterval( function () {
// Once the case is the case, the action occurs and the fact is no more
self.determineURL(); // determine the url
// Ensure that enough posts are loaded to fill the initial viewport, to compensate for short posts and large displays.
self.ensureFilledViewport();
this.body.addEventListener( 'is.post-load', self.checkViewportOnLoadBound );
} else if ( type === 'click' ) {
if ( this.click_handle ) {
this.element.appendChild( this.handle );
this.handle.addEventListener( 'click', function () {
if ( self.click_handle ) {
self.handle.parentNode.removeChild( self.handle );
// Initialize any Core audio or video players loaded via IS
this.body.addEventListener( 'is.post-load', self.initializeMejs );
* Normalize the access to the document scrollTop value.
Scroller.prototype.getScrollTop = function () {
return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
* Polyfill jQuery.extend.
Scroller.prototype.extend = function ( out ) {
for ( var i = 1; i < arguments.length; i++ ) {
if ( ! arguments[ i ] ) {
for ( var key in arguments[ i ] ) {
if ( Object.hasOwn( arguments[ i ], key ) ) {
out[ key ] = arguments[ i ][ key ];
* Check whether we should fetch any additional posts.
Scroller.prototype.check = function () {
var wrapperMeasurements = this.measure( this.element, [ this.wrapperClass ] );
// Fetch more posts when we're less than 2 screens away from the bottom.
return wrapperMeasurements.bottom < 2 * this.window.innerHeight;
* Renders the results from a successful response.
Scroller.prototype.render = function ( response ) {
var childrenToAppend = Array.prototype.slice.call( response.fragment.childNodes );
this.body.classList.add( 'infinity-success' );
// Render the retrieved nodes.
while ( childrenToAppend.length > 0 ) {
var currentNode = childrenToAppend.shift();
this.element.appendChild( currentNode );
this.trigger( this.body, 'is.post-load', {
jqueryEventName: 'post-load',
* Returns the object used to query for new posts.
Scroller.prototype.query = function () {
page: this.page + this.offset, // Load the next page.
currentday: this.currentday,
scripts: window.infiniteScroll.settings.scripts,
styles: window.infiniteScroll.settings.styles,
query_args: window.infiniteScroll.settings.query_args,
query_before: window.infiniteScroll.settings.query_before,
last_post_date: window.infiniteScroll.settings.last_post_date,
Scroller.prototype.animate = function ( cb, duration ) {
var start = performance.now();
requestAnimationFrame( function animate( time ) {
var timeFraction = Math.min( 1, ( time - start ) / duration );
if ( timeFraction < 1 ) {
requestAnimationFrame( animate );
Scroller.prototype.gotop = function () {
var blog = document.getElementById( 'infinity-blog-title' );
blog.setAttribute( 'title', totop );
blog.addEventListener( 'click', function ( e ) {
var sourceScroll = self.window.pageYOffset;
self.animate( function ( progress ) {
var currentScroll = sourceScroll - sourceScroll * progress;
document.documentElement.scrollTop = document.body.scrollTop = currentScroll;
Scroller.prototype.thefooter = function () {
footerEnabled = this.footer && this.footer.el;
// Check if we have an id for the page wrapper
if ( 'string' === typeof this.footer.wrap ) {
pageWrapper = document.getElementById( this.footer.wrap );
width = pageWrapper.getBoundingClientRect();
// Make the footer match the width of the page
footerContainer = this.footer.el.querySelector( '.container' );
footerContainer.style.width = width + 'px';
sourceBottom = parseInt( self.footer.el.style.bottom || -50, 10 );
targetBottom = this.window.pageYOffset >= 350 ? 0 : -50;
if ( sourceBottom !== targetBottom ) {
self.animate( function ( progress ) {
var currentBottom = sourceBottom + ( targetBottom - sourceBottom ) * progress;
self.footer.el.style.bottom = currentBottom + 'px';
sourceBottom = targetBottom;
* Recursively convert a JS object into URL encoded data.
Scroller.prototype.urlEncodeJSON = function ( obj, prefix ) {
encodedKey = encodeURIComponent( key );
newPrefix = prefix ? prefix + '[' + encodedKey + ']' : encodedKey;
if ( 'object' === typeof obj[ key ] ) {
if ( ! Array.isArray( obj[ key ] ) || obj[ key ].length > 0 ) {
params.push( this.urlEncodeJSON( obj[ key ], newPrefix ) );
// Explicitly expose empty arrays with no values
params.push( newPrefix + '[]=' );
params.push( newPrefix + '=' + encodeURIComponent( obj[ key ] ) );
return params.join( '&' );
* Controls the flow of the refresh. Don't mess.
Scroller.prototype.refresh = function () {
// If we're disabled, ready, or don't pass the check, bail.
if ( this.disabled || ! this.ready || ! this.check() ) {
// Let's get going -- set ready to false to prevent
// multiple refreshes from occurring at once.
// Create a loader element to show it's working.
if ( this.click_handle ) {
document.getElementById( 'infinite-aria' ).textContent = loading_text;
loader = document.createElement( 'div' );
loader.classList.add( 'infinite-loader' );
loader.setAttribute( 'role', 'progress' );
'<div class="spinner"><div class="spinner-inner"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div></div>';
this.element.appendChild( loader );
// Generate our query vars.
action: 'infinite_scroll',
// Inject Customizer state.
if ( 'undefined' !== typeof wp && wp.customize && wp.customize.settings.theme ) {
query.wp_customize = 'on';
query.theme = wp.customize.settings.theme.stylesheet;
wp.customize.each( function ( setting ) {
customized[ setting.id ] = setting();
query.customized = JSON.stringify( customized );
query.nonce = wp.customize.settings.nonce.preview;
// Fire the ajax request.
xhr = new XMLHttpRequest();
xhr.open( 'POST', infiniteScroll.settings.ajaxurl, true );
xhr.setRequestHeader( 'X-Requested-With', 'XMLHttpRequest' );
xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8' );
xhr.send( self.urlEncodeJSON( query ) );
// Allow refreshes to occur again if an error is triggered.
xhr.onerror = function () {
if ( self.click_handle && loader.parentNode ) {
loader.parentNode.removeChild( loader );
xhr.onload = function () {
var response = JSON.parse( xhr.responseText ),
httpCheck = xhr.status >= 200 && xhr.status < 300,
responseCheck = 'undefined' !== typeof response.html;
if ( ! response || ! httpCheck || ! responseCheck ) {
if ( self.click_handle && loader.parentNode ) {
loader.parentNode.removeChild( loader );
// On success, let's hide the loader circle.
if ( self.click_handle && loader.parentNode ) {
loader.parentNode.removeChild( loader );
// If additional scripts are required by the incoming set of posts, parse them
if ( response.scripts && Array.isArray( response.scripts ) ) {
response.scripts.forEach( function ( item ) {
var elementToAppendTo = item.footer ? 'body' : 'head';
// Add script handle to list of those already parsed
window.infiniteScroll.settings.scripts.push( item.handle );
// Output extra data, if present
self.appendInlineScript( item.extra_data, elementToAppendTo );
if ( item.before_handle ) {
self.appendInlineScript( item.before_handle, elementToAppendTo );
// Build script tag and append to DOM in requested location
var script = document.createElement( 'script' );
script.type = 'text/javascript';
// Dynamically loaded scripts are async by default.
// We don't want that, it breaks stuff, e.g. wp-mediaelement init.
if ( item.after_handle ) {
script.onload = function () {
self.appendInlineScript( item.after_handle, elementToAppendTo );
// If MediaElement.js is loaded in by item set of posts, don't initialize the players a second time as it breaks them all
if ( 'wp-mediaelement' === item.handle ) {
self.body.removeEventListener( 'is.post-load', self.initializeMejs );
if ( 'wp-mediaelement' === item.handle && 'undefined' === typeof mejs ) {
self.wpMediaelement = {};
self.wpMediaelement.tag = script;
self.wpMediaelement.element = elementToAppendTo;
setTimeout( self.maybeLoadMejs.bind( self ), 250 );
document.getElementsByTagName( elementToAppendTo )[ 0 ].appendChild( script );
// If additional stylesheets are required by the incoming set of posts, parse them
if ( response.styles && Array.isArray( response.styles ) ) {
response.styles.forEach( function ( item ) {
// Add stylesheet handle to list of those already parsed
window.infiniteScroll.settings.styles.push( item.handle );
var style = document.createElement( 'link' );
style.rel = 'stylesheet';
style.id = item.handle + '-css';
// Destroy link tag if a conditional statement is present and either the browser isn't IE, or the conditional doesn't evaluate true
( ! isIE || ! eval( item.conditional.replace( /%ver/g, IEVersion ) ) )
// Append link tag if necessary
document.getElementsByTagName( 'head' )[ 0 ].appendChild( style );
// Convert the response.html to a fragment element.
// Using a div instead of DocumentFragment, because the latter doesn't support innerHTML.
response.fragment = document.createElement( 'div' );
response.fragment.innerHTML = response.html;
// Increment the page number
// Record pageview in WP Stats, if available.
document.location.protocol +
'//pixel.wp.com/g.gif?' +
// Add new posts to the postflair object
if ( 'object' === typeof response.postflair && 'object' === typeof WPCOM_sharing_counts ) {
WPCOM_sharing_counts = self.extend( WPCOM_sharing_counts, response.postflair ); // eslint-disable-line no-global-assign
self.render.call( self, response );
// If 'click' type and there are still posts to fetch, add back the handle
if ( type === 'click' ) {
// add focus to new posts, only in button mode as we know where page focus currently is and only if we have a wrapper
if ( infiniteScroll.settings.wrapper ) {
'#infinite-view-' + ( self.page + self.offset - 1 ) + ' a:first-of-type'