diff --git a/app/internal_packages/message-list/lib/email-frame.jsx b/app/internal_packages/message-list/lib/email-frame.jsx
index dad978319..4cf179037 100644
--- a/app/internal_packages/message-list/lib/email-frame.jsx
+++ b/app/internal_packages/message-list/lib/email-frame.jsx
@@ -24,6 +24,12 @@ export default class EmailFrame extends React.Component {
this._mounted = true;
this._writeContent();
this._unlisten = EmailFrameStylesStore.listen(this._writeContent);
+
+ // Update the iframe's size whenever it's content size changes. Doing this
+ // with ResizeObserver is /so/ elegant compared to polling for it's height.
+ const iframeEl = ReactDOM.findDOMNode(this._iframeComponent);
+ this._iframeDocObserver = new ResizeObserver(this._onReevaluateContentSize);
+ this._iframeDocObserver.observe(iframeEl.contentDocument.firstElementChild);
}
shouldComponentUpdate(nextProps) {
@@ -31,9 +37,9 @@ export default class EmailFrame extends React.Component {
const nextMessage = nextProps.message || {};
return (
+ message.id !== nextMessage.id ||
content !== nextProps.content ||
showQuotedText !== nextProps.showQuotedText ||
- message.id !== nextMessage.id ||
!Utils.isEqualReact(message.pluginMetadata, nextMessage.pluginMetadata)
);
}
@@ -44,9 +50,8 @@ export default class EmailFrame extends React.Component {
componentWillUnmount() {
this._mounted = false;
- if (this._unlisten) {
- this._unlisten();
- }
+ if (this._iframeDocObserver) this._iframeDocObserver.disconnect();
+ if (this._unlisten) this._unlisten();
}
_emailContent = () => {
@@ -60,71 +65,60 @@ export default class EmailFrame extends React.Component {
};
_writeContent = () => {
- const iframeNode = ReactDOM.findDOMNode(this._iframeComponent);
- const doc = iframeNode.contentDocument;
- if (!doc) {
- return;
- }
- doc.open();
+ const iframeEl = ReactDOM.findDOMNode(this._iframeComponent);
+ const doc = iframeEl.contentDocument;
+ if (!doc) return;
// NOTE: The iframe must have a modern DOCTYPE. The lack of this line
// will cause some bizzare non-standards compliant rendering with the
// message bodies. This is particularly felt with
elements use
// the `border-collapse: collapse` css property while setting a
// `padding`.
- doc.write('');
const styles = EmailFrameStylesStore.styles();
- if (styles) {
- doc.write(``);
- }
+ doc.open();
doc.write(
- `${this._emailContent()}
`
+ `` +
+ (styles ? `` : '') +
+ `${this._emailContent()}
`
);
doc.close();
- iframeNode.addEventListener('load', this._onLoad);
-
- autolink(doc, { async: true });
- adjustImages(doc);
-
- for (const extension of MessageStore.extensions()) {
- if (!extension.renderedMessageBodyIntoDocument) {
- continue;
- }
- try {
- extension.renderedMessageBodyIntoDocument({
- document: doc,
- message: this.props.message,
- iframe: iframeNode,
- });
- } catch (e) {
- AppEnv.reportError(e);
- }
- }
-
// Notify the EventedIFrame that we've replaced it's document (with `open`)
// so it can attach event listeners again.
this._iframeComponent.didReplaceDocument();
- this._onMustRecalculateFrameHeight();
+
+ window.requestAnimationFrame(() => {
+ autolink(doc, { async: true });
+ adjustImages(doc);
+
+ for (const extension of MessageStore.extensions()) {
+ if (!extension.renderedMessageBodyIntoDocument) {
+ continue;
+ }
+ try {
+ extension.renderedMessageBodyIntoDocument({
+ document: doc,
+ message: this.props.message,
+ iframe: iframeEl,
+ });
+ } catch (e) {
+ AppEnv.reportError(e);
+ }
+ }
+ });
};
- _onLoad = () => {
- const iframeNode = ReactDOM.findDOMNode(this._iframeComponent);
- iframeNode.removeEventListener('load', this._onLoad);
- this._setFrameHeight();
- };
+ _onReevaluateContentSize = () => {
+ const iframeEl = ReactDOM.findDOMNode(this._iframeComponent);
+ const doc = iframeEl && iframeEl.contentDocument;
- _onMustRecalculateFrameHeight = () => {
+ // We must set the height to zero in order to get a valid scrollHeight
+ // if the document is wider and has a lower height now.
this._iframeComponent.setHeightQuietly(0);
- this._lastComputedHeight = 0;
- this._setFrameHeight();
- };
-
- _getFrameHeight = doc => {
- let height = 0;
// If documentElement has a scroll height, prioritize that as height
// If not, fall back to body scroll height by setting it to auto
+ let height = 0;
if (doc && doc.documentElement && doc.documentElement.scrollHeight > 0) {
height = doc.documentElement.scrollHeight;
} else if (doc && doc.body) {
@@ -134,59 +128,28 @@ export default class EmailFrame extends React.Component {
}
height = doc.body.scrollHeight;
}
- return height;
+
+ this._iframeComponent.setHeightQuietly(height);
};
- _setFrameHeight = () => {
- if (!this._mounted) {
- return;
- }
-
- // Q: What's up with this holder?
- // A: If you resize the window, or do something to trigger setFrameHeight
- // on an already-loaded message view, all the heights go to zero for a brief
- // second while the heights are recomputed. This causes the ScrollRegion to
- // reset it's scrollTop to ~0 (the new combined heiht of all children).
- // To prevent this, the holderNode holds the last computed height until
- // the new height is computed.
- const iframeNode = ReactDOM.findDOMNode(this._iframeComponent);
- let height = this._getFrameHeight(iframeNode.contentDocument);
-
- // Why 5px? Some emails have elements with a height of 100%, and then put
- // tracking pixels beneath that. In these scenarios, the scrollHeight of the
- // message is always <100% + 1px>, which leads us to resize them constantly.
- // This is a hack, but I'm not sure of a better solution.
- if (Math.abs(height - this._lastComputedHeight) > 5) {
- this._iframeComponent.setHeightQuietly(height);
- this._iframeHeightHolderEl.style.height = `${height}px`;
- this._lastComputedHeight = height;
- }
-
- if (iframeNode.contentDocument.readyState !== 'complete') {
- window.requestAnimationFrame(() => {
- this._setFrameHeight();
- });
- }
+ _onResize = () => {
+ const iframeEl = ReactDOM.findDOMNode(this._iframeComponent);
+ if (!iframeEl) return;
+ this._iframeDocObserver.disconnect();
+ this._iframeDocObserver.observe(iframeEl.contentDocument.firstElementChild);
};
render() {
return (
- {
- this._iframeHeightHolderEl = el;
+ {
+ this._iframeComponent = cm;
}}
- style={{ height: this._lastComputedHeight }}
- >
- {
- this._iframeComponent = cm;
- }}
- seamless="seamless"
- searchable
- onResize={this._onMustRecalculateFrameHeight}
- />
-
+ />
);
}
}
diff --git a/app/internal_packages/message-list/styles/message-list.less b/app/internal_packages/message-list/styles/message-list.less
index f997e414e..e896865ef 100644
--- a/app/internal_packages/message-list/styles/message-list.less
+++ b/app/internal_packages/message-list/styles/message-list.less
@@ -483,16 +483,12 @@ body.platform-win32 {
margin: 0 auto;
padding: 0 20px @spacing-standard 20px;
- .iframe-container {
+ iframe {
margin-top: 10px;
width: 100%;
-
- iframe {
- width: 100%;
- border: 0;
- padding: 0;
- overflow: auto;
- }
+ border: 0;
+ padding: 0;
+ overflow: auto;
}
}
diff --git a/app/src/components/evented-iframe.jsx b/app/src/components/evented-iframe.jsx
index e5c6a141d..5b4a785a8 100644
--- a/app/src/components/evented-iframe.jsx
+++ b/app/src/components/evented-iframe.jsx
@@ -87,8 +87,11 @@ class EventedIFrame extends React.Component {
}
setHeightQuietly(height) {
- this._ignoreNextResize = true;
- ReactDOM.findDOMNode(this).height = `${height}px`;
+ const el = ReactDOM.findDOMNode(this);
+ if (el.style.height !== `${height}px`) {
+ this._ignoreNextResize = true;
+ el.style.height = `${height}px`;
+ }
}
_onSearchableStoreChange = () => {