How to change the themes “Live Preview” url within the Appearance->Themes page?

External Live Previews For Themes

The Live Preview buttons, on the /wp-admin/themes.php page, are generated from the tmpl-theme micro template:

<script id="tmpl-wpse" type="text/template">

    ...cut...

        <div class="theme-actions">

        <# if ( data.active ) { #>
                <# if ( data.actions.customize ) { #>
                        <a class="button button-primary customize load-customize hide-if-no-customize" href="https://wordpress.stackexchange.com/questions/220473/{{{ data.actions.customize }}}"><?php _e( 'Customize' ); ?></a>
                <# } #>
        <# } else { #>
                <a class="button button-secondary activate" href="{{{ data.actions.activate }}}"><?php _e( 'Activate' ); ?></a>
                <a class="button button-primary load-customize hide-if-no-customize" href="https://wordpress.stackexchange.com/questions/220473/{{{ data.actions.customize }}}"><?php _e( 'Live Preview' ); ?></a>
        <# } #>
        </div>

    ...cut...

  </script>

We can modify the template data via the wp_prepare_themes_for_js filter.

Here’s an example:

/**
 * External Live Previews
 */
add_filter( 'wp_prepare_themes_for_js', function(  $prepared_themes )
{
    //--------------------------
    // Edit this to your needs:

    $externals = [ 
        'twentysixteen' => 'http://foo.tld/demo/twentysixteen/', 
        'twentyfifteen' => 'http://bar.tld/demo/twentyfifteen/' 
    ];
    //--------------------------

    foreach( $externals as $slug => $url )
    {
        if( isset( $prepared_themes[$slug]['actions']['customize'] ) )
            $prepared_themes[$slug]['actions']['customize'] = $url;
    }   

    return $prepared_themes;
} );

But this will not work as expected, because of the load-customize class, that will trigger a click event and open up an iframe overlay with:

$('#wpbody').on( 'click', '.load-customize', function( event ) {
    event.preventDefault();

    // Store a reference to the link that opened the Customizer.
    Loader.link = $(this);
    // Load the theme.
    Loader.open( Loader.link.attr('href') );
});

When we click on the Live Preview button, with an external url, it will trigger an error like:

Uncaught SecurityError: Failed to execute ‘pushState‘ on ‘History’: A
history state object with URL ‘http://foo.tld/demo/twentysixteen/
cannot be created in a document with origin ‘http://example.tld‘ and
URL ‘http://example.tld/wp-admin/themes.php‘.

We could prevent this, by double clicking (not really a reliable option) or by removing the load-customize class for the external preview links. Here’s one such hack:

/**
 * Remove the .load-customize class from buttons with external links
 */
add_action( 'admin_print_styles-themes.php', function()
{ ?>
    <script>
        jQuery(document).ready( function($) {
            $('.load-customize').each( function( index ){
                if( this.host !== window.location.host )
                    $( this ).removeClass( 'load-customize' );
            } );
        });
    </script> <?php
} );

where I got the this.host idea from @daved here.

Another more drastic approach would be to override the tmpl-theme template.