diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go
index a622d75085..08502e12ab 100644
--- a/modules/markup/renderer.go
+++ b/modules/markup/renderer.go
@@ -248,15 +248,14 @@ type nopCloser struct {
func (nopCloser) Close() error { return nil }
func renderIFrame(ctx *RenderContext, output io.Writer) error {
- // set height="0" ahead, otherwise the scrollHeight would be max(150, realHeight)
+ // set height="300", otherwise if the postMessage mechanism breaks, we are left with a 0-height iframe
// at the moment, only "allow-scripts" is allowed for sandbox mode.
// "allow-same-origin" should never be used, it leads to XSS attack, and it makes the JS in iframe can access parent window's config and CSRF token
// TODO: when using dark theme, if the rendered content doesn't have proper style, the default text color is black, which is not easy to read
_, err := io.WriteString(output, fmt.Sprintf(`
`,
setting.AppSubURL,
@@ -317,6 +316,12 @@ func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Wr
if err1 := renderer.Render(ctx, input, pw); err1 != nil {
return err1
}
+
+ if r, ok := renderer.(ExternalRenderer); ok && r.DisplayInIFrame() {
+ // Append a short script to the iframe's contents, which will communicate the scroll height of the embedded document via postMessage, either once loaded (in case the containing page loads first) in response to a postMessage from external.js, in case the iframe loads first
+ // We use '*' as a target origin for postMessage, because can be certain we are embedded on the same domain, due to X-Frame-Options configured elsewhere. (Plus, the offsetHeight of an embedded document is likely not sensitive data anyway.)
+ _, _ = pw.Write([]byte(""))
+ }
_ = pw.Close()
wg.Wait()
diff --git a/web_src/js/markup/content.js b/web_src/js/markup/content.js
index 1d29dc07f2..349727fbe2 100644
--- a/web_src/js/markup/content.js
+++ b/web_src/js/markup/content.js
@@ -2,6 +2,7 @@ import {renderMermaid} from './mermaid.js';
import {renderMath} from './math.js';
import {renderCodeCopy} from './codecopy.js';
import {renderAsciicast} from './asciicast.js';
+import {renderExternal} from './external.js';
import {initMarkupTasklist} from './tasklist.js';
// code that runs for all markup content
@@ -10,6 +11,7 @@ export function initMarkupContent() {
renderMath();
renderCodeCopy();
renderAsciicast();
+ renderExternal();
}
// code that only runs for comments
diff --git a/web_src/js/markup/external.js b/web_src/js/markup/external.js
new file mode 100644
index 0000000000..cae0e7488d
--- /dev/null
+++ b/web_src/js/markup/external.js
@@ -0,0 +1,16 @@
+export function renderExternal() {
+ const giteaExternalRender = document.querySelector('iframe.external-render');
+ if (!giteaExternalRender) return;
+
+ giteaExternalRender.contentWindow.postMessage({requestOffsetHeight: true}, '*');
+
+ const eventListener = (event) => {
+ if (event.source !== giteaExternalRender.contentWindow) return;
+ const height = Number(event.data?.frameHeight);
+ if (!height) return;
+ giteaExternalRender.height = height;
+ giteaExternalRender.style.overflow = 'hidden';
+ window.removeEventListener('message', eventListener);
+ };
+ window.addEventListener('message', eventListener);
+}