diff --git a/includes/amp-helper-functions.php b/includes/amp-helper-functions.php index bae1d58b212..478e970c926 100644 --- a/includes/amp-helper-functions.php +++ b/includes/amp-helper-functions.php @@ -873,6 +873,32 @@ function amp_add_generator_metadata() { printf( '', esc_attr( $content ) ); } +/** + * Determine whether the use of Bento components is enabled. + * + * When Bento is enabled, newer experimental versions of AMP components are used which incorporate the next generation + * of the component framework. + * + * @since 2.2 + * @link https://blog.amp.dev/2021/01/28/bento/ + * + * @return bool Whether Bento components are enabled. + */ +function amp_is_bento_enabled() { + /** + * Filters whether the use of Bento components is enabled. + * + * When Bento is enabled, newer experimental versions of AMP components are used which incorporate the next generation + * of the component framework. + * + * @since 2.2 + * @link https://blog.amp.dev/2021/01/28/bento/ + * + * @param bool $enabled Enabled. + */ + return apply_filters( 'amp_bento_enabled', false ); +} + /** * Register default scripts for AMP components. * @@ -924,17 +950,24 @@ function amp_register_default_scripts( $wp_scripts ) { $extension_specs['amp-carousel']['latest'] = '0.2'; } + $bento_enabled = amp_is_bento_enabled(); foreach ( $extension_specs as $extension_name => $extension_spec ) { + if ( $bento_enabled && ! empty( $extension_spec['bento'] ) ) { + $version = $extension_spec['bento']['version']; + } else { + $version = $extension_spec['latest']; + } + $src = sprintf( 'https://cdn.ampproject.org/v0/%s-%s.js', $extension_name, - $extension_spec['latest'] + $version ); $wp_scripts->add( $extension_name, $src, - [ 'amp-runtime' ], + [ 'amp-runtime' ], // @todo Eventually this will not be present for Bento. null ); } @@ -964,6 +997,28 @@ function amp_register_default_styles( WP_Styles $styles ) { AMP__VERSION ); $styles->add_data( 'amp-icons', 'rtl', 'replace' ); + + // These are registered exclusively for non-AMP pages that manually enqueue them. They aren't needed on + // AMP pages due to the runtime style being present and because the styles are inlined in the scripts already. + if ( amp_is_bento_enabled() ) { + foreach ( AMP_Allowed_Tags_Generated::get_extension_specs() as $extension_name => $extension_spec ) { + if ( empty( $extension_spec['bento']['has_css'] ) ) { + continue; + } + + $src = sprintf( + 'https://cdn.ampproject.org/v0/%s-%s.css', + $extension_name, + $extension_spec['bento']['version'] + ); + $styles->add( + $extension_name, + $src, + [], + null + ); + } + } } /** @@ -1325,6 +1380,10 @@ function amp_is_dev_mode() { ( is_admin_bar_showing() && is_user_logged_in() ) || is_customize_preview() + || + // Force dev mode for Bento since it currently requires the Bento experiment opt-in script. + // @todo Remove this once Bento no longer requires an experiment to opt-in. See . + amp_is_bento_enabled() ) ); } diff --git a/includes/class-amp-theme-support.php b/includes/class-amp-theme-support.php index 8190f4c0a24..9a4760b2491 100644 --- a/includes/class-amp-theme-support.php +++ b/includes/class-amp-theme-support.php @@ -915,6 +915,23 @@ static function() { wp_dequeue_script( 'comment-reply' ); // Handled largely by AMP_Comments_Sanitizer and *reply* methods in this class. } ); + + // Enable Bento experiment per . + // @todo Remove this once Bento no longer requires an experiment to opt-in. + if ( amp_is_bento_enabled() ) { + add_action( + 'wp_head', + static function () { + ?> + + . + if ( amp_is_bento_enabled() ) { + return false; + } + // @codeCoverageIgnoreEnd + return array_key_exists( ConfigurationArgument::ENABLE_SSR, $args ) ? $args[ ConfigurationArgument::ENABLE_SSR ] : true; diff --git a/tests/php/test-amp-helper-functions.php b/tests/php/test-amp-helper-functions.php index 7125f560420..a09528ecfe5 100644 --- a/tests/php/test-amp-helper-functions.php +++ b/tests/php/test-amp-helper-functions.php @@ -1252,10 +1252,15 @@ static function ( $script ) { $this->assertStringContainsString( '', $output ); // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript } - /** @covers ::amp_register_default_scripts() */ - public function test_amp_register_default_scripts() { - global $wp_scripts; + /** + * @covers ::amp_register_default_scripts() + * @covers ::amp_register_default_styles() + */ + public function test_amp_register_default_scripts_and_styles() { + global $wp_scripts, $wp_styles; $wp_scripts = null; + $wp_styles = null; + add_filter( 'amp_bento_enabled', '__return_false' ); $registered_script_srcs = []; foreach ( wp_scripts()->registered as $handle => $dependency ) { @@ -1267,7 +1272,7 @@ public function test_amp_register_default_scripts() { ksort( $registered_script_srcs ); // This allows us to ensure that we catch any version changes in scripts. - $expected = [ + $expected_scripts = [ 'amp-3d-gltf' => 'v0/amp-3d-gltf-0.1.js', 'amp-3q-player' => 'v0/amp-3q-player-0.1.js', 'amp-access' => 'v0/amp-access-0.1.js', @@ -1403,10 +1408,220 @@ public function test_amp_register_default_scripts() { 'amp-youtube' => 'v0/amp-youtube-0.1.js', ]; - ksort( $expected ); + ksort( $expected_scripts ); + ksort( $registered_script_srcs ); + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export + $this->assertEquals( $expected_scripts, $registered_script_srcs, "Actual fixture:\n" . var_export( $registered_script_srcs, true ) ); + + $this->assertTrue( wp_style_is( 'amp-default', 'registered' ) ); + $this->assertTrue( wp_style_is( 'amp-icons', 'registered' ) ); + $this->assertFalse( wp_style_is( 'amp-base-carousel', 'registered' ) ); + } + + /** + * @covers ::amp_register_default_scripts() + * @covers ::amp_register_default_styles() + */ + public function test_amp_register_default_scripts_and_styles_with_bento() { + global $wp_scripts, $wp_styles; + $wp_scripts = null; + $wp_styles = null; + add_filter( 'amp_bento_enabled', '__return_true' ); + + $registered_script_srcs = []; + foreach ( wp_scripts()->registered as $handle => $dependency ) { + if ( 'amp-' === substr( $handle, 0, 4 ) ) { + $this->assertStringStartsWith( 'https://cdn.ampproject.org/', $dependency->src ); + $registered_script_srcs[ $handle ] = str_replace( 'https://cdn.ampproject.org/', '', $dependency->src ); + } + } + ksort( $registered_script_srcs ); + + // This allows us to ensure that we catch any version changes in scripts. + $expected_scripts = [ + 'amp-3d-gltf' => 'v0/amp-3d-gltf-0.1.js', + 'amp-3q-player' => 'v0/amp-3q-player-0.1.js', + 'amp-access' => 'v0/amp-access-0.1.js', + 'amp-access-laterpay' => 'v0/amp-access-laterpay-0.2.js', + 'amp-access-poool' => 'v0/amp-access-poool-0.1.js', + 'amp-access-scroll' => 'v0/amp-access-scroll-0.1.js', + 'amp-accordion' => 'v0/amp-accordion-1.0.js', + 'amp-action-macro' => 'v0/amp-action-macro-0.1.js', + 'amp-ad' => 'v0/amp-ad-0.1.js', + 'amp-ad-custom' => 'v0/amp-ad-custom-0.1.js', + 'amp-addthis' => 'v0/amp-addthis-0.1.js', + 'amp-analytics' => 'v0/amp-analytics-0.1.js', + 'amp-anim' => 'v0/amp-anim-0.1.js', + 'amp-animation' => 'v0/amp-animation-0.1.js', + 'amp-apester-media' => 'v0/amp-apester-media-0.1.js', + 'amp-app-banner' => 'v0/amp-app-banner-0.1.js', + 'amp-audio' => 'v0/amp-audio-0.1.js', + 'amp-auto-ads' => 'v0/amp-auto-ads-0.1.js', + 'amp-autocomplete' => 'v0/amp-autocomplete-0.1.js', + 'amp-base-carousel' => 'v0/amp-base-carousel-1.0.js', + 'amp-beopinion' => 'v0/amp-beopinion-0.1.js', + 'amp-bind' => 'v0/amp-bind-0.1.js', + 'amp-bodymovin-animation' => 'v0/amp-bodymovin-animation-0.1.js', + 'amp-brid-player' => 'v0/amp-brid-player-0.1.js', + 'amp-brightcove' => 'v0/amp-brightcove-1.0.js', + 'amp-byside-content' => 'v0/amp-byside-content-0.1.js', + 'amp-cache-url' => 'v0/amp-cache-url-0.1.js', + 'amp-call-tracking' => 'v0/amp-call-tracking-0.1.js', + 'amp-carousel' => 'v0/amp-carousel-0.2.js', + 'amp-connatix-player' => 'v0/amp-connatix-player-0.1.js', + 'amp-consent' => 'v0/amp-consent-0.1.js', + 'amp-dailymotion' => 'v0/amp-dailymotion-0.1.js', + 'amp-date-countdown' => 'v0/amp-date-countdown-1.0.js', + 'amp-date-display' => 'v0/amp-date-display-1.0.js', + 'amp-date-picker' => 'v0/amp-date-picker-0.1.js', + 'amp-delight-player' => 'v0/amp-delight-player-0.1.js', + 'amp-dynamic-css-classes' => 'v0/amp-dynamic-css-classes-0.1.js', + 'amp-embedly-card' => 'v0/amp-embedly-card-0.1.js', + 'amp-experiment' => 'v0/amp-experiment-0.1.js', + 'amp-facebook' => 'v0/amp-facebook-1.0.js', + 'amp-facebook-comments' => 'v0/amp-facebook-comments-0.1.js', + 'amp-facebook-like' => 'v0/amp-facebook-like-0.1.js', + 'amp-facebook-page' => 'v0/amp-facebook-page-0.1.js', + 'amp-fit-text' => 'v0/amp-fit-text-1.0.js', + 'amp-font' => 'v0/amp-font-0.1.js', + 'amp-form' => 'v0/amp-form-0.1.js', + 'amp-fx-collection' => 'v0/amp-fx-collection-0.1.js', + 'amp-fx-flying-carpet' => 'v0/amp-fx-flying-carpet-0.1.js', + 'amp-geo' => 'v0/amp-geo-0.1.js', + 'amp-gfycat' => 'v0/amp-gfycat-0.1.js', + 'amp-gist' => 'v0/amp-gist-0.1.js', + 'amp-google-document-embed' => 'v0/amp-google-document-embed-0.1.js', + 'amp-hulu' => 'v0/amp-hulu-0.1.js', + 'amp-iframe' => 'v0/amp-iframe-0.1.js', + 'amp-iframely' => 'v0/amp-iframely-0.1.js', + 'amp-ima-video' => 'v0/amp-ima-video-0.1.js', + 'amp-image-lightbox' => 'v0/amp-image-lightbox-0.1.js', + 'amp-image-slider' => 'v0/amp-image-slider-0.1.js', + 'amp-imgur' => 'v0/amp-imgur-0.1.js', + 'amp-inline-gallery' => 'v0/amp-inline-gallery-1.0.js', + 'amp-inputmask' => 'v0/amp-inputmask-0.1.js', + 'amp-instagram' => 'v0/amp-instagram-1.0.js', + 'amp-install-serviceworker' => 'v0/amp-install-serviceworker-0.1.js', + 'amp-izlesene' => 'v0/amp-izlesene-0.1.js', + 'amp-jwplayer' => 'v0/amp-jwplayer-0.1.js', + 'amp-kaltura-player' => 'v0/amp-kaltura-player-0.1.js', + 'amp-lightbox' => 'v0/amp-lightbox-1.0.js', + 'amp-lightbox-gallery' => 'v0/amp-lightbox-gallery-1.0.js', + 'amp-link-rewriter' => 'v0/amp-link-rewriter-0.1.js', + 'amp-list' => 'v0/amp-list-0.1.js', + 'amp-live-list' => 'v0/amp-live-list-0.1.js', + 'amp-mathml' => 'v0/amp-mathml-0.1.js', + 'amp-mega-menu' => 'v0/amp-mega-menu-0.1.js', + 'amp-megaphone' => 'v0/amp-megaphone-0.1.js', + 'amp-minute-media-player' => 'v0/amp-minute-media-player-0.1.js', + 'amp-mowplayer' => 'v0/amp-mowplayer-0.1.js', + 'amp-mustache' => 'v0/amp-mustache-0.2.js', + 'amp-nested-menu' => 'v0/amp-nested-menu-0.1.js', + 'amp-next-page' => 'v0/amp-next-page-1.0.js', + 'amp-nexxtv-player' => 'v0/amp-nexxtv-player-0.1.js', + 'amp-o2-player' => 'v0/amp-o2-player-0.1.js', + 'amp-onetap-google' => 'v0/amp-onetap-google-0.1.js', + 'amp-ooyala-player' => 'v0/amp-ooyala-player-0.1.js', + 'amp-orientation-observer' => 'v0/amp-orientation-observer-0.1.js', + 'amp-pan-zoom' => 'v0/amp-pan-zoom-0.1.js', + 'amp-pinterest' => 'v0/amp-pinterest-0.1.js', + 'amp-playbuzz' => 'v0/amp-playbuzz-0.1.js', + 'amp-position-observer' => 'v0/amp-position-observer-0.1.js', + 'amp-powr-player' => 'v0/amp-powr-player-0.1.js', + 'amp-reach-player' => 'v0/amp-reach-player-0.1.js', + 'amp-recaptcha-input' => 'v0/amp-recaptcha-input-0.1.js', + 'amp-redbull-player' => 'v0/amp-redbull-player-0.1.js', + 'amp-reddit' => 'v0/amp-reddit-0.1.js', + 'amp-render' => 'v0/amp-render-1.0.js', + 'amp-riddle-quiz' => 'v0/amp-riddle-quiz-0.1.js', + 'amp-runtime' => 'v0.js', + 'amp-script' => 'v0/amp-script-0.1.js', + 'amp-selector' => 'v0/amp-selector-1.0.js', + 'amp-shadow' => 'shadow-v0.js', + 'amp-sidebar' => 'v0/amp-sidebar-0.1.js', + 'amp-skimlinks' => 'v0/amp-skimlinks-0.1.js', + 'amp-smartlinks' => 'v0/amp-smartlinks-0.1.js', + 'amp-social-share' => 'v0/amp-social-share-1.0.js', + 'amp-soundcloud' => 'v0/amp-soundcloud-0.1.js', + 'amp-springboard-player' => 'v0/amp-springboard-player-0.1.js', + 'amp-sticky-ad' => 'v0/amp-sticky-ad-1.0.js', + 'amp-story' => 'v0/amp-story-1.0.js', + 'amp-story-360' => 'v0/amp-story-360-0.1.js', + 'amp-story-auto-ads' => 'v0/amp-story-auto-ads-0.1.js', + 'amp-story-auto-analytics' => 'v0/amp-story-auto-analytics-0.1.js', + 'amp-story-interactive' => 'v0/amp-story-interactive-0.1.js', + 'amp-story-panning-media' => 'v0/amp-story-panning-media-0.1.js', + 'amp-story-player' => 'v0/amp-story-player-0.1.js', + 'amp-stream-gallery' => 'v0/amp-stream-gallery-1.0.js', + 'amp-subscriptions' => 'v0/amp-subscriptions-0.1.js', + 'amp-subscriptions-google' => 'v0/amp-subscriptions-google-0.1.js', + 'amp-tiktok' => 'v0/amp-tiktok-0.1.js', + 'amp-timeago' => 'v0/amp-timeago-1.0.js', + 'amp-truncate-text' => 'v0/amp-truncate-text-0.1.js', + 'amp-twitter' => 'v0/amp-twitter-1.0.js', + 'amp-user-notification' => 'v0/amp-user-notification-0.1.js', + 'amp-video' => 'v0/amp-video-1.0.js', + 'amp-video-docking' => 'v0/amp-video-docking-0.1.js', + 'amp-video-iframe' => 'v0/amp-video-iframe-1.0.js', + 'amp-vimeo' => 'v0/amp-vimeo-1.0.js', + 'amp-vine' => 'v0/amp-vine-0.1.js', + 'amp-viqeo-player' => 'v0/amp-viqeo-player-0.1.js', + 'amp-vk' => 'v0/amp-vk-0.1.js', + 'amp-web-push' => 'v0/amp-web-push-0.1.js', + 'amp-wistia-player' => 'v0/amp-wistia-player-0.1.js', + 'amp-wordpress-embed' => 'v0/amp-wordpress-embed-1.0.js', + 'amp-yotpo' => 'v0/amp-yotpo-0.1.js', + 'amp-youtube' => 'v0/amp-youtube-1.0.js', + ]; + + ksort( $expected_scripts ); ksort( $registered_script_srcs ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export - $this->assertEquals( $expected, $registered_script_srcs, "Actual fixture:\n" . var_export( $registered_script_srcs, true ) ); + $this->assertEquals( $expected_scripts, $registered_script_srcs, "Actual fixture:\n" . var_export( $registered_script_srcs, true ) ); + + $bundled_styles = [ 'amp-default', 'amp-icons' ]; + foreach ( $bundled_styles as $bundled_style ) { + $this->assertTrue( wp_style_is( $bundled_style, 'registered' ) ); + } + $registered_style_srcs = []; + foreach ( wp_styles()->registered as $handle => $dependency ) { + if ( in_array( $handle, $bundled_styles, true ) ) { + continue; + } + + if ( 'amp-' === substr( $handle, 0, 4 ) ) { + $this->assertStringStartsWith( 'https://cdn.ampproject.org/', $dependency->src ); + $registered_style_srcs[ $handle ] = str_replace( 'https://cdn.ampproject.org/', '', $dependency->src ); + } + } + ksort( $registered_style_srcs ); + + // This allows us to ensure that we catch any version changes in styles. + $expected_styles = [ + 'amp-accordion' => 'v0/amp-accordion-1.0.css', + 'amp-base-carousel' => 'v0/amp-base-carousel-1.0.css', + 'amp-brightcove' => 'v0/amp-brightcove-1.0.css', + 'amp-facebook' => 'v0/amp-facebook-1.0.css', + 'amp-fit-text' => 'v0/amp-fit-text-1.0.css', + 'amp-inline-gallery' => 'v0/amp-inline-gallery-1.0.css', + 'amp-instagram' => 'v0/amp-instagram-1.0.css', + 'amp-lightbox' => 'v0/amp-lightbox-1.0.css', + 'amp-lightbox-gallery' => 'v0/amp-lightbox-gallery-1.0.css', + 'amp-selector' => 'v0/amp-selector-1.0.css', + 'amp-social-share' => 'v0/amp-social-share-1.0.css', + 'amp-stream-gallery' => 'v0/amp-stream-gallery-1.0.css', + 'amp-timeago' => 'v0/amp-timeago-1.0.css', + 'amp-twitter' => 'v0/amp-twitter-1.0.css', + 'amp-video' => 'v0/amp-video-1.0.css', + 'amp-video-iframe' => 'v0/amp-video-iframe-1.0.css', + 'amp-vimeo' => 'v0/amp-vimeo-1.0.css', + 'amp-youtube' => 'v0/amp-youtube-1.0.css', + ]; + + ksort( $expected_styles ); + ksort( $registered_style_srcs ); + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export + $this->assertEquals( $expected_styles, $registered_style_srcs, "Actual fixture:\n" . var_export( $registered_style_srcs, true ) ); } /** diff --git a/tests/php/test-class-amp-theme-support.php b/tests/php/test-class-amp-theme-support.php index 6948ca4396e..43b357f7b6a 100644 --- a/tests/php/test-class-amp-theme-support.php +++ b/tests/php/test-class-amp-theme-support.php @@ -1758,11 +1758,7 @@ static function ( $url ) { /** * Test prepare_response when dev mode is forced. * - * @global WP_Widget_Factory $wp_widget_factory - * @global WP_Scripts $wp_scripts * @covers AMP_Theme_Support::prepare_response() - * @covers AMP_Theme_Support::ensure_required_markup() - * @covers ::amp_render_scripts() */ public function test_prepare_response_in_forced_dev_mode() { $this->set_template_mode( AMP_Theme_Support::STANDARD_MODE_SLUG ); @@ -1779,6 +1775,21 @@ public function test_prepare_response_in_forced_dev_mode() { $this->assertStringNotContainsString( 'set_template_mode( AMP_Theme_Support::STANDARD_MODE_SLUG ); + + add_filter( 'amp_bento_enabled', '__return_true' ); + wp(); + + $html = AMP_Theme_Support::prepare_response( $this->get_original_html() ); + $this->assertStringContainsString( 'AMP.toggleExperiment(\'bento\', true);', $html ); + } + /** * Test prepare_response for standard mode when some validation errors aren't auto-sanitized. *