Make WordPress Core

Changeset 52132


Ignore:
Timestamp:
11/11/2021 02:47:10 AM (3 years ago)
Author:
westonruter
Message:

Embeds: Conditionally enqueue wp-embed only if needed and send ready message in case script loads after post embed windows.

  • Prevent loading wp-embed script unconditionally on every page in favor of conditionally enqueueing when a post embed is detected. The wp-embed script is also explicitly marked as being in the footer group. Sites which currently disable post embed scripts from being enqueued via remove_action( 'wp_head', 'wp_oembed_add_host_js' ) will continue to do so.
  • Send a ready message from the host page to each post embed window in case the iframe loads before the wp-embed script does. When the ready message is received by the post embed window, it sends the same height message as it sends when it loads.
  • Eliminate use of grunt-include to inject emoji script and the post embed script. Instead obtain the script contents via file_get_contents() (as is done elsewhere in core) and utilize wp_print_inline_script_tag()/wp_get_inline_script_tag() to construct out the script. This simplifies the logic and allows the running of src without SCRIPT_DEBUG enabled.
  • For the embed code that users are provided to copy for embedding outside of WP, add the secret on the blockquote and iframe. This ensures the blockquote will be hidden when the iframe loads. The embed code in question is accessed here via get_post_embed_html().

Props westonruter, swissspidy, pento, flixos90, ocean90.
Fixes #44632, #44306.

Location:
trunk
Files:
10 edited

Legend:

