-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'update/embed-block' of github.com:WordPress/gutenberg i…
…nto update/embed-block
- Loading branch information
Showing
3 changed files
with
267 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } /> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } /> | ||
); | ||
} | ||
} |