Make WordPress Core

Changeset 56687


Ignore:
Timestamp:
09/25/2023 09:03:19 PM (7 months ago)
Author:
westonruter
Message:

Script Loader: Use wp_get_script_tag() and wp_get_inline_script_tag()/wp_print_inline_script_tag() helper functions to output scripts on the frontend and login screen.

Using script tag helper functions allows plugins to employ the wp_script_attributes and wp_inline_script_attributes filters to inject the nonce attribute to apply Content Security Policy (e.g. Strict CSP). Use of helper functions also simplifies logic in WP_Scripts.

  • Update wp_get_inline_script_tag() to wrap inline script in CDATA blocks for XHTML-compatibility when not using HTML5.
  • Ensure the type attribute is printed first in wp_get_inline_script_tag() for back-compat.
  • Wrap existing <script> tags in output buffering to retain IDE supports.
  • In wp_get_inline_script_tag(), append the newline to $javascript before it is passed into the wp_inline_script_attributes filter so that the CSP hash can be computed properly.
  • In the_block_template_skip_link(), opt to enqueue the inline script rather than print it.
  • Add ext-php to composer.json under suggest as previously it was an undeclared dependency for running PHPUnit tests.
  • Update tests to rely on DOMDocument to compare script markup, normalizing unsemantic differences.

Props westonruter, spacedmonkey, flixos90, 10upsimon, dmsnell, mukesh27, joemcgill, swissspidy, azaozz.
Fixes #58664.
See #39941.

Location:
trunk
Files:
17 edited

Legend:

Unmodified
Added
Removed
  • trunk/composer.json

    r56421 r56687  
    1212    "require": {
    1313        "php": ">=7.0"
     14    },
     15    "suggest": {
     16        "ext-dom": "*"
    1417    },
    1518    "require-dev": {
  • trunk/src/wp-includes/class-wp-customize-manager.php

    r56549 r56687  
    465465                'error'         => $ajax_message,
    466466            );
     467            $message .= ob_get_clean();
     468            ob_start();
    467469            ?>
    468470            <script>
     
    473475            </script>
    474476            <?php
    475             $message .= ob_get_clean();
     477            $message .= wp_get_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
    476478        }
    477479
     
    20842086            return;
    20852087        }
     2088        ob_start();
    20862089        ?>
    20872090        <script>
     
    21072110        </script>
    21082111        <?php
     2112        wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
    21092113    }
    21102114
     
    22022206        }
    22032207
     2208        ob_start();
    22042209        ?>
    2205         <script type="text/javascript">
     2210        <script>
    22062211            var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
    22072212            _wpCustomizeSettings.values = {};
     
    22262231        </script>
    22272232        <?php
     2233        wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
    22282234    }
    22292235
     
    49774983        }
    49784984
     4985        ob_start();
    49794986        ?>
    4980         <script type="text/javascript">
     4987        <script>
    49814988            var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
    49824989            _wpCustomizeSettings.initialClientTimestamp = _.now();
     
    50135020        </script>
    50145021        <?php
     5022        wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
    50155023    }
    50165024
  • trunk/src/wp-includes/class-wp-customize-nav-menus.php

    r55990 r56687  
    15601560            'navMenuInstanceArgs' => $this->preview_nav_menu_instance_args,
    15611561        );
    1562         printf( '<script>var _wpCustomizePreviewNavMenusExports = %s;</script>', wp_json_encode( $exports ) );
     1562        wp_print_inline_script_tag( sprintf( 'var _wpCustomizePreviewNavMenusExports = %s;', wp_json_encode( $exports ) ) );
    15631563    }
    15641564
  • trunk/src/wp-includes/class-wp-customize-widgets.php

    r56559 r56687  
    13111311            unset( $registered_widget['callback'] ); // May not be JSON-serializeable.
    13121312        }
    1313 
    1314         ?>
    1315         <script type="text/javascript">
    1316             var _wpWidgetCustomizerPreviewSettings = <?php echo wp_json_encode( $settings ); ?>;
    1317         </script>
    1318         <?php
     1313        wp_print_inline_script_tag(
     1314            sprintf( 'var _wpWidgetCustomizerPreviewSettings = %s;', wp_json_encode( $settings ) )
     1315        );
    13191316    }
    13201317
  • trunk/src/wp-includes/class-wp-scripts.php

    r56273 r56687  
    124124
    125125    /**
    126      * Holds a string which contains the type attribute for script tag.
    127      *
    128      * If the active theme does not declare HTML5 support for 'script',
    129      * then it initializes as `type='text/javascript'`.
    130      *
    131      * @since 5.3.0
    132      * @var string
    133      */
    134     private $type_attr = '';
    135 
    136     /**
    137126     * Holds a mapping of dependents (as handles) for a given script handle.
    138127     * Used to optimize recursive dependency tree checks.
     
    168157     */
    169158    public function init() {
    170         if (
    171             function_exists( 'is_admin' ) && ! is_admin()
    172         &&
    173             function_exists( 'current_theme_supports' ) && ! current_theme_supports( 'html5', 'script' )
    174         ) {
    175             $this->type_attr = " type='text/javascript'";
    176         }
    177 
    178159        /**
    179160         * Fires when the WP_Scripts instance is initialized.
     
    246227        }
    247228
    248         printf( "<script%s id='%s-js-extra'>\n", $this->type_attr, esc_attr( $handle ) );
    249 
    250         // CDATA is not needed for HTML 5.
    251         if ( $this->type_attr ) {
    252             echo "/* <![CDATA[ */\n";
    253         }
    254 
    255         echo "$output\n";
    256 
    257         if ( $this->type_attr ) {
    258             echo "/* ]]> */\n";
    259         }
    260 
    261         echo "</script>\n";
     229        wp_print_inline_script_tag( $output, array( 'id' => "{$handle}-js-extra" ) );
    262230
    263231        return true;
     
    336304        $translations = $this->print_translations( $handle, false );
    337305        if ( $translations ) {
    338             $translations = sprintf( "<script%s id='%s-js-translations'>\n%s\n</script>\n", $this->type_attr, esc_attr( $handle ), $translations );
     306            $translations = wp_get_inline_script_tag( $translations, array( 'id' => "{$handle}-js-translations" ) );
    339307        }
    340308
     
    404372
    405373        /** This filter is documented in wp-includes/class-wp-scripts.php */
    406         $src = esc_url( apply_filters( 'script_loader_src', $src, $handle ) );
     374        $src = esc_url_raw( apply_filters( 'script_loader_src', $src, $handle ) );
    407375
    408376        if ( ! $src ) {
     
    410378        }
    411379
     380        $attr = array(
     381            'src' => $src,
     382            'id'  => "{$handle}-js",
     383        );
     384        if ( $strategy ) {
     385            $attr[ $strategy ] = true;
     386        }
     387        if ( $intended_strategy ) {
     388            $attr['data-wp-strategy'] = $intended_strategy;
     389        }
    412390        $tag  = $translations . $cond_before . $before_script;
    413         $tag .= sprintf(
    414             "<script%s src='%s' id='%s-js'%s%s></script>\n",
    415             $this->type_attr,
    416             $src, // Value is escaped above.
    417             esc_attr( $handle ),
    418             $strategy ? " {$strategy}" : '',
    419             $intended_strategy ? " data-wp-strategy='{$intended_strategy}'" : ''
    420         );
     391        $tag .= wp_get_script_tag( $attr );
    421392        $tag .= $after_script . $cond_after;
    422393
     
    721692
    722693        if ( $display ) {
    723             printf( "<script%s id='%s-js-translations'>\n%s\n</script>\n", $this->type_attr, esc_attr( $handle ), $output );
     694            wp_print_inline_script_tag( $output, array( 'id' => "{$handle}-js-translations" ) );
    724695        }
    725696
  • trunk/src/wp-includes/comment-template.php

    r56635 r56687  
    13671367    if ( current_user_can( 'unfiltered_html' ) ) {
    13681368        wp_nonce_field( 'unfiltered-html-comment_' . $post_id, '_wp_unfiltered_html_comment_disabled', false );
    1369         echo "<script>(function(){if(window===window.parent){document.getElementById('_wp_unfiltered_html_comment_disabled').name='_wp_unfiltered_html_comment';}})();</script>\n";
     1369        wp_print_inline_script_tag( "(function(){if(window===window.parent){document.getElementById('_wp_unfiltered_html_comment_disabled').name='_wp_unfiltered_html_comment';}})();" );
    13701370    }
    13711371}
  • trunk/src/wp-includes/customize/class-wp-customize-selective-refresh.php

    r55161 r56687  
    194194
    195195        // Export data to JS.
    196         printf( '<script>var _customizePartialRefreshExports = %s;</script>', wp_json_encode( $exports ) );
     196        wp_print_inline_script_tag( sprintf( 'var _customizePartialRefreshExports = %s;', wp_json_encode( $exports ) ) );
    197197    }
    198198
  • trunk/src/wp-includes/functions.php

    r56662 r56687  
    76567656    $name = 'wp-preview-' . (int) $post->ID;
    76577657
     7658    ob_start();
    76587659    ?>
    76597660    <script>
     
    76717672    </script>
    76727673    <?php
     7674    wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
    76737675}
    76747676
  • trunk/src/wp-includes/script-loader.php

    r56646 r56687  
    27882788function wp_get_script_tag( $attributes ) {
    27892789    if ( ! isset( $attributes['type'] ) && ! is_admin() && ! current_theme_supports( 'html5', 'script' ) ) {
    2790         $attributes['type'] = 'text/javascript';
     2790        // Keep the type attribute as the first for legacy reasons (it has always been this way in core).
     2791        $attributes = array_merge(
     2792            array( 'type' => 'text/javascript' ),
     2793            $attributes
     2794        );
    27912795    }
    27922796    /**
     
    28312835 */
    28322836function wp_get_inline_script_tag( $javascript, $attributes = array() ) {
    2833     if ( ! isset( $attributes['type'] ) && ! is_admin() && ! current_theme_supports( 'html5', 'script' ) ) {
    2834         $attributes['type'] = 'text/javascript';
    2835     }
     2837    $is_html5 = current_theme_supports( 'html5', 'script' ) || is_admin();
     2838    if ( ! isset( $attributes['type'] ) && ! $is_html5 ) {
     2839        // Keep the type attribute as the first for legacy reasons (it has always been this way in core).
     2840        $attributes = array_merge(
     2841            array( 'type' => 'text/javascript' ),
     2842            $attributes
     2843        );
     2844    }
     2845
     2846    // Ensure markup is XHTML compatible if not HTML5.
     2847    if ( ! $is_html5 ) {
     2848        $javascript = str_replace( ']]>', ']]]]><![CDATA[>', $javascript ); // Escape any existing CDATA section.
     2849        $javascript = sprintf( "/* <![CDATA[ */\n%s\n/* ]]> */", $javascript );
     2850    }
     2851
     2852    $javascript = "\n" . trim( $javascript, "\n\r " ) . "\n";
     2853
    28362854    /**
    28372855     * Filters attributes to be added to a script tag.
     
    28452863     */
    28462864    $attributes = apply_filters( 'wp_inline_script_attributes', $attributes, $javascript );
    2847 
    2848     $javascript = "\n" . trim( $javascript, "\n\r " ) . "\n";
    28492865
    28502866    return sprintf( "<script%s>%s</script>\n", wp_sanitize_script_attributes( $attributes ), $javascript );
  • trunk/src/wp-includes/theme-templates.php

    r56682 r56687  
    161161
    162162    /**
    163      * Print the skip-link script.
     163     * Enqueue the skip-link script.
    164164     */
     165    ob_start();
    165166    ?>
    166167    <script>
     
    205206    </script>
    206207    <?php
     208    $skip_link_script = str_replace( array( '<script>', '</script>' ), '', ob_get_clean() );
     209    $script_handle    = 'wp-block-template-skip-link';
     210    wp_register_script( $script_handle, false );
     211    wp_add_inline_script( $script_handle, $skip_link_script );
     212    wp_enqueue_script( $script_handle );
    207213}
    208214
  • trunk/src/wp-includes/theme.php

    r56686 r56687  
    37843784    $home_origin  = parse_url( home_url() );
    37853785    $cross_domain = ( strtolower( $admin_origin['host'] ) !== strtolower( $home_origin['host'] ) );
    3786     $type_attr    = current_theme_supports( 'html5', 'script' ) ? '' : ' type="text/javascript"';
     3786    ob_start();
    37873787    ?>
    3788     <script<?php echo $type_attr; ?>>
     3788    <script>
    37893789        (function() {
    37903790            var request, b = document.body, c = 'className', cs = 'customize-support', rcs = new RegExp('(^|\\s+)(no-)?'+cs+'(\\s+|$)');
     
    38023802    </script>
    38033803    <?php
     3804    wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
    38043805}
    38053806
  • trunk/src/wp-includes/widgets/class-wp-widget-archives.php

    r54062 r56687  
    101101                    break;
    102102            }
    103 
    104             $type_attr = current_theme_supports( 'html5', 'script' ) ? '' : ' type="text/javascript"';
    105103            ?>
    106104
     
    110108        </select>
    111109
    112 <script<?php echo $type_attr; ?>>
    113 /* <![CDATA[ */
     110            <?php ob_start(); ?>
     111<script>
    114112(function() {
    115113    var dropdown = document.getElementById( "<?php echo esc_js( $dropdown_id ); ?>" );
     
    121119    dropdown.onchange = onSelectChange;
    122120})();
    123 /* ]]> */
    124121</script>
    125122            <?php
     123            wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
    126124        } else {
    127125            $format = current_theme_supports( 'html5', 'navigation-widgets' ) ? 'html5' : 'xhtml';
  • trunk/src/wp-includes/widgets/class-wp-widget-categories.php

    r56547 r56687  
    9393            echo '</form>';
    9494
    95             $type_attr = current_theme_supports( 'html5', 'script' ) ? '' : ' type="text/javascript"';
     95            ob_start();
    9696            ?>
    9797
    98 <script<?php echo $type_attr; ?>>
    99 /* <![CDATA[ */
     98<script>
    10099(function() {
    101100    var dropdown = document.getElementById( "<?php echo esc_js( $dropdown_id ); ?>" );
     
    107106    dropdown.onchange = onCatChange;
    108107})();
    109 /* ]]> */
    110108</script>
    111109
    112110            <?php
     111            wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
    113112        } else {
    114113            $format = current_theme_supports( 'html5', 'navigation-widgets' ) ? 'html5' : 'xhtml';
  • trunk/src/wp-login.php

    r56654 r56687  
    102102     */
    103103    if ( 'loggedout' === $wp_error->get_error_code() ) {
     104        ob_start();
    104105        ?>
    105106        <script>if("sessionStorage" in window){try{for(var key in sessionStorage){if(key.indexOf("wp-autosave-")!=-1){sessionStorage.removeItem(key)}}}catch(e){}};</script>
    106107        <?php
     108        wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
    107109    }
    108110
     
    194196    </head>
    195197    <body class="login no-js <?php echo esc_attr( implode( ' ', $classes ) ); ?>">
    196     <script type="text/javascript">
    197         document.body.className = document.body.className.replace('no-js','js');
    198     </script>
     198    <?php
     199    wp_print_inline_script_tag( "document.body.className = document.body.className.replace('no-js','js');" );
     200    ?>
     201
    199202    <?php
    200203    /**
     
    415418
    416419    if ( ! empty( $input_id ) ) {
     420        ob_start();
    417421        ?>
    418         <script type="text/javascript">
     422        <script>
    419423        try{document.getElementById('<?php echo $input_id; ?>').focus();}catch(e){}
    420424        if(typeof wpOnload==='function')wpOnload();
    421425        </script>
    422426        <?php
     427        wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
    423428    }
    424429
     
    442447 */
    443448function wp_shake_js() {
    444     ?>
    445     <script type="text/javascript">
    446     document.querySelector('form').classList.add('shake');
    447     </script>
    448     <?php
     449    wp_print_inline_script_tag( "document.querySelector('form').classList.add('shake');" );
    449450}
    450451
     
    13581359
    13591360                if ( $customize_login ) {
     1361                    ob_start();
    13601362                    ?>
    1361                     <script type="text/javascript">setTimeout( function(){ new wp.customize.Messenger({ url: '<?php echo wp_customize_url(); ?>', channel: 'login' }).send('login') }, 1000 );</script>
     1363                    <script>setTimeout( function(){ new wp.customize.Messenger({ url: '<?php echo wp_customize_url(); ?>', channel: 'login' }).send('login') }, 1000 );</script>
    13621364                    <?php
     1365                    wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
    13631366                }
    13641367
     
    16061609        $login_script .= "if ( typeof wpOnload === 'function' ) { wpOnload() }";
    16071610
    1608         ?>
    1609         <script type="text/javascript">
    1610             <?php echo $login_script; ?>
    1611         </script>
    1612         <?php
     1611        wp_print_inline_script_tag( $login_script );
    16131612
    16141613        if ( $interim_login ) {
     1614            ob_start();
    16151615            ?>
    1616             <script type="text/javascript">
     1616            <script>
    16171617            ( function() {
    16181618                try {
     
    16281628            </script>
    16291629            <?php
     1630            wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
    16301631        }
    16311632
  • trunk/tests/phpunit/tests/customize/manager.php

    r56547 r56687  
    31373137        $manager->remove_frameless_preview_messenger_channel();
    31383138        $output = ob_get_clean();
    3139         $this->assertStringContainsString( '<script>', $output );
     3139        $this->assertStringContainsString( '<script', $output );
    31403140    }
    31413141
  • trunk/tests/phpunit/tests/dependencies/scripts.php

    r56559 r56687  
    4343        $this->wp_scripts_print_translations_output  = <<<JS
    4444<script type='text/javascript' id='__HANDLE__-js-translations'>
     45/* <![CDATA[ */
    4546( function( domain, translations ) {
    4647    var localeData = translations.locale_data[ domain ] || translations.locale_data.messages;
     
    4849    wp.i18n.setLocaleData( localeData, domain );
    4950} )( "__DOMAIN__", __JSON_TRANSLATIONS__ );
     51/* ]]> */
    5052</script>
    5153JS;
     
    7880        $expected .= "<script type='text/javascript' src='http://example.com' id='empty-deps-null-version-js'></script>\n";
    7981
    80         $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     82        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    8183
    8284        // No scripts left to print.
     
    119121        $expected  = "<script type='text/javascript' src='http://example.org/ms-isa-1.js' id='ms-isa-1-js' data-wp-strategy='{$strategy}'></script>\n";
    120122        $expected .= wp_get_inline_script_tag(
    121             "console.log(\"after one\");\n",
     123            'console.log("after one");',
    122124            array(
    123125                'id' => 'ms-isa-1-js-after',
    124126            )
    125127        );
    126         $this->assertSame( $expected, $output, 'Inline scripts in the "after" position, that are attached to a deferred main script, are failing to print/execute.' );
     128        $this->assertEqualMarkup( $expected, $output, 'Inline scripts in the "after" position, that are attached to a deferred main script, are failing to print/execute.' );
    127129    }
    128130
     
    147149        $expected  = "<script type='text/javascript' src='http://example.org/ms-insa-3.js' id='ms-insa-3-js'></script>\n";
    148150        $expected .= wp_get_inline_script_tag(
    149             "console.log(\"after one\");\n",
     151            'console.log("after one");',
    150152            array(
    151153                'id' => 'ms-insa-3-js-after',
     
    153155        );
    154156
    155         $this->assertSame( $expected, $output, 'Inline scripts in the "after" position, that are attached to a blocking main script, are failing to print/execute.' );
     157        $this->assertEqualMarkup( $expected, $output, 'Inline scripts in the "after" position, that are attached to a blocking main script, are failing to print/execute.' );
    156158    }
    157159
     
    181183
    182184        $expected  = wp_get_inline_script_tag(
    183             "console.log(\"before first\");\n",
     185            'console.log("before first");',
    184186            array(
    185187                'id' => 'ds-i1-1-js-before',
     
    190192        $expected .= "<script type='text/javascript' src='http://example.org/ds-i1-3.js' id='ds-i1-3-js' $strategy data-wp-strategy='{$strategy}'></script>\n";
    191193        $expected .= wp_get_inline_script_tag(
    192             "console.log(\"before last\");\n",
     194            'console.log("before last");',
    193195            array(
    194196                'id'   => 'ms-i1-1-js-before',
     
    198200        $expected .= "<script type='text/javascript' src='http://example.org/ms-i1-1.js' id='ms-i1-1-js' {$strategy} data-wp-strategy='{$strategy}'></script>\n";
    199201
    200         $this->assertSame( $expected, $output, 'Inline scripts in the "before" position, that are attached to a deferred main script, are failing to print/execute.' );
     202        $this->assertEqualMarkup( $expected, $output, 'Inline scripts in the "before" position, that are attached to a deferred main script, are failing to print/execute.' );
    201203    }
    202204
     
    216218        $output   = get_echo( 'wp_print_scripts' );
    217219        $expected = "<script type='text/javascript' src='/main-script-a1.js' id='main-script-a1-js' async data-wp-strategy='async'></script>\n";
    218         $this->assertSame( $expected, $output, 'Scripts enqueued with an async loading strategy are failing to have the async attribute applied to the script handle when being printed.' );
     220        $this->assertEqualMarkup( $expected, $output, 'Scripts enqueued with an async loading strategy are failing to have the async attribute applied to the script handle when being printed.' );
    219221    }
    220222
     
    236238        wp_enqueue_script( 'dependency-script-a2', '/dependency-script-a2.js', array(), null );
    237239        wp_enqueue_script( 'main-script-a2', '/main-script-a2.js', array( 'dependency-script-a2' ), null, compact( 'strategy' ) );
    238         $output   = get_echo( 'wp_print_scripts' );
    239         $expected = "<script type='text/javascript' src='/main-script-a2.js' id='main-script-a2-js' {$strategy} data-wp-strategy='{$strategy}'></script>";
    240         $this->assertStringContainsString( $expected, $output, 'Dependents of a blocking dependency are free to have any strategy.' );
     240        $output    = get_echo( 'wp_print_scripts' );
     241        $expected  = "<script id='dependency-script-a2-js' src='/dependency-script-a2.js'></script>\n";
     242        $expected .= "<script type='text/javascript' src='/main-script-a2.js' id='main-script-a2-js' {$strategy} data-wp-strategy='{$strategy}'></script>";
     243        $this->assertEqualMarkup( $expected, $output, 'Dependents of a blocking dependency are free to have any strategy.' );
    241244    }
    242245
     
    258261        wp_enqueue_script( 'dependent-script-a3', '/dependent-script-a3.js', array( 'main-script-a3' ), null );
    259262        $output   = get_echo( 'wp_print_scripts' );
    260         $expected = "<script type='text/javascript' src='/main-script-a3.js' id='main-script-a3-js' data-wp-strategy='{$strategy}'></script>";
     263        $expected = str_replace( "'", '"', "<script type='text/javascript' src='/main-script-a3.js' id='main-script-a3-js' data-wp-strategy='{$strategy}'></script>" );
    261264        $this->assertStringContainsString( $expected, $output, 'Blocking dependents must force delayed dependencies to become blocking.' );
    262265    }
     
    276279     */
    277280    public function test_delayed_dependent_with_blocking_dependency_not_enqueued( $strategy ) {
     281        $this->add_html5_script_theme_support();
    278282        wp_enqueue_script( 'main-script-a4', '/main-script-a4.js', array(), null, compact( 'strategy' ) );
    279283        // This dependent is registered but not enqueued, so it should not factor into the eligible loading strategy.
    280284        wp_register_script( 'dependent-script-a4', '/dependent-script-a4.js', array( 'main-script-a4' ), null );
    281285        $output   = get_echo( 'wp_print_scripts' );
    282         $expected = "<script type='text/javascript' src='/main-script-a4.js' id='main-script-a4-js' {$strategy} data-wp-strategy='{$strategy}'></script>";
     286        $expected = str_replace( "'", '"', "<script src='/main-script-a4.js' id='main-script-a4-js' {$strategy} data-wp-strategy='{$strategy}'></script>" );
    283287        $this->assertStringContainsString( $expected, $output, 'Only enqueued dependents should affect the eligible strategy.' );
    284288    }
     
    965969     */
    966970    public function test_loading_strategy_with_defer_having_no_dependents_nor_dependencies() {
     971        $this->add_html5_script_theme_support();
    967972        wp_enqueue_script( 'main-script-d1', 'http://example.com/main-script-d1.js', array(), null, array( 'strategy' => 'defer' ) );
    968973        $output   = get_echo( 'wp_print_scripts' );
    969         $expected = "<script type='text/javascript' src='http://example.com/main-script-d1.js' id='main-script-d1-js' defer data-wp-strategy='defer'></script>\n";
     974        $expected = str_replace( "'", '"', "<script src='http://example.com/main-script-d1.js' id='main-script-d1-js' defer data-wp-strategy='defer'></script>\n" );
    970975        $this->assertStringContainsString( $expected, $output, 'Expected defer, as there is no dependent or dependency' );
    971976    }
     
    981986     */
    982987    public function test_loading_strategy_with_defer_dependent_and_varied_dependencies() {
     988        $this->add_html5_script_theme_support();
    983989        wp_enqueue_script( 'dependency-script-d2-1', 'http://example.com/dependency-script-d2-1.js', array(), null, array( 'strategy' => 'defer' ) );
    984990        wp_enqueue_script( 'dependency-script-d2-2', 'http://example.com/dependency-script-d2-2.js', array(), null );
     
    986992        wp_enqueue_script( 'main-script-d2', 'http://example.com/main-script-d2.js', array( 'dependency-script-d2-1', 'dependency-script-d2-3' ), null, array( 'strategy' => 'defer' ) );
    987993        $output   = get_echo( 'wp_print_scripts' );
    988         $expected = "<script type='text/javascript' src='http://example.com/main-script-d2.js' id='main-script-d2-js' defer data-wp-strategy='defer'></script>\n";
     994        $expected = '<script src="http://example.com/main-script-d2.js" id="main-script-d2-js" defer data-wp-strategy="defer"></script>';
    989995        $this->assertStringContainsString( $expected, $output, 'Expected defer, as all dependencies are either deferred or blocking' );
    990996    }
     
    10001006     */
    10011007    public function test_loading_strategy_with_all_defer_dependencies() {
     1008        $this->add_html5_script_theme_support();
    10021009        wp_enqueue_script( 'main-script-d3', 'http://example.com/main-script-d3.js', array(), null, array( 'strategy' => 'defer' ) );
    10031010        wp_enqueue_script( 'dependent-script-d3-1', 'http://example.com/dependent-script-d3-1.js', array( 'main-script-d3' ), null, array( 'strategy' => 'defer' ) );
     
    10051012        wp_enqueue_script( 'dependent-script-d3-3', 'http://example.com/dependent-script-d3-3.js', array( 'dependent-script-d3-2' ), null, array( 'strategy' => 'defer' ) );
    10061013        $output   = get_echo( 'wp_print_scripts' );
    1007         $expected = "<script type='text/javascript' src='http://example.com/main-script-d3.js' id='main-script-d3-js' defer data-wp-strategy='defer'></script>\n";
     1014        $expected = '<script src="http://example.com/main-script-d3.js" id="main-script-d3-js" defer data-wp-strategy="defer"></script>';
    10081015        $this->assertStringContainsString( $expected, $output, 'Expected defer, as all dependents have defer loading strategy' );
    10091016    }
     
    10301037        $expected .= "<script type='text/javascript' src='/dependent-script-d4-3.js' id='dependent-script-d4-3-js' defer data-wp-strategy='defer'></script>\n";
    10311038
    1032         $this->assertSame( $expected, $output, 'Scripts registered as defer but that have dependents that are async are expected to have said dependents deferred.' );
     1039        $this->assertEqualMarkup( $expected, $output, 'Scripts registered as defer but that have dependents that are async are expected to have said dependents deferred.' );
    10331040    }
    10341041
     
    10501057        wp_enqueue_script( 'dependent-script-d4-3', '/dependent-script-d4-3.js', array( 'dependent-script-d4-2' ), null, array( 'strategy' => 'defer' ) );
    10511058        $output   = get_echo( 'wp_print_scripts' );
    1052         $expected = "<script type='text/javascript' src='/main-script-d4.js' id='main-script-d4-js' data-wp-strategy='defer'></script>\n";
     1059        $expected = str_replace( "'", '"', "<script type='text/javascript' src='/main-script-d4.js' id='main-script-d4-js' data-wp-strategy='defer'></script>\n" );
    10531060        $this->assertStringContainsString( $expected, $output, 'Scripts registered as defer but that have all dependents with no strategy, should become blocking (no strategy).' );
    10541061    }
     
    10681075        $output   = get_echo( 'wp_print_scripts' );
    10691076        $expected = "<script type='text/javascript' src='/main-script-b1.js' id='main-script-b1-js'></script>\n";
     1077        $expected = str_replace( "'", '"', $expected );
    10701078        $this->assertSame( $expected, $output, 'Scripts registered with a "blocking" strategy, and who have no dependencies, should have no loading strategy attributes printed.' );
    10711079
     
    10741082        $output   = get_echo( 'wp_print_scripts' );
    10751083        $expected = "<script type='text/javascript' src='/main-script-b2.js' id='main-script-b2-js'></script>\n";
     1084        $expected = str_replace( "'", '"', $expected );
    10761085        $this->assertSame( $expected, $output, 'Scripts registered with no strategy assigned, and who have no dependencies, should have no loading strategy attributes printed.' );
    10771086    }
     
    11001109        $expected_header .= "<script type='text/javascript' src='/enqueue-header-new.js' id='enqueue-header-new-js'></script>\n";
    11011110
    1102         $this->assertSame( $expected_header, $actual_header, 'Scripts registered/enqueued using the older $in_footer parameter or the newer $args parameter should have the same outcome.' );
     1111        $this->assertEqualMarkup( $expected_header, $actual_header, 'Scripts registered/enqueued using the older $in_footer parameter or the newer $args parameter should have the same outcome.' );
    11031112        $this->assertEmpty( $actual_footer, 'Expected footer to be empty since all scripts were for head.' );
    11041113    }
     
    11281137
    11291138        $this->assertEmpty( $actual_header, 'Expected header to be empty since all scripts targeted footer.' );
    1130         $this->assertSame( $expected_footer, $actual_footer, 'Scripts registered/enqueued using the older $in_footer parameter or the newer $args parameter should have the same outcome.' );
     1139        $this->assertEqualMarkup( $expected_footer, $actual_footer, 'Scripts registered/enqueued using the older $in_footer parameter or the newer $args parameter should have the same outcome.' );
    11311140    }
    11321141
     
    12471256        wp_enqueue_script( 'invalid-strategy' );
    12481257
    1249         $this->assertSame(
     1258        $this->assertEqualMarkup(
    12501259            "<script type='text/javascript' src='/defaults.js' id='invalid-strategy-js'></script>\n",
    12511260            get_echo( 'wp_print_scripts' )
     
    12721281        wp_enqueue_script( 'invalid-strategy' );
    12731282
    1274         $this->assertSame(
     1283        $this->assertEqualMarkup(
    12751284            "<script type='text/javascript' src='/defaults.js' id='invalid-strategy-js'></script>\n",
    12761285            get_echo( 'wp_print_scripts' )
     
    12931302        wp_enqueue_script( 'invalid-strategy', '/defaults.js', array(), null, array( 'strategy' => 'random-strategy' ) );
    12941303
    1295         $this->assertSame(
     1304        $this->assertEqualMarkup(
    12961305            "<script type='text/javascript' src='/defaults.js' id='invalid-strategy-js'></script>\n",
    12971306            get_echo( 'wp_print_scripts' )
     
    13311340        $expected .= "<script type='text/javascript' src='/main-script.js' id='main-defer-script-js' defer data-wp-strategy='defer'></script>\n";
    13321341
    1333         $this->assertSame( $expected, $print_scripts, 'Scripts are being incorrectly concatenated when a main script is registered with a "defer" loading strategy. Deferred scripts should not be part of the script concat loading query.' );
     1342        $this->assertEqualMarkup( $expected, $print_scripts, 'Scripts are being incorrectly concatenated when a main script is registered with a "defer" loading strategy. Deferred scripts should not be part of the script concat loading query.' );
    13341343    }
    13351344
     
    13661375        $expected .= "<script type='text/javascript' src='/main-script.js' id='main-async-script-1-js' async data-wp-strategy='async'></script>\n";
    13671376
    1368         $this->assertSame( $expected, $print_scripts, 'Scripts are being incorrectly concatenated when a main script is registered with an "async" loading strategy. Async scripts should not be part of the script concat loading query.' );
     1377        $this->assertEqualMarkup( $expected, $print_scripts, 'Scripts are being incorrectly concatenated when a main script is registered with an "async" loading strategy. Async scripts should not be part of the script concat loading query.' );
    13691378    }
    13701379
     
    14051414        $expected .= "<script type='text/javascript' src='/main-script.js' id='deferred-script-2-js' defer data-wp-strategy='defer'></script>\n";
    14061415
    1407         $this->assertSame( $expected, $print_scripts, 'Scripts are being incorrectly concatenated when a main script is registered as deferred after other blocking scripts are registered. Deferred scripts should not be part of the script concat loader query string. ' );
     1416        $this->assertEqualMarkup( $expected, $print_scripts, 'Scripts are being incorrectly concatenated when a main script is registered as deferred after other blocking scripts are registered. Deferred scripts should not be part of the script concat loader query string. ' );
    14081417    }
    14091418
     
    14131422    public function test_wp_enqueue_script_with_html5_support_does_not_contain_type_attribute() {
    14141423        global $wp_version;
    1415         add_theme_support( 'html5', array( 'script' ) );
    14161424
    14171425        $GLOBALS['wp_scripts']                  = new WP_Scripts();
     
    14221430        $expected = "<script src='http://example.com?ver={$wp_version}' id='empty-deps-no-version-js'></script>\n";
    14231431
    1424         $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     1432        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    14251433    }
    14261434
     
    14611469
    14621470        // Go!
    1463         $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     1471        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    14641472
    14651473        // No scripts left to print.
     
    15041512
    15051513        // Go!
    1506         $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     1514        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    15071515
    15081516        // No scripts left to print.
     
    15221530
    15231531        // Go!
    1524         $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     1532        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    15251533
    15261534        // No scripts left to print.
     
    15401548        $expected  = "<!--[if lt IE 9]>\n<script type='text/javascript' id='test-conditional-with-data-js-extra'>\n/* <![CDATA[ */\ntesting\n/* ]]> */\n</script>\n<![endif]-->\n";
    15411549        $expected .= "<!--[if lt IE 9]>\n<script type='text/javascript' src='http://example.com' id='test-conditional-with-data-js'></script>\n<![endif]-->\n";
     1550        $expected  = str_replace( "'", '"', $expected );
    15421551
    15431552        // Go!
    1544         $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     1553        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    15451554
    15461555        // No scripts left to print.
     
    15601569
    15611570        // Go!
    1562         $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     1571        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    15631572
    15641573        // No scripts left to print.
    1565         $this->assertSame( '', get_echo( 'wp_print_scripts' ) );
     1574        $this->assertEqualMarkup( '', get_echo( 'wp_print_scripts' ) );
    15661575    }
    15671576
     
    15891598        wp_enqueue_script( 'handle-three' );
    15901599
    1591         $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     1600        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    15921601    }
    15931602
     
    16771686        $expected_footer  = "<script type='text/javascript' src='/parent.js' id='parent-js'></script>\n";
    16781687
    1679         $this->assertSame( $expected_header, $header, 'Expected same header markup.' );
    1680         $this->assertSame( $expected_footer, $footer, 'Expected same footer markup.' );
     1688        $this->assertEqualMarkup( $expected_header, $header, 'Expected same header markup.' );
     1689        $this->assertEqualMarkup( $expected_footer, $footer, 'Expected same footer markup.' );
    16811690    }
    16821691
     
    16981707        $expected_footer .= "<script type='text/javascript' src='/parent.js' id='parent-js'></script>\n";
    16991708
    1700         $this->assertSame( $expected_header, $header, 'Expected same header markup.' );
    1701         $this->assertSame( $expected_footer, $footer, 'Expected same footer markup.' );
     1709        $this->assertEqualMarkup( $expected_header, $header, 'Expected same header markup.' );
     1710        $this->assertEqualMarkup( $expected_footer, $footer, 'Expected same footer markup.' );
    17021711    }
    17031712
     
    17291738        $expected_footer .= "<script type='text/javascript' src='/parent-footer.js' id='parent-footer-js'></script>\n";
    17301739
    1731         $this->assertSame( $expected_header, $header, 'Expected same header markup.' );
    1732         $this->assertSame( $expected_footer, $footer, 'Expected same footer markup.' );
     1740        $this->assertEqualMarkup( $expected_header, $header, 'Expected same header markup.' );
     1741        $this->assertEqualMarkup( $expected_footer, $footer, 'Expected same footer markup.' );
    17331742    }
    17341743
     
    19531962        $expected_localized .= "<script type='text/javascript' id='test-example-js-extra'>\n/* <![CDATA[ */\nvar testExample = {\"foo\":\"bar\"};\n/* ]]> */\n</script>\n";
    19541963        $expected_localized .= "<![endif]-->\n";
     1964        $expected_localized  = str_replace( "'", '"', $expected_localized );
    19551965
    19561966        $expected  = "<!--[if gte IE 9]>\n";
     
    19591969        $expected .= "<script type='text/javascript' id='test-example-js-after'>\nconsole.log(\"after\");\n</script>\n";
    19601970        $expected .= "<![endif]-->\n";
     1971        $expected  = str_replace( "'", '"', $expected );
    19611972
    19621973        wp_enqueue_script( 'test-example', 'example.com', array(), null );
     
    21252136        $print_scripts = $this->getActualOutput();
    21262137
    2127         $tail = substr( $print_scripts, strrpos( $print_scripts, "<script type='text/javascript' src='/customize-dependency.js' id='customize-dependency-js'>" ) );
     2138        $tail = substr( $print_scripts, strrpos( $print_scripts, '<script type="text/javascript" src="/customize-dependency.js" id="customize-dependency-js">' ) );
     2139
    21282140        $this->assertEqualMarkup( $expected_tail, $tail );
    21292141    }
     
    23052317        $expected .= "<script type='text/javascript' src='/wp-includes/js/script.js' id='test-example-js'></script>\n";
    23062318
    2307         $this->assertSameIgnoreEOL( $expected, get_echo( 'wp_print_scripts' ) );
     2319        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    23082320    }
    23092321
     
    23322344        $expected .= "<script type='text/javascript' src='/wp-content/plugins/my-plugin/js/script.js' id='plugin-example-js'></script>\n";
    23332345
    2334         $this->assertSameIgnoreEOL( $expected, get_echo( 'wp_print_scripts' ) );
     2346        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    23352347    }
    23362348
     
    23592371        $expected .= "<script type='text/javascript' src='/wp-content/themes/my-theme/js/script.js' id='theme-example-js'></script>\n";
    23602372
    2361         $this->assertSameIgnoreEOL( $expected, get_echo( 'wp_print_scripts' ) );
     2373        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    23622374    }
    23632375
     
    23862398        $expected .= "<script type='text/javascript' src='/wp-admin/js/script.js' id='script-handle-js'></script>\n";
    23872399
    2388         $this->assertSameIgnoreEOL( $expected, get_echo( 'wp_print_scripts' ) );
     2400        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    23892401    }
    23902402
     
    24162428        $expected .= "<script type='text/javascript' src='/wp-admin/js/script.js' id='test-example-js'></script>\n";
    24172429
    2418         $this->assertSameIgnoreEOL( $expected, get_echo( 'wp_print_scripts' ) );
     2430        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    24192431    }
    24202432
     
    24452457        $expected .= "<script type='text/javascript' src='/wp-includes/js/script.js' id='test-example-js'></script>\n";
    24462458
    2447         $this->assertSameIgnoreEOL( $expected, get_echo( 'wp_print_scripts' ) );
     2459        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    24482460    }
    24492461
     
    24752487        $expected .= "<script type='text/javascript' src='/wp-includes/js/script2.js' id='test-example-js'></script>\n";
    24762488
    2477         $this->assertSameIgnoreEOL( $expected, get_echo( 'wp_print_scripts' ) );
     2489        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    24782490    }
    24792491
     
    28642876        $expected .= "<script type='text/javascript' src='http://example.com' id='test-example-js'></script>\n";
    28652877
    2866         $this->assertSame( $expected, get_echo( 'wp_print_scripts' ) );
     2878        $this->assertEqualMarkup( $expected, get_echo( 'wp_print_scripts' ) );
    28672879    }
    28682880
     
    29292941        $expected .= "<script type='text/javascript' src='/default/common.js' id='common-js'></script>\n";
    29302942
    2931         $this->assertSame( $expected, $print_scripts );
     2943        $this->assertEqualMarkup( $expected, $print_scripts );
    29322944    }
    29332945
     
    29682980     *
    29692981     * @param string $markup Markup.
    2970      * @return DOMElement Body element wrapping supplied markup fragment.
     2982     * @return DOMDocument Document containing the normalized markup fragment.
    29712983     */
    29722984    protected function parse_markup_fragment( $markup ) {
     
    29862998        }
    29872999
    2988         return $body;
    2989     }
    2990 
    2991     /**
    2992      * Assert markup is equal.
     3000        return $dom;
     3001    }
     3002
     3003    /**
     3004     * Assert markup is equal after normalizing script tags.
    29933005     *
    29943006     * @param string $expected Expected markup.
     
    29973009     */
    29983010    protected function assertEqualMarkup( $expected, $actual, $message = '' ) {
     3011        $expected_dom = $this->parse_markup_fragment( $expected );
     3012        $actual_dom   = $this->parse_markup_fragment( $actual );
     3013        foreach ( array( $expected_dom, $actual_dom ) as $dom ) {
     3014            $xpath = new DOMXPath( $dom );
     3015            /** @var DOMElement $script */
     3016
     3017            // Normalize type attribute. When missing, it defaults to text/javascript.
     3018            foreach ( $xpath->query( '//script[ not( @type ) ]' ) as $script ) {
     3019                $script->setAttribute( 'type', 'text/javascript' );
     3020            }
     3021
     3022            // Normalize script contents to remove CDATA wrapper.
     3023            foreach ( $xpath->query( '//script[ contains( text(), "<![CDATA[" ) ]' ) as $script ) {
     3024                $script->textContent = str_replace(
     3025                    array(
     3026                        "/* <![CDATA[ */\n",
     3027                        "\n/* ]]> */",
     3028                    ),
     3029                    '',
     3030                    $script->textContent
     3031                );
     3032            }
     3033
     3034            // Normalize XHTML-compatible boolean attributes to HTML5 ones.
     3035            foreach ( array( 'async', 'defer' ) as $attribute ) {
     3036                foreach ( iterator_to_array( $xpath->query( "//script[ @{$attribute} = '{$attribute}' ]" ) ) as $script ) {
     3037                    $script->removeAttribute( $attribute );
     3038                    $script->setAttributeNode( $dom->createAttribute( $attribute ) );
     3039                }
     3040            }
     3041        }
     3042
    29993043        $this->assertEquals(
    3000             $this->parse_markup_fragment( $expected ),
    3001             $this->parse_markup_fragment( $actual ),
     3044            $expected_dom->getElementsByTagName( 'body' )->item( 0 ),
     3045            $actual_dom->getElementsByTagName( 'body' )->item( 0 ),
    30023046            $message
    30033047        );
    30043048    }
     3049
     3050    /**
     3051     * Adds html5 script theme support.
     3052     */
     3053    protected function add_html5_script_theme_support() {
     3054        add_theme_support( 'html5', array( 'script' ) );
     3055    }
    30053056}
  • trunk/tests/phpunit/tests/dependencies/wpInlineScriptTag.php

    r51657 r56687  
    120120        );
    121121    }
     122
     123    /**
     124     * Tests that CDATA wrapper duplication is handled.
     125     *
     126     * @ticket 58664
     127     */
     128    public function test_get_inline_script_tag_with_duplicated_cdata_wrappers() {
     129        remove_theme_support( 'html5' );
     130
     131        $this->assertSame(
     132            "<script type=\"text/javascript\">\n/* <![CDATA[ */\n/* <![CDATA[ */ console.log( 'Hello World!' ); /* ]]]]><![CDATA[> */\n/* ]]> */\n</script>\n",
     133            wp_get_inline_script_tag( "/* <![CDATA[ */ console.log( 'Hello World!' ); /* ]]> */" )
     134        );
     135    }
    122136}
Note: See TracChangeset for help on using the changeset viewer.