From 75dc22fa711edace36fe2a3d1bd6f96276da1b5f Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 13 May 2019 20:04:34 -0700 Subject: [PATCH 1/2] First attempt at resizable iframes --- .gitignore | 1 + amp.php | 1 + assets/js/wp-embed-template.js | 234 ++++++++++++++++++ includes/admin/functions.php | 11 + includes/class-amp-autoloader.php | 1 + includes/class-amp-story-post-type.php | 103 -------- .../class-amp-wordpress-embed-template.php | 59 +++++ .../sanitizers/class-amp-iframe-sanitizer.php | 39 +++ 8 files changed, 346 insertions(+), 103 deletions(-) create mode 100644 assets/js/wp-embed-template.js create mode 100644 includes/class-amp-wordpress-embed-template.php diff --git a/.gitignore b/.gitignore index 0b19108b894..43af50c2e9d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ assets/css/*-compiled-rtl.css assets/css/*.map assets/js/*.js !assets/js/amp-service-worker-runtime-precaching.js +!assets/js/wp-embed-template.js assets/js/*.map built /amphtml diff --git a/amp.php b/amp.php index 258dd0278ca..80372515d72 100644 --- a/amp.php +++ b/amp.php @@ -348,6 +348,7 @@ function amp_init() { add_action( 'wp_loaded', 'amp_story_templates' ); add_action( 'wp_loaded', 'amp_add_options_menu' ); add_action( 'wp_loaded', 'amp_admin_pointer' ); + add_action( 'wp_loaded', 'amp_wordpress_embed_templates' ); add_action( 'parse_query', 'amp_correct_query_when_is_front_page' ); add_action( 'admin_bar_menu', 'amp_add_admin_bar_view_link', 100 ); diff --git a/assets/js/wp-embed-template.js b/assets/js/wp-embed-template.js new file mode 100644 index 00000000000..be5808cfc9d --- /dev/null +++ b/assets/js/wp-embed-template.js @@ -0,0 +1,234 @@ +/** + * Customized version of wp-includes/js/wp-embed-template.js that sends + * AMP-compatible resize messages. + */ + +(function ( window, document ) { + 'use strict'; + + var supportedBrowser = ( document.querySelector && window.addEventListener ), + loaded = false, + secret, + secretTimeout, + resizing; + + function sendEmbedMessage( message, value ) { + window.parent.postMessage( { + message: message, + value: value, + secret: secret + }, '*' ); + } + + function sendAmpMessage( type, key, value ) { + var message = { + sentinel: 'amp', + type: type, + }; + + if ( key && value ) { + message[ key ] = value; + } + + window.parent.postMessage( message, '*' ); + } + + function onLoad() { + if ( loaded ) { + return; + } + loaded = true; + + var share_dialog = document.querySelector( '.wp-embed-share-dialog' ), + share_dialog_open = document.querySelector( '.wp-embed-share-dialog-open' ), + share_dialog_close = document.querySelector( '.wp-embed-share-dialog-close' ), + share_input = document.querySelectorAll( '.wp-embed-share-input' ), + share_dialog_tabs = document.querySelectorAll( '.wp-embed-share-tab-button button' ), + featured_image = document.querySelector( '.wp-embed-featured-image img' ), + i; + + if ( share_input ) { + for ( i = 0; i < share_input.length; i++ ) { + share_input[ i ].addEventListener( 'click', function ( e ) { + e.target.select(); + } ); + } + } + + function openSharingDialog() { + share_dialog.className = share_dialog.className.replace( 'hidden', '' ); + // Initial focus should go on the currently selected tab in the dialog. + document.querySelector( '.wp-embed-share-tab-button [aria-selected="true"]' ).focus(); + } + + function closeSharingDialog() { + share_dialog.className += ' hidden'; + document.querySelector( '.wp-embed-share-dialog-open' ).focus(); + } + + if ( share_dialog_open ) { + share_dialog_open.addEventListener( 'click', function () { + openSharingDialog(); + } ); + } + + if ( share_dialog_close ) { + share_dialog_close.addEventListener( 'click', function () { + closeSharingDialog(); + } ); + } + + function shareClickHandler( e ) { + var currentTab = document.querySelector( '.wp-embed-share-tab-button [aria-selected="true"]' ); + currentTab.setAttribute( 'aria-selected', 'false' ); + document.querySelector( '#' + currentTab.getAttribute( 'aria-controls' ) ).setAttribute( 'aria-hidden', 'true' ); + + e.target.setAttribute( 'aria-selected', 'true' ); + document.querySelector( '#' + e.target.getAttribute( 'aria-controls' ) ).setAttribute( 'aria-hidden', 'false' ); + } + + function shareKeyHandler( e ) { + var target = e.target, + previousSibling = target.parentElement.previousElementSibling, + nextSibling = target.parentElement.nextElementSibling, + newTab, newTabChild; + + if ( 37 === e.keyCode ) { + newTab = previousSibling; + } else if ( 39 === e.keyCode ) { + newTab = nextSibling; + } else { + return false; + } + + if ( 'rtl' === document.documentElement.getAttribute( 'dir' ) ) { + newTab = ( newTab === previousSibling ) ? nextSibling : previousSibling; + } + + if ( newTab ) { + newTabChild = newTab.firstElementChild; + + target.setAttribute( 'tabindex', '-1' ); + target.setAttribute( 'aria-selected', false ); + document.querySelector( '#' + target.getAttribute( 'aria-controls' ) ).setAttribute( 'aria-hidden', 'true' ); + + newTabChild.setAttribute( 'tabindex', '0' ); + newTabChild.setAttribute( 'aria-selected', 'true' ); + newTabChild.focus(); + document.querySelector( '#' + newTabChild.getAttribute( 'aria-controls' ) ).setAttribute( 'aria-hidden', 'false' ); + } + } + + if ( share_dialog_tabs ) { + for ( i = 0; i < share_dialog_tabs.length; i++ ) { + share_dialog_tabs[ i ].addEventListener( 'click', shareClickHandler ); + + share_dialog_tabs[ i ].addEventListener( 'keydown', shareKeyHandler ); + } + } + + document.addEventListener( 'keydown', function ( e ) { + if ( 27 === e.keyCode && -1 === share_dialog.className.indexOf( 'hidden' ) ) { + closeSharingDialog(); + } else if ( 9 === e.keyCode ) { + constrainTabbing( e ); + } + }, false ); + + function constrainTabbing( e ) { + // Need to re-get the selected tab each time. + var firstFocusable = document.querySelector( '.wp-embed-share-tab-button [aria-selected="true"]' ); + + if ( share_dialog_close === e.target && ! e.shiftKey ) { + firstFocusable.focus(); + e.preventDefault(); + } else if ( firstFocusable === e.target && e.shiftKey ) { + share_dialog_close.focus(); + e.preventDefault(); + } + } + + if ( window.self === window.top ) { + return; + } + + /** + * Send this document's height to the parent (embedding) site. + */ + sendEmbedMessage( 'height', Math.ceil( document.body.getBoundingClientRect().height ) ); + sendAmpMessage( 'embed-size', 'height', Math.ceil( document.body.getBoundingClientRect().height ) ); + + // Send the document's height again after the featured image has been loaded. + if ( featured_image ) { + featured_image.addEventListener( 'load', function() { + sendEmbedMessage( 'height', Math.ceil( document.body.getBoundingClientRect().height ) ); + sendAmpMessage( 'embed-ready' ); + sendAmpMessage( 'embed-size', 'height', Math.ceil( document.body.getBoundingClientRect().height ) ); + } ); + } + + /** + * Detect clicks to external (_top) links. + */ + function linkClickHandler( e ) { + var target = e.target, + href; + if ( target.hasAttribute( 'href' ) ) { + href = target.getAttribute( 'href' ); + } else { + href = target.parentElement.getAttribute( 'href' ); + } + + /** + * Send link target to the parent (embedding) site. + */ + if ( href ) { + sendEmbedMessage( 'link', href ); + e.preventDefault(); + } + } + + document.addEventListener( 'click', linkClickHandler ); + } + + /** + * Iframe resize handler. + */ + function onResize() { + if ( window.self === window.top ) { + return; + } + + clearTimeout( resizing ); + + resizing = setTimeout( function () { + sendEmbedMessage( 'height', Math.ceil( document.body.getBoundingClientRect().height ) ); + sendAmpMessage( 'embed-size', 'height', Math.ceil( document.body.getBoundingClientRect().height ) ); + }, 100 ); + } + + /** + * Re-get the secret when it was added later on. + */ + function getSecret() { + if ( window.self === window.top || !!secret ) { + return; + } + + secret = window.location.hash.replace( /.*secret=([\d\w]{10}).*/, '$1' ); + + clearTimeout( secretTimeout ); + + secretTimeout = setTimeout( function () { + getSecret(); + }, 100 ); + } + + if ( supportedBrowser ) { + getSecret(); + document.documentElement.className = document.documentElement.className.replace( /\bno-js\b/, '' ) + ' js'; + document.addEventListener( 'DOMContentLoaded', onLoad, false ); + window.addEventListener( 'load', onLoad, false ); + window.addEventListener( 'resize', onResize, false ); + } +})( window, document ); diff --git a/includes/admin/functions.php b/includes/admin/functions.php index 5c46ae7dd81..3e40c962663 100644 --- a/includes/admin/functions.php +++ b/includes/admin/functions.php @@ -209,3 +209,14 @@ function amp_story_templates() { $story_templates = new AMP_Story_Templates(); $story_templates->init(); } + + +/** + * Bootstrap the Story Templates needed in editor. + * + * @since 1.? + */ +function amp_wordpress_embed_templates() { + $story_templates = new AMP_WordPress_Embed_Template(); + $story_templates->init(); +} diff --git a/includes/class-amp-autoloader.php b/includes/class-amp-autoloader.php index 98d8d26a2ae..6f0ff503a38 100644 --- a/includes/class-amp-autoloader.php +++ b/includes/class-amp-autoloader.php @@ -106,6 +106,7 @@ class AMP_Autoloader { 'AMP_Widget_Archives' => 'includes/widgets/class-amp-widget-archives', 'AMP_Widget_Categories' => 'includes/widgets/class-amp-widget-categories', 'AMP_Widget_Text' => 'includes/widgets/class-amp-widget-text', + 'AMP_WordPress_Embed_Template' => 'includes/class-amp-embed-wordpress-template', 'WPCOM_AMP_Polldaddy_Embed' => 'wpcom/class-amp-polldaddy-embed', 'AMP_Test_Stub_Sanitizer' => 'tests/stubs', 'AMP_Test_World_Sanitizer' => 'tests/stubs', diff --git a/includes/class-amp-story-post-type.php b/includes/class-amp-story-post-type.php index fcc7690e88d..2e56f6d6fd4 100644 --- a/includes/class-amp-story-post-type.php +++ b/includes/class-amp-story-post-type.php @@ -210,15 +210,6 @@ public static function register() { // Enqueue the styling for the /embed endpoint. add_action( 'embed_footer', array( __CLASS__, 'enqueue_embed_styling' ) ); - // In the block editor, remove the title from above the AMP Stories embed. - add_filter( 'embed_html', array( __CLASS__, 'remove_title_from_embed' ), 10, 2 ); - - // Change some attributes for the AMP story embed. - add_filter( 'embed_html', array( __CLASS__, 'change_embed_iframe_attributes' ), 10, 2 ); - - // Override the render_callback for AMP story embeds. - add_filter( 'pre_render_block', array( __CLASS__, 'override_story_embed_callback' ), 10, 2 ); - // The AJAX handler for when an image is cropped and sent via POST. add_action( 'wp_ajax_custom-header-crop', array( __CLASS__, 'crop_featured_image' ) ); @@ -1129,61 +1120,6 @@ public static function enqueue_embed_styling() { } } - /** - * Overrides the render_callback of an AMP story post embed, when using the WordPress (embed) block. - * - * WordPress post embeds are usually wrapped in an