Skip to content

Commit

Permalink
Merge pull request #3039 from ampproject/update/template-head
Browse files Browse the repository at this point in the history
Improve generation of AMP pages in post-processor
  • Loading branch information
westonruter authored Sep 3, 2019
2 parents 7ba548d + a2554a4 commit 065fc06
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 81 deletions.
19 changes: 17 additions & 2 deletions includes/amp-helper-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -361,8 +361,23 @@ function amp_get_asset_url( $file ) {
* @return string Boilerplate code.
*/
function amp_get_boilerplate_code() {
return '<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style>'
. '<noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>';
$stylesheets = amp_get_boilerplate_stylesheets();
return sprintf( '<style amp-boilerplate>%s</style><noscript><style amp-boilerplate>%s</style></noscript>', $stylesheets[0], $stylesheets[1] );
}

/**
* Get AMP boilerplate stylesheets.
*
* @since 1.3
* @link https://www.ampproject.org/docs/reference/spec#boilerplate
*
* @return string[] Stylesheets, where first is contained in style[amp-boilerplate] and the second in noscript>style[amp-boilerplate].
*/
function amp_get_boilerplate_stylesheets() {
return [
'body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}',
'body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}',
];
}

/**
Expand Down
24 changes: 24 additions & 0 deletions includes/class-amp-story-post-type.php
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,30 @@ public static function register() {

add_action( 'wp_enqueue_scripts', [ __CLASS__, 'add_custom_stories_styles' ] );

add_action(
'amp_story_head',
function() {
// Theme support for title-tag is implied for stories. See _wp_render_title_tag().
echo '<title>' . esc_html( wp_get_document_title() ) . '</title>' . "\n";
},
1
);
add_action( 'amp_story_head', 'wp_enqueue_scripts', 1 );
add_action(
'amp_story_head',
function() {
/*
* Same as wp_print_styles() but importantly omitting the wp_print_styles action, which themes/plugins
* can use to output arbitrary styling. Styling is constrained in story template via the
* \AMP_Story_Post_Type::filter_frontend_print_styles_array() method.
*/
wp_styles()->do_items();
},
8
);
add_action( 'amp_story_head', 'rel_canonical' );
add_action( 'amp_story_head', 'amp_add_generator_metadata' );

// Remove unnecessary settings.
add_filter( 'block_editor_settings', [ __CLASS__, 'filter_block_editor_settings' ], 10, 2 );

Expand Down
144 changes: 90 additions & 54 deletions includes/class-amp-theme-support.php
Original file line number Diff line number Diff line change
Expand Up @@ -1016,34 +1016,6 @@ static function( $html ) {
1
);

