Make WordPress Core


Ignore:
Timestamp:
09/17/2015 07:41:35 PM (10 years ago)
Author:
westonruter
Message:

Customize: Reduce peak memory usage by JSON-encoding settings and controls individually.

When there are hundreds of settings and controls (e.g. nav menu items and widget instances) the resulting object that is JSON-encoded can become very large, and wp_json_encode() can consume a lot of memory to serialize it. By breaking down the serialization into multiple calls the peak memory usage can be kept in line.

Moves logic out of wp-admin/customize.php into the WP_Customize_Manager class with new methods:

  • is_ios()
  • get_document_title_template()
  • get_preview_url()/set_preview_url()
  • get_return_url()/set_return_url()
  • get_autofocus()/set_autofocus()
  • customize_pane_settings()

Includes unit tests for these methods, for which the logic was formerly untestable in customize.php.

Fixes #33898.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-customize-manager.php

    r34233 r34269  
    9595
    9696    /**
     97     * Initial URL being previewed.
     98     *
     99     * @since 4.4.0
     100     * @access protected
     101     * @var string
     102     */
     103    protected $preview_url;
     104
     105    /**
     106     * URL to link the user to when closing the Customizer.
     107     *
     108     * @since 4.4.0
     109     * @access protected
     110     * @var string
     111     */
     112    protected $return_url;
     113
     114    /**
     115     * Mapping of 'panel', 'section', 'control' to the ID which should be autofocused.
     116     *
     117     * @since 4.4.0
     118     * @access protected
     119     * @var array
     120     */
     121    protected $autofocus = array();
     122
     123    /**
    97124     * Unsanitized values for Customize Settings parsed from $_POST['customized'].
    98125     *
     
    140167        add_action( 'customize_controls_init',            array( $this, 'prepare_controls' ) );
    141168        add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) );
     169
     170        // Render Panel, Section, and Control templates.
     171        add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_panel_templates' ), 1 );
     172        add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_section_templates' ), 1 );
     173        add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_control_templates' ), 1 );
     174
     175        // Export the settings to JS via the _wpCustomizeSettings variable.
     176        add_action( 'customize_controls_print_footer_scripts', array( $this, 'customize_pane_settings' ), 1000 );
    142177    }
    143178
     
    622657    public function customize_preview_settings() {
    623658        $settings = array(
    624             'values'  => array(),
    625659            'channel' => wp_unslash( $_POST['customize_messenger_channel'] ),
    626660            'activePanels' => array(),
     
    639673        }
    640674
    641         foreach ( $this->settings as $id => $setting ) {
    642             if ( $setting->check_capabilities() ) {
    643                 $settings['values'][ $id ] = $setting->js_value();
    644             }
    645         }
    646675        foreach ( $this->panels as $panel_id => $panel ) {
    647676            if ( $panel->check_capabilities() ) {
     
    668697        <script type="text/javascript">
    669698            var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
     699            _wpCustomizeSettings.values = {};
     700            (function( v ) {
     701                <?php
     702                /*
     703                 * Serialize settings separately from the initial _wpCustomizeSettings
     704                 * serialization in order to avoid a peak memory usage spike.
     705                 * @todo We may not even need to export the values at all since the pane syncs them anyway.
     706                 */
     707                foreach ( $this->settings as $id => $setting ) {
     708                    if ( $setting->check_capabilities() ) {
     709                        printf(
     710                            "v[%s] = %s;\n",
     711                            wp_json_encode( $id ),
     712                            wp_json_encode( $setting->js_value() )
     713                        );
     714                    }
     715                }
     716                ?>
     717            })( _wpCustomizeSettings.values );
    670718        </script>
    671719        <?php
     
    12631311            $control->enqueue();
    12641312        }
     1313    }
     1314
     1315    /**
     1316     * Return whether the user agent is iOS.
     1317     *
     1318     * @since 4.4.0
     1319     * @access public
     1320     *
     1321     * @return bool
     1322     */
     1323    public function is_ios() {
     1324        return wp_is_mobile() && preg_match( '/iPad|iPod|iPhone/', $_SERVER['HTTP_USER_AGENT'] );
     1325    }
     1326
     1327    /**
     1328     * Get the template string for the Customizer pane document title.
     1329     *
     1330     * @since 4.4.0
     1331     * @access public
     1332     *
     1333     * @return string
     1334     */
     1335    public function get_document_title_template() {
     1336        if ( $this->is_theme_active() ) {
     1337            $document_title_tmpl = _x( 'Customize: %s', 'Placeholder is the document title from the preview' );
     1338        } else {
     1339            $document_title_tmpl = _x( 'Live Preview: %s', 'Placeholder is the document title from the preview' );
     1340        }
     1341        $document_title_tmpl = html_entity_decode( $document_title_tmpl, ENT_QUOTES, 'UTF-8' ); // Because exported to JS and assigned to document.title.
     1342        return $document_title_tmpl;
     1343    }
     1344
     1345    /**
     1346     * Set the initial URL to be previewed.
     1347     *
     1348     * URL is validated.
     1349     *
     1350     * @since 4.4.0
     1351     * @access public
     1352     *
     1353     * @param string $preview_url  URL to be previewed.
     1354     */
     1355    public function set_preview_url( $preview_url ) {
     1356        $this->preview_url = wp_validate_redirect( $preview_url, home_url( '/' ) );
     1357    }
     1358
     1359    /**
     1360     * Get the initial URL to be previewed.
     1361     *
     1362     * @since 4.4.0
     1363     * @access public
     1364     *
     1365     * @return string
     1366     */
     1367    public function get_preview_url() {
     1368        if ( empty( $this->preview_url ) ) {
     1369            $preview_url = home_url( '/' );
     1370        } else {
     1371            $preview_url = $this->preview_url;
     1372        }
     1373        return $preview_url;
     1374    }
     1375
     1376    /**
     1377     * Set URL to link the user to when closing the Customizer.
     1378     *
     1379     * URL is validated.
     1380     *
     1381     * @since 4.4.0
     1382     * @access public
     1383     *
     1384     * @param string $return_url  URL for return link.
     1385     */
     1386    public function set_return_url( $return_url ) {
     1387        $return_url = remove_query_arg( wp_removable_query_args(), $return_url );
     1388        $return_url = wp_validate_redirect( $return_url );
     1389        $this->return_url = $return_url;
     1390    }
     1391
     1392    /**
     1393     * Get URL to link the user to when closing the Customizer.
     1394     *
     1395     * @since 4.4.0
     1396     * @access public
     1397     *
     1398     * @return string
     1399     */
     1400    public function get_return_url() {
     1401        if ( $this->return_url ) {
     1402            $return_url = $this->return_url;
     1403        } else if ( $this->preview_url ) {
     1404            $return_url = $this->preview_url;
     1405        } else if ( current_user_can( 'edit_theme_options' ) || current_user_can( 'switch_themes' ) ) {
     1406            $return_url = admin_url( 'themes.php' );
     1407        } else {
     1408            $return_url = admin_url();
     1409        }
     1410        return $return_url;
     1411    }
     1412
     1413    /**
     1414     * Set the autofocused constructs.
     1415     *
     1416     * @param array $autofocus {
     1417     *     Mapping of 'panel', 'section', 'control' to the ID which should be autofocused.
     1418     *
     1419     *     @type string [$control]  ID for control to be autofocused.
     1420     *     @type string [$section]  ID for section to be autofocused.
     1421     *     @type string [$panel]    ID for panel to be autofocused.
     1422     * }
     1423     */
     1424    public function set_autofocus( $autofocus ) {
     1425        $this->autofocus = array_filter( wp_array_slice_assoc( $autofocus, array( 'panel', 'section', 'control' ) ), 'is_string' );
     1426    }
     1427
     1428    /**
     1429     * Get the autofocused constructs.
     1430     *
     1431     * @since 4.4.0
     1432     * @access public
     1433     *
     1434     * @return array {
     1435     *     Mapping of 'panel', 'section', 'control' to the ID which should be autofocused.
     1436     *
     1437     *     @type string [$control]  ID for control to be autofocused.
     1438     *     @type string [$section]  ID for section to be autofocused.
     1439     *     @type string [$panel]    ID for panel to be autofocused.
     1440     * }
     1441     */
     1442    public function get_autofocus() {
     1443        return $this->autofocus;
     1444    }
     1445
     1446    /**
     1447     * Print JavaScript settings for parent window.
     1448     *
     1449     * @since 4.3.0
     1450     */
     1451    public function customize_pane_settings() {
     1452        /*
     1453         * If the frontend and the admin are served from the same domain, load the
     1454         * preview over ssl if the Customizer is being loaded over ssl. This avoids
     1455         * insecure content warnings. This is not attempted if the admin and frontend
     1456         * are on different domains to avoid the case where the frontend doesn't have
     1457         * ssl certs. Domain mapping plugins can allow other urls in these conditions
     1458         * using the customize_allowed_urls filter.
     1459         */
     1460
     1461        $allowed_urls = array( home_url( '/' ) );
     1462        $admin_origin = parse_url( admin_url() );
     1463        $home_origin  = parse_url( home_url() );
     1464        $cross_domain = ( strtolower( $admin_origin['host'] ) !== strtolower( $home_origin['host'] ) );
     1465
     1466        if ( is_ssl() && ! $cross_domain ) {
     1467            $allowed_urls[] = home_url( '/', 'https' );
     1468        }
     1469
     1470        /**
     1471         * Filter the list of URLs allowed to be clicked and followed in the Customizer preview.
     1472         *
     1473         * @since 3.4.0
     1474         *
     1475         * @param array $allowed_urls An array of allowed URLs.
     1476         */
     1477        $allowed_urls = array_unique( apply_filters( 'customize_allowed_urls', $allowed_urls ) );
     1478
     1479        $login_url = add_query_arg( array(
     1480            'interim-login' => 1,
     1481            'customize-login' => 1,
     1482        ), wp_login_url() );
     1483
     1484        // Prepare Customizer settings to pass to JavaScript.
     1485        $settings = array(
     1486            'theme'    => array(
     1487                'stylesheet' => $this->get_stylesheet(),
     1488                'active'     => $this->is_theme_active(),
     1489            ),
     1490            'url'      => array(
     1491                'preview'       => esc_url_raw( $this->get_preview_url() ),
     1492                'parent'        => esc_url_raw( admin_url() ),
     1493                'activated'     => esc_url_raw( home_url( '/' ) ),
     1494                'ajax'          => esc_url_raw( admin_url( 'admin-ajax.php', 'relative' ) ),
     1495                'allowed'       => array_map( 'esc_url_raw', $allowed_urls ),
     1496                'isCrossDomain' => $cross_domain,
     1497                'home'          => esc_url_raw( home_url( '/' ) ),
     1498                'login'         => esc_url_raw( $login_url ),
     1499            ),
     1500            'browser'  => array(
     1501                'mobile' => wp_is_mobile(),
     1502                'ios'    => $this->is_ios(),
     1503            ),
     1504            'panels'   => array(),
     1505            'sections' => array(),
     1506            'nonce'    => array(
     1507                'save'    => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
     1508                'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ),
     1509            ),
     1510            'autofocus' => array(),
     1511            'documentTitleTmpl' => $this->get_document_title_template(),
     1512        );
     1513
     1514        // Prepare Customize Section objects to pass to JavaScript.
     1515        foreach ( $this->sections() as $id => $section ) {
     1516            if ( $section->check_capabilities() ) {
     1517                $settings['sections'][ $id ] = $section->json();
     1518            }
     1519        }
     1520
     1521        // Prepare Customize Panel objects to pass to JavaScript.
     1522        foreach ( $this->panels() as $panel_id => $panel ) {
     1523            if ( $panel->check_capabilities() ) {
     1524                $settings['panels'][ $panel_id ] = $panel->json();
     1525                foreach ( $panel->sections as $section_id => $section ) {
     1526                    if ( $section->check_capabilities() ) {
     1527                        $settings['sections'][ $section_id ] = $section->json();
     1528                    }
     1529                }
     1530            }
     1531        }
     1532
     1533        // Pass to frontend the Customizer construct being deeplinked.
     1534        foreach ( $this->get_autofocus() as $type => $id ) {
     1535            $can_autofocus = (
     1536                ( 'control' === $type && $this->get_control( $id ) && $this->get_control( $id )->check_capabilities() )
     1537                ||
     1538                ( 'section' === $type && isset( $settings['sections'][ $id ] ) )
     1539                ||
     1540                ( 'panel' === $type && isset( $settings['panels'][ $id ] ) )
     1541            );
     1542            if ( $can_autofocus ) {
     1543                $settings['autofocus'][ $type ] = $id;
     1544            }
     1545        }
     1546
     1547        ?>
     1548        <script type="text/javascript">
     1549            var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
     1550            _wpCustomizeSettings.controls = {};
     1551            _wpCustomizeSettings.settings = {};
     1552            <?php
     1553
     1554            // Serialize settings one by one to improve memory usage.
     1555            echo "(function ( s ){\n";
     1556            foreach ( $this->settings() as $setting ) {
     1557                if ( $setting->check_capabilities() ) {
     1558                    printf(
     1559                        "s[%s] = %s;\n",
     1560                        wp_json_encode( $setting->id ),
     1561                        wp_json_encode( array(
     1562                            'value'     => $setting->js_value(),
     1563                            'transport' => $setting->transport,
     1564                            'dirty'     => $setting->dirty,
     1565                        ) )
     1566                    );
     1567                }
     1568            }
     1569            echo "})( _wpCustomizeSettings.settings );\n";
     1570
     1571            // Serialize controls one by one to improve memory usage.
     1572            echo "(function ( c ){\n";
     1573            foreach ( $this->controls() as $control ) {
     1574                if ( $control->check_capabilities() ) {
     1575                    printf(
     1576                        "c[%s] = %s;\n",
     1577                        wp_json_encode( $control->id ),
     1578                        wp_json_encode( $control->json() )
     1579                    );
     1580                }
     1581            }
     1582            echo "})( _wpCustomizeSettings.controls );\n";
     1583        ?>
     1584        </script>
     1585        <?php
    12651586    }
    12661587
Note: See TracChangeset for help on using the changeset viewer.