Unmodified
Added
Removed
  • trunk/Gruntfile.js

    r52042 r52132  
    10211021            }
    10221022        },
    1023         includes: {
    1024             emoji: {
    1025                 src: BUILD_DIR + 'wp-includes/formatting.php',
    1026                 dest: '.'
    1027             },
    1028             embed: {
    1029                 src: BUILD_DIR + 'wp-includes/embed.php',
    1030                 dest: '.'
    1031             }
    1032         },
    10331023        replace: {
    10341024            'emoji-regex': {
     
    15941584                'build:js',
    15951585                'build:css',
    1596                 'includes:emoji',
    1597                 'includes:embed',
    15981586                'replace:emoji-banner-text',
    15991587                'replace:source-maps',
  • trunk/package-lock.json

    r52103 r52132  
    1342313423            "dev": true
    1342413424        },
    13425         "grunt-includes": {
    13426             "version": "1.1.0",
    13427             "resolved": "https://registry.npmjs.org/grunt-includes/-/grunt-includes-1.1.0.tgz",
    13428             "integrity": "sha512-aZQfL+fiAonPI173QUjGyuCkaUTJci7+a5SkmSAbezUikwLban7Jp6W+vbA/Mnacmy+EPipnuK5kAF6O0SvrDw==",
    13429             "dev": true
    13430         },
    1343113425        "grunt-jsdoc": {
    1343213426            "version": "2.4.1",
  • trunk/package.json

    r52103 r52132  
    5050        "grunt-contrib-watch": "~1.1.0",
    5151        "grunt-file-append": "0.0.7",
    52         "grunt-includes": "~1.1.0",
    5352        "grunt-jsdoc": "2.4.1",
    5453        "grunt-jsvalidate": "~0.2.2",
  • trunk/src/js/_enqueues/lib/embed-template.js

    r49202 r52132  
    1717            secret: secret
    1818        }, '*' );
     19    }
     20
     21    /**
     22     * Send the height message to the parent window.
     23     */
     24    function sendHeightMessage() {
     25        sendEmbedMessage( 'height', Math.ceil( document.body.getBoundingClientRect().height ) );
    1926    }
    2027
     
    139146
    140147        // Send this document's height to the parent (embedding) site.
    141         sendEmbedMessage( 'height', Math.ceil( document.body.getBoundingClientRect().height ) );
     148        sendHeightMessage();
    142149
    143150        // Send the document's height again after the featured image has been loaded.
    144151        if ( featured_image ) {
    145             featured_image.addEventListener( 'load', function() {
    146                 sendEmbedMessage( 'height', Math.ceil( document.body.getBoundingClientRect().height ) );
    147             } );
     152            featured_image.addEventListener( 'load', sendHeightMessage );
    148153        }
    149154
     
    185190        clearTimeout( resizing );
    186191
    187         resizing = setTimeout( function () {
    188             sendEmbedMessage( 'height', Math.ceil( document.body.getBoundingClientRect().height ) );
    189         }, 100 );
     192        resizing = setTimeout( sendHeightMessage, 100 );
     193    }
     194
     195    /**
     196     * Message handler.
     197     *
     198     * @param {MessageEvent} event
     199     */
     200    function onMessage( event ) {
     201        var data = event.data;
     202
     203        if ( ! data ) {
     204            return;
     205        }
     206
     207        if ( event.source !== window.parent ) {
     208            return;
     209        }
     210
     211        if ( ! ( data.secret || data.message ) ) {
     212            return;
     213        }
     214
     215        if ( data.secret !== secret ) {
     216            return;
     217        }
     218
     219        if ( 'ready' === data.message ) {
     220            sendHeightMessage();
     221        }
    190222    }
    191223
     
    213245        window.addEventListener( 'load', onLoad, false );
    214246        window.addEventListener( 'resize', onResize, false );
     247        window.addEventListener( 'message', onMessage, false );
    215248    }
    216249})( window, document );
  • trunk/src/js/_enqueues/wp/embed.js

    r46586 r52132  
    2828    }
    2929
     30    /**
     31     * Receive embed message.
     32     *
     33     * @param {MessageEvent} e
     34     */
    3035    window.wp.receiveEmbedMessage = function( e ) {
    3136        var data = e.data;
     
    103108
    104109        for ( i = 0; i < iframes.length; i++ ) {
     110            /** @var {IframeElement} */
    105111            source = iframes[ i ];
    106112
    107             if ( ! source.getAttribute( 'data-secret' ) ) {
     113            secret = source.getAttribute( 'data-secret' );
     114            if ( ! secret ) {
    108115                /* Add secret to iframe */
    109116                secret = Math.random().toString( 36 ).substr( 2, 10 );
     
    118125                source.parentNode.replaceChild( iframeClone, source );
    119126            }
     127
     128            /*
     129             * Let post embed window know that the parent is ready for receiving the height message, in case the iframe
     130             * loaded before wp-embed.js was loaded. When the ready message is received by the post embed window, the
     131             * window will then (re-)send the height message right away.
     132             */
     133            source.contentWindow.postMessage( {
     134                message: 'ready',
     135                secret: secret
     136            }, '*' );
    120137        }
    121138    }
  • trunk/src/wp-includes/embed.php

    r51298 r52132  
    360360 */
    361361function wp_oembed_add_host_js() {
    362     wp_enqueue_script( 'wp-embed' );
     362    add_filter( 'embed_oembed_html', 'wp_maybe_enqueue_oembed_host_js' );
     363}
     364
     365/**
     366 * Enqueue the wp-embed script if the provided oEmbed HTML contains a post embed.
     367 *
     368 * In order to only enqueue the wp-embed script on pages that actually contain post embeds, this function checks if the
     369 * provided HTML contains post embed markup and if so enqueues the script so that it will get printed in the footer.
     370 *
     371 * @since 5.9.0
     372 *
     373 * @param string $html Embed markup.
     374 * @return string Embed markup (without modifications).
     375 */
     376function wp_maybe_enqueue_oembed_host_js( $html ) {
     377    if ( preg_match( '/<blockquote\s[^>]*wp-embedded-content/', $html ) ) {
     378        wp_enqueue_script( 'wp-embed' );
     379    }
     380    return $html;
    363381}
    364382
     
    451469    $embed_url = get_post_embed_url( $post );
    452470
    453     $output = '<blockquote class="wp-embedded-content"><a href="' . esc_url( get_permalink( $post ) ) . '">' . get_the_title( $post ) . "</a></blockquote>\n";
    454 
    455     $output .= "<script type='text/javascript'>\n";
    456     $output .= "<!--//--><![CDATA[//><!--\n";
    457     if ( SCRIPT_DEBUG ) {
    458         $output .= file_get_contents( ABSPATH . WPINC . '/js/wp-embed.js' );
    459     } else {
    460         /*
    461          * If you're looking at a src version of this file, you'll see an "include"
    462          * statement below. This is used by the `npm run build` process to directly
    463          * include a minified version of wp-embed.js, instead of using the
    464          * file_get_contents() method from above.
    465          *
    466          * If you're looking at a build version of this file, you'll see a string of
    467          * minified JavaScript. If you need to debug it, please turn on SCRIPT_DEBUG
    468          * and edit wp-embed.js directly.
    469          */
    470         $output .= <<<JS
    471         include "js/wp-embed.min.js"
    472 JS;
    473     }
    474     $output .= "\n//--><!]]>";
    475     $output .= "\n</script>";
     471    $secret     = wp_generate_password( 10, false );
     472    $embed_url .= "#?secret={$secret}";
     473
     474    $output = wp_get_inline_script_tag(
     475        file_get_contents( sprintf( ABSPATH . WPINC . '/js/wp-embed' . wp_scripts_get_suffix() . '.js' ) )
     476    );
    476477
    477478    $output .= sprintf(
    478         '<iframe sandbox="allow-scripts" security="restricted" src="%1$s" width="%2$d" height="%3$d" title="%4$s" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" class="wp-embedded-content"></iframe>',
     479        '<blockquote class="wp-embedded-content" data-secret="%1$s"><a href="%2$s">%3$s</a></blockquote>',
     480        esc_attr( $secret ),
     481        esc_url( get_permalink( $post ) ),
     482        get_the_title( $post )
     483    );
     484
     485    $output .= sprintf(
     486        '<iframe sandbox="allow-scripts" security="restricted" src="%1$s" width="%2$d" height="%3$d" title="%4$s" data-secret="%5$s" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" class="wp-embedded-content"></iframe>',
    479487        esc_url( $embed_url ),
    480488        absint( $width ),
     
    487495                get_bloginfo( 'name' )
    488496            )
    489         )
     497        ),
     498        esc_attr( $secret )
    490499    );
    491500
  • trunk/src/wp-includes/formatting.php

    r52035 r52132  
    57575757    );
    57585758
    5759     $version   = 'ver=' . get_bloginfo( 'version' );
    5760     $type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/javascript"';
     5759    $version = 'ver=' . get_bloginfo( 'version' );
    57615760
    57625761    if ( SCRIPT_DEBUG ) {
     
    57675766            'twemoji' => apply_filters( 'script_loader_src', includes_url( "js/twemoji.js?$version" ), 'twemoji' ),
    57685767        );
    5769 
    5770         ?>
    5771         <script<?php echo $type_attr; ?>>
    5772             window._wpemojiSettings = <?php echo wp_json_encode( $settings ); ?>;
    5773             <?php readfile( ABSPATH . WPINC . '/js/wp-emoji-loader.js' ); ?>
    5774         </script>
    5775         <?php
    57765768    } else {
    57775769        $settings['source'] = array(
     
    57795771            'concatemoji' => apply_filters( 'script_loader_src', includes_url( "js/wp-emoji-release.min.js?$version" ), 'concatemoji' ),
    57805772        );
    5781 
    5782         /*
    5783          * If you're looking at a src version of this file, you'll see an "include"
    5784          * statement below. This is used by the `npm run build` process to directly
    5785          * include a minified version of wp-emoji-loader.js, instead of using the
    5786          * readfile() method from above.
    5787          *
    5788          * If you're looking at a build version of this file, you'll see a string of
    5789          * minified JavaScript. If you need to debug it, please turn on SCRIPT_DEBUG
    5790          * and edit wp-emoji-loader.js directly.
    5791          */
    5792         ?>
    5793         <script<?php echo $type_attr; ?>>
    5794             window._wpemojiSettings = <?php echo wp_json_encode( $settings ); ?>;
    5795             include "js/wp-emoji-loader.min.js"
    5796         </script>
    5797         <?php
    5798     }
     5773    }
     5774
     5775    wp_print_inline_script_tag(
     5776        sprintf( 'window._wpemojiSettings = %s;', wp_json_encode( $settings ) ) .
     5777            file_get_contents( sprintf( ABSPATH . WPINC . '/js/wp-emoji-loader' . wp_scripts_get_suffix() . '.js' ) )
     5778    );
    57995779}
    58005780
  • trunk/src/wp-includes/script-loader.php

    r52127 r52132  
    12481248    );
    12491249
    1250     $scripts->add( 'wp-embed', "/wp-includes/js/wp-embed$suffix.js" );
     1250    $scripts->add( 'wp-embed', "/wp-includes/js/wp-embed$suffix.js", array(), false, 1 );
    12511251
    12521252    // To enqueue media-views or media-editor, call wp_enqueue_media().
  • trunk/tests/phpunit/tests/oembed/getResponseData.php

    r52010 r52132  
    1111        // `get_post_embed_html()` assumes `wp-includes/js/wp-embed.js` is present:
    1212        self::touch( ABSPATH . WPINC . '/js/wp-embed.js' );
     13    }
     14
     15    private function normalize_secret_attribute( $data ) {
     16        if ( is_array( $data ) ) {
     17            $html = $data['html'];
     18        } else {
     19            $html = $data;
     20        }
     21
     22        $html = preg_replace( '/secret=("?)\w+\1/', 'secret=__SECRET__', $html );
     23
     24        if ( is_array( $data ) ) {
     25            $data['html'] = $html;
     26        } else {
     27            $data = $html;
     28        }
     29
     30        return $data;
    1331    }
    1432
     
    3755                'width'         => 400,
    3856                'height'        => 225,
    39                 'html'          => get_post_embed_html( 400, 225, $post ),
     57                'html'          => $this->normalize_secret_attribute( get_post_embed_html( 400, 225, $post ) ),
    4058            ),
    41             $data
     59            $this->normalize_secret_attribute( $data )
    4260        );
    4361    }
     
    7391                'width'         => 400,
    7492                'height'        => 225,
    75                 'html'          => get_post_embed_html( 400, 225, $post ),
     93                'html'          => $this->normalize_secret_attribute( get_post_embed_html( 400, 225, $post ) ),
    7694            ),
    77             $data
     95            $this->normalize_secret_attribute( $data )
    7896        );
    7997    }
  • trunk/tests/phpunit/tests/oembed/template.php

    r52010 r52132  
    55 */
    66class Tests_Embed_Template extends WP_UnitTestCase {
     7
     8    public function set_up() {
     9        parent::set_up();
     10
     11        global $wp_scripts;
     12        $wp_scripts = null;
     13    }
     14
     15    public function tear_down() {
     16        parent::tear_down();
     17
     18        global $wp_scripts;
     19        $wp_scripts = null;
     20    }
     21
    722    public function test_oembed_output_post() {
    823        $user = self::factory()->user->create_and_get(
     
    282297        );
    283298
    284         $expected = '<iframe sandbox="allow-scripts" security="restricted" src="' . esc_url( get_post_embed_url( $post_id ) ) . '" width="200" height="200" title="' . $title . '" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" class="wp-embedded-content"></iframe>';
    285 
    286         $this->assertStringEndsWith( $expected, get_post_embed_html( 200, 200, $post_id ) );
    287     }
    288 
     299        $expected = '<iframe sandbox="allow-scripts" security="restricted" src="' . esc_url( get_post_embed_url( $post_id ) ) . '#?secret=__SECRET__" width="200" height="200" title="' . $title . '" data-secret=__SECRET__ frameborder="0" marginwidth="0" marginheight="0" scrolling="no" class="wp-embedded-content"></iframe>';
     300        $actual   = get_post_embed_html( 200, 200, $post_id );
     301        $actual   = preg_replace( '/secret=("?)\w+\1/', 'secret=__SECRET__', $actual );
     302
     303        $this->assertStringEndsWith( $expected, $actual );
     304    }
     305
     306    /** @covers ::wp_oembed_add_host_js() */
    289307    public function test_add_host_js() {
     308        remove_all_filters( 'embed_oembed_html' );
     309
    290310        wp_oembed_add_host_js();
    291311
    292         $this->assertTrue( wp_script_is( 'wp-embed' ) );
     312        $this->assertEquals( 10, has_filter( 'embed_oembed_html', 'wp_maybe_enqueue_oembed_host_js' ) );
     313    }
     314
     315    /** @covers ::wp_maybe_enqueue_oembed_host_js() */
     316    function test_wp_maybe_enqueue_oembed_host_js() {
     317        $scripts = wp_scripts();
     318
     319        $this->assertFalse( $scripts->query( 'wp-embed', 'enqueued' ) );
     320
     321        $post_embed     = '<blockquote class="wp-embedded-content" data-secret="S24AQCJW9i"><a href="https://make.wordpress.org/core/2016/03/11/embeds-changes-in-wordpress-4-5/">Embeds Changes in WordPress 4.5</a></blockquote><iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted" style="position: absolute; clip: rect(1px, 1px, 1px, 1px);" title="&#8220;Embeds Changes in WordPress 4.5&#8221; &#8212; Make WordPress Core" src="https://make.wordpress.org/core/2016/03/11/embeds-changes-in-wordpress-4-5/embed/#?secret=S24AQCJW9i" data-secret="S24AQCJW9i" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>';
     322        $non_post_embed = '<iframe title="Zoo Cares For 23 Tiny Pond Turtles" width="750" height="422" src="https://www.youtube.com/embed/6ZXHqUjL6f8?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>';
     323
     324        wp_maybe_enqueue_oembed_host_js( $non_post_embed );
     325        $this->assertFalse( $scripts->query( 'wp-embed', 'enqueued' ) );
     326
     327        wp_maybe_enqueue_oembed_host_js( $post_embed );
     328        $this->assertTrue( $scripts->query( 'wp-embed', 'enqueued' ) );
    293329    }
    294330
Note: See TracChangeset for help on using the changeset viewer.