/*
* "AMP HTML documents MUST contain the AMP boilerplate code (head > style[amp-boilerplate] and noscript > style[amp-boilerplate])
* in their head tag." {@link https://www.ampproject.org/docs/fundamentals/spec#required-markup AMP Required markup}
*
* After "Specify the <link> tag for your favicon.", then
* "Specify any custom styles by using the <style amp-custom> tag."
*
* Note that the boilerplate is added at the very end because:
* "Finally, specify the AMP boilerplate code. By putting the boilerplate code last, it prevents custom styles from accidentally
* overriding the boilerplate css rules." {@link https://docs.google.com/document/d/169XUxtSSEJb16NfkrCr9y5lqhUR7vxXEAsNxBzg07fM/edit AMP Hosting Guide}
*
* Other required markup is added in the ensure_required_markup method, including meta charset, meta viewport, and rel=canonical link.
*/
add_action(
'wp_head',
static function() {
echo '<style amp-custom></style>';
},
0
);
add_action(
'wp_head',
static function() {
echo amp_get_boilerplate_code(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
},
PHP_INT_MAX
);

add_action( 'admin_bar_init', [ __CLASS__, 'init_admin_bar' ] );
add_action( 'wp_head', 'amp_add_generator_metadata', 20 );
add_action( 'wp_enqueue_scripts', [ __CLASS__, 'enqueue_assets' ], 0 ); // Enqueue before theme's styles.
Expand Down Expand Up @@ -1434,8 +1406,9 @@ static function( $body_classes ) {
*
* @since 0.7
* @link https://www.ampproject.org/docs/reference/spec#required-markup
* @link https://docs.google.com/document/d/169XUxtSSEJb16NfkrCr9y5lqhUR7vxXEAsNxBzg07fM/edit#heading=h.2ha259c3ffos
* @link https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp/
* @todo All of this might be better placed inside of a sanitizer.
* @todo Consider removing any scripts that are not among the $script_handles.
*
* @param DOMDocument $dom Document.
* @param string[] $script_handles AMP script handles for components identified during output buffering.
Expand All @@ -1447,6 +1420,8 @@ public static function ensure_required_markup( DOMDocument $dom, $script_handles
* @var DOMElement $meta
* @var DOMElement $script
* @var DOMElement $link
* @var DOMElement $style
* @var DOMElement $noscript
*/

$xpath = new DOMXPath( $dom );
Expand Down Expand Up @@ -1496,9 +1471,9 @@ public static function ensure_required_markup( DOMDocument $dom, $script_handles
* there are a few basic optimizations that you can apply. The key is to structure the <head> section
* in a way so that all render-blocking scripts and custom fonts load as fast as possible."
*
* "The first tag should be the meta charset tag, followed by any remaining meta tags."
* "1. The first tag should be the meta charset tag, followed by any remaining meta tags."
*
* {@link https://docs.google.com/document/d/169XUxtSSEJb16NfkrCr9y5lqhUR7vxXEAsNxBzg07fM/edit#heading=h.2ha259c3ffos Optimize the AMP Runtime loading}
* {@link https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp/ Optimize the AMP Runtime loading}
*/
$meta_charset = null;
$meta_viewport = null;
Expand Down Expand Up @@ -1606,10 +1581,10 @@ public static function ensure_required_markup( DOMDocument $dom, $script_handles

/* phpcs:ignore Squiz.PHP.CommentedOutCode.Found
*
* "Next, preload the AMP runtime v0.js <script> tag with <link as=script href=https://cdn.ampproject.org/v0.js rel=preload>.
* "2. Next, preload the AMP runtime v0.js <script> tag with <link as=script href=https://cdn.ampproject.org/v0.js rel=preload>.
* The AMP runtime should start downloading as soon as possible because the AMP boilerplate hides the document via body { visibility:hidden }
* until the AMP runtime has loaded. Preloading the AMP runtime tells the browser to download the script with a higher priority."
* {@link https://docs.google.com/document/d/169XUxtSSEJb16NfkrCr9y5lqhUR7vxXEAsNxBzg07fM/edit#heading=h.2ha259c3ffos Optimize the AMP Runtime loading}
* {@link https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp/ Optimize the AMP Runtime loading}
*/
$prioritized_preloads = [];
if ( ! isset( $links['preload'] ) ) {
Expand All @@ -1626,6 +1601,10 @@ public static function ensure_required_markup( DOMDocument $dom, $script_handles
]
);

/*
* "3. If your page includes render-delaying extensions (e.g., amp-experiment, amp-dynamic-css-classes, amp-story),
* preload those extensions as they're required by the AMP runtime for rendering the page."
*/
$amp_script_handles = array_keys( $amp_scripts );
foreach ( array_intersect( $render_delaying_extensions, $amp_script_handles ) as $script_handle ) {
if ( ! in_array( $script_handle, $render_delaying_extensions, true ) ) {
Expand All @@ -1643,6 +1622,12 @@ public static function ensure_required_markup( DOMDocument $dom, $script_handles
}
$links['preload'] = array_merge( $prioritized_preloads, $links['preload'] );

/*
* "4. Use preconnect to speedup the connection to other origin where the full resource URL is not known ahead of time,
* for example, when using Google Fonts."
*
* Note that \AMP_Style_Sanitizer::process_link_element() will ensure preconnect links for Google Fonts are present.
*/
$link_relations = [ 'preconnect', 'dns-prefetch', 'preload', 'prerender', 'prefetch' ];
foreach ( $link_relations as $rel ) {
if ( ! isset( $links[ $rel ] ) ) {
Expand All @@ -1657,41 +1642,88 @@ public static function ensure_required_markup( DOMDocument $dom, $script_handles
}
}

/*
* "Specify the <script> tags for render-delaying extensions (e.g., amp-experiment, amp-dynamic-css-classes, and amp-story)."
* "Specify the <script> tags for remaining extensions (e.g., amp-bind, ...). These extensions are not render-delaying and therefore
* should not be preloaded because they might take away important bandwidth for the initial render."
* {@link https://docs.google.com/document/d/169XUxtSSEJb16NfkrCr9y5lqhUR7vxXEAsNxBzg07fM/edit AMP Hosting Guide}
*/
// "5. Load the AMP runtime."
if ( isset( $amp_scripts['amp-runtime'] ) ) {
$ordered_scripts['amp-runtime'] = $amp_scripts['amp-runtime'];
unset( $amp_scripts['amp-runtime'] );
} else {
$script = $dom->createElement( 'script' );
$script->setAttribute( 'async', '' );
$script->setAttribute( 'src', $runtime_src );
$ordered_scripts['amp-runtime'] = $script;
}

/*
* "6. Specify the <script> tags for render-delaying extensions (e.g., amp-experiment amp-dynamic-css-classes and amp-story"
*
* {@link https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp/ AMP Hosting Guide}
*/
foreach ( $render_delaying_extensions as $extension ) {
if ( isset( $amp_scripts[ $extension ] ) ) {
$ordered_scripts[ $extension ] = $amp_scripts[ $extension ];
unset( $amp_scripts[ $extension ] );
}
}

/*
* "7. Specify the <script> tags for remaining extensions (e.g., amp-bind ...). These extensions are not render-delaying
* and therefore should not be preloaded as they might take away important bandwidth for the initial render."
*/
$ordered_scripts = array_merge( $ordered_scripts, $amp_scripts );
foreach ( $ordered_scripts as $ordered_script ) {
$head->insertBefore( $ordered_script, $previous_node->nextSibling );
$previous_node = $ordered_script;
}

/*
* "Specify the <link> tag for your favicon."
* {@link https://docs.google.com/document/d/169XUxtSSEJb16NfkrCr9y5lqhUR7vxXEAsNxBzg07fM/edit AMP Hosting Guide}
* "8. Specify any custom styles by using the <style amp-custom> tag."
*/
if ( isset( $links['icon'] ) ) {
foreach ( $links['icon'] as $link ) {
$link->parentNode->removeChild( $link ); // So we can move it.
$head->insertBefore( $link, $previous_node->nextSibling );
$previous_node = $link;
$style = $xpath->query( './style[ @amp-custom ]', $head )->item( 0 );
if ( $style ) {
// Ensure the CSS manifest comment remains before style[amp-custom].
if ( $style->previousSibling instanceof DOMComment ) {
$comment = $style->previousSibling;
$comment->parentNode->removeChild( $comment );
$head->insertBefore( $comment, $previous_node->nextSibling );
$previous_node = $comment;
}

$style->parentNode->removeChild( $style );
$head->insertBefore( $style, $previous_node->nextSibling );
$previous_node = $style;
}

/*
* "9. Add any other tags allowed in the <head> section. In particular, any external fonts should go last since
* they block rendering."
*/

/*
* "10. Finally, specify the AMP boilerplate code. By putting the boilerplate code last, it prevents custom styles
* from accidentally overriding the boilerplate css rules."
*/
$style = $xpath->query( './style[ @amp-boilerplate ]', $head )->item( 0 );
if ( ! $style ) {
$style = $dom->createElement( 'style' );
$style->setAttribute( 'amp-boilerplate', '' );
$style->appendChild( $dom->createTextNode( amp_get_boilerplate_stylesheets()[0] ) );
} else {
$style->parentNode->removeChild( $style ); // So we can move it.
}
$head->appendChild( $style );

$noscript = $xpath->query( './noscript[ style[ @amp-boilerplate ] ]', $head )->item( 0 );
if ( ! $noscript ) {
$noscript = $dom->createElement( 'noscript' );
$style = $dom->createElement( 'style' );
$style->setAttribute( 'amp-boilerplate', '' );
$style->appendChild( $dom->createTextNode( amp_get_boilerplate_stylesheets()[1] ) );
$noscript->appendChild( $style );
} else {
$noscript->parentNode->removeChild( $noscript ); // So we can move it.
}
$head->appendChild( $noscript );

// Note the style[amp-custom] and style[amp-boilerplate] are output in the add_hooks() method.
unset( $previous_node );
}

Expand Down Expand Up @@ -1840,8 +1872,11 @@ public static function prepare_response( $response, $args = [] ) {
);
}

// Abort if the response was not HTML.
if ( 'text/html' !== substr( AMP_HTTP::get_response_content_type(), 0, 9 ) || '<' !== substr( ltrim( $response ), 0, 1 ) ) {
/*
* Abort if the response was not HTML. To be post-processed as an AMP page, the output-buffered document must
* have the HTML mime type and it must start with <html> followed by <head> tag (with whitespace, doctype, and comments optionally interspersed).
*/
if ( 'text/html' !== substr( AMP_HTTP::get_response_content_type(), 0, 9 ) || ! preg_match( '#^(?:<!.*?>|\s+)*<html.*?>(?:<!.*?>|\s+)*<head\b(.*?)>#is', $response ) ) {
return $response;
}

Expand Down Expand Up @@ -2025,11 +2060,14 @@ public static function prepare_response( $response, $args = [] ) {
* See <https://www.w3.org/International/questions/qa-html-encoding-declarations>.
*/
if ( ! preg_match( '#<meta[^>]+charset=#i', substr( $response, 0, 1024 ) ) ) {
$meta_charset = sprintf( '<meta charset="%s">', esc_attr( get_bloginfo( 'charset' ) ) );

$response = preg_replace(
'/(<head[^>]*>)/i',
'$1' . sprintf( '<meta charset="%s">', esc_attr( get_bloginfo( 'charset' ) ) ),
'/(<head\b.*?>)/is',
'$1' . $meta_charset,
$response,
1
1,
$count
);
}

Expand Down Expand Up @@ -2253,8 +2291,6 @@ public static function whitelist_layout_in_wp_kses_allowed_html( $context ) {
* @return void
*/
public static function enqueue_assets() {
wp_enqueue_script( 'amp-runtime' );

// Enqueue default styles expected by sanitizer.
wp_enqueue_style( 'amp-default', amp_get_asset_url( 'css/amp-default.css' ), [], AMP__VERSION );
wp_styles()->add_data( 'amp-default', 'rtl', 'replace' );
Expand Down
17 changes: 8 additions & 9 deletions includes/templates/single-amp_story.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,25 @@

the_post();

$metadata = amp_get_schemaorg_metadata();
?>
<!DOCTYPE html>
<html amp <?php language_attributes(); ?>>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<title><?php echo esc_html( wp_get_document_title() ); ?></title>

<?php
wp_enqueue_scripts();
wp_scripts()->do_items( [ 'amp-runtime' ] ); // @todo Duplicate with AMP_Theme_Support::enqueue_assets().
wp_styles()->do_items();
/**
* Prints scripts or data in the head tag on the front end.
*
* @since 1.3
*/
do_action( 'amp_story_head' );
?>
<?php rel_canonical(); ?>
<?php amp_add_generator_metadata(); ?>
<script type="application/ld+json"><?php echo wp_json_encode( $metadata, JSON_UNESCAPED_UNICODE ); ?></script>
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
</head>
<body>
<?php
$metadata = amp_get_schemaorg_metadata();
if ( isset( $metadata['publisher']['logo']['url'] ) ) {
$publisher_logo_src = $metadata['publisher']['logo']['url'];
} elseif ( isset( $metadata['publisher']['logo'] ) && is_string( $metadata['publisher']['logo'] ) ) {
Expand Down
33 changes: 33 additions & 0 deletions tests/php/test-amp-helper-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,39 @@ public function capture_filter_call( $value ) {
return $value;
}

/**
* Test amp_get_asset_url.
*
* @covers ::amp_get_asset_url()
*/
public function test_amp_get_asset_url() {
$this->assertStringEndsWith( '/assets/foo.jpg', amp_get_asset_url( 'foo.jpg' ) );
}

/**
* Test amp_get_boilerplate_code.
*
* @covers ::amp_get_boilerplate_code()
*/
public function test_amp_get_boilerplate_code() {
$boilerplate_code = amp_get_boilerplate_code();
$this->assertStringStartsWith( '<style amp-boilerplate>', $boilerplate_code );
$this->assertContains( '<noscript><style amp-boilerplate>', $boilerplate_code );
}

/**
* Test amp_get_boilerplate_stylesheets.
*
* @covers ::amp_get_boilerplate_stylesheets()
*/
public function test_amp_get_boilerplate_stylesheets() {
$stylesheets = amp_get_boilerplate_stylesheets();
$this->assertInternalType( 'array', $stylesheets );
$this->assertCount( 2, $stylesheets );
$this->assertContains( 'body{-webkit-animation:-amp-start', $stylesheets[0] );
$this->assertContains( 'body{-webkit-animation:none', $stylesheets[1] );
}

/**
* Test amp_add_generator_metadata.
*
Expand Down
Loading

0 comments on commit 065fc06

Please sign in to comment.