WordPress.org

Make WordPress Core

Ticket #28536: 28536.2.diff

File 28536.2.diff, 12.4 KB (added by westonruter, 5 years ago)

Fix Uncaught TypeError when accessing window. Use message passing instead of directly accessing parent window. Use message passing instead of directly accessing parent window. Commit: https://github.com/x-team/wordpress-develop/commit/0464541f300ea3889503abc2da8b08db45d17ffc PR: https://github.com/x-team/wordpress-develop/pull/16

  • src/wp-admin/customize.php

    diff --git src/wp-admin/customize.php src/wp-admin/customize.php
    index 1faf371..cdb7351 100644
    define( 'IFRAME_REQUEST', true ); 
    1212/** Load WordPress Administration Bootstrap */
    1313require_once( dirname( __FILE__ ) . '/admin.php' );
    1414
    15 if ( ! current_user_can( 'edit_theme_options' ) )
     15if ( ! current_user_can( 'edit_theme_options' ) ) {
    1616        wp_die( __( 'Cheatin’ uh?' ) );
     17}
     18
     19global $wp_scripts, $wp_customize;
    1720
    1821wp_reset_vars( array( 'url', 'return' ) );
    1922$url = urldecode( $url );
    2023$url = wp_validate_redirect( $url, home_url( '/' ) );
    21 if ( $return )
     24
     25if ( $return ) {
    2226        $return = wp_validate_redirect( urldecode( $return ) );
    23 if ( ! $return )
     27} elseif ( ! $wp_customize->is_theme_active() ) {
     28        $return = admin_url( 'themes.php' );
     29} else {
    2430        $return = $url;
    25 
    26 global $wp_scripts, $wp_customize;
     31}
    2732
    2833$registered = $wp_scripts->registered;
    2934$wp_scripts = new WP_Scripts;
  • src/wp-admin/js/customize-controls.js

    diff --git src/wp-admin/js/customize-controls.js src/wp-admin/js/customize-controls.js
    index 4bfc252..11ef18a 100644
     
    728728                                                }
    729729                                        }
    730730
    731                                         clearTimeout( timeout );
    732                                         timeout = setTimeout( callback, self.refreshBuffer );
     731                                        try {
     732                                                clearTimeout( timeout );
     733                                                timeout = setTimeout( callback, self.refreshBuffer );
     734                                        } catch ( e ) {
     735                                                // Handle Uncaught InvalidAccessError: Failed to execute 'setTimeout' on 'Window': No script context is available in which to execute the script.
     736                                                return callback();
     737                                        }
    733738                                };
    734739                        })( this );
    735740
     
    794799
    795800                        // Update the URL when the iframe sends a URL message.
    796801                        this.bind( 'url', this.previewUrl );
     802
     803                        this.addHistory();
     804                },
     805
     806                /**
     807                 * Given a URL query string, return the query vars contained within it
     808                 *
     809                 * @param {String} queryString
     810                 * @returns {Object}
     811                 */
     812                parseQueryVars: function ( queryString ) {
     813                        var queryVars = {};
     814                        if ( queryString ) {
     815                                $.each( queryString.split( '&' ), function () {
     816                                        var key, value, pair;
     817                                        pair = this.split( '=', 2 );
     818                                        key = decodeURIComponent( pair[0] );
     819                                        value = pair[1] ? decodeURIComponent( pair[1] ) : null;
     820                                        queryVars[ key ] = value;
     821                                } );
     822                        }
     823                        return queryVars;
     824                },
     825
     826                /**
     827                 * Add support for history for navigation in Customize preview
     828                 */
     829                addHistory: function () {
     830                        var self, backLink, pushStatePreviewUrl, popState;
     831                        if ( ! history.pushState ) {
     832                                return;
     833                        }
     834                        self = this;
     835                        backLink = $( '.back.button:first' );
     836
     837                        // Push state
     838                        pushStatePreviewUrl = function ( url ) {
     839                                var state, parentLocation, queryVars;
     840                                state = { customizePreviewUrl: url };
     841                                parentLocation = location.pathname;
     842                                queryVars = self.parseQueryVars( location.search.substr( 1 ) );
     843                                queryVars.url = url;
     844                                parentLocation += '?' + $.param( queryVars );
     845                                parentLocation += location.hash;
     846
     847                                // parent frame is for the sake of customizer loaded into iframe for Live Preview
     848                                if ( api.parent.targetWindow() ) {
     849                                        api.parent.send( 'pushstate', [ state, '', parentLocation ] );
     850                                } else {
     851                                        window.history.pushState( state, '', parentLocation );
     852                                }
     853
     854                                if ( api.settings.theme.active ) {
     855                                        backLink.prop( 'href', url );
     856                                }
     857                        };
     858                        this.previewUrl.bind( pushStatePreviewUrl );
     859
     860                        // Pop state
     861                        popState = function ( state, windowLocation ) {
     862                                var url, queryVars;
     863
     864                                queryVars = self.parseQueryVars( windowLocation.search.substr( 1 ) );
     865
     866                                // Handle hitting back to themes page after having reloaded on a page in the Customizer
     867                                if ( ! state && /themes\.php$/.test( windowLocation.pathname ) ) {
     868                                        location.replace( windowLocation.href );
     869                                        return;
     870                                }
     871
     872                                if ( state && state.customizePreviewUrl ) {
     873                                        url = state.customizePreviewUrl;
     874                                } else if ( queryVars.url ) {
     875                                        /*
     876                                         * When popped to initial state, then state is null and so
     877                                         * we don't have customizePreviewUrl, so we can grab it from
     878                                         * the URL query parameter. Note that the previewUrl value
     879                                         * has a validator in place which will prevent illegal
     880                                         * or malicious URLs from being injected into the preview.
     881                                         */
     882                                        url = queryVars.url;
     883                                } else {
     884                                        // Use the homepage as the preview URL as default
     885                                        url = api.settings.url.home;
     886                                }
     887                                self.previewUrl.unbind( pushStatePreviewUrl );
     888                                self.previewUrl( url );
     889                                self.previewUrl.bind( pushStatePreviewUrl );
     890
     891                                if ( api.settings.theme.active ) {
     892                                        backLink.prop( 'href', url );
     893                                }
     894                        };
     895
     896                        if ( api.parent.targetWindow() ) {
     897                                api.parent.bind( 'popstate', function ( args ) {
     898                                        popState.apply( null, args );
     899                                } );
     900                        } else {
     901                                $( window ).on( 'popstate', function ( e ) {
     902                                        popState( e.originalEvent.state, window.location );
     903                                } );
     904                        }
    797905                },
    798906
    799907                query: function() {},
     
    9131021                if ( ! $.support.postMessage || ( ! $.support.cors && api.settings.isCrossDomain ) )
    9141022                        return window.location = api.settings.url.fallback;
    9151023
    916                 var previewer, parent, topFocus,
     1024                var previewer, topFocus,
    9171025                        body = $( document.body ),
    9181026                        overlay = body.children('.wp-full-overlay');
    9191027
     
    9271035                        }
    9281036                });
    9291037
     1038                // Create a potential postMessage connection with the parent frame.
     1039                api.parent = new api.Messenger( {
     1040                        url: api.settings.url.parent,
     1041                        channel: 'loader'
     1042                } );
     1043
    9301044                // Initialize Previewer
    9311045                previewer = new api.Previewer({
    9321046                        container:   '#customize-preview',
     
    11131227                        event.preventDefault();
    11141228                });
    11151229
    1116                 // Create a potential postMessage connection with the parent frame.
    1117                 parent = new api.Messenger({
    1118                         url: api.settings.url.parent,
    1119                         channel: 'loader'
    1120                 });
    1121 
    11221230                // If we receive a 'back' event, we're inside an iframe.
    11231231                // Send any clicks to the 'Return' link to the parent page.
    1124                 parent.bind( 'back', function() {
     1232                api.parent.bind( 'back', function() {
    11251233                        $('.back').on( 'click.back', function( event ) {
    11261234                                event.preventDefault();
    1127                                 parent.send( 'close' );
     1235                                api.parent.send( 'close' );
    11281236                        });
    11291237                });
    11301238
    11311239                // Pass events through to the parent.
    11321240                api.bind( 'saved', function() {
    1133                         parent.send( 'saved' );
     1241                        api.parent.send( 'saved' );
    11341242                });
    11351243
    11361244                // When activated, let the loader handle redirecting the page.
    11371245                // If no loader exists, redirect the page ourselves (if a url exists).
    11381246                api.bind( 'activated', function() {
    1139                         if ( parent.targetWindow() )
    1140                                 parent.send( 'activated', api.settings.url.activated );
    1141                         else if ( api.settings.url.activated )
     1247                        if ( api.parent.targetWindow() ) {
     1248                                api.parent.send( 'activated', api.settings.url.activated );
     1249                        } else if ( api.settings.url.activated ) {
    11421250                                window.location = api.settings.url.activated;
     1251                        }
    11431252                });
    11441253
    11451254                // Initialize the connection with the parent frame.
    1146                 parent.send( 'ready' );
     1255                api.parent.send( 'ready' );
    11471256
    11481257                // Control visibility for default controls
    11491258                $.each({
  • src/wp-includes/js/customize-base.js

    diff --git src/wp-includes/js/customize-base.js src/wp-includes/js/customize-base.js
    index db573b5..b2e32c9 100644
    window.wp = window.wp || {}; 
    494494                 * @param  {object} options       Extend any instance parameter or method with this object.
    495495                 */
    496496                initialize: function( params, options ) {
    497                         // Target the parent frame by default, but only if a parent frame exists.
    498                         var defaultTarget = window.parent == window ? null : window.parent;
     497                        var defaultTarget;
     498                        try {
     499                                // Target the parent frame by default, but only if a parent frame exists.
     500                                defaultTarget = window.parent === window ? null : window.parent;
     501                        } catch ( e ) {
     502                                // Handle case where window goes away in race condition. Uncaught TypeError: Cannot read property 'parent' of null
     503                                defaultTarget = null;
     504                        }
    499505
    500506                        $.extend( this, options || {} );
    501507
  • src/wp-includes/js/customize-loader.js

    diff --git src/wp-includes/js/customize-loader.js src/wp-includes/js/customize-loader.js
    index cccf71a..ce099e9 100644
    window.wp = window.wp || {}; 
    3636                        });
    3737
    3838                        // Add navigation listeners.
    39                         if ( $.support.history )
     39                        if ( $.support.history ) {
    4040                                this.window.on( 'popstate', Loader.popstate );
     41                        }
    4142
    4243                        if ( $.support.hashchange ) {
    4344                                this.window.on( 'hashchange', Loader.hashchange );
    window.wp = window.wp || {}; 
    4748
    4849                popstate: function( e ) {
    4950                        var state = e.originalEvent.state;
    50                         if ( state && state.customize )
     51                        if ( state && state.customize ) {
    5152                                Loader.open( state.customize );
    52                         else if ( Loader.active )
     53                        } else if ( Loader.active && ( ! state || ! state.customizePreviewUrl ) ) {
    5354                                Loader.close();
     55                        }
    5456                },
    5557
    5658                hashchange: function() {
    5759                        var hash = window.location.toString().split('#')[1];
    5860
    5961                        if ( hash && 0 === hash.indexOf( 'wp_customize=on' ) )
    60                                 Loader.open( Loader.settings.url + '?' + hash );
     62                                Loader.open( Loader.settings.url.customize + '?' + hash );
    6163
    6264                        if ( ! hash && ! $.support.history )
    6365                                Loader.close();
    6466                },
    6567
    6668                open: function( src ) {
    67                         var hash;
     69                        var hash, messenger;
    6870
    6971                        if ( this.active )
    7072                                return;
    window.wp = window.wp || {}; 
    8082                        this.iframe.one( 'load', this.loaded );
    8183
    8284                        // Create a postMessage connection with the iframe.
    83                         this.messenger = new api.Messenger({
     85                        this.messenger = messenger = new api.Messenger({
    8486                                url: src,
    8587                                channel: 'loader',
    8688                                targetWindow: this.iframe[0].contentWindow
    8789                        });
    8890
    8991                        // Wait for the connection from the iframe before sending any postMessage events.
    90                         this.messenger.bind( 'ready', function() {
     92                        messenger.bind( 'ready', function() {
    9193                                Loader.messenger.send( 'back' );
    92                         });
    93 
    94                         this.messenger.bind( 'close', function() {
    95                                 if ( $.support.history )
     94                        } );
     95
     96                        messenger.bind( 'close', function() {
     97                                var goBackToThemesPage;
     98                                if ( $.support.history ) {
     99                                        goBackToThemesPage = function ( e ) {
     100                                                var state;
     101                                                state = e.originalEvent.state;
     102                                                if ( state && ( state.customize || state.customizePreviewUrl ) ) {
     103                                                        history.back();
     104                                                } else {
     105                                                        $( window ).off( 'popstate', goBackToThemesPage );
     106                                                }
     107                                        };
     108                                        $( window ).on( 'popstate', goBackToThemesPage );
    96109                                        history.back();
    97                                 else if ( $.support.hashchange )
     110                                } else if ( $.support.hashchange ) {
    98111                                        window.location.hash = '';
    99                                 else
     112                                } else {
    100113                                        Loader.close();
     114                                }
    101115                        });
    102116
    103                         this.messenger.bind( 'activated', function( location ) {
     117                        messenger.bind( 'activated', function( location ) {
    104118                                if ( location )
    105119                                        window.location = location;
    106120                        });
    107121
    108122                        hash = src.split('?')[1];
    109123
    110                         // Ensure we don't call pushState if the user hit the forward button.
    111                         if ( $.support.history && window.location.href !== src )
    112                                 history.pushState( { customize: src }, '', src );
    113                         else if ( ! $.support.history && $.support.hashchange && hash )
     124                        if ( $.support.history ) {
     125                                // Ensure we don't call pushState if the user hit the forward button.
     126                                if ( window.location.href !== src ) {
     127                                        history.pushState( { customize: src }, '', src );
     128                                }
     129
     130                                // Allow customizer to control history of parent
     131                                messenger.bind( 'pushstate', function( args ) {
     132                                        history.pushState.apply( history, args );
     133                                } );
     134
     135                                // Forward popstate events to customizer
     136                                $( window ).on( 'popstate', function ( e ) {
     137                                        messenger.send( 'popstate', [ e.originalEvent.state, window.location ] );
     138                                } );
     139
     140                        } else if ( $.support.hashchange && hash ) {
    114141                                window.location.hash = 'wp_customize=on&' + hash;
     142                        }
    115143
    116144                        this.trigger( 'open' );
    117145                },
    window.wp = window.wp || {}; 
    121149                },
    122150
    123151                close: function() {
    124                         if ( ! this.active )
     152                        if ( ! this.active ) {
    125153                                return;
     154                        }
    126155                        this.active = false;
    127156
    128157                        this.trigger( 'close' );
    129158
    130159                        // Return focus to link that was originally clicked.
    131                         if ( this.link )
     160                        if ( this.link ) {
    132161                                this.link.focus();
     162                        }
    133163                },
    134164
    135165                closed: function() {
  • src/wp-includes/theme.php

    diff --git src/wp-includes/theme.php src/wp-includes/theme.php
    index 40acf6b..f27d79b 100644
    function _wp_customize_loader_settings() { 
    18711871        );
    18721872
    18731873        $settings = array(
    1874                 'url'           => esc_url( admin_url( 'customize.php' ) ),
     1874                'url'           => array(
     1875                        'customize' => esc_url_raw( admin_url( 'customize.php' ) ),
     1876                        'home'      => esc_url_raw( home_url( '/' ) ),
     1877                ),
    18751878                'isCrossDomain' => $cross_domain,
    18761879                'browser'       => $browser,
    18771880        );