Skip to content

Commit

Permalink
Merge branch 'update/embed-block' of github.com:WordPress/gutenberg i…
Browse files Browse the repository at this point in the history
…nto update/embed-block
  • Loading branch information
notnownikki committed May 24, 2017
2 parents a2d391a + fb462d6 commit 70a67f8
Show file tree
Hide file tree
Showing 3 changed files with 267 additions and 0 deletions.
46 changes: 46 additions & 0 deletions components/resizable-iframe/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
Resizable Iframe
================

Resizable Iframe is a React component for rendering an `<iframe>` element which can dynamically update its own dimensions using [`window.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window.postMessage). This is useful in cases where an inline frame of an unknown size is to be displayed on the page.

## Example

The `ResizableIframe` component can be used in much the same way that you would use an `<iframe>` DOM element. Props are automatically transferred to the rendered `<iframe>`, in case you need to specify additional properties or styles.

```html
<ResizableIframe src={ myFrameUrl } frameBorder={ 0 } />
```

## Usage

To allow for resizing of the element, a `ResizableIframe` element listens for `message` events on the `window` object. If the rendered frame is not sandboxed, a script is injected in the frame to manage this behavior on your behalf. If the frame is sandboxed, any page you reference as the `src` URL is responsible for invoking this event using [`window.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window.postMessage). The message should be a JSON string with an `action` value of "resize" and a numeric `width` and `height` to define the new pixel dimensions of the element.

For example, a page can trigger a resize using the following code snippet:

```javascript
if ( window.parent ) {
window.parent.postMessage( JSON.stringify( {
action: 'resize',
width: document.body.clientWidth,
height: document.body.clientHeight
} ), '*' );
}
```

## Props

### `src`

Treated as the `src` URL to be used in the rendered `<iframe>` DOM element.

### `width`

An optional fixed width value, if you don't want this to be the responsibility of the child window.

### `height`

An optional fixed height value, if you don't want this to be the responsibility of the child window.

### `onResize`

An optional function to trigger when the rendered frame has been resized.
171 changes: 171 additions & 0 deletions components/resizable-iframe/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/**
* Imported from Calypso, with some lint fixes and gutenburg specific changes.
*/

/**
* External dependencies
*/
import { omit } from 'lodash';

export default class ResizableIframe extends wp.element.Component {

constructor() {
super( ...arguments );
this.state = {
width: 0,
height: 0,
};
this.getFrameBody = this.getFrameBody.bind( this );
this.maybeConnect = this.maybeConnect.bind( this );
this.isFrameAccessible = this.isFrameAccessible.bind( this );
this.checkMessageForResize = this.checkMessageForResize.bind( this );
}

static get defaultProps() {
return {
onLoad: () => {},
onResize: () => {},
title: '',
};
}

componentDidMount() {
window.addEventListener( 'message', this.checkMessageForResize, false );
this.maybeConnect();
}

componentDidUpdate() {
this.maybeConnect();
}

componentWillUnmount() {
window.removeEventListener( 'message', this.checkMessageForResize );
}

getFrameBody() {
return this.iframe.contentDocument.body;
}

maybeConnect() {
if ( ! this.isFrameAccessible() ) {
return;
}

const body = this.getFrameBody();
if ( null !== body.getAttribute( 'data-resizable-iframe-connected' ) ) {
return;
}

const script = document.createElement( 'script' );
script.innerHTML = `
( function() {
var observer;
if ( ! window.MutationObserver || ! document.body || ! window.top ) {
return;
}
function sendResize() {
window.top.postMessage( {
action: 'resize',
width: document.body.offsetWidth,
height: document.body.offsetHeight
}, '*' );
}
observer = new MutationObserver( sendResize );
observer.observe( document.body, {
attributes: true,
attributeOldValue: false,
characterData: true,
characterDataOldValue: false,
childList: true,
subtree: true
} );
window.addEventListener( 'load', sendResize, true );
// Hack: Remove viewport unit styles, as these are relative
// the iframe root and interfere with our mechanism for
// determining the unconstrained page bounds.
function removeViewportStyles( ruleOrNode ) {
[ 'width', 'height', 'minHeight', 'maxHeight' ].forEach( function( style ) {
if ( /^\\d+(vmin|vmax|vh|vw)$/.test( ruleOrNode.style[ style ] ) ) {
ruleOrNode.style[ style ] = '';
}
} );
}
Array.prototype.forEach.call( document.querySelectorAll( '[style]' ), removeViewportStyles );
Array.prototype.forEach.call( document.styleSheets, function( stylesheet ) {
Array.prototype.forEach.call( stylesheet.cssRules || stylesheet.rules, removeViewportStyles );
} );
document.body.style.position = 'absolute';
document.body.setAttribute( 'data-resizable-iframe-connected', '' );
sendResize();
} )();
`;
body.appendChild( script );
}

isFrameAccessible() {
try {
return !! this.getFrameBody();
} catch ( e ) {
return false;
}
}

checkMessageForResize( event ) {
const iframe = this.iframe;

// Attempt to parse the message data as JSON if passed as string
let data = event.data || {};
if ( 'string' === typeof data ) {
try {
data = JSON.parse( data );
} catch ( e ) {} // eslint-disable-line no-empty
}

// Verify that the mounted element is the source of the message
if ( ! iframe || iframe.contentWindow !== event.source ) {
return;
}

// Update the state only if the message is formatted as we expect, i.e.
// as an object with a 'resize' action, width, and height
const { action, width, height } = data;
const { width: oldWidth, height: oldHeight } = this.state;

if ( 'resize' === action && ( oldWidth !== width || oldHeight !== height ) ) {
this.setState( { width, height } );
this.props.onResize();
}
}

onLoad( event ) {
this.maybeConnect();
this.props.onLoad( event );
}

render() {
const omitProps = [ 'onResize' ];

if ( ! this.props.src ) {
omitProps.push( 'src' );
}
return (
<iframe
ref={ ( node ) => this.iframe = node }
title={ this.props.title }
seamless="seamless"
scrolling="no"
{ ...omit( this.props, omitProps ) }
onLoad={ this.onLoad }
width={ this.props.width || this.state.width }
height={ this.props.height || this.state.height } />
);
}
}
50 changes: 50 additions & 0 deletions components/sandbox/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Internal dependencies
*/
import ResizableIframe from 'components/resizable-iframe';

export default class Sandbox extends wp.element.Component {

static get defaultProps() {
return {
html: '',
title: '',
};
}

componentDidMount() {
const body = this.node.getFrameBody();
const { html } = this.props;

body.innerHTML = html;

// setting the inner HTML will not cause scripts to load,
// so this prepares a bunch of new script elements and
// appends them to the iframe's body
const scripts = body.getElementsByTagName( 'script' );
const newscripts = [];

for ( let i = 0; i < scripts.length; i++ ) {
const newscript = document.createElement( 'script' );
if ( scripts[ i ].src ) {
newscript.src = scripts[ i ].src;
} else {
newscript.innerHTML = scripts[ i ].innerHTML;
}
newscripts.push( newscript );
}

for ( let i = 0; i < newscripts.length; i++ ) {
body.appendChild( newscripts[ i ] );
}
}

render() {
return (
<ResizableIframe
sandbox="allow-same-origin allow-scripts"
title={ this.props.title }
ref={ ( node ) => this.node = node } />
);
}
}

0 comments on commit 70a67f8

Please sign in to comment.