Skip to content

Commit

Permalink
FSE: load content in iframe
Browse files Browse the repository at this point in the history
  • Loading branch information
ellatrix committed Dec 10, 2020
1 parent a110765 commit 83c8d1c
Show file tree
Hide file tree
Showing 22 changed files with 478 additions and 98 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/performance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,10 @@ jobs:
- name: Run the performance tests
run: ./bin/plugin/cli.js perf --ci $GITHUB_SHA master --tests-branch $GITHUB_SHA

- name: Archive debug artifacts (screenshots, HTML snapshots)
uses: actions/upload-artifact@v2
if: always()
with:
name: failures-artifacts
path: artifacts
37 changes: 37 additions & 0 deletions lib/client-assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -662,3 +662,40 @@ function gutenberg_extend_block_editor_settings_with_fse_theme_flag( $settings )
return $settings;
}
add_filter( 'block_editor_settings', 'gutenberg_extend_block_editor_settings_with_fse_theme_flag' );

/**
* Sets the editor styles to be consumed by JS.
*/
function gutenberg_extend_block_editor_styles_html() {
$handles = array(
'wp-block-editor',
'wp-block-library',
'wp-edit-blocks',
);

$block_registry = WP_Block_Type_Registry::get_instance();

foreach ( $block_registry->get_all_registered() as $block_name => $block_type ) {
if ( ! empty( $block_type->style ) ) {
$handles[] = $block_type->style;
}

if ( ! empty( $block_type->editor_style ) ) {
$handles[] = $block_type->editor_style;
}
}

$handles = array_unique( $handles );
$done = wp_styles()->done;

ob_start();

wp_styles()->done = array();
wp_styles()->do_items( $handles );
wp_styles()->done = $done;

$editor_styles = wp_json_encode( array( 'html' => ob_get_clean() ) );

echo "<script>window.__editorStyles = $editor_styles</script>";
}
add_action( 'admin_footer-toplevel_page_gutenberg-edit-site', 'gutenberg_extend_block_editor_styles_html' );
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,9 @@ function BlockPopover( {
: 'top right left';
const stickyBoundaryElement = showEmptyBlockSideInserter
? undefined
: getScrollContainer( node ) || ownerDocument.body;
: getScrollContainer( ownerDocument.defaultView.frameElement ) ||
getScrollContainer( node ) ||
ownerDocument.body;

return (
<Popover
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,12 @@ function BlockSelectionButton( { clientId, rootClientId, blockElement } ) {

if ( navigateDown ) {
nextTabbable = focus.tabbable.findNext( blockElement );

if ( ! nextTabbable ) {
nextTabbable =
blockElement.ownerDocument.defaultView.frameElement;
nextTabbable = focus.tabbable.findNext( nextTabbable );
}
} else {
nextTabbable = focus.tabbable.findPrevious( blockElement );
}
Expand Down
226 changes: 226 additions & 0 deletions packages/block-editor/src/components/iframe/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
/**
* WordPress dependencies
*/
import { useState, useEffect, createPortal, useRef } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { useResizeObserver } from '@wordpress/compose';

const BODY_CLASS_NAME = 'editor-styles-wrapper';
const BLOCK_PREFIX = 'wp-block';

/**
* Clones stylesheets targetting the editor canvas to the given document. A
* stylesheet is considered targetting the editor a canvas if it contains the
* `editor-styles-wrapper`, `wp-block`, or `wp-block-*` class selectors.
*
* Ideally, this hook should be removed in the future and styles should be added
* explicitly as editor styles.
*
* @param {Document} doc The document to append cloned stylesheets to.
*/
function useStyleSheetsCompat( doc ) {
useEffect( () => {
// Search the document for stylesheets targetting the editor canvas.
Array.from( document.styleSheets ).forEach( ( styleSheet ) => {
try {
// May fail for external styles.
// eslint-disable-next-line no-unused-expressions
styleSheet.cssRules;
} catch ( e ) {
return;
}

const { ownerNode, cssRules } = styleSheet;

if ( ! cssRules ) {
return;
}

const isMatch = Array.from( cssRules ).find(
( { selectorText } ) =>
selectorText &&
( selectorText.includes( `.${ BODY_CLASS_NAME }` ) ||
selectorText.includes( `.${ BLOCK_PREFIX }` ) )
);

if ( isMatch && ! doc.getElementById( ownerNode.id ) ) {
doc.head.appendChild( ownerNode.cloneNode( true ) );
}
} );
}, [] );
}

/**
* Bubbles some event types (keydown, keypress, and dragover) to parent document
* document to ensure that the keyboard shortcuts and drag and drop work.
*
* Ideally, we should remove event bubbling in the future. Keyboard shortcuts
* should be context dependent, e.g. actions on blocks like Cmd+A should not
* work globally outside the block editor.
*
* @param {Document} doc Document to attach listeners to.
*/
function useBubbleEvents( doc ) {
useEffect( () => {
const { defaultView } = doc;
const { frameElement } = defaultView;

function bubbleEvent( event ) {
const prototype = Object.getPrototypeOf( event );
const constructorName = prototype.constructor.name;
const Constructor = window[ constructorName ];

const init = {};

for ( const key in event ) {
init[ key ] = event[ key ];
}

if ( event instanceof defaultView.MouseEvent ) {
const rect = frameElement.getBoundingClientRect();
init.clientX += rect.left;
init.clientY += rect.top;
}

const newEvent = new Constructor( event.type, init );
const cancelled = ! frameElement.dispatchEvent( newEvent );

if ( cancelled ) {
event.preventDefault();
}
}

const eventTypes = [ 'keydown', 'keypress', 'dragover' ];

for ( const name of eventTypes ) {
doc.addEventListener( name, bubbleEvent );
}

return () => {
for ( const name of eventTypes ) {
doc.removeEventListener( name, bubbleEvent );
}
};
}, [] );
}

/**
* Sets the document direction.
*
* Sets the `editor-styles-wrapper` class name on the body.
*
* Copies the `admin-color-*` class name to the body so that the admin color
* scheme applies to components in the iframe.
*
* @param {Document} doc Document to add class name to.
*/
function useBodyClassName( doc ) {
useEffect( () => {
doc.dir = document.dir;
doc.body.className = BODY_CLASS_NAME;

for ( const name of document.body.classList ) {
if ( name.startsWith( 'admin-color-' ) ) {
doc.body.classList.add( name );
}
}
}, [] );
}

/**
* Positions the body element so that the resize listener works correctly. We're
* using an absolute position here because the resize listener doesn't seem to
* report shrinking when the position is relative, causing the iframe not to
* shrink when content is removed.
*
* @see https://github.com/FezVrasta/react-resize-aware#usage
*
* @param {Document} doc Document to set styles to.
*/
function useResizeListenerCompat( doc ) {
useEffect( () => {
// Necessary for the resize listener to work correctly.
doc.body.style.position = 'absolute';
doc.body.style.right = '0';
doc.body.style.left = '0';
}, [] );
}

/**
* Sets the document head and default styles.
*
* @param {Document} doc Document to set the head for.
* @param {string} head HTML to set as the head.
*/
function useHead( doc, head ) {
useEffect( () => {
doc.head.innerHTML =
// Body margin must be overridable by themes.
'<style>body{margin:0}</style>' +
'<style>.wp-block[data-align="full"],.wp-block.alignfull{max-width:100vw!important;width:100vw!important;}</style>' +
head;
}, [] );
}

function IframeContent( { doc, head, children } ) {
useHead( doc, head );
useStyleSheetsCompat( doc );
useBubbleEvents( doc );
useBodyClassName( doc );
useResizeListenerCompat( doc );
return createPortal( children, doc.body );
}

export default function Iframe( { children, head, style = {}, ...props } ) {
const [ resizeListener, sizes ] = useResizeObserver();
const [ contentDocument, setContentDocument ] = useState();
const ref = useRef();

function setDocumentIfReady( doc ) {
const { readyState } = doc;

if ( readyState === 'interactive' || readyState === 'complete' ) {
setContentDocument( doc );
}
}

useEffect( () => {
setDocumentIfReady( ref.current.contentDocument );
}, [] );

function setRef( newRef ) {
ref.current = newRef;

if ( newRef ) {
setDocumentIfReady( newRef.contentDocument );
}
}

return (
<iframe
{ ...props }
style={ {
display: 'block',
width: '100%',
height: sizes.height + 'px',
minHeight: '100%',
...style,
} }
ref={ setRef }
tabIndex="0"
title={ __( 'Editor canvas' ) }
name="editor-canvas"
onLoad={ () => {
// Document is not immediately loaded in Firefox.
setDocumentIfReady( ref.current.contentDocument );
} }
>
{ contentDocument && (
<IframeContent doc={ contentDocument } head={ head }>
{ children( { current: contentDocument.body } ) }
{ resizeListener }
</IframeContent>
) }
</iframe>
);
}
1 change: 1 addition & 0 deletions packages/block-editor/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export {
export { default as Warning } from './warning';
export { default as WritingFlow } from './writing-flow';
export { useCanvasClickRedirect as __unstableUseCanvasClickRedirect } from './use-canvas-click-redirect';
export { default as __unstableIframe } from './iframe';

/*
* State Related Components
Expand Down
17 changes: 11 additions & 6 deletions packages/block-editor/src/components/use-resize-canvas/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ import { default as useSimulatedMediaQuery } from '../../components/use-simulate
/**
* Function to resize the editor window.
*
* @param {string} deviceType Used for determining the size of the container (e.g. Desktop, Tablet, Mobile)
* @param {string} deviceType Used for determining the size of the container (e.g. Desktop, Tablet, Mobile)
* @param {boolean} __unstableDisableSimulate
*
* @return {Object} Inline styles to be added to resizable container.
*/
export default function useResizeCanvas( deviceType ) {
export default function useResizeCanvas(
deviceType,
__unstableDisableSimulate
) {
const [ actualWidth, updateActualWidth ] = useState( window.innerWidth );

useEffect( () => {
Expand Down Expand Up @@ -69,10 +73,11 @@ export default function useResizeCanvas( deviceType ) {
}
};

useSimulatedMediaQuery(
'resizable-editor-section',
getCanvasWidth( deviceType )
);
const width = __unstableDisableSimulate
? null
: getCanvasWidth( deviceType );

useSimulatedMediaQuery( 'resizable-editor-section', width );

return contentInlineStyles( deviceType );
}
2 changes: 2 additions & 0 deletions packages/block-editor/src/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,5 @@
@import "./components/block-toolbar/style.scss";
@import "./components/inserter/style.scss";
@import "./components/preview-options/style.scss";

@include wordpress-admin-schemes();
Loading

0 comments on commit 83c8d1c

Please sign in to comment.