-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replace injected html with sandboxing iframe #1392
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,13 +4,14 @@ export { default as ClipboardButton } from './clipboard-button'; | |
export { default as Dashicon } from './dashicon'; | ||
export { default as FormToggle } from './form-toggle'; | ||
export { default as FormTokenField } from './form-token-field'; | ||
export { default as HtmlEmbed } from './html-embed'; | ||
export { default as IconButton } from './icon-button'; | ||
export { default as Panel } from './panel'; | ||
export { default as PanelHeader } from './panel/header'; | ||
export { default as PanelBody } from './panel/body'; | ||
export { default as Placeholder } from './placeholder'; | ||
export { default as ResizableIframe } from './resizable-iframe'; | ||
export { default as ResponsiveWrapper } from './responsive-wrapper'; | ||
export { default as SandBox } from './sandbox'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "Sandbox" is a single word, shouldn't need the PascalCase-ing here. |
||
export { default as Spinner } from './spinner'; | ||
export { default as Toolbar } from './toolbar'; | ||
export { default as Popover } from './popover'; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
/** | ||
* Imported from Calypso, with some lint fixes and gutenburg specific changes. | ||
*/ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we should keep this comment. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤷♀️ I mean, it's a direct copy, but with some react changes for how it handles the references, and lint fixes. |
||
|
||
/** | ||
* 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', '' ); | ||
|
||
// Make sure that we don't miss very quick loading documents here that the observer | ||
// doesn't see load, but haven't completely loaded when we call sendResize for the | ||
// first time. | ||
setTimeout( sendResize, 1000 ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This works... most of the time. There's still something going on though that stops some embeds sending a resize when they load. I'm going to tackle that specific problem in another PR. I have a good idea of how to solve it from looking at the embed code in Calypso, but I don't want to delay this and have this branch fall out of sync too much, as I'm not sure how long it will take to fully solve this. |
||
} )(); | ||
`; | ||
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 } | ||
scrolling="no" | ||
{ ...omit( this.props, omitProps ) } | ||
onLoad={ this.onLoad } | ||
width={ this.props.width || this.state.width } | ||
height={ this.props.height || this.state.height } /> | ||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this have been localized?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, see #